v0.1.4-p4
parent
3ca7eff38b
commit
08b35d632b
|
|
@ -216,11 +216,10 @@
|
|||
display: grid;
|
||||
gap: 8px;
|
||||
padding: 10px;
|
||||
border: 1px dashed color-mix(in oklab, #6f94ff 46%, var(--line) 54%);
|
||||
border: 1px dashed color-mix(in oklab, #aeb7c4 52%, var(--line) 48%);
|
||||
border-radius: 14px;
|
||||
background:
|
||||
linear-gradient(180deg, color-mix(in oklab, var(--panel) 68%, #dfe9ff 32%), color-mix(in oklab, var(--panel) 82%, #d5e3ff 18%));
|
||||
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.22);
|
||||
background: color-mix(in oklab, #eef1f4 82%, var(--panel) 18%);
|
||||
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.16);
|
||||
}
|
||||
|
||||
.ops-staged-submission-item {
|
||||
|
|
@ -229,9 +228,9 @@
|
|||
align-items: center;
|
||||
gap: 10px;
|
||||
padding: 8px 10px;
|
||||
border: 1px solid color-mix(in oklab, #7aa2ff 18%, var(--line) 82%);
|
||||
border: 1px solid color-mix(in oklab, #bcc5cf 26%, var(--line) 74%);
|
||||
border-radius: 12px;
|
||||
background: color-mix(in oklab, var(--panel) 84%, white 16%);
|
||||
background: color-mix(in oklab, #f5f7f9 88%, var(--panel) 12%);
|
||||
}
|
||||
|
||||
.ops-staged-submission-index {
|
||||
|
|
@ -242,9 +241,9 @@
|
|||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 11px;
|
||||
color: var(--brand);
|
||||
background: color-mix(in oklab, var(--panel) 64%, white 36%);
|
||||
border: 1px solid color-mix(in oklab, var(--line) 70%, transparent);
|
||||
color: color-mix(in oklab, var(--text) 72%, var(--muted) 28%);
|
||||
background: color-mix(in oklab, #ffffff 72%, #e5e9ee 28%);
|
||||
border: 1px solid color-mix(in oklab, #c7ced6 46%, transparent);
|
||||
}
|
||||
|
||||
.ops-staged-submission-body {
|
||||
|
|
@ -272,12 +271,12 @@
|
|||
.ops-staged-submission-pill {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
border: 1px solid color-mix(in oklab, var(--line) 74%, transparent);
|
||||
border: 1px solid color-mix(in oklab, #c3cad2 44%, transparent);
|
||||
border-radius: 999px;
|
||||
padding: 2px 8px;
|
||||
font-size: 11px;
|
||||
color: var(--subtitle);
|
||||
background: color-mix(in oklab, var(--panel) 82%, transparent);
|
||||
background: color-mix(in oklab, #ffffff 74%, #e8edf2 26%);
|
||||
}
|
||||
|
||||
.ops-staged-submission-actions {
|
||||
|
|
@ -290,9 +289,9 @@
|
|||
width: 28px;
|
||||
height: 28px;
|
||||
padding: 0;
|
||||
border: 1px solid color-mix(in oklab, var(--line) 76%, transparent);
|
||||
border: 1px solid color-mix(in oklab, #c2c9d2 42%, transparent);
|
||||
border-radius: 999px;
|
||||
background: color-mix(in oklab, var(--panel) 88%, white 12%);
|
||||
background: color-mix(in oklab, #fbfcfd 82%, #e6ebf0 18%);
|
||||
color: var(--icon-muted);
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
|
|
@ -303,8 +302,8 @@
|
|||
|
||||
.ops-staged-submission-icon-btn:hover:not(:disabled) {
|
||||
color: var(--icon);
|
||||
background: color-mix(in oklab, var(--panel) 70%, var(--brand-soft) 30%);
|
||||
border-color: color-mix(in oklab, var(--brand) 38%, var(--line) 62%);
|
||||
background: color-mix(in oklab, #edf1f5 78%, #d8dde4 22%);
|
||||
border-color: color-mix(in oklab, #97a3af 34%, var(--line) 66%);
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { ArrowUp, ChevronLeft, Clock3, Command, Download, Eye, FileText, Mic, Paperclip, Pencil, Plus, RefreshCw, RotateCcw, Square, Trash2, X } from 'lucide-react';
|
||||
import type { Components } from 'react-markdown';
|
||||
import type { ChangeEventHandler, KeyboardEventHandler, RefObject } from 'react';
|
||||
import { memo, type ChangeEventHandler, type KeyboardEventHandler, type RefObject } from 'react';
|
||||
|
||||
import { LucentIconButton } from '../../../components/lucent/LucentIconButton';
|
||||
import nanobotLogo from '../../../assets/nanobot-logo.png';
|
||||
|
|
@ -121,6 +121,145 @@ interface DashboardChatPanelProps {
|
|||
onSubmitAction: () => Promise<void> | void;
|
||||
}
|
||||
|
||||
interface DashboardChatTranscriptProps {
|
||||
conversation: ChatMessage[];
|
||||
isZh: boolean;
|
||||
labels: DashboardChatPanelLabels;
|
||||
chatScrollRef: RefObject<HTMLDivElement | null>;
|
||||
onChatScroll: () => void;
|
||||
expandedProgressByKey: Record<string, boolean>;
|
||||
expandedUserByKey: Record<string, boolean>;
|
||||
deletingMessageIdMap: Record<number, boolean>;
|
||||
feedbackSavingByMessageId: Record<number, boolean>;
|
||||
markdownComponents: Components;
|
||||
workspaceDownloadExtensionSet: ReadonlySet<string>;
|
||||
onToggleProgressExpand: (key: string) => void;
|
||||
onToggleUserExpand: (key: string) => void;
|
||||
onEditUserPrompt: (text: string) => void;
|
||||
onCopyUserPrompt: (text: string) => Promise<void> | void;
|
||||
onDeleteConversationMessage: (message: ChatMessage) => Promise<void> | void;
|
||||
onOpenWorkspacePath: (path: string) => Promise<void> | void;
|
||||
onSubmitAssistantFeedback: (message: ChatMessage, direction: 'up' | 'down') => Promise<void> | void;
|
||||
onQuoteAssistantReply: (message: ChatMessage) => void;
|
||||
onCopyAssistantReply: (text: string) => Promise<void> | void;
|
||||
isThinking: boolean;
|
||||
}
|
||||
|
||||
const MemoizedChatTranscript = memo(function MemoizedChatTranscript({
|
||||
conversation,
|
||||
isZh,
|
||||
labels,
|
||||
chatScrollRef,
|
||||
onChatScroll,
|
||||
expandedProgressByKey,
|
||||
expandedUserByKey,
|
||||
deletingMessageIdMap,
|
||||
feedbackSavingByMessageId,
|
||||
markdownComponents,
|
||||
workspaceDownloadExtensionSet,
|
||||
onToggleProgressExpand,
|
||||
onToggleUserExpand,
|
||||
onEditUserPrompt,
|
||||
onCopyUserPrompt,
|
||||
onDeleteConversationMessage,
|
||||
onOpenWorkspacePath,
|
||||
onSubmitAssistantFeedback,
|
||||
onQuoteAssistantReply,
|
||||
onCopyAssistantReply,
|
||||
isThinking,
|
||||
}: DashboardChatTranscriptProps) {
|
||||
return (
|
||||
<div className="ops-chat-scroll" ref={chatScrollRef} onScroll={onChatScroll}>
|
||||
{conversation.length === 0 ? (
|
||||
<div className="ops-chat-empty">
|
||||
{labels.noConversation}
|
||||
</div>
|
||||
) : (
|
||||
<DashboardConversationMessages
|
||||
conversation={conversation}
|
||||
isZh={isZh}
|
||||
labels={{
|
||||
badReply: labels.badReply,
|
||||
copyPrompt: labels.copyPrompt,
|
||||
copyReply: labels.copyReply,
|
||||
deleteMessage: labels.deleteMessage,
|
||||
download: labels.download,
|
||||
editPrompt: labels.editPrompt,
|
||||
fileNotPreviewable: labels.fileNotPreviewable,
|
||||
goodReply: labels.goodReply,
|
||||
previewTitle: labels.previewTitle,
|
||||
quoteReply: labels.quoteReply,
|
||||
quotedReplyLabel: labels.quotedReplyLabel,
|
||||
user: labels.user,
|
||||
you: labels.you,
|
||||
}}
|
||||
expandedProgressByKey={expandedProgressByKey}
|
||||
expandedUserByKey={expandedUserByKey}
|
||||
deletingMessageIdMap={deletingMessageIdMap}
|
||||
feedbackSavingByMessageId={feedbackSavingByMessageId}
|
||||
markdownComponents={markdownComponents}
|
||||
workspaceDownloadExtensionSet={workspaceDownloadExtensionSet}
|
||||
onToggleProgressExpand={onToggleProgressExpand}
|
||||
onToggleUserExpand={onToggleUserExpand}
|
||||
onEditUserPrompt={onEditUserPrompt}
|
||||
onCopyUserPrompt={onCopyUserPrompt}
|
||||
onDeleteConversationMessage={onDeleteConversationMessage}
|
||||
onOpenWorkspacePath={onOpenWorkspacePath}
|
||||
onSubmitAssistantFeedback={onSubmitAssistantFeedback}
|
||||
onQuoteAssistantReply={onQuoteAssistantReply}
|
||||
onCopyAssistantReply={onCopyAssistantReply}
|
||||
/>
|
||||
)}
|
||||
|
||||
{isThinking ? (
|
||||
<div className="ops-chat-row is-assistant">
|
||||
<div className="ops-chat-item is-assistant">
|
||||
<div className="ops-avatar bot" title="Nanobot">
|
||||
<img src={nanobotLogo} alt="Nanobot" />
|
||||
</div>
|
||||
<div className="ops-thinking-bubble">
|
||||
<div className="ops-thinking-cloud">
|
||||
<span className="dot" />
|
||||
<span className="dot" />
|
||||
<span className="dot" />
|
||||
</div>
|
||||
<div className="ops-thinking-text">{labels.thinking}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
<div />
|
||||
</div>
|
||||
);
|
||||
}, (prev, next) => (
|
||||
prev.conversation === next.conversation
|
||||
&& prev.isZh === next.isZh
|
||||
&& prev.isThinking === next.isThinking
|
||||
&& prev.chatScrollRef === next.chatScrollRef
|
||||
&& prev.expandedProgressByKey === next.expandedProgressByKey
|
||||
&& prev.expandedUserByKey === next.expandedUserByKey
|
||||
&& prev.deletingMessageIdMap === next.deletingMessageIdMap
|
||||
&& prev.feedbackSavingByMessageId === next.feedbackSavingByMessageId
|
||||
&& prev.markdownComponents === next.markdownComponents
|
||||
&& prev.workspaceDownloadExtensionSet === next.workspaceDownloadExtensionSet
|
||||
&& prev.labels.badReply === next.labels.badReply
|
||||
&& prev.labels.copyPrompt === next.labels.copyPrompt
|
||||
&& prev.labels.copyReply === next.labels.copyReply
|
||||
&& prev.labels.deleteMessage === next.labels.deleteMessage
|
||||
&& prev.labels.download === next.labels.download
|
||||
&& prev.labels.editPrompt === next.labels.editPrompt
|
||||
&& prev.labels.fileNotPreviewable === next.labels.fileNotPreviewable
|
||||
&& prev.labels.goodReply === next.labels.goodReply
|
||||
&& prev.labels.noConversation === next.labels.noConversation
|
||||
&& prev.labels.previewTitle === next.labels.previewTitle
|
||||
&& prev.labels.quoteReply === next.labels.quoteReply
|
||||
&& prev.labels.quotedReplyLabel === next.labels.quotedReplyLabel
|
||||
&& prev.labels.thinking === next.labels.thinking
|
||||
&& prev.labels.user === next.labels.user
|
||||
&& prev.labels.you === next.labels.you
|
||||
));
|
||||
|
||||
export function DashboardChatPanel({
|
||||
conversation,
|
||||
isZh,
|
||||
|
|
@ -195,30 +334,12 @@ export function DashboardChatPanel({
|
|||
const hasComposerDraft = Boolean(String(command || '').trim()) || pendingAttachments.length > 0 || Boolean(quotedReply);
|
||||
return (
|
||||
<div className={`ops-chat-frame ${isChatEnabled ? '' : 'is-disabled'}`}>
|
||||
<div className="ops-chat-scroll" ref={chatScrollRef} onScroll={onChatScroll}>
|
||||
{conversation.length === 0 ? (
|
||||
<div className="ops-chat-empty">
|
||||
{labels.noConversation}
|
||||
</div>
|
||||
) : (
|
||||
<DashboardConversationMessages
|
||||
<MemoizedChatTranscript
|
||||
conversation={conversation}
|
||||
isZh={isZh}
|
||||
labels={{
|
||||
badReply: labels.badReply,
|
||||
copyPrompt: labels.copyPrompt,
|
||||
copyReply: labels.copyReply,
|
||||
deleteMessage: labels.deleteMessage,
|
||||
download: labels.download,
|
||||
editPrompt: labels.editPrompt,
|
||||
fileNotPreviewable: labels.fileNotPreviewable,
|
||||
goodReply: labels.goodReply,
|
||||
previewTitle: labels.previewTitle,
|
||||
quoteReply: labels.quoteReply,
|
||||
quotedReplyLabel: labels.quotedReplyLabel,
|
||||
user: labels.user,
|
||||
you: labels.you,
|
||||
}}
|
||||
labels={labels}
|
||||
chatScrollRef={chatScrollRef}
|
||||
onChatScroll={onChatScroll}
|
||||
expandedProgressByKey={expandedProgressByKey}
|
||||
expandedUserByKey={expandedUserByKey}
|
||||
deletingMessageIdMap={deletingMessageIdMap}
|
||||
|
|
@ -234,29 +355,8 @@ export function DashboardChatPanel({
|
|||
onSubmitAssistantFeedback={onSubmitAssistantFeedback}
|
||||
onQuoteAssistantReply={onQuoteAssistantReply}
|
||||
onCopyAssistantReply={onCopyAssistantReply}
|
||||
isThinking={isThinking}
|
||||
/>
|
||||
)}
|
||||
|
||||
{isThinking ? (
|
||||
<div className="ops-chat-row is-assistant">
|
||||
<div className="ops-chat-item is-assistant">
|
||||
<div className="ops-avatar bot" title="Nanobot">
|
||||
<img src={nanobotLogo} alt="Nanobot" />
|
||||
</div>
|
||||
<div className="ops-thinking-bubble">
|
||||
<div className="ops-thinking-cloud">
|
||||
<span className="dot" />
|
||||
<span className="dot" />
|
||||
<span className="dot" />
|
||||
</div>
|
||||
<div className="ops-thinking-text">{labels.thinking}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
<div />
|
||||
</div>
|
||||
|
||||
<div className="ops-chat-dock">
|
||||
{stagedSubmissions.length > 0 ? (
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import { ChevronDown, ChevronUp, Copy, Download, Eye, FileText, Pencil, Reply, ThumbsDown, ThumbsUp, Trash2, UserRound } from 'lucide-react';
|
||||
import { memo } from 'react';
|
||||
import ReactMarkdown, { type Components } from 'react-markdown';
|
||||
import rehypeRaw from 'rehype-raw';
|
||||
import rehypeSanitize from 'rehype-sanitize';
|
||||
|
|
@ -50,6 +51,29 @@ interface DashboardConversationMessagesProps {
|
|||
onCopyAssistantReply: (text: string) => Promise<void> | void;
|
||||
}
|
||||
|
||||
interface DashboardConversationMessageRowProps {
|
||||
item: ChatMessage;
|
||||
itemKey: string;
|
||||
isZh: boolean;
|
||||
labels: DashboardConversationLabels;
|
||||
showDateDivider: boolean;
|
||||
expandedProgress: boolean;
|
||||
expandedUser: boolean;
|
||||
isDeleting: boolean;
|
||||
isFeedbackSaving: boolean;
|
||||
markdownComponents: Components;
|
||||
workspaceDownloadExtensionSet: ReadonlySet<string>;
|
||||
onToggleProgressExpand: (key: string) => void;
|
||||
onToggleUserExpand: (key: string) => void;
|
||||
onEditUserPrompt: (text: string) => void;
|
||||
onCopyUserPrompt: (text: string) => Promise<void> | void;
|
||||
onDeleteConversationMessage: (message: ChatMessage) => Promise<void> | void;
|
||||
onOpenWorkspacePath: (path: string) => Promise<void> | void;
|
||||
onSubmitAssistantFeedback: (message: ChatMessage, direction: 'up' | 'down') => Promise<void> | void;
|
||||
onQuoteAssistantReply: (message: ChatMessage) => void;
|
||||
onCopyAssistantReply: (text: string) => Promise<void> | void;
|
||||
}
|
||||
|
||||
function shouldCollapseProgress(text: string) {
|
||||
const normalized = String(text || '').trim();
|
||||
if (!normalized) return false;
|
||||
|
|
@ -65,14 +89,23 @@ function getConversationItemKey(item: ChatMessage, idx: number) {
|
|||
return `temp:${item.role}:${item.kind || 'final'}:${item.ts}:${idx}`;
|
||||
}
|
||||
|
||||
export function DashboardConversationMessages({
|
||||
conversation,
|
||||
function sameAttachments(left?: string[], right?: string[]) {
|
||||
const aa = left || [];
|
||||
const bb = right || [];
|
||||
if (aa.length !== bb.length) return false;
|
||||
return aa.every((value, index) => value === bb[index]);
|
||||
}
|
||||
|
||||
const DashboardConversationMessageRow = memo(function DashboardConversationMessageRow({
|
||||
item,
|
||||
itemKey,
|
||||
isZh,
|
||||
labels,
|
||||
expandedProgressByKey,
|
||||
expandedUserByKey,
|
||||
deletingMessageIdMap,
|
||||
feedbackSavingByMessageId,
|
||||
showDateDivider,
|
||||
expandedProgress,
|
||||
expandedUser,
|
||||
isDeleting,
|
||||
isFeedbackSaving,
|
||||
markdownComponents,
|
||||
workspaceDownloadExtensionSet,
|
||||
onToggleProgressExpand,
|
||||
|
|
@ -84,11 +117,7 @@ export function DashboardConversationMessages({
|
|||
onSubmitAssistantFeedback,
|
||||
onQuoteAssistantReply,
|
||||
onCopyAssistantReply,
|
||||
}: DashboardConversationMessagesProps) {
|
||||
return (
|
||||
<>
|
||||
{conversation.map((item, idx) => {
|
||||
const itemKey = getConversationItemKey(item, idx);
|
||||
}: DashboardConversationMessageRowProps) {
|
||||
const isProgressBubble = item.role !== 'user' && (item.kind || 'final') === 'progress';
|
||||
const isUserBubble = item.role === 'user';
|
||||
const fullText = String(item.text || '');
|
||||
|
|
@ -99,12 +128,8 @@ export function DashboardConversationMessages({
|
|||
const userLineCount = isUserBubble ? normalizedUserText.split('\n').length : 0;
|
||||
const userCollapsible = isUserBubble && userLineCount > 5;
|
||||
const collapsible = isProgressBubble ? progressCollapsible : userCollapsible;
|
||||
const expanded = isProgressBubble ? Boolean(expandedProgressByKey[itemKey]) : Boolean(expandedUserByKey[itemKey]);
|
||||
const expanded = isProgressBubble ? expandedProgress : expandedUser;
|
||||
const displayText = isProgressBubble && !expanded ? summaryText : fullText;
|
||||
const currentDayKey = new Date(item.ts).toDateString();
|
||||
const prevDayKey = idx > 0 ? new Date(conversation[idx - 1].ts).toDateString() : '';
|
||||
const showDateDivider = idx === 0 || currentDayKey !== prevDayKey;
|
||||
const isDeleting = Boolean(item.id && deletingMessageIdMap[item.id]);
|
||||
|
||||
return (
|
||||
<div
|
||||
|
|
@ -233,7 +258,7 @@ export function DashboardConversationMessages({
|
|||
<LucentIconButton
|
||||
className={`ops-chat-inline-action ${item.feedback === 'up' ? 'active-up' : ''}`}
|
||||
onClick={() => void onSubmitAssistantFeedback(item, 'up')}
|
||||
disabled={Boolean(item.id && feedbackSavingByMessageId[item.id])}
|
||||
disabled={isFeedbackSaving}
|
||||
tooltip={labels.goodReply}
|
||||
aria-label={labels.goodReply}
|
||||
>
|
||||
|
|
@ -242,7 +267,7 @@ export function DashboardConversationMessages({
|
|||
<LucentIconButton
|
||||
className={`ops-chat-inline-action ${item.feedback === 'down' ? 'active-down' : ''}`}
|
||||
onClick={() => void onSubmitAssistantFeedback(item, 'down')}
|
||||
disabled={Boolean(item.id && feedbackSavingByMessageId[item.id])}
|
||||
disabled={isFeedbackSaving}
|
||||
tooltip={labels.badReply}
|
||||
aria-label={labels.badReply}
|
||||
>
|
||||
|
|
@ -286,6 +311,92 @@ export function DashboardConversationMessages({
|
|||
</div>
|
||||
</div>
|
||||
);
|
||||
}, (prev, next) => (
|
||||
prev.itemKey === next.itemKey
|
||||
&& prev.isZh === next.isZh
|
||||
&& prev.showDateDivider === next.showDateDivider
|
||||
&& prev.expandedProgress === next.expandedProgress
|
||||
&& prev.expandedUser === next.expandedUser
|
||||
&& prev.isDeleting === next.isDeleting
|
||||
&& prev.isFeedbackSaving === next.isFeedbackSaving
|
||||
&& prev.markdownComponents === next.markdownComponents
|
||||
&& prev.workspaceDownloadExtensionSet === next.workspaceDownloadExtensionSet
|
||||
&& prev.labels.badReply === next.labels.badReply
|
||||
&& prev.labels.copyPrompt === next.labels.copyPrompt
|
||||
&& prev.labels.copyReply === next.labels.copyReply
|
||||
&& prev.labels.deleteMessage === next.labels.deleteMessage
|
||||
&& prev.labels.download === next.labels.download
|
||||
&& prev.labels.editPrompt === next.labels.editPrompt
|
||||
&& prev.labels.fileNotPreviewable === next.labels.fileNotPreviewable
|
||||
&& prev.labels.goodReply === next.labels.goodReply
|
||||
&& prev.labels.previewTitle === next.labels.previewTitle
|
||||
&& prev.labels.quoteReply === next.labels.quoteReply
|
||||
&& prev.labels.quotedReplyLabel === next.labels.quotedReplyLabel
|
||||
&& prev.labels.user === next.labels.user
|
||||
&& prev.labels.you === next.labels.you
|
||||
&& prev.item.id === next.item.id
|
||||
&& prev.item.role === next.item.role
|
||||
&& prev.item.text === next.item.text
|
||||
&& prev.item.quoted_reply === next.item.quoted_reply
|
||||
&& prev.item.ts === next.item.ts
|
||||
&& prev.item.kind === next.item.kind
|
||||
&& prev.item.feedback === next.item.feedback
|
||||
&& sameAttachments(prev.item.attachments, next.item.attachments)
|
||||
));
|
||||
|
||||
export function DashboardConversationMessages({
|
||||
conversation,
|
||||
isZh,
|
||||
labels,
|
||||
expandedProgressByKey,
|
||||
expandedUserByKey,
|
||||
deletingMessageIdMap,
|
||||
feedbackSavingByMessageId,
|
||||
markdownComponents,
|
||||
workspaceDownloadExtensionSet,
|
||||
onToggleProgressExpand,
|
||||
onToggleUserExpand,
|
||||
onEditUserPrompt,
|
||||
onCopyUserPrompt,
|
||||
onDeleteConversationMessage,
|
||||
onOpenWorkspacePath,
|
||||
onSubmitAssistantFeedback,
|
||||
onQuoteAssistantReply,
|
||||
onCopyAssistantReply,
|
||||
}: DashboardConversationMessagesProps) {
|
||||
return (
|
||||
<>
|
||||
{conversation.map((item, idx) => {
|
||||
const itemKey = getConversationItemKey(item, idx);
|
||||
const currentDayKey = new Date(item.ts).toDateString();
|
||||
const prevDayKey = idx > 0 ? new Date(conversation[idx - 1].ts).toDateString() : '';
|
||||
const showDateDivider = idx === 0 || currentDayKey !== prevDayKey;
|
||||
|
||||
return (
|
||||
<DashboardConversationMessageRow
|
||||
key={itemKey}
|
||||
item={item}
|
||||
itemKey={itemKey}
|
||||
isZh={isZh}
|
||||
labels={labels}
|
||||
showDateDivider={showDateDivider}
|
||||
expandedProgress={Boolean(expandedProgressByKey[itemKey])}
|
||||
expandedUser={Boolean(expandedUserByKey[itemKey])}
|
||||
isDeleting={Boolean(item.id && deletingMessageIdMap[item.id])}
|
||||
isFeedbackSaving={Boolean(item.id && feedbackSavingByMessageId[item.id])}
|
||||
markdownComponents={markdownComponents}
|
||||
workspaceDownloadExtensionSet={workspaceDownloadExtensionSet}
|
||||
onToggleProgressExpand={onToggleProgressExpand}
|
||||
onToggleUserExpand={onToggleUserExpand}
|
||||
onEditUserPrompt={onEditUserPrompt}
|
||||
onCopyUserPrompt={onCopyUserPrompt}
|
||||
onDeleteConversationMessage={onDeleteConversationMessage}
|
||||
onOpenWorkspacePath={onOpenWorkspacePath}
|
||||
onSubmitAssistantFeedback={onSubmitAssistantFeedback}
|
||||
onQuoteAssistantReply={onQuoteAssistantReply}
|
||||
onCopyAssistantReply={onCopyAssistantReply}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</>
|
||||
);
|
||||
|
|
|
|||
Loading…
Reference in New Issue