v0.1.4-p4
parent
3ca7eff38b
commit
08b35d632b
|
|
@ -216,11 +216,10 @@
|
||||||
display: grid;
|
display: grid;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
padding: 10px;
|
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;
|
border-radius: 14px;
|
||||||
background:
|
background: color-mix(in oklab, #eef1f4 82%, var(--panel) 18%);
|
||||||
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.16);
|
||||||
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.22);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.ops-staged-submission-item {
|
.ops-staged-submission-item {
|
||||||
|
|
@ -229,9 +228,9 @@
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 10px;
|
gap: 10px;
|
||||||
padding: 8px 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;
|
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 {
|
.ops-staged-submission-index {
|
||||||
|
|
@ -242,9 +241,9 @@
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
font-size: 11px;
|
font-size: 11px;
|
||||||
color: var(--brand);
|
color: color-mix(in oklab, var(--text) 72%, var(--muted) 28%);
|
||||||
background: color-mix(in oklab, var(--panel) 64%, white 36%);
|
background: color-mix(in oklab, #ffffff 72%, #e5e9ee 28%);
|
||||||
border: 1px solid color-mix(in oklab, var(--line) 70%, transparent);
|
border: 1px solid color-mix(in oklab, #c7ced6 46%, transparent);
|
||||||
}
|
}
|
||||||
|
|
||||||
.ops-staged-submission-body {
|
.ops-staged-submission-body {
|
||||||
|
|
@ -272,12 +271,12 @@
|
||||||
.ops-staged-submission-pill {
|
.ops-staged-submission-pill {
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
align-items: center;
|
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;
|
border-radius: 999px;
|
||||||
padding: 2px 8px;
|
padding: 2px 8px;
|
||||||
font-size: 11px;
|
font-size: 11px;
|
||||||
color: var(--subtitle);
|
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 {
|
.ops-staged-submission-actions {
|
||||||
|
|
@ -290,9 +289,9 @@
|
||||||
width: 28px;
|
width: 28px;
|
||||||
height: 28px;
|
height: 28px;
|
||||||
padding: 0;
|
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;
|
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);
|
color: var(--icon-muted);
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
@ -303,8 +302,8 @@
|
||||||
|
|
||||||
.ops-staged-submission-icon-btn:hover:not(:disabled) {
|
.ops-staged-submission-icon-btn:hover:not(:disabled) {
|
||||||
color: var(--icon);
|
color: var(--icon);
|
||||||
background: color-mix(in oklab, var(--panel) 70%, var(--brand-soft) 30%);
|
background: color-mix(in oklab, #edf1f5 78%, #d8dde4 22%);
|
||||||
border-color: color-mix(in oklab, var(--brand) 38%, var(--line) 62%);
|
border-color: color-mix(in oklab, #97a3af 34%, var(--line) 66%);
|
||||||
transform: translateY(-1px);
|
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 { 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 { 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 { LucentIconButton } from '../../../components/lucent/LucentIconButton';
|
||||||
import nanobotLogo from '../../../assets/nanobot-logo.png';
|
import nanobotLogo from '../../../assets/nanobot-logo.png';
|
||||||
|
|
@ -121,6 +121,145 @@ interface DashboardChatPanelProps {
|
||||||
onSubmitAction: () => Promise<void> | void;
|
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({
|
export function DashboardChatPanel({
|
||||||
conversation,
|
conversation,
|
||||||
isZh,
|
isZh,
|
||||||
|
|
@ -195,30 +334,12 @@ export function DashboardChatPanel({
|
||||||
const hasComposerDraft = Boolean(String(command || '').trim()) || pendingAttachments.length > 0 || Boolean(quotedReply);
|
const hasComposerDraft = Boolean(String(command || '').trim()) || pendingAttachments.length > 0 || Boolean(quotedReply);
|
||||||
return (
|
return (
|
||||||
<div className={`ops-chat-frame ${isChatEnabled ? '' : 'is-disabled'}`}>
|
<div className={`ops-chat-frame ${isChatEnabled ? '' : 'is-disabled'}`}>
|
||||||
<div className="ops-chat-scroll" ref={chatScrollRef} onScroll={onChatScroll}>
|
<MemoizedChatTranscript
|
||||||
{conversation.length === 0 ? (
|
|
||||||
<div className="ops-chat-empty">
|
|
||||||
{labels.noConversation}
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<DashboardConversationMessages
|
|
||||||
conversation={conversation}
|
conversation={conversation}
|
||||||
isZh={isZh}
|
isZh={isZh}
|
||||||
labels={{
|
labels={labels}
|
||||||
badReply: labels.badReply,
|
chatScrollRef={chatScrollRef}
|
||||||
copyPrompt: labels.copyPrompt,
|
onChatScroll={onChatScroll}
|
||||||
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}
|
expandedProgressByKey={expandedProgressByKey}
|
||||||
expandedUserByKey={expandedUserByKey}
|
expandedUserByKey={expandedUserByKey}
|
||||||
deletingMessageIdMap={deletingMessageIdMap}
|
deletingMessageIdMap={deletingMessageIdMap}
|
||||||
|
|
@ -234,29 +355,8 @@ export function DashboardChatPanel({
|
||||||
onSubmitAssistantFeedback={onSubmitAssistantFeedback}
|
onSubmitAssistantFeedback={onSubmitAssistantFeedback}
|
||||||
onQuoteAssistantReply={onQuoteAssistantReply}
|
onQuoteAssistantReply={onQuoteAssistantReply}
|
||||||
onCopyAssistantReply={onCopyAssistantReply}
|
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">
|
<div className="ops-chat-dock">
|
||||||
{stagedSubmissions.length > 0 ? (
|
{stagedSubmissions.length > 0 ? (
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import { ChevronDown, ChevronUp, Copy, Download, Eye, FileText, Pencil, Reply, ThumbsDown, ThumbsUp, Trash2, UserRound } from 'lucide-react';
|
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 ReactMarkdown, { type Components } from 'react-markdown';
|
||||||
import rehypeRaw from 'rehype-raw';
|
import rehypeRaw from 'rehype-raw';
|
||||||
import rehypeSanitize from 'rehype-sanitize';
|
import rehypeSanitize from 'rehype-sanitize';
|
||||||
|
|
@ -50,6 +51,29 @@ interface DashboardConversationMessagesProps {
|
||||||
onCopyAssistantReply: (text: string) => Promise<void> | void;
|
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) {
|
function shouldCollapseProgress(text: string) {
|
||||||
const normalized = String(text || '').trim();
|
const normalized = String(text || '').trim();
|
||||||
if (!normalized) return false;
|
if (!normalized) return false;
|
||||||
|
|
@ -65,14 +89,23 @@ function getConversationItemKey(item: ChatMessage, idx: number) {
|
||||||
return `temp:${item.role}:${item.kind || 'final'}:${item.ts}:${idx}`;
|
return `temp:${item.role}:${item.kind || 'final'}:${item.ts}:${idx}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function DashboardConversationMessages({
|
function sameAttachments(left?: string[], right?: string[]) {
|
||||||
conversation,
|
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,
|
isZh,
|
||||||
labels,
|
labels,
|
||||||
expandedProgressByKey,
|
showDateDivider,
|
||||||
expandedUserByKey,
|
expandedProgress,
|
||||||
deletingMessageIdMap,
|
expandedUser,
|
||||||
feedbackSavingByMessageId,
|
isDeleting,
|
||||||
|
isFeedbackSaving,
|
||||||
markdownComponents,
|
markdownComponents,
|
||||||
workspaceDownloadExtensionSet,
|
workspaceDownloadExtensionSet,
|
||||||
onToggleProgressExpand,
|
onToggleProgressExpand,
|
||||||
|
|
@ -84,11 +117,7 @@ export function DashboardConversationMessages({
|
||||||
onSubmitAssistantFeedback,
|
onSubmitAssistantFeedback,
|
||||||
onQuoteAssistantReply,
|
onQuoteAssistantReply,
|
||||||
onCopyAssistantReply,
|
onCopyAssistantReply,
|
||||||
}: DashboardConversationMessagesProps) {
|
}: DashboardConversationMessageRowProps) {
|
||||||
return (
|
|
||||||
<>
|
|
||||||
{conversation.map((item, idx) => {
|
|
||||||
const itemKey = getConversationItemKey(item, idx);
|
|
||||||
const isProgressBubble = item.role !== 'user' && (item.kind || 'final') === 'progress';
|
const isProgressBubble = item.role !== 'user' && (item.kind || 'final') === 'progress';
|
||||||
const isUserBubble = item.role === 'user';
|
const isUserBubble = item.role === 'user';
|
||||||
const fullText = String(item.text || '');
|
const fullText = String(item.text || '');
|
||||||
|
|
@ -99,12 +128,8 @@ export function DashboardConversationMessages({
|
||||||
const userLineCount = isUserBubble ? normalizedUserText.split('\n').length : 0;
|
const userLineCount = isUserBubble ? normalizedUserText.split('\n').length : 0;
|
||||||
const userCollapsible = isUserBubble && userLineCount > 5;
|
const userCollapsible = isUserBubble && userLineCount > 5;
|
||||||
const collapsible = isProgressBubble ? progressCollapsible : userCollapsible;
|
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 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 (
|
return (
|
||||||
<div
|
<div
|
||||||
|
|
@ -233,7 +258,7 @@ export function DashboardConversationMessages({
|
||||||
<LucentIconButton
|
<LucentIconButton
|
||||||
className={`ops-chat-inline-action ${item.feedback === 'up' ? 'active-up' : ''}`}
|
className={`ops-chat-inline-action ${item.feedback === 'up' ? 'active-up' : ''}`}
|
||||||
onClick={() => void onSubmitAssistantFeedback(item, 'up')}
|
onClick={() => void onSubmitAssistantFeedback(item, 'up')}
|
||||||
disabled={Boolean(item.id && feedbackSavingByMessageId[item.id])}
|
disabled={isFeedbackSaving}
|
||||||
tooltip={labels.goodReply}
|
tooltip={labels.goodReply}
|
||||||
aria-label={labels.goodReply}
|
aria-label={labels.goodReply}
|
||||||
>
|
>
|
||||||
|
|
@ -242,7 +267,7 @@ export function DashboardConversationMessages({
|
||||||
<LucentIconButton
|
<LucentIconButton
|
||||||
className={`ops-chat-inline-action ${item.feedback === 'down' ? 'active-down' : ''}`}
|
className={`ops-chat-inline-action ${item.feedback === 'down' ? 'active-down' : ''}`}
|
||||||
onClick={() => void onSubmitAssistantFeedback(item, 'down')}
|
onClick={() => void onSubmitAssistantFeedback(item, 'down')}
|
||||||
disabled={Boolean(item.id && feedbackSavingByMessageId[item.id])}
|
disabled={isFeedbackSaving}
|
||||||
tooltip={labels.badReply}
|
tooltip={labels.badReply}
|
||||||
aria-label={labels.badReply}
|
aria-label={labels.badReply}
|
||||||
>
|
>
|
||||||
|
|
@ -286,6 +311,92 @@ export function DashboardConversationMessages({
|
||||||
</div>
|
</div>
|
||||||
</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