Commit c3f99f53 authored by edy's avatar edy

feat(ui): add chat message delete action

parent 286f1972
...@@ -31,6 +31,7 @@ import { ...@@ -31,6 +31,7 @@ import {
RedBookIcon, RedBookIcon,
RefreshIcon, RefreshIcon,
ThumbIcon, ThumbIcon,
TrashIcon,
getIntentSuggestionIcon, getIntentSuggestionIcon,
renderExpertIcon renderExpertIcon
} from "./components/icons/AppIcons"; } from "./components/icons/AppIcons";
...@@ -404,7 +405,8 @@ export default function App() { ...@@ -404,7 +405,8 @@ export default function App() {
renameMessageTrace, renameMessageTrace,
initializeMessageTrace, initializeMessageTrace,
setMessageTraceExpanded, setMessageTraceExpanded,
collapseMessageTrace collapseMessageTrace,
removeMessageTrace
} = useMessageTraces(); } = useMessageTraces();
const { const {
saveConfig, saveConfig,
...@@ -1024,6 +1026,28 @@ export default function App() { ...@@ -1024,6 +1026,28 @@ export default function App() {
} }
} }
function deleteMessage(messageId: string) {
if (!visibleSessionId) {
return;
}
updateSessionMessages(visibleSessionId, (current) => {
const nextMessages = current.filter((message) => message.id !== messageId);
return nextMessages.length === current.length ? current : nextMessages;
});
removeMessageTrace(messageId);
setMessageReactions((current) => {
if (!(messageId in current)) {
return current;
}
const { [messageId]: _removed, ...rest } = current;
return rest;
});
if (copiedToken === `message:${messageId}`) {
setCopiedToken("");
}
}
async function regenerateAssistantMessage(messageId: string) { async function regenerateAssistantMessage(messageId: string) {
if (sending || !visibleSessionId || !sessionScopeProjectId) { if (sending || !visibleSessionId || !sessionScopeProjectId) {
return; return;
...@@ -1219,6 +1243,7 @@ export default function App() { ...@@ -1219,6 +1243,7 @@ export default function App() {
}, },
copyIcon: <CopyIcon />, copyIcon: <CopyIcon />,
copiedIcon: <CheckIcon />, copiedIcon: <CheckIcon />,
deleteIcon: <TrashIcon />,
regenerateIcon: <RefreshIcon />, regenerateIcon: <RefreshIcon />,
renderThumbIcon: (direction) => <ThumbIcon direction={direction} />, renderThumbIcon: (direction) => <ThumbIcon direction={direction} />,
renderMarkdownContent, renderMarkdownContent,
...@@ -1226,6 +1251,7 @@ export default function App() { ...@@ -1226,6 +1251,7 @@ export default function App() {
formatMessageTimestamp, formatMessageTimestamp,
onMessageListScroll: handleMessageListScroll, onMessageListScroll: handleMessageListScroll,
onCopyText: handleCopyText, onCopyText: handleCopyText,
onDeleteMessage: deleteMessage,
onTraceExpandedChange: setMessageTraceExpanded, onTraceExpandedChange: setMessageTraceExpanded,
onRegenerateAssistantMessage: regenerateAssistantMessage, onRegenerateAssistantMessage: regenerateAssistantMessage,
onToggleMessageReaction: toggleMessageReaction, onToggleMessageReaction: toggleMessageReaction,
......
...@@ -309,6 +309,17 @@ export function CheckIcon() { ...@@ -309,6 +309,17 @@ export function CheckIcon() {
); );
} }
export function TrashIcon() {
return (
<svg viewBox="0 0 24 24" fill="none" aria-hidden="true" focusable="false">
<path d="M5 7h14" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" />
<path d="M10 11v6M14 11v6" stroke="currentColor" strokeWidth="1.7" strokeLinecap="round" />
<path d="M8 7l.6 12.2A2 2 0 0 0 10.6 21h2.8a2 2 0 0 0 2-1.8L16 7" stroke="currentColor" strokeWidth="1.7" strokeLinecap="round" strokeLinejoin="round" />
<path d="M9 7V5.6A1.6 1.6 0 0 1 10.6 4h2.8A1.6 1.6 0 0 1 15 5.6V7" stroke="currentColor" strokeWidth="1.7" strokeLinecap="round" strokeLinejoin="round" />
</svg>
);
}
export function ArrowUpIcon() { export function ArrowUpIcon() {
return ( return (
<svg viewBox="0 0 24 24" fill="none" aria-hidden="true" focusable="false"> <svg viewBox="0 0 24 24" fill="none" aria-hidden="true" focusable="false">
......
...@@ -87,6 +87,7 @@ interface ConversationWorkspaceViewProps { ...@@ -87,6 +87,7 @@ interface ConversationWorkspaceViewProps {
} }
copyIcon: ReactNode copyIcon: ReactNode
copiedIcon: ReactNode copiedIcon: ReactNode
deleteIcon: ReactNode
regenerateIcon: ReactNode regenerateIcon: ReactNode
renderThumbIcon: (direction: MessageReaction) => ReactNode renderThumbIcon: (direction: MessageReaction) => ReactNode
renderMarkdownContent: ( renderMarkdownContent: (
...@@ -105,6 +106,7 @@ interface ConversationWorkspaceViewProps { ...@@ -105,6 +106,7 @@ interface ConversationWorkspaceViewProps {
formatMessageTimestamp: (value: string) => string formatMessageTimestamp: (value: string) => string
onMessageListScroll: (event: ReactUIEvent<HTMLDivElement>) => void onMessageListScroll: (event: ReactUIEvent<HTMLDivElement>) => void
onCopyText: (token: string, text: string) => void | Promise<void> onCopyText: (token: string, text: string) => void | Promise<void>
onDeleteMessage: (messageId: string) => void
onTraceExpandedChange: (messageId: string, expanded: boolean) => void onTraceExpandedChange: (messageId: string, expanded: boolean) => void
onRegenerateAssistantMessage: (messageId: string) => void | Promise<void> onRegenerateAssistantMessage: (messageId: string) => void | Promise<void>
onToggleMessageReaction: (messageId: string, reaction: MessageReaction) => void onToggleMessageReaction: (messageId: string, reaction: MessageReaction) => void
...@@ -187,6 +189,7 @@ export function ConversationWorkspaceView({ ...@@ -187,6 +189,7 @@ export function ConversationWorkspaceView({
messageLabels, messageLabels,
copyIcon, copyIcon,
copiedIcon, copiedIcon,
deleteIcon,
regenerateIcon, regenerateIcon,
renderThumbIcon, renderThumbIcon,
renderMarkdownContent, renderMarkdownContent,
...@@ -194,6 +197,7 @@ export function ConversationWorkspaceView({ ...@@ -194,6 +197,7 @@ export function ConversationWorkspaceView({
formatMessageTimestamp, formatMessageTimestamp,
onMessageListScroll, onMessageListScroll,
onCopyText, onCopyText,
onDeleteMessage,
onTraceExpandedChange, onTraceExpandedChange,
onRegenerateAssistantMessage, onRegenerateAssistantMessage,
onToggleMessageReaction, onToggleMessageReaction,
...@@ -342,6 +346,7 @@ export function ConversationWorkspaceView({ ...@@ -342,6 +346,7 @@ export function ConversationWorkspaceView({
labels={messageLabels} labels={messageLabels}
copyIcon={copyIcon} copyIcon={copyIcon}
copiedIcon={copiedIcon} copiedIcon={copiedIcon}
deleteIcon={deleteIcon}
regenerateIcon={regenerateIcon} regenerateIcon={regenerateIcon}
renderThumbIcon={renderThumbIcon} renderThumbIcon={renderThumbIcon}
renderMarkdownContent={renderMarkdownContent} renderMarkdownContent={renderMarkdownContent}
...@@ -349,6 +354,7 @@ export function ConversationWorkspaceView({ ...@@ -349,6 +354,7 @@ export function ConversationWorkspaceView({
formatMessageTimestamp={formatMessageTimestamp} formatMessageTimestamp={formatMessageTimestamp}
onScroll={onMessageListScroll} onScroll={onMessageListScroll}
onCopyText={onCopyText} onCopyText={onCopyText}
onDeleteMessage={onDeleteMessage}
onTraceExpandedChange={onTraceExpandedChange} onTraceExpandedChange={onTraceExpandedChange}
onRegenerateAssistantMessage={onRegenerateAssistantMessage} onRegenerateAssistantMessage={onRegenerateAssistantMessage}
onToggleMessageReaction={onToggleMessageReaction} onToggleMessageReaction={onToggleMessageReaction}
......
This diff is collapsed.
...@@ -96,12 +96,23 @@ export function useMessageTraces() { ...@@ -96,12 +96,23 @@ export function useMessageTraces() {
setMessageTraceExpanded(messageId, false) setMessageTraceExpanded(messageId, false)
}, [setMessageTraceExpanded]) }, [setMessageTraceExpanded])
const removeMessageTrace = useCallback((messageId: string) => {
setMessageTraces((current) => {
if (!(messageId in current)) {
return current
}
const { [messageId]: _removed, ...rest } = current
return rest
})
}, [])
return { return {
messageTraces, messageTraces,
renameMessageTrace, renameMessageTrace,
initializeMessageTrace, initializeMessageTrace,
appendTrace, appendTrace,
setMessageTraceExpanded, setMessageTraceExpanded,
collapseMessageTrace collapseMessageTrace,
removeMessageTrace
} }
} }
...@@ -747,10 +747,17 @@ ...@@ -747,10 +747,17 @@
} }
.conversation-shell .message-bubble, .conversation-shell .message-bubble,
.conversation-shell .message-card-body,
.conversation-shell .message-bubble-assistant, .conversation-shell .message-bubble-assistant,
.conversation-shell .message-card.assistant .message-bubble { .conversation-shell .message-card.assistant .message-bubble {
max-width: 100%; max-width: 100%;
} }
.conversation-shell .message-card-meta {
opacity: 0.76;
pointer-events: auto;
transform: translateY(0);
}
} }
@media (max-width: 720px) { @media (max-width: 720px) {
......
...@@ -270,6 +270,33 @@ ...@@ -270,6 +270,33 @@
margin-top: 6px; margin-top: 6px;
} }
.message-card-body {
display: grid;
gap: 4px;
max-width: 100%;
}
.message-card-body-user {
justify-self: end;
}
.message-card-body-assistant {
justify-self: start;
}
.message-card-meta {
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
min-height: 28px;
padding: 0 2px;
opacity: 0;
pointer-events: none;
transform: translateY(-2px);
transition: opacity 150ms ease, transform 150ms ease;
}
.thinking-spinner { .thinking-spinner {
width: 16px; width: 16px;
height: 16px; height: 16px;
...@@ -819,30 +846,57 @@ ...@@ -819,30 +846,57 @@
.message-card-actions { .message-card-actions {
display: flex; display: flex;
align-items: center; align-items: center;
gap: 8px; gap: 1px;
margin-top: 12px; width: max-content;
justify-content: flex-start; margin-top: 0;
opacity: 0; padding: 2px;
transition: opacity 150ms ease; border: 1px solid rgba(203, 213, 225, 0.68);
border-radius: 999px;
background: rgba(248, 250, 252, 0.9);
box-shadow: 0 6px 18px rgba(15, 23, 42, 0.06);
backdrop-filter: blur(12px);
justify-content: flex-end;
pointer-events: auto;
opacity: 1;
} }
.message-card:hover .message-card-actions { .message-card:hover .message-card-meta,
.message-card:focus-within .message-card-meta {
pointer-events: auto;
opacity: 1; opacity: 1;
transform: translateY(0);
}
.message-action-delete:hover {
background: #fef2f2;
color: #dc2626;
} }
.message-action-icon { .message-action-icon {
width: 32px; width: 26px;
height: 32px; height: 26px;
padding: 0; padding: 0;
border: 0; border: 0;
border-radius: 999px; border-radius: 999px;
background: #ffffff; background: transparent;
color: #5f7773; color: #738196;
box-shadow: 0 10px 24px rgba(17, 24, 39, 0.08); cursor: pointer;
box-shadow: none;
transition: background-color 150ms ease, color 150ms ease, transform 120ms ease;
} }
.message-action-icon:hover { .message-action-icon:hover {
background: #f4fbfa; background: #eef6ff;
color: #0f67de;
}
.message-action-icon:active {
transform: scale(0.94);
}
.message-action-icon:focus-visible {
outline: 2px solid rgba(15, 103, 222, 0.28);
outline-offset: 2px;
} }
.message-action-icon.copied { .message-action-icon.copied {
......
...@@ -1014,6 +1014,23 @@ ...@@ -1014,6 +1014,23 @@
justify-content: flex-start; justify-content: flex-start;
} }
.conversation-shell .message-card-body {
width: auto;
max-width: min(72%, 760px);
}
.conversation-shell .message-card-body-user {
margin-left: auto;
}
.conversation-shell .message-card-body-assistant {
max-width: min(100%, 880px);
}
.conversation-shell .message-card-body .message-bubble {
max-width: 100%;
}
.conversation-shell .message-bubble { .conversation-shell .message-bubble {
width: auto; width: auto;
display: grid; display: grid;
...@@ -1095,30 +1112,17 @@ ...@@ -1095,30 +1112,17 @@
} }
.conversation-shell .message-timestamp { .conversation-shell .message-timestamp {
position: absolute; position: static;
bottom: 0;
left: 4px;
z-index: 1;
color: #7c8da3; color: #7c8da3;
font-size: 11px; font-size: 11px;
line-height: 1; line-height: 1;
opacity: 0;
pointer-events: none;
transition: opacity 140ms ease;
}
.conversation-shell .message-card.user .message-timestamp {
left: auto;
right: 4px;
}
.conversation-shell .message-card:hover .message-timestamp,
.conversation-shell .message-card:focus-within .message-timestamp {
opacity: 1; opacity: 1;
pointer-events: none;
white-space: nowrap;
} }
.conversation-shell .message-card-actions { .conversation-shell .message-card-actions {
margin-top: 8px; margin-right: 0;
} }
.conversation-shell .composer-shell { .conversation-shell .composer-shell {
......
...@@ -73,6 +73,7 @@ ...@@ -73,6 +73,7 @@
.nav-item-icon svg, .nav-item-icon svg,
.expert-chip-icon svg, .expert-chip-icon svg,
.message-action-icon svg, .message-action-icon svg,
.message-action-delete svg,
.composer-submit svg, .composer-submit svg,
.attachment-trigger svg, .attachment-trigger svg,
.markdown-code-copy svg { .markdown-code-copy svg {
...@@ -104,11 +105,13 @@ ...@@ -104,11 +105,13 @@
@apply mb-0; @apply mb-0;
} }
.message-card.assistant .message-card-actions { .message-card .message-card-meta {
@apply pointer-events-none; @apply pointer-events-none;
} }
.message-card.assistant:hover .message-card-actions { .message-card:hover .message-card-meta,
.message-card:focus-within .message-card-meta,
.message-card .message-card-meta:focus-within {
@apply pointer-events-auto; @apply pointer-events-auto;
} }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment