feat(ui): add delete-up-to action and range hover overlay
This commit is contained in:
@@ -22,6 +22,7 @@ interface MessageBlockListProps {
|
|||||||
scrollContainer: Accessor<HTMLDivElement | undefined>
|
scrollContainer: Accessor<HTMLDivElement | undefined>
|
||||||
loading?: boolean
|
loading?: boolean
|
||||||
onRevert?: (messageId: string) => void
|
onRevert?: (messageId: string) => void
|
||||||
|
onDeleteMessagesUpTo?: (messageId: string) => void | Promise<void>
|
||||||
onFork?: (messageId?: string) => void
|
onFork?: (messageId?: string) => void
|
||||||
onContentRendered?: () => void
|
onContentRendered?: () => void
|
||||||
deleteHover?: Accessor<DeleteHoverState>
|
deleteHover?: Accessor<DeleteHoverState>
|
||||||
@@ -57,6 +58,7 @@ export default function MessageBlockList(props: MessageBlockListProps) {
|
|||||||
deleteHover={props.deleteHover}
|
deleteHover={props.deleteHover}
|
||||||
onDeleteHoverChange={props.onDeleteHoverChange}
|
onDeleteHoverChange={props.onDeleteHoverChange}
|
||||||
onRevert={props.onRevert}
|
onRevert={props.onRevert}
|
||||||
|
onDeleteMessagesUpTo={props.onDeleteMessagesUpTo}
|
||||||
onFork={props.onFork}
|
onFork={props.onFork}
|
||||||
onContentRendered={props.onContentRendered}
|
onContentRendered={props.onContentRendered}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { For, Match, Show, Switch, createEffect, createMemo, createSignal, untrack } from "solid-js"
|
import { For, Match, Show, Switch, createEffect, createMemo, createSignal, untrack } from "solid-js"
|
||||||
import { ChevronsDownUp, ChevronsUpDown, ExternalLink, FoldVertical, Trash2 } from "lucide-solid"
|
import { ChevronsDownUp, ChevronsUpDown, ExternalLink, FoldVertical, ListStart, Trash } from "lucide-solid"
|
||||||
import MessageItem from "./message-item"
|
import MessageItem from "./message-item"
|
||||||
import ToolCall from "./tool-call"
|
import ToolCall from "./tool-call"
|
||||||
import type { InstanceMessageStore } from "../stores/message-v2/instance-store"
|
import type { InstanceMessageStore } from "../stores/message-v2/instance-store"
|
||||||
@@ -16,6 +16,14 @@ import { deleteMessage } from "../stores/session-actions"
|
|||||||
import { useI18n } from "../lib/i18n"
|
import { useI18n } from "../lib/i18n"
|
||||||
import type { DeleteHoverState } from "../types/delete-hover"
|
import type { DeleteHoverState } from "../types/delete-hover"
|
||||||
|
|
||||||
|
function DeleteUpToIcon() {
|
||||||
|
return (
|
||||||
|
<span class="relative inline-block w-3.5 h-3.5" aria-hidden="true">
|
||||||
|
<ListStart class="absolute inset-0 w-3.5 h-3.5" aria-hidden="true" />
|
||||||
|
</span>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
const TOOL_ICON = "🔧"
|
const TOOL_ICON = "🔧"
|
||||||
const USER_BORDER_COLOR = "var(--message-user-border)"
|
const USER_BORDER_COLOR = "var(--message-user-border)"
|
||||||
const ASSISTANT_BORDER_COLOR = "var(--message-assistant-border)"
|
const ASSISTANT_BORDER_COLOR = "var(--message-assistant-border)"
|
||||||
@@ -195,6 +203,7 @@ interface MessageContentItemProps {
|
|||||||
messageIndex: number
|
messageIndex: number
|
||||||
lastAssistantIndex: () => number
|
lastAssistantIndex: () => number
|
||||||
onRevert?: (messageId: string) => void
|
onRevert?: (messageId: string) => void
|
||||||
|
onDeleteMessagesUpTo?: (messageId: string) => void | Promise<void>
|
||||||
onFork?: (messageId?: string) => void
|
onFork?: (messageId?: string) => void
|
||||||
onContentRendered?: () => void
|
onContentRendered?: () => void
|
||||||
showDeleteMessage?: boolean
|
showDeleteMessage?: boolean
|
||||||
@@ -288,6 +297,7 @@ function MessageContentItem(props: MessageContentItemProps) {
|
|||||||
showDeleteMessage={props.showDeleteMessage}
|
showDeleteMessage={props.showDeleteMessage}
|
||||||
onDeleteHoverChange={props.onDeleteHoverChange}
|
onDeleteHoverChange={props.onDeleteHoverChange}
|
||||||
onRevert={props.onRevert}
|
onRevert={props.onRevert}
|
||||||
|
onDeleteMessagesUpTo={props.onDeleteMessagesUpTo}
|
||||||
onFork={props.onFork}
|
onFork={props.onFork}
|
||||||
onContentRendered={props.onContentRendered}
|
onContentRendered={props.onContentRendered}
|
||||||
/>
|
/>
|
||||||
@@ -305,11 +315,13 @@ interface ToolCallItemProps {
|
|||||||
onContentRendered?: () => void
|
onContentRendered?: () => void
|
||||||
showDeleteMessage?: boolean
|
showDeleteMessage?: boolean
|
||||||
onDeleteHoverChange?: (state: DeleteHoverState) => void
|
onDeleteHoverChange?: (state: DeleteHoverState) => void
|
||||||
|
onDeleteMessagesUpTo?: (messageId: string) => void | Promise<void>
|
||||||
}
|
}
|
||||||
|
|
||||||
function ToolCallItem(props: ToolCallItemProps) {
|
function ToolCallItem(props: ToolCallItemProps) {
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
const [deletingMessage, setDeletingMessage] = createSignal(false)
|
const [deletingMessage, setDeletingMessage] = createSignal(false)
|
||||||
|
const [deletingUpTo, setDeletingUpTo] = createSignal(false)
|
||||||
|
|
||||||
const record = createMemo(() => props.store().getMessage(props.messageId))
|
const record = createMemo(() => props.store().getMessage(props.messageId))
|
||||||
const messageInfo = createMemo(() => props.store().getMessageInfo(props.messageId))
|
const messageInfo = createMemo(() => props.store().getMessageInfo(props.messageId))
|
||||||
@@ -370,6 +382,21 @@ function ToolCallItem(props: ToolCallItemProps) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const handleDeleteUpTo = async (event: MouseEvent) => {
|
||||||
|
event.preventDefault()
|
||||||
|
event.stopPropagation()
|
||||||
|
if (!props.showDeleteMessage) return
|
||||||
|
if (!props.onDeleteMessagesUpTo) return
|
||||||
|
if (deletingUpTo()) return
|
||||||
|
|
||||||
|
setDeletingUpTo(true)
|
||||||
|
try {
|
||||||
|
await props.onDeleteMessagesUpTo(props.messageId)
|
||||||
|
} finally {
|
||||||
|
setDeletingUpTo(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Show when={toolPart()}>
|
<Show when={toolPart()}>
|
||||||
{(resolvedToolPart) => (
|
{(resolvedToolPart) => (
|
||||||
@@ -396,6 +423,19 @@ function ToolCallItem(props: ToolCallItemProps) {
|
|||||||
</Show>
|
</Show>
|
||||||
|
|
||||||
<Show when={props.showDeleteMessage}>
|
<Show when={props.showDeleteMessage}>
|
||||||
|
<button
|
||||||
|
class="tool-call-header-button"
|
||||||
|
type="button"
|
||||||
|
disabled={!props.onDeleteMessagesUpTo || deletingUpTo()}
|
||||||
|
onClick={handleDeleteUpTo}
|
||||||
|
onMouseEnter={() => props.onDeleteHoverChange?.({ kind: "deleteUpTo", messageId: props.messageId })}
|
||||||
|
onMouseLeave={() => props.onDeleteHoverChange?.({ kind: "none" })}
|
||||||
|
title={t("messageItem.actions.deleteMessagesUpTo")}
|
||||||
|
aria-label={t("messageItem.actions.deleteMessagesUpTo")}
|
||||||
|
>
|
||||||
|
<DeleteUpToIcon />
|
||||||
|
</button>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
class="tool-call-header-button"
|
class="tool-call-header-button"
|
||||||
type="button"
|
type="button"
|
||||||
@@ -406,7 +446,7 @@ function ToolCallItem(props: ToolCallItemProps) {
|
|||||||
title={deletingMessage() ? t("messageItem.actions.deletingMessage") : t("messageItem.actions.deleteMessage")}
|
title={deletingMessage() ? t("messageItem.actions.deletingMessage") : t("messageItem.actions.deleteMessage")}
|
||||||
aria-label={deletingMessage() ? t("messageItem.actions.deletingMessage") : t("messageItem.actions.deleteMessage")}
|
aria-label={deletingMessage() ? t("messageItem.actions.deletingMessage") : t("messageItem.actions.deleteMessage")}
|
||||||
>
|
>
|
||||||
<Trash2 class="w-3.5 h-3.5" aria-hidden="true" />
|
<Trash class="w-3.5 h-3.5" aria-hidden="true" />
|
||||||
</button>
|
</button>
|
||||||
</Show>
|
</Show>
|
||||||
</div>
|
</div>
|
||||||
@@ -477,6 +517,7 @@ interface MessageBlockProps {
|
|||||||
deleteHover?: () => DeleteHoverState
|
deleteHover?: () => DeleteHoverState
|
||||||
onDeleteHoverChange?: (state: DeleteHoverState) => void
|
onDeleteHoverChange?: (state: DeleteHoverState) => void
|
||||||
onRevert?: (messageId: string) => void
|
onRevert?: (messageId: string) => void
|
||||||
|
onDeleteMessagesUpTo?: (messageId: string) => void | Promise<void>
|
||||||
onFork?: (messageId?: string) => void
|
onFork?: (messageId?: string) => void
|
||||||
onContentRendered?: () => void
|
onContentRendered?: () => void
|
||||||
}
|
}
|
||||||
@@ -489,7 +530,21 @@ export default function MessageBlock(props: MessageBlockProps) {
|
|||||||
|
|
||||||
const isDeleteMessageHovered = () => {
|
const isDeleteMessageHovered = () => {
|
||||||
const hover = props.deleteHover?.() ?? ({ kind: "none" } as DeleteHoverState)
|
const hover = props.deleteHover?.() ?? ({ kind: "none" } as DeleteHoverState)
|
||||||
return hover.kind === "message" && hover.messageId === props.messageId
|
|
||||||
|
if (hover.kind === "message") {
|
||||||
|
return hover.messageId === props.messageId
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hover.kind === "deleteUpTo") {
|
||||||
|
const ids = props.store().getSessionMessageIds(props.sessionId)
|
||||||
|
const targetIndex = ids.indexOf(hover.messageId)
|
||||||
|
if (targetIndex === -1) return false
|
||||||
|
const currentIndex = ids.indexOf(props.messageId)
|
||||||
|
if (currentIndex === -1) return false
|
||||||
|
return currentIndex >= targetIndex
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
const block = createMemo<MessageDisplayBlock | null>(() => {
|
const block = createMemo<MessageDisplayBlock | null>(() => {
|
||||||
@@ -699,6 +754,7 @@ export default function MessageBlock(props: MessageBlockProps) {
|
|||||||
showDeleteMessage={index() === 0}
|
showDeleteMessage={index() === 0}
|
||||||
onDeleteHoverChange={props.onDeleteHoverChange}
|
onDeleteHoverChange={props.onDeleteHoverChange}
|
||||||
onRevert={props.onRevert}
|
onRevert={props.onRevert}
|
||||||
|
onDeleteMessagesUpTo={props.onDeleteMessagesUpTo}
|
||||||
onFork={props.onFork}
|
onFork={props.onFork}
|
||||||
onContentRendered={props.onContentRendered}
|
onContentRendered={props.onContentRendered}
|
||||||
/>
|
/>
|
||||||
@@ -716,6 +772,7 @@ export default function MessageBlock(props: MessageBlockProps) {
|
|||||||
partId={toolItem.partId}
|
partId={toolItem.partId}
|
||||||
showDeleteMessage={index() === 0}
|
showDeleteMessage={index() === 0}
|
||||||
onDeleteHoverChange={props.onDeleteHoverChange}
|
onDeleteHoverChange={props.onDeleteHoverChange}
|
||||||
|
onDeleteMessagesUpTo={props.onDeleteMessagesUpTo}
|
||||||
onContentRendered={props.onContentRendered}
|
onContentRendered={props.onContentRendered}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -733,6 +790,7 @@ export default function MessageBlock(props: MessageBlockProps) {
|
|||||||
sessionId={props.sessionId}
|
sessionId={props.sessionId}
|
||||||
messageId={props.messageId}
|
messageId={props.messageId}
|
||||||
onDeleteHoverChange={props.onDeleteHoverChange}
|
onDeleteHoverChange={props.onDeleteHoverChange}
|
||||||
|
onDeleteMessagesUpTo={props.onDeleteMessagesUpTo}
|
||||||
/>
|
/>
|
||||||
</Match>
|
</Match>
|
||||||
<Match when={item.type === "step-finish"}>
|
<Match when={item.type === "step-finish"}>
|
||||||
@@ -747,6 +805,7 @@ export default function MessageBlock(props: MessageBlockProps) {
|
|||||||
sessionId={props.sessionId}
|
sessionId={props.sessionId}
|
||||||
messageId={props.messageId}
|
messageId={props.messageId}
|
||||||
onDeleteHoverChange={props.onDeleteHoverChange}
|
onDeleteHoverChange={props.onDeleteHoverChange}
|
||||||
|
onDeleteMessagesUpTo={props.onDeleteMessagesUpTo}
|
||||||
/>
|
/>
|
||||||
</Match>
|
</Match>
|
||||||
<Match when={item.type === "compaction"}>
|
<Match when={item.type === "compaction"}>
|
||||||
@@ -759,6 +818,7 @@ export default function MessageBlock(props: MessageBlockProps) {
|
|||||||
messageId={(item as CompactionDisplayItem).messageId}
|
messageId={(item as CompactionDisplayItem).messageId}
|
||||||
showDeleteMessage={index() === 0}
|
showDeleteMessage={index() === 0}
|
||||||
onDeleteHoverChange={props.onDeleteHoverChange}
|
onDeleteHoverChange={props.onDeleteHoverChange}
|
||||||
|
onDeleteMessagesUpTo={props.onDeleteMessagesUpTo}
|
||||||
/>
|
/>
|
||||||
</Match>
|
</Match>
|
||||||
<Match when={item.type === "reasoning"}>
|
<Match when={item.type === "reasoning"}>
|
||||||
@@ -772,6 +832,7 @@ export default function MessageBlock(props: MessageBlockProps) {
|
|||||||
defaultExpanded={(item as ReasoningDisplayItem).defaultExpanded}
|
defaultExpanded={(item as ReasoningDisplayItem).defaultExpanded}
|
||||||
showDeleteMessage={index() === 0}
|
showDeleteMessage={index() === 0}
|
||||||
onDeleteHoverChange={props.onDeleteHoverChange}
|
onDeleteHoverChange={props.onDeleteHoverChange}
|
||||||
|
onDeleteMessagesUpTo={props.onDeleteMessagesUpTo}
|
||||||
/>
|
/>
|
||||||
</Match>
|
</Match>
|
||||||
</Switch>
|
</Switch>
|
||||||
@@ -795,6 +856,7 @@ interface StepCardProps {
|
|||||||
sessionId?: string
|
sessionId?: string
|
||||||
messageId?: string
|
messageId?: string
|
||||||
onDeleteHoverChange?: (state: DeleteHoverState) => void
|
onDeleteHoverChange?: (state: DeleteHoverState) => void
|
||||||
|
onDeleteMessagesUpTo?: (messageId: string) => void | Promise<void>
|
||||||
}
|
}
|
||||||
|
|
||||||
interface CompactionCardProps {
|
interface CompactionCardProps {
|
||||||
@@ -806,11 +868,13 @@ interface CompactionCardProps {
|
|||||||
messageId: string
|
messageId: string
|
||||||
showDeleteMessage?: boolean
|
showDeleteMessage?: boolean
|
||||||
onDeleteHoverChange?: (state: DeleteHoverState) => void
|
onDeleteHoverChange?: (state: DeleteHoverState) => void
|
||||||
|
onDeleteMessagesUpTo?: (messageId: string) => void | Promise<void>
|
||||||
}
|
}
|
||||||
|
|
||||||
function CompactionCard(props: CompactionCardProps) {
|
function CompactionCard(props: CompactionCardProps) {
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
const [deletingMessage, setDeletingMessage] = createSignal(false)
|
const [deletingMessage, setDeletingMessage] = createSignal(false)
|
||||||
|
const [deletingUpTo, setDeletingUpTo] = createSignal(false)
|
||||||
const isAuto = () => Boolean((props.part as any)?.auto)
|
const isAuto = () => Boolean((props.part as any)?.auto)
|
||||||
const label = () => (isAuto() ? t("messageBlock.compaction.autoLabel") : t("messageBlock.compaction.manualLabel"))
|
const label = () => (isAuto() ? t("messageBlock.compaction.autoLabel") : t("messageBlock.compaction.manualLabel"))
|
||||||
const borderColor = () => props.borderColor ?? (isAuto() ? "var(--session-status-compacting-fg)" : USER_BORDER_COLOR)
|
const borderColor = () => props.borderColor ?? (isAuto() ? "var(--session-status-compacting-fg)" : USER_BORDER_COLOR)
|
||||||
@@ -839,6 +903,21 @@ function CompactionCard(props: CompactionCardProps) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const handleDeleteUpTo = async (event: MouseEvent) => {
|
||||||
|
event.preventDefault()
|
||||||
|
event.stopPropagation()
|
||||||
|
if (!props.showDeleteMessage) return
|
||||||
|
if (!props.onDeleteMessagesUpTo) return
|
||||||
|
if (deletingUpTo()) return
|
||||||
|
|
||||||
|
setDeletingUpTo(true)
|
||||||
|
try {
|
||||||
|
await props.onDeleteMessagesUpTo(props.messageId)
|
||||||
|
} finally {
|
||||||
|
setDeletingUpTo(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
class={`delete-hover-scope ${containerClass()} relative`}
|
class={`delete-hover-scope ${containerClass()} relative`}
|
||||||
@@ -848,6 +927,19 @@ function CompactionCard(props: CompactionCardProps) {
|
|||||||
>
|
>
|
||||||
<div class="absolute right-2 top-1/2 -translate-y-1/2 flex items-center gap-1">
|
<div class="absolute right-2 top-1/2 -translate-y-1/2 flex items-center gap-1">
|
||||||
<Show when={props.showDeleteMessage}>
|
<Show when={props.showDeleteMessage}>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="tool-call-header-button"
|
||||||
|
disabled={!props.onDeleteMessagesUpTo || deletingUpTo()}
|
||||||
|
onClick={handleDeleteUpTo}
|
||||||
|
onMouseEnter={() => props.onDeleteHoverChange?.({ kind: "deleteUpTo", messageId: props.messageId })}
|
||||||
|
onMouseLeave={() => props.onDeleteHoverChange?.({ kind: "none" })}
|
||||||
|
title={t("messageItem.actions.deleteMessagesUpTo")}
|
||||||
|
aria-label={t("messageItem.actions.deleteMessagesUpTo")}
|
||||||
|
>
|
||||||
|
<DeleteUpToIcon />
|
||||||
|
</button>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
class="tool-call-header-button"
|
class="tool-call-header-button"
|
||||||
@@ -858,7 +950,7 @@ function CompactionCard(props: CompactionCardProps) {
|
|||||||
title={deletingMessage() ? t("messageItem.actions.deletingMessage") : t("messageItem.actions.deleteMessage")}
|
title={deletingMessage() ? t("messageItem.actions.deletingMessage") : t("messageItem.actions.deleteMessage")}
|
||||||
aria-label={deletingMessage() ? t("messageItem.actions.deletingMessage") : t("messageItem.actions.deleteMessage")}
|
aria-label={deletingMessage() ? t("messageItem.actions.deletingMessage") : t("messageItem.actions.deleteMessage")}
|
||||||
>
|
>
|
||||||
<Trash2 class="w-3.5 h-3.5" aria-hidden="true" />
|
<Trash class="w-3.5 h-3.5" aria-hidden="true" />
|
||||||
</button>
|
</button>
|
||||||
</Show>
|
</Show>
|
||||||
</div>
|
</div>
|
||||||
@@ -874,6 +966,7 @@ function CompactionCard(props: CompactionCardProps) {
|
|||||||
function StepCard(props: StepCardProps) {
|
function StepCard(props: StepCardProps) {
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
const [deletingMessage, setDeletingMessage] = createSignal(false)
|
const [deletingMessage, setDeletingMessage] = createSignal(false)
|
||||||
|
const [deletingUpTo, setDeletingUpTo] = createSignal(false)
|
||||||
const timestamp = () => {
|
const timestamp = () => {
|
||||||
const value = props.messageInfo?.time?.created ?? (props.part as any)?.time?.start ?? Date.now()
|
const value = props.messageInfo?.time?.created ?? (props.part as any)?.time?.start ?? Date.now()
|
||||||
const date = new Date(value)
|
const date = new Date(value)
|
||||||
@@ -939,6 +1032,21 @@ function StepCard(props: StepCardProps) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const handleDeleteUpTo = async (event: MouseEvent) => {
|
||||||
|
event.preventDefault()
|
||||||
|
event.stopPropagation()
|
||||||
|
if (!props.messageId) return
|
||||||
|
if (!props.onDeleteMessagesUpTo) return
|
||||||
|
if (deletingUpTo()) return
|
||||||
|
|
||||||
|
setDeletingUpTo(true)
|
||||||
|
try {
|
||||||
|
await props.onDeleteMessagesUpTo(props.messageId)
|
||||||
|
} finally {
|
||||||
|
setDeletingUpTo(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
const renderUsageChips = (usage: NonNullable<ReturnType<typeof usageStats>>) => {
|
const renderUsageChips = (usage: NonNullable<ReturnType<typeof usageStats>>) => {
|
||||||
const entries = [
|
const entries = [
|
||||||
@@ -971,18 +1079,33 @@ function StepCard(props: StepCardProps) {
|
|||||||
return (
|
return (
|
||||||
<div class={`message-step-card message-step-finish message-step-finish-flush relative`} style={finishStyle()}>
|
<div class={`message-step-card message-step-finish message-step-finish-flush relative`} style={finishStyle()}>
|
||||||
<Show when={props.showDeleteMessage}>
|
<Show when={props.showDeleteMessage}>
|
||||||
<button
|
<div class="absolute right-2 top-1/2 -translate-y-1/2 flex items-center gap-1">
|
||||||
type="button"
|
<button
|
||||||
class="message-action-button absolute right-2 top-1/2 -translate-y-1/2"
|
type="button"
|
||||||
disabled={!canDeleteMessage()}
|
class="message-action-button"
|
||||||
onClick={handleDeleteMessage}
|
disabled={!props.onDeleteMessagesUpTo || deletingUpTo()}
|
||||||
onMouseEnter={() => props.onDeleteHoverChange?.({ kind: "message", messageId: props.messageId! })}
|
onClick={handleDeleteUpTo}
|
||||||
onMouseLeave={() => props.onDeleteHoverChange?.({ kind: "none" })}
|
onMouseEnter={() => props.onDeleteHoverChange?.({ kind: "deleteUpTo", messageId: props.messageId! })}
|
||||||
title={deletingMessage() ? t("messageItem.actions.deletingMessage") : t("messageItem.actions.deleteMessage")}
|
onMouseLeave={() => props.onDeleteHoverChange?.({ kind: "none" })}
|
||||||
aria-label={deletingMessage() ? t("messageItem.actions.deletingMessage") : t("messageItem.actions.deleteMessage")}
|
title={t("messageItem.actions.deleteMessagesUpTo")}
|
||||||
>
|
aria-label={t("messageItem.actions.deleteMessagesUpTo")}
|
||||||
<Trash2 class="w-3.5 h-3.5" aria-hidden="true" />
|
>
|
||||||
</button>
|
<DeleteUpToIcon />
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="message-action-button"
|
||||||
|
disabled={!canDeleteMessage()}
|
||||||
|
onClick={handleDeleteMessage}
|
||||||
|
onMouseEnter={() => props.onDeleteHoverChange?.({ kind: "message", messageId: props.messageId! })}
|
||||||
|
onMouseLeave={() => props.onDeleteHoverChange?.({ kind: "none" })}
|
||||||
|
title={deletingMessage() ? t("messageItem.actions.deletingMessage") : t("messageItem.actions.deleteMessage")}
|
||||||
|
aria-label={deletingMessage() ? t("messageItem.actions.deletingMessage") : t("messageItem.actions.deleteMessage")}
|
||||||
|
>
|
||||||
|
<Trash class="w-3.5 h-3.5" aria-hidden="true" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</Show>
|
</Show>
|
||||||
|
|
||||||
{renderUsageChips(usage)}
|
{renderUsageChips(usage)}
|
||||||
@@ -1025,12 +1148,14 @@ interface ReasoningCardProps {
|
|||||||
defaultExpanded?: boolean
|
defaultExpanded?: boolean
|
||||||
showDeleteMessage?: boolean
|
showDeleteMessage?: boolean
|
||||||
onDeleteHoverChange?: (state: DeleteHoverState) => void
|
onDeleteHoverChange?: (state: DeleteHoverState) => void
|
||||||
|
onDeleteMessagesUpTo?: (messageId: string) => void | Promise<void>
|
||||||
}
|
}
|
||||||
|
|
||||||
function ReasoningCard(props: ReasoningCardProps) {
|
function ReasoningCard(props: ReasoningCardProps) {
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
const [expanded, setExpanded] = createSignal(Boolean(props.defaultExpanded))
|
const [expanded, setExpanded] = createSignal(Boolean(props.defaultExpanded))
|
||||||
const [deletingMessage, setDeletingMessage] = createSignal(false)
|
const [deletingMessage, setDeletingMessage] = createSignal(false)
|
||||||
|
const [deletingUpTo, setDeletingUpTo] = createSignal(false)
|
||||||
|
|
||||||
createEffect(() => {
|
createEffect(() => {
|
||||||
setExpanded(Boolean(props.defaultExpanded))
|
setExpanded(Boolean(props.defaultExpanded))
|
||||||
@@ -1118,6 +1243,21 @@ function ReasoningCard(props: ReasoningCardProps) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const handleDeleteUpTo = async (event: MouseEvent) => {
|
||||||
|
event.preventDefault()
|
||||||
|
event.stopPropagation()
|
||||||
|
if (!props.showDeleteMessage) return
|
||||||
|
if (!props.onDeleteMessagesUpTo) return
|
||||||
|
if (deletingUpTo()) return
|
||||||
|
|
||||||
|
setDeletingUpTo(true)
|
||||||
|
try {
|
||||||
|
await props.onDeleteMessagesUpTo(props.messageId)
|
||||||
|
} finally {
|
||||||
|
setDeletingUpTo(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div class="delete-hover-scope message-reasoning-card">
|
<div class="delete-hover-scope message-reasoning-card">
|
||||||
<div class="message-reasoning-header">
|
<div class="message-reasoning-header">
|
||||||
@@ -1165,6 +1305,19 @@ function ReasoningCard(props: ReasoningCardProps) {
|
|||||||
</button>
|
</button>
|
||||||
|
|
||||||
<Show when={props.showDeleteMessage}>
|
<Show when={props.showDeleteMessage}>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="message-action-button"
|
||||||
|
onClick={handleDeleteUpTo}
|
||||||
|
disabled={!props.onDeleteMessagesUpTo || deletingUpTo()}
|
||||||
|
onMouseEnter={() => props.onDeleteHoverChange?.({ kind: "deleteUpTo", messageId: props.messageId })}
|
||||||
|
onMouseLeave={() => props.onDeleteHoverChange?.({ kind: "none" })}
|
||||||
|
aria-label={t("messageItem.actions.deleteMessagesUpTo")}
|
||||||
|
title={t("messageItem.actions.deleteMessagesUpTo")}
|
||||||
|
>
|
||||||
|
<DeleteUpToIcon />
|
||||||
|
</button>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
class="message-action-button"
|
class="message-action-button"
|
||||||
@@ -1175,7 +1328,7 @@ function ReasoningCard(props: ReasoningCardProps) {
|
|||||||
aria-label={deletingMessage() ? t("messageItem.actions.deletingMessage") : t("messageItem.actions.deleteMessage")}
|
aria-label={deletingMessage() ? t("messageItem.actions.deletingMessage") : t("messageItem.actions.deleteMessage")}
|
||||||
title={deletingMessage() ? t("messageItem.actions.deletingMessage") : t("messageItem.actions.deleteMessage")}
|
title={deletingMessage() ? t("messageItem.actions.deletingMessage") : t("messageItem.actions.deleteMessage")}
|
||||||
>
|
>
|
||||||
<Trash2 class="w-3.5 h-3.5" aria-hidden="true" />
|
<Trash class="w-3.5 h-3.5" aria-hidden="true" />
|
||||||
</button>
|
</button>
|
||||||
</Show>
|
</Show>
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { For, Show, createSignal } from "solid-js"
|
import { For, Show, createSignal } from "solid-js"
|
||||||
import { Copy, Split, Trash2, Undo } from "lucide-solid"
|
import { Copy, ListStart, Split, Trash, Undo } from "lucide-solid"
|
||||||
import type { MessageInfo, ClientPart, SDKAssistantMessageV2 } from "../types/message"
|
import type { MessageInfo, ClientPart, SDKAssistantMessageV2 } from "../types/message"
|
||||||
import { partHasRenderableText } from "../types/message"
|
import { partHasRenderableText } from "../types/message"
|
||||||
import type { MessageRecord } from "../stores/message-v2/types"
|
import type { MessageRecord } from "../stores/message-v2/types"
|
||||||
@@ -11,6 +11,14 @@ import { deleteMessage } from "../stores/session-actions"
|
|||||||
import { isTauriHost } from "../lib/runtime-env"
|
import { isTauriHost } from "../lib/runtime-env"
|
||||||
import type { DeleteHoverState } from "../types/delete-hover"
|
import type { DeleteHoverState } from "../types/delete-hover"
|
||||||
|
|
||||||
|
function DeleteUpToIcon() {
|
||||||
|
return (
|
||||||
|
<span class="relative inline-block w-3.5 h-3.5" aria-hidden="true">
|
||||||
|
<ListStart class="absolute inset-0 w-3.5 h-3.5" aria-hidden="true" />
|
||||||
|
</span>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
interface MessageItemProps {
|
interface MessageItemProps {
|
||||||
record: MessageRecord
|
record: MessageRecord
|
||||||
messageInfo?: MessageInfo
|
messageInfo?: MessageInfo
|
||||||
@@ -19,6 +27,7 @@ interface MessageItemProps {
|
|||||||
isQueued?: boolean
|
isQueued?: boolean
|
||||||
parts: ClientPart[]
|
parts: ClientPart[]
|
||||||
onRevert?: (messageId: string) => void
|
onRevert?: (messageId: string) => void
|
||||||
|
onDeleteMessagesUpTo?: (messageId: string) => void | Promise<void>
|
||||||
onFork?: (messageId?: string) => void
|
onFork?: (messageId?: string) => void
|
||||||
showAgentMeta?: boolean
|
showAgentMeta?: boolean
|
||||||
onContentRendered?: () => void
|
onContentRendered?: () => void
|
||||||
@@ -30,6 +39,7 @@ export default function MessageItem(props: MessageItemProps) {
|
|||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
const [copied, setCopied] = createSignal(false)
|
const [copied, setCopied] = createSignal(false)
|
||||||
const [deletingMessage, setDeletingMessage] = createSignal(false)
|
const [deletingMessage, setDeletingMessage] = createSignal(false)
|
||||||
|
const [deletingUpTo, setDeletingUpTo] = createSignal(false)
|
||||||
|
|
||||||
const isUser = () => props.record.role === "user"
|
const isUser = () => props.record.role === "user"
|
||||||
const createdTimestamp = () => props.messageInfo?.time?.created ?? props.record.createdAt
|
const createdTimestamp = () => props.messageInfo?.time?.created ?? props.record.createdAt
|
||||||
@@ -209,6 +219,17 @@ export default function MessageItem(props: MessageItemProps) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const handleDeleteUpTo = async () => {
|
||||||
|
if (!props.onDeleteMessagesUpTo) return
|
||||||
|
if (deletingUpTo()) return
|
||||||
|
setDeletingUpTo(true)
|
||||||
|
try {
|
||||||
|
await props.onDeleteMessagesUpTo(props.record.id)
|
||||||
|
} finally {
|
||||||
|
setDeletingUpTo(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (!isUser() && !hasContent() && !isGenerating()) {
|
if (!isUser() && !hasContent() && !isGenerating()) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
@@ -305,6 +326,18 @@ export default function MessageItem(props: MessageItemProps) {
|
|||||||
</Show>
|
</Show>
|
||||||
|
|
||||||
<Show when={props.showDeleteMessage}>
|
<Show when={props.showDeleteMessage}>
|
||||||
|
<button
|
||||||
|
class="message-action-button"
|
||||||
|
onClick={() => void handleDeleteUpTo()}
|
||||||
|
disabled={!props.onDeleteMessagesUpTo || deletingUpTo()}
|
||||||
|
onMouseEnter={() => props.onDeleteHoverChange?.({ kind: "deleteUpTo", messageId: props.record.id })}
|
||||||
|
onMouseLeave={() => props.onDeleteHoverChange?.({ kind: "none" })}
|
||||||
|
title={t("messageItem.actions.deleteMessagesUpTo")}
|
||||||
|
aria-label={t("messageItem.actions.deleteMessagesUpTo")}
|
||||||
|
>
|
||||||
|
<DeleteUpToIcon />
|
||||||
|
</button>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
class="message-action-button"
|
class="message-action-button"
|
||||||
onClick={handleDeleteMessage}
|
onClick={handleDeleteMessage}
|
||||||
@@ -314,7 +347,7 @@ export default function MessageItem(props: MessageItemProps) {
|
|||||||
title={deletingMessage() ? t("messageItem.actions.deletingMessage") : t("messageItem.actions.deleteMessage")}
|
title={deletingMessage() ? t("messageItem.actions.deletingMessage") : t("messageItem.actions.deleteMessage")}
|
||||||
aria-label={deletingMessage() ? t("messageItem.actions.deletingMessage") : t("messageItem.actions.deleteMessage")}
|
aria-label={deletingMessage() ? t("messageItem.actions.deletingMessage") : t("messageItem.actions.deleteMessage")}
|
||||||
>
|
>
|
||||||
<Trash2 class="w-3.5 h-3.5" aria-hidden="true" />
|
<Trash class="w-3.5 h-3.5" aria-hidden="true" />
|
||||||
</button>
|
</button>
|
||||||
</Show>
|
</Show>
|
||||||
</div>
|
</div>
|
||||||
@@ -331,6 +364,18 @@ export default function MessageItem(props: MessageItemProps) {
|
|||||||
</button>
|
</button>
|
||||||
|
|
||||||
<Show when={props.showDeleteMessage}>
|
<Show when={props.showDeleteMessage}>
|
||||||
|
<button
|
||||||
|
class="message-action-button"
|
||||||
|
onClick={() => void handleDeleteUpTo()}
|
||||||
|
disabled={!props.onDeleteMessagesUpTo || deletingUpTo()}
|
||||||
|
onMouseEnter={() => props.onDeleteHoverChange?.({ kind: "deleteUpTo", messageId: props.record.id })}
|
||||||
|
onMouseLeave={() => props.onDeleteHoverChange?.({ kind: "none" })}
|
||||||
|
title={t("messageItem.actions.deleteMessagesUpTo")}
|
||||||
|
aria-label={t("messageItem.actions.deleteMessagesUpTo")}
|
||||||
|
>
|
||||||
|
<DeleteUpToIcon />
|
||||||
|
</button>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
class="message-action-button"
|
class="message-action-button"
|
||||||
onClick={handleDeleteMessage}
|
onClick={handleDeleteMessage}
|
||||||
@@ -340,7 +385,7 @@ export default function MessageItem(props: MessageItemProps) {
|
|||||||
title={deletingMessage() ? t("messageItem.actions.deletingMessage") : t("messageItem.actions.deleteMessage")}
|
title={deletingMessage() ? t("messageItem.actions.deletingMessage") : t("messageItem.actions.deleteMessage")}
|
||||||
aria-label={deletingMessage() ? t("messageItem.actions.deletingMessage") : t("messageItem.actions.deleteMessage")}
|
aria-label={deletingMessage() ? t("messageItem.actions.deletingMessage") : t("messageItem.actions.deleteMessage")}
|
||||||
>
|
>
|
||||||
<Trash2 class="w-3.5 h-3.5" aria-hidden="true" />
|
<Trash class="w-3.5 h-3.5" aria-hidden="true" />
|
||||||
</button>
|
</button>
|
||||||
</Show>
|
</Show>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ interface MessagePreviewProps {
|
|||||||
store: () => InstanceMessageStore
|
store: () => InstanceMessageStore
|
||||||
deleteHover?: () => DeleteHoverState
|
deleteHover?: () => DeleteHoverState
|
||||||
onDeleteHoverChange?: (state: DeleteHoverState) => void
|
onDeleteHoverChange?: (state: DeleteHoverState) => void
|
||||||
|
onDeleteMessagesUpTo?: (messageId: string) => void | Promise<void>
|
||||||
}
|
}
|
||||||
|
|
||||||
const MessagePreview: Component<MessagePreviewProps> = (props) => {
|
const MessagePreview: Component<MessagePreviewProps> = (props) => {
|
||||||
@@ -29,6 +30,7 @@ const MessagePreview: Component<MessagePreviewProps> = (props) => {
|
|||||||
showUsageMetrics={() => false}
|
showUsageMetrics={() => false}
|
||||||
deleteHover={props.deleteHover}
|
deleteHover={props.deleteHover}
|
||||||
onDeleteHoverChange={props.onDeleteHoverChange}
|
onDeleteHoverChange={props.onDeleteHoverChange}
|
||||||
|
onDeleteMessagesUpTo={props.onDeleteMessagesUpTo}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ export interface MessageSectionProps {
|
|||||||
sessionId: string
|
sessionId: string
|
||||||
loading?: boolean
|
loading?: boolean
|
||||||
onRevert?: (messageId: string) => void
|
onRevert?: (messageId: string) => void
|
||||||
|
onDeleteMessagesUpTo?: (messageId: string) => void | Promise<void>
|
||||||
onFork?: (messageId?: string) => void
|
onFork?: (messageId?: string) => void
|
||||||
registerScrollToBottom?: (fn: () => void) => void
|
registerScrollToBottom?: (fn: () => void) => void
|
||||||
showSidebarToggle?: boolean
|
showSidebarToggle?: boolean
|
||||||
@@ -900,6 +901,7 @@ export default function MessageSection(props: MessageSectionProps) {
|
|||||||
scrollContainer={scrollElement}
|
scrollContainer={scrollElement}
|
||||||
loading={props.loading}
|
loading={props.loading}
|
||||||
onRevert={props.onRevert}
|
onRevert={props.onRevert}
|
||||||
|
onDeleteMessagesUpTo={props.onDeleteMessagesUpTo}
|
||||||
onFork={props.onFork}
|
onFork={props.onFork}
|
||||||
onContentRendered={handleContentRendered}
|
onContentRendered={handleContentRendered}
|
||||||
deleteHover={deleteHover}
|
deleteHover={deleteHover}
|
||||||
@@ -964,6 +966,7 @@ export default function MessageSection(props: MessageSectionProps) {
|
|||||||
showToolSegments={showTimelineToolsPreference()}
|
showToolSegments={showTimelineToolsPreference()}
|
||||||
deleteHover={deleteHover}
|
deleteHover={deleteHover}
|
||||||
onDeleteHoverChange={setDeleteHover}
|
onDeleteHoverChange={setDeleteHover}
|
||||||
|
onDeleteMessagesUpTo={props.onDeleteMessagesUpTo}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</Show>
|
</Show>
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ interface MessageTimelineProps {
|
|||||||
showToolSegments?: boolean
|
showToolSegments?: boolean
|
||||||
deleteHover?: () => DeleteHoverState
|
deleteHover?: () => DeleteHoverState
|
||||||
onDeleteHoverChange?: (state: DeleteHoverState) => void
|
onDeleteHoverChange?: (state: DeleteHoverState) => void
|
||||||
|
onDeleteMessagesUpTo?: (messageId: string) => void | Promise<void>
|
||||||
}
|
}
|
||||||
|
|
||||||
const MAX_TOOLTIP_LENGTH = 220
|
const MAX_TOOLTIP_LENGTH = 220
|
||||||
@@ -419,6 +420,16 @@ const MessageTimeline: Component<MessageTimelineProps> = (props) => {
|
|||||||
if (hover.kind === "message") {
|
if (hover.kind === "message") {
|
||||||
return hover.messageId === segment.messageId
|
return hover.messageId === segment.messageId
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (hover.kind === "deleteUpTo") {
|
||||||
|
const ids = store().getSessionMessageIds(props.sessionId)
|
||||||
|
const targetIndex = ids.indexOf(hover.messageId)
|
||||||
|
if (targetIndex === -1) return false
|
||||||
|
const segmentIndex = ids.indexOf(segment.messageId)
|
||||||
|
if (segmentIndex === -1) return false
|
||||||
|
return segmentIndex >= targetIndex
|
||||||
|
}
|
||||||
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -490,6 +501,7 @@ const MessageTimeline: Component<MessageTimelineProps> = (props) => {
|
|||||||
store={store}
|
store={store}
|
||||||
deleteHover={props.deleteHover}
|
deleteHover={props.deleteHover}
|
||||||
onDeleteHoverChange={props.onDeleteHoverChange}
|
onDeleteHoverChange={props.onDeleteHoverChange}
|
||||||
|
onDeleteMessagesUpTo={props.onDeleteMessagesUpTo}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import { getAttachments, removeAttachment } from "../../stores/attachments"
|
|||||||
import { instances } from "../../stores/instances"
|
import { instances } from "../../stores/instances"
|
||||||
import { loadMessages, sendMessage, forkSession, renameSession, isSessionMessagesLoading, setActiveParentSession, setActiveSession, runShellCommand, abortSession } from "../../stores/sessions"
|
import { loadMessages, sendMessage, forkSession, renameSession, isSessionMessagesLoading, setActiveParentSession, setActiveSession, runShellCommand, abortSession } from "../../stores/sessions"
|
||||||
import { isSessionBusy as getSessionBusyStatus } from "../../stores/session-status"
|
import { isSessionBusy as getSessionBusyStatus } from "../../stores/session-status"
|
||||||
|
import { deleteMessage } from "../../stores/session-actions"
|
||||||
import { showAlertDialog } from "../../stores/alerts"
|
import { showAlertDialog } from "../../stores/alerts"
|
||||||
import { getLogger } from "../../lib/logger"
|
import { getLogger } from "../../lib/logger"
|
||||||
import { requestData } from "../../lib/opencode-api"
|
import { requestData } from "../../lib/opencode-api"
|
||||||
@@ -225,6 +226,35 @@ export const SessionView: Component<SessionViewProps> = (props) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function handleDeleteMessagesUpTo(messageId: string) {
|
||||||
|
const ids = messageStore().getSessionMessageIds(props.sessionId)
|
||||||
|
const index = ids.indexOf(messageId)
|
||||||
|
if (index === -1) return
|
||||||
|
|
||||||
|
const restoredText = getUserMessageText(messageId)
|
||||||
|
const toDelete = ids.slice(index)
|
||||||
|
|
||||||
|
try {
|
||||||
|
for (let idx = toDelete.length - 1; idx >= 0; idx -= 1) {
|
||||||
|
await deleteMessage(props.instanceId, props.sessionId, toDelete[idx])
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
log.error("Failed to delete messages up to", error)
|
||||||
|
showAlertDialog(t("sessionView.alerts.deleteUpToFailed.message"), {
|
||||||
|
title: t("sessionView.alerts.deleteUpToFailed.title"),
|
||||||
|
variant: "error",
|
||||||
|
})
|
||||||
|
} finally {
|
||||||
|
if (restoredText) {
|
||||||
|
if (promptInputApi) {
|
||||||
|
promptInputApi.setPromptText(restoredText, { focus: true })
|
||||||
|
} else {
|
||||||
|
pendingPromptText = restoredText
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function handleFork(messageId?: string) {
|
async function handleFork(messageId?: string) {
|
||||||
if (!messageId) {
|
if (!messageId) {
|
||||||
log.warn("Fork requires a user message id")
|
log.warn("Fork requires a user message id")
|
||||||
@@ -283,10 +313,11 @@ export const SessionView: Component<SessionViewProps> = (props) => {
|
|||||||
<MessageSection
|
<MessageSection
|
||||||
instanceId={props.instanceId}
|
instanceId={props.instanceId}
|
||||||
sessionId={activeSession.id}
|
sessionId={activeSession.id}
|
||||||
loading={messagesLoading()}
|
loading={messagesLoading()}
|
||||||
onRevert={handleRevert}
|
onRevert={handleRevert}
|
||||||
onFork={handleFork}
|
onDeleteMessagesUpTo={handleDeleteMessagesUpTo}
|
||||||
isActive={props.isActive}
|
onFork={handleFork}
|
||||||
|
isActive={props.isActive}
|
||||||
registerScrollToBottom={(fn) => {
|
registerScrollToBottom={(fn) => {
|
||||||
scrollToBottomHandle = fn
|
scrollToBottomHandle = fn
|
||||||
if (props.isActive) {
|
if (props.isActive) {
|
||||||
|
|||||||
@@ -71,13 +71,14 @@ export const messagingMessages = {
|
|||||||
"messageItem.speaker.you": "You",
|
"messageItem.speaker.you": "You",
|
||||||
"messageItem.speaker.assistant": "Assistant",
|
"messageItem.speaker.assistant": "Assistant",
|
||||||
"messageItem.actions.revert": "Revert",
|
"messageItem.actions.revert": "Revert",
|
||||||
"messageItem.actions.revertTitle": "Undo changes up to here",
|
"messageItem.actions.revertTitle": "Undo changes up to here (deletes messages)",
|
||||||
"messageItem.actions.fork": "Fork",
|
"messageItem.actions.fork": "Fork",
|
||||||
"messageItem.actions.forkTitle": "Fork from this message",
|
"messageItem.actions.forkTitle": "Fork from this message",
|
||||||
"messageItem.actions.copy": "Copy",
|
"messageItem.actions.copy": "Copy",
|
||||||
"messageItem.actions.copyTitle": "Copy message",
|
"messageItem.actions.copyTitle": "Copy message",
|
||||||
"messageItem.actions.copied": "Copied!",
|
"messageItem.actions.copied": "Copied!",
|
||||||
"messageItem.actions.deleteMessage": "Delete message",
|
"messageItem.actions.deleteMessage": "Delete message (doesn't undo changes)",
|
||||||
|
"messageItem.actions.deleteMessagesUpTo": "Delete messages up to here (doesn't undo changes)",
|
||||||
"messageItem.actions.deletingMessage": "Deleting...",
|
"messageItem.actions.deletingMessage": "Deleting...",
|
||||||
"messageItem.actions.deleteMessageFailedTitle": "Delete failed",
|
"messageItem.actions.deleteMessageFailedTitle": "Delete failed",
|
||||||
"messageItem.actions.deleteMessageFailedMessage": "Failed to delete message",
|
"messageItem.actions.deleteMessageFailedMessage": "Failed to delete message",
|
||||||
|
|||||||
@@ -67,6 +67,8 @@ export const sessionMessages = {
|
|||||||
"sessionView.alerts.abortFailed.title": "Stop failed",
|
"sessionView.alerts.abortFailed.title": "Stop failed",
|
||||||
"sessionView.alerts.revertFailed.message": "Failed to revert to message",
|
"sessionView.alerts.revertFailed.message": "Failed to revert to message",
|
||||||
"sessionView.alerts.revertFailed.title": "Revert failed",
|
"sessionView.alerts.revertFailed.title": "Revert failed",
|
||||||
|
"sessionView.alerts.deleteUpToFailed.message": "Failed to delete messages",
|
||||||
|
"sessionView.alerts.deleteUpToFailed.title": "Delete failed",
|
||||||
"sessionView.alerts.forkFailed.message": "Failed to fork session",
|
"sessionView.alerts.forkFailed.message": "Failed to fork session",
|
||||||
"sessionView.alerts.forkFailed.title": "Fork failed",
|
"sessionView.alerts.forkFailed.title": "Fork failed",
|
||||||
"sessionView.attachments.expandPastedTextAriaLabel": "Expand pasted text",
|
"sessionView.attachments.expandPastedTextAriaLabel": "Expand pasted text",
|
||||||
|
|||||||
@@ -71,13 +71,14 @@ export const messagingMessages = {
|
|||||||
"messageItem.speaker.you": "Tú",
|
"messageItem.speaker.you": "Tú",
|
||||||
"messageItem.speaker.assistant": "Asistente",
|
"messageItem.speaker.assistant": "Asistente",
|
||||||
"messageItem.actions.revert": "Revertir",
|
"messageItem.actions.revert": "Revertir",
|
||||||
"messageItem.actions.revertTitle": "Deshacer cambios hasta aqui",
|
"messageItem.actions.revertTitle": "Deshacer cambios hasta aqui (elimina mensajes)",
|
||||||
"messageItem.actions.fork": "Fork",
|
"messageItem.actions.fork": "Fork",
|
||||||
"messageItem.actions.forkTitle": "Fork desde este mensaje",
|
"messageItem.actions.forkTitle": "Fork desde este mensaje",
|
||||||
"messageItem.actions.copy": "Copiar",
|
"messageItem.actions.copy": "Copiar",
|
||||||
"messageItem.actions.copyTitle": "Copiar mensaje",
|
"messageItem.actions.copyTitle": "Copiar mensaje",
|
||||||
"messageItem.actions.copied": "¡Copiado!",
|
"messageItem.actions.copied": "¡Copiado!",
|
||||||
"messageItem.actions.deleteMessage": "Eliminar mensaje",
|
"messageItem.actions.deleteMessage": "Eliminar mensaje (no deshace cambios)",
|
||||||
|
"messageItem.actions.deleteMessagesUpTo": "Eliminar mensajes hasta aqui (no deshace cambios)",
|
||||||
"messageItem.actions.deletingMessage": "Eliminando...",
|
"messageItem.actions.deletingMessage": "Eliminando...",
|
||||||
"messageItem.actions.deleteMessageFailedTitle": "Error al eliminar",
|
"messageItem.actions.deleteMessageFailedTitle": "Error al eliminar",
|
||||||
"messageItem.actions.deleteMessageFailedMessage": "No se pudo eliminar el mensaje",
|
"messageItem.actions.deleteMessageFailedMessage": "No se pudo eliminar el mensaje",
|
||||||
|
|||||||
@@ -67,6 +67,8 @@ export const sessionMessages = {
|
|||||||
"sessionView.alerts.abortFailed.title": "No se pudo detener",
|
"sessionView.alerts.abortFailed.title": "No se pudo detener",
|
||||||
"sessionView.alerts.revertFailed.message": "No se pudo revertir al mensaje",
|
"sessionView.alerts.revertFailed.message": "No se pudo revertir al mensaje",
|
||||||
"sessionView.alerts.revertFailed.title": "No se pudo revertir",
|
"sessionView.alerts.revertFailed.title": "No se pudo revertir",
|
||||||
|
"sessionView.alerts.deleteUpToFailed.message": "No se pudieron eliminar los mensajes",
|
||||||
|
"sessionView.alerts.deleteUpToFailed.title": "Error al eliminar",
|
||||||
"sessionView.alerts.forkFailed.message": "No se pudo hacer fork de la sesión",
|
"sessionView.alerts.forkFailed.message": "No se pudo hacer fork de la sesión",
|
||||||
"sessionView.alerts.forkFailed.title": "No se pudo hacer fork",
|
"sessionView.alerts.forkFailed.title": "No se pudo hacer fork",
|
||||||
"sessionView.attachments.expandPastedTextAriaLabel": "Expandir texto pegado",
|
"sessionView.attachments.expandPastedTextAriaLabel": "Expandir texto pegado",
|
||||||
|
|||||||
@@ -71,13 +71,14 @@ export const messagingMessages = {
|
|||||||
"messageItem.speaker.you": "Vous",
|
"messageItem.speaker.you": "Vous",
|
||||||
"messageItem.speaker.assistant": "Assistant",
|
"messageItem.speaker.assistant": "Assistant",
|
||||||
"messageItem.actions.revert": "Revenir",
|
"messageItem.actions.revert": "Revenir",
|
||||||
"messageItem.actions.revertTitle": "Annuler les changements jusqu'ici",
|
"messageItem.actions.revertTitle": "Annuler les changements jusqu'ici (supprime les messages)",
|
||||||
"messageItem.actions.fork": "Fork",
|
"messageItem.actions.fork": "Fork",
|
||||||
"messageItem.actions.forkTitle": "Fork depuis ce message",
|
"messageItem.actions.forkTitle": "Fork depuis ce message",
|
||||||
"messageItem.actions.copy": "Copier",
|
"messageItem.actions.copy": "Copier",
|
||||||
"messageItem.actions.copyTitle": "Copier le message",
|
"messageItem.actions.copyTitle": "Copier le message",
|
||||||
"messageItem.actions.copied": "Copié !",
|
"messageItem.actions.copied": "Copié !",
|
||||||
"messageItem.actions.deleteMessage": "Supprimer le message",
|
"messageItem.actions.deleteMessage": "Supprimer le message (sans annuler les changements)",
|
||||||
|
"messageItem.actions.deleteMessagesUpTo": "Supprimer les messages jusqu'ici (sans annuler les changements)",
|
||||||
"messageItem.actions.deletingMessage": "Suppression...",
|
"messageItem.actions.deletingMessage": "Suppression...",
|
||||||
"messageItem.actions.deleteMessageFailedTitle": "Échec de suppression",
|
"messageItem.actions.deleteMessageFailedTitle": "Échec de suppression",
|
||||||
"messageItem.actions.deleteMessageFailedMessage": "Impossible de supprimer le message",
|
"messageItem.actions.deleteMessageFailedMessage": "Impossible de supprimer le message",
|
||||||
|
|||||||
@@ -67,6 +67,8 @@ export const sessionMessages = {
|
|||||||
"sessionView.alerts.abortFailed.title": "Échec de l'arrêt",
|
"sessionView.alerts.abortFailed.title": "Échec de l'arrêt",
|
||||||
"sessionView.alerts.revertFailed.message": "Impossible de revenir au message",
|
"sessionView.alerts.revertFailed.message": "Impossible de revenir au message",
|
||||||
"sessionView.alerts.revertFailed.title": "Échec du retour",
|
"sessionView.alerts.revertFailed.title": "Échec du retour",
|
||||||
|
"sessionView.alerts.deleteUpToFailed.message": "Impossible de supprimer les messages",
|
||||||
|
"sessionView.alerts.deleteUpToFailed.title": "Échec de suppression",
|
||||||
"sessionView.alerts.forkFailed.message": "Impossible de forker la session",
|
"sessionView.alerts.forkFailed.message": "Impossible de forker la session",
|
||||||
"sessionView.alerts.forkFailed.title": "Échec du fork",
|
"sessionView.alerts.forkFailed.title": "Échec du fork",
|
||||||
"sessionView.attachments.expandPastedTextAriaLabel": "Développer le texte collé",
|
"sessionView.attachments.expandPastedTextAriaLabel": "Développer le texte collé",
|
||||||
|
|||||||
@@ -71,13 +71,14 @@ export const messagingMessages = {
|
|||||||
"messageItem.speaker.you": "あなた",
|
"messageItem.speaker.you": "あなた",
|
||||||
"messageItem.speaker.assistant": "アシスタント",
|
"messageItem.speaker.assistant": "アシスタント",
|
||||||
"messageItem.actions.revert": "戻す",
|
"messageItem.actions.revert": "戻す",
|
||||||
"messageItem.actions.revertTitle": "ここまでの変更を元に戻す",
|
"messageItem.actions.revertTitle": "ここまでの変更を元に戻す(メッセージを削除)",
|
||||||
"messageItem.actions.fork": "フォーク",
|
"messageItem.actions.fork": "フォーク",
|
||||||
"messageItem.actions.forkTitle": "このメッセージからフォーク",
|
"messageItem.actions.forkTitle": "このメッセージからフォーク",
|
||||||
"messageItem.actions.copy": "コピー",
|
"messageItem.actions.copy": "コピー",
|
||||||
"messageItem.actions.copyTitle": "メッセージをコピー",
|
"messageItem.actions.copyTitle": "メッセージをコピー",
|
||||||
"messageItem.actions.copied": "コピーしました!",
|
"messageItem.actions.copied": "コピーしました!",
|
||||||
"messageItem.actions.deleteMessage": "メッセージを削除",
|
"messageItem.actions.deleteMessage": "メッセージを削除(変更は元に戻さない)",
|
||||||
|
"messageItem.actions.deleteMessagesUpTo": "ここまでのメッセージを削除(変更は元に戻さない)",
|
||||||
"messageItem.actions.deletingMessage": "削除中...",
|
"messageItem.actions.deletingMessage": "削除中...",
|
||||||
"messageItem.actions.deleteMessageFailedTitle": "削除に失敗しました",
|
"messageItem.actions.deleteMessageFailedTitle": "削除に失敗しました",
|
||||||
"messageItem.actions.deleteMessageFailedMessage": "メッセージの削除に失敗しました",
|
"messageItem.actions.deleteMessageFailedMessage": "メッセージの削除に失敗しました",
|
||||||
|
|||||||
@@ -67,6 +67,8 @@ export const sessionMessages = {
|
|||||||
"sessionView.alerts.abortFailed.title": "停止に失敗",
|
"sessionView.alerts.abortFailed.title": "停止に失敗",
|
||||||
"sessionView.alerts.revertFailed.message": "メッセージへ戻せませんでした",
|
"sessionView.alerts.revertFailed.message": "メッセージへ戻せませんでした",
|
||||||
"sessionView.alerts.revertFailed.title": "復元に失敗",
|
"sessionView.alerts.revertFailed.title": "復元に失敗",
|
||||||
|
"sessionView.alerts.deleteUpToFailed.message": "メッセージの削除に失敗しました",
|
||||||
|
"sessionView.alerts.deleteUpToFailed.title": "削除に失敗しました",
|
||||||
"sessionView.alerts.forkFailed.message": "セッションのフォークに失敗しました",
|
"sessionView.alerts.forkFailed.message": "セッションのフォークに失敗しました",
|
||||||
"sessionView.alerts.forkFailed.title": "フォークに失敗",
|
"sessionView.alerts.forkFailed.title": "フォークに失敗",
|
||||||
"sessionView.attachments.expandPastedTextAriaLabel": "貼り付けたテキストを展開",
|
"sessionView.attachments.expandPastedTextAriaLabel": "貼り付けたテキストを展開",
|
||||||
|
|||||||
@@ -71,13 +71,14 @@ export const messagingMessages = {
|
|||||||
"messageItem.speaker.you": "Вы",
|
"messageItem.speaker.you": "Вы",
|
||||||
"messageItem.speaker.assistant": "Ассистент",
|
"messageItem.speaker.assistant": "Ассистент",
|
||||||
"messageItem.actions.revert": "Откатить",
|
"messageItem.actions.revert": "Откатить",
|
||||||
"messageItem.actions.revertTitle": "Отменить изменения до этого места",
|
"messageItem.actions.revertTitle": "Отменить изменения до этого места (удалит сообщения)",
|
||||||
"messageItem.actions.fork": "Форк",
|
"messageItem.actions.fork": "Форк",
|
||||||
"messageItem.actions.forkTitle": "Форкнуть от этого сообщения",
|
"messageItem.actions.forkTitle": "Форкнуть от этого сообщения",
|
||||||
"messageItem.actions.copy": "Копировать",
|
"messageItem.actions.copy": "Копировать",
|
||||||
"messageItem.actions.copyTitle": "Копировать сообщение",
|
"messageItem.actions.copyTitle": "Копировать сообщение",
|
||||||
"messageItem.actions.copied": "Скопировано!",
|
"messageItem.actions.copied": "Скопировано!",
|
||||||
"messageItem.actions.deleteMessage": "Удалить сообщение",
|
"messageItem.actions.deleteMessage": "Удалить сообщение (без отката изменений)",
|
||||||
|
"messageItem.actions.deleteMessagesUpTo": "Удалить сообщения до этого места (без отката изменений)",
|
||||||
"messageItem.actions.deletingMessage": "Удаление...",
|
"messageItem.actions.deletingMessage": "Удаление...",
|
||||||
"messageItem.actions.deleteMessageFailedTitle": "Ошибка удаления",
|
"messageItem.actions.deleteMessageFailedTitle": "Ошибка удаления",
|
||||||
"messageItem.actions.deleteMessageFailedMessage": "Не удалось удалить сообщение",
|
"messageItem.actions.deleteMessageFailedMessage": "Не удалось удалить сообщение",
|
||||||
|
|||||||
@@ -67,6 +67,8 @@ export const sessionMessages = {
|
|||||||
"sessionView.alerts.abortFailed.title": "Не удалось остановить",
|
"sessionView.alerts.abortFailed.title": "Не удалось остановить",
|
||||||
"sessionView.alerts.revertFailed.message": "Не удалось откатиться к сообщению",
|
"sessionView.alerts.revertFailed.message": "Не удалось откатиться к сообщению",
|
||||||
"sessionView.alerts.revertFailed.title": "Не удалось откатиться",
|
"sessionView.alerts.revertFailed.title": "Не удалось откатиться",
|
||||||
|
"sessionView.alerts.deleteUpToFailed.message": "Не удалось удалить сообщения",
|
||||||
|
"sessionView.alerts.deleteUpToFailed.title": "Ошибка удаления",
|
||||||
"sessionView.alerts.forkFailed.message": "Не удалось форкнуть сессию",
|
"sessionView.alerts.forkFailed.message": "Не удалось форкнуть сессию",
|
||||||
"sessionView.alerts.forkFailed.title": "Не удалось форкнуть",
|
"sessionView.alerts.forkFailed.title": "Не удалось форкнуть",
|
||||||
"sessionView.attachments.expandPastedTextAriaLabel": "Развернуть вставленный текст",
|
"sessionView.attachments.expandPastedTextAriaLabel": "Развернуть вставленный текст",
|
||||||
|
|||||||
@@ -71,13 +71,14 @@ export const messagingMessages = {
|
|||||||
"messageItem.speaker.you": "你",
|
"messageItem.speaker.you": "你",
|
||||||
"messageItem.speaker.assistant": "助手",
|
"messageItem.speaker.assistant": "助手",
|
||||||
"messageItem.actions.revert": "回退",
|
"messageItem.actions.revert": "回退",
|
||||||
"messageItem.actions.revertTitle": "撤销到此处的更改",
|
"messageItem.actions.revertTitle": "撤销到此处的更改(会删除消息)",
|
||||||
"messageItem.actions.fork": "分叉",
|
"messageItem.actions.fork": "分叉",
|
||||||
"messageItem.actions.forkTitle": "从这条消息分叉",
|
"messageItem.actions.forkTitle": "从这条消息分叉",
|
||||||
"messageItem.actions.copy": "复制",
|
"messageItem.actions.copy": "复制",
|
||||||
"messageItem.actions.copyTitle": "复制消息",
|
"messageItem.actions.copyTitle": "复制消息",
|
||||||
"messageItem.actions.copied": "已复制!",
|
"messageItem.actions.copied": "已复制!",
|
||||||
"messageItem.actions.deleteMessage": "删除消息",
|
"messageItem.actions.deleteMessage": "删除消息(不会撤销更改)",
|
||||||
|
"messageItem.actions.deleteMessagesUpTo": "删除到此处的消息(不会撤销更改)",
|
||||||
"messageItem.actions.deletingMessage": "正在删除...",
|
"messageItem.actions.deletingMessage": "正在删除...",
|
||||||
"messageItem.actions.deleteMessageFailedTitle": "删除失败",
|
"messageItem.actions.deleteMessageFailedTitle": "删除失败",
|
||||||
"messageItem.actions.deleteMessageFailedMessage": "无法删除消息",
|
"messageItem.actions.deleteMessageFailedMessage": "无法删除消息",
|
||||||
|
|||||||
@@ -67,6 +67,8 @@ export const sessionMessages = {
|
|||||||
"sessionView.alerts.abortFailed.title": "停止失败",
|
"sessionView.alerts.abortFailed.title": "停止失败",
|
||||||
"sessionView.alerts.revertFailed.message": "回退到消息失败",
|
"sessionView.alerts.revertFailed.message": "回退到消息失败",
|
||||||
"sessionView.alerts.revertFailed.title": "回退失败",
|
"sessionView.alerts.revertFailed.title": "回退失败",
|
||||||
|
"sessionView.alerts.deleteUpToFailed.message": "无法删除消息",
|
||||||
|
"sessionView.alerts.deleteUpToFailed.title": "删除失败",
|
||||||
"sessionView.alerts.forkFailed.message": "分叉会话失败",
|
"sessionView.alerts.forkFailed.message": "分叉会话失败",
|
||||||
"sessionView.alerts.forkFailed.title": "分叉失败",
|
"sessionView.alerts.forkFailed.title": "分叉失败",
|
||||||
"sessionView.attachments.expandPastedTextAriaLabel": "展开粘贴的文本",
|
"sessionView.attachments.expandPastedTextAriaLabel": "展开粘贴的文本",
|
||||||
|
|||||||
@@ -9,7 +9,6 @@
|
|||||||
position: absolute;
|
position: absolute;
|
||||||
inset: -2px;
|
inset: -2px;
|
||||||
background: var(--status-error-bg);
|
background: var(--status-error-bg);
|
||||||
box-shadow: inset 0 0 0 1px var(--status-error-fg);
|
|
||||||
border-radius: 0;
|
border-radius: 0;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
/* Overlay must sit above the message cards (they have opaque backgrounds). */
|
/* Overlay must sit above the message cards (they have opaque backgrounds). */
|
||||||
@@ -29,7 +28,6 @@
|
|||||||
position: absolute;
|
position: absolute;
|
||||||
inset: -2px;
|
inset: -2px;
|
||||||
background: var(--status-error-bg);
|
background: var(--status-error-bg);
|
||||||
box-shadow: inset 0 0 0 1px var(--status-error-fg);
|
|
||||||
border-radius: 0;
|
border-radius: 0;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
/* Overlay must sit above the part card background. */
|
/* Overlay must sit above the part card background. */
|
||||||
|
|||||||
@@ -104,7 +104,6 @@
|
|||||||
position: absolute;
|
position: absolute;
|
||||||
inset: 0;
|
inset: 0;
|
||||||
background: var(--status-error-bg);
|
background: var(--status-error-bg);
|
||||||
box-shadow: inset 0 0 0 1px var(--status-error-fg);
|
|
||||||
border-radius: 0;
|
border-radius: 0;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
z-index: 2;
|
z-index: 2;
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
export type DeleteHoverState =
|
export type DeleteHoverState =
|
||||||
| { kind: "none" }
|
| { kind: "none" }
|
||||||
| { kind: "message"; messageId: string }
|
| { kind: "message"; messageId: string }
|
||||||
|
| { kind: "deleteUpTo"; messageId: string }
|
||||||
|
|||||||
Reference in New Issue
Block a user