mirror of
https://gitea.toothfairyai.com/ToothFairyAI/tf_code.git
synced 2026-03-30 05:43:55 +00:00
feat(app): interruption state
This commit is contained in:
parent
268855dc5a
commit
c173988aaa
@ -23,10 +23,6 @@
|
|||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
gap: 0;
|
gap: 0;
|
||||||
|
|
||||||
&[data-interrupted] {
|
|
||||||
color: var(--text-weak);
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-slot="user-message-attachments"] {
|
[data-slot="user-message-attachments"] {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
@ -165,10 +161,6 @@
|
|||||||
text-align: right;
|
text-align: right;
|
||||||
}
|
}
|
||||||
|
|
||||||
[data-slot="user-message-copy-wrapper"][data-interrupted] {
|
|
||||||
gap: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:hover [data-slot="user-message-copy-wrapper"],
|
&:hover [data-slot="user-message-copy-wrapper"],
|
||||||
&:focus-within [data-slot="user-message-copy-wrapper"] {
|
&:focus-within [data-slot="user-message-copy-wrapper"] {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
|
|||||||
@ -131,7 +131,6 @@ export interface MessageProps {
|
|||||||
parts: PartType[]
|
parts: PartType[]
|
||||||
actions?: UserActions
|
actions?: UserActions
|
||||||
showAssistantCopyPartID?: string | null
|
showAssistantCopyPartID?: string | null
|
||||||
interrupted?: boolean
|
|
||||||
showReasoningSummaries?: boolean
|
showReasoningSummaries?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -691,12 +690,7 @@ export function Message(props: MessageProps) {
|
|||||||
<Switch>
|
<Switch>
|
||||||
<Match when={props.message.role === "user" && props.message}>
|
<Match when={props.message.role === "user" && props.message}>
|
||||||
{(userMessage) => (
|
{(userMessage) => (
|
||||||
<UserMessageDisplay
|
<UserMessageDisplay message={userMessage() as UserMessage} parts={props.parts} actions={props.actions} />
|
||||||
message={userMessage() as UserMessage}
|
|
||||||
parts={props.parts}
|
|
||||||
actions={props.actions}
|
|
||||||
interrupted={props.interrupted}
|
|
||||||
/>
|
|
||||||
)}
|
)}
|
||||||
</Match>
|
</Match>
|
||||||
<Match when={props.message.role === "assistant" && props.message}>
|
<Match when={props.message.role === "assistant" && props.message}>
|
||||||
@ -887,12 +881,7 @@ function ContextToolGroup(props: { parts: ToolPart[]; busy?: boolean }) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function UserMessageDisplay(props: {
|
export function UserMessageDisplay(props: { message: UserMessage; parts: PartType[]; actions?: UserActions }) {
|
||||||
message: UserMessage
|
|
||||||
parts: PartType[]
|
|
||||||
actions?: UserActions
|
|
||||||
interrupted?: boolean
|
|
||||||
}) {
|
|
||||||
const data = useData()
|
const data = useData()
|
||||||
const dialog = useDialog()
|
const dialog = useDialog()
|
||||||
const i18n = useI18n()
|
const i18n = useI18n()
|
||||||
@ -947,10 +936,7 @@ export function UserMessageDisplay(props: {
|
|||||||
return items.filter((x) => !!x).join("\u00A0\u00B7\u00A0")
|
return items.filter((x) => !!x).join("\u00A0\u00B7\u00A0")
|
||||||
})
|
})
|
||||||
|
|
||||||
const metaTail = createMemo(() => {
|
const metaTail = stamp
|
||||||
const items = [stamp(), props.interrupted ? i18n.t("ui.message.interrupted") : ""]
|
|
||||||
return items.filter((x) => !!x).join("\u00A0\u00B7\u00A0")
|
|
||||||
})
|
|
||||||
|
|
||||||
const openImagePreview = (url: string, alt?: string) => {
|
const openImagePreview = (url: string, alt?: string) => {
|
||||||
dialog.show(() => <ImagePreview src={url} alt={alt} />)
|
dialog.show(() => <ImagePreview src={url} alt={alt} />)
|
||||||
@ -981,7 +967,7 @@ export function UserMessageDisplay(props: {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div data-component="user-message" data-interrupted={props.interrupted ? "" : undefined}>
|
<div data-component="user-message">
|
||||||
<Show when={attachments().length > 0}>
|
<Show when={attachments().length > 0}>
|
||||||
<div data-slot="user-message-attachments">
|
<div data-slot="user-message-attachments">
|
||||||
<For each={attachments()}>
|
<For each={attachments()}>
|
||||||
@ -1021,7 +1007,7 @@ export function UserMessageDisplay(props: {
|
|||||||
<HighlightedText text={text()} references={inlineFiles()} agents={agents()} />
|
<HighlightedText text={text()} references={inlineFiles()} agents={agents()} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div data-slot="user-message-copy-wrapper" data-interrupted={props.interrupted ? "" : undefined}>
|
<div data-slot="user-message-copy-wrapper">
|
||||||
<Show when={metaHead() || metaTail()}>
|
<Show when={metaHead() || metaTail()}>
|
||||||
<span data-slot="user-message-meta-wrap">
|
<span data-slot="user-message-meta-wrap">
|
||||||
<Show when={metaHead()}>
|
<Show when={metaHead()}>
|
||||||
@ -1305,14 +1291,13 @@ PART_MAPPING["tool"] = function ToolPartDisplay(props) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
PART_MAPPING["compaction"] = function CompactionPartDisplay() {
|
export function MessageDivider(props: { label: string }) {
|
||||||
const i18n = useI18n()
|
|
||||||
return (
|
return (
|
||||||
<div data-component="compaction-part">
|
<div data-component="compaction-part">
|
||||||
<div data-slot="compaction-part-divider">
|
<div data-slot="compaction-part-divider">
|
||||||
<span data-slot="compaction-part-line" />
|
<span data-slot="compaction-part-line" />
|
||||||
<span data-slot="compaction-part-label" class="text-12-regular text-text-weak">
|
<span data-slot="compaction-part-label" class="text-12-regular text-text-weak">
|
||||||
{i18n.t("ui.messagePart.compaction")}
|
{props.label}
|
||||||
</span>
|
</span>
|
||||||
<span data-slot="compaction-part-line" />
|
<span data-slot="compaction-part-line" />
|
||||||
</div>
|
</div>
|
||||||
@ -1320,6 +1305,11 @@ PART_MAPPING["compaction"] = function CompactionPartDisplay() {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
PART_MAPPING["compaction"] = function CompactionPartDisplay() {
|
||||||
|
const i18n = useI18n()
|
||||||
|
return <MessageDivider label={i18n.t("ui.messagePart.compaction")} />
|
||||||
|
}
|
||||||
|
|
||||||
PART_MAPPING["text"] = function TextPartDisplay(props) {
|
PART_MAPPING["text"] = function TextPartDisplay(props) {
|
||||||
const data = useData()
|
const data = useData()
|
||||||
const i18n = useI18n()
|
const i18n = useI18n()
|
||||||
|
|||||||
@ -7,7 +7,7 @@ import { Binary } from "@opencode-ai/util/binary"
|
|||||||
import { getDirectory, getFilename } from "@opencode-ai/util/path"
|
import { getDirectory, getFilename } from "@opencode-ai/util/path"
|
||||||
import { createEffect, createMemo, createSignal, For, on, ParentProps, Show } from "solid-js"
|
import { createEffect, createMemo, createSignal, For, on, ParentProps, Show } from "solid-js"
|
||||||
import { Dynamic } from "solid-js/web"
|
import { Dynamic } from "solid-js/web"
|
||||||
import { AssistantParts, Message, Part, PART_MAPPING, type UserActions } from "./message-part"
|
import { AssistantParts, Message, MessageDivider, PART_MAPPING, type UserActions } from "./message-part"
|
||||||
import { Card } from "./card"
|
import { Card } from "./card"
|
||||||
import { Accordion } from "./accordion"
|
import { Accordion } from "./accordion"
|
||||||
import { StickyAccordionHeader } from "./sticky-accordion-header"
|
import { StickyAccordionHeader } from "./sticky-accordion-header"
|
||||||
@ -276,6 +276,11 @@ export function SessionTurn(
|
|||||||
)
|
)
|
||||||
|
|
||||||
const interrupted = createMemo(() => assistantMessages().some((m) => m.error?.name === "MessageAbortedError"))
|
const interrupted = createMemo(() => assistantMessages().some((m) => m.error?.name === "MessageAbortedError"))
|
||||||
|
const divider = createMemo(() => {
|
||||||
|
if (compaction()) return i18n.t("ui.messagePart.compaction")
|
||||||
|
if (interrupted()) return i18n.t("ui.message.interrupted")
|
||||||
|
return ""
|
||||||
|
})
|
||||||
const error = createMemo(
|
const error = createMemo(
|
||||||
() => assistantMessages().find((m) => m.error && m.error.name !== "MessageAbortedError")?.error,
|
() => assistantMessages().find((m) => m.error && m.error.name !== "MessageAbortedError")?.error,
|
||||||
)
|
)
|
||||||
@ -384,11 +389,11 @@ export function SessionTurn(
|
|||||||
class={props.classes?.container}
|
class={props.classes?.container}
|
||||||
>
|
>
|
||||||
<div data-slot="session-turn-message-content" aria-live="off">
|
<div data-slot="session-turn-message-content" aria-live="off">
|
||||||
<Message message={message()!} parts={parts()} actions={props.actions} interrupted={interrupted()} />
|
<Message message={message()!} parts={parts()} actions={props.actions} />
|
||||||
</div>
|
</div>
|
||||||
<Show when={compaction()}>
|
<Show when={divider()}>
|
||||||
<div data-slot="session-turn-compaction">
|
<div data-slot="session-turn-compaction">
|
||||||
<Part part={compaction()!} message={message()!} hideDetails />
|
<MessageDivider label={divider()} />
|
||||||
</div>
|
</div>
|
||||||
</Show>
|
</Show>
|
||||||
<Show when={assistantMessages().length > 0}>
|
<Show when={assistantMessages().length > 0}>
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user