deslopity deslopity (#18343)
parent
83cdb4de64
commit
0bbf26a1ce
|
|
@ -4,15 +4,15 @@ import {
|
||||||
createMemo,
|
createMemo,
|
||||||
createSignal,
|
createSignal,
|
||||||
For,
|
For,
|
||||||
Index,
|
|
||||||
Match,
|
Match,
|
||||||
onMount,
|
onMount,
|
||||||
Show,
|
Show,
|
||||||
Switch,
|
Switch,
|
||||||
onCleanup,
|
onCleanup,
|
||||||
|
Index,
|
||||||
type JSX,
|
type JSX,
|
||||||
} from "solid-js"
|
} from "solid-js"
|
||||||
import { createStore, unwrap } from "solid-js/store"
|
import { createStore } from "solid-js/store"
|
||||||
import stripAnsi from "strip-ansi"
|
import stripAnsi from "strip-ansi"
|
||||||
import { Dynamic } from "solid-js/web"
|
import { Dynamic } from "solid-js/web"
|
||||||
import {
|
import {
|
||||||
|
|
@ -481,15 +481,6 @@ function partDefaultOpen(part: PartType, shell = false, edit = false) {
|
||||||
return toolDefaultOpen(part.tool, shell, edit)
|
return toolDefaultOpen(part.tool, shell, edit)
|
||||||
}
|
}
|
||||||
|
|
||||||
function bindMessage<T extends MessageType>(input: T) {
|
|
||||||
const data = useData()
|
|
||||||
const base = structuredClone(unwrap(input)) as T
|
|
||||||
return createMemo(() => {
|
|
||||||
const next = data.store.message?.[base.sessionID]?.find((item) => item.id === base.id)
|
|
||||||
return (next as T | undefined) ?? base
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
export function AssistantParts(props: {
|
export function AssistantParts(props: {
|
||||||
messages: AssistantMessage[]
|
messages: AssistantMessage[]
|
||||||
showAssistantCopyPartID?: string | null
|
showAssistantCopyPartID?: string | null
|
||||||
|
|
@ -530,55 +521,62 @@ export function AssistantParts(props: {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Index each={grouped()}>
|
<Index each={grouped()}>
|
||||||
{(entry) => {
|
{(entryAccessor) => {
|
||||||
const kind = createMemo(() => entry().type)
|
const entryType = createMemo(() => entryAccessor().type)
|
||||||
const parts = createMemo(
|
|
||||||
() => {
|
|
||||||
const value = entry()
|
|
||||||
if (value.type !== "context") return emptyTools
|
|
||||||
return value.refs
|
|
||||||
.map((ref) => part().get(ref.messageID)?.get(ref.partID))
|
|
||||||
.filter((part): part is ToolPart => !!part && isContextGroupTool(part))
|
|
||||||
},
|
|
||||||
emptyTools,
|
|
||||||
{ equals: same },
|
|
||||||
)
|
|
||||||
const busy = createMemo(() => props.working && last() === entry().key)
|
|
||||||
const message = createMemo(() => {
|
|
||||||
const value = entry()
|
|
||||||
if (value.type !== "part") return
|
|
||||||
return msgs().get(value.ref.messageID)
|
|
||||||
})
|
|
||||||
const item = createMemo(() => {
|
|
||||||
const value = entry()
|
|
||||||
if (value.type !== "part") return
|
|
||||||
return part().get(value.ref.messageID)?.get(value.ref.partID)
|
|
||||||
})
|
|
||||||
const ready = createMemo(() => {
|
|
||||||
if (kind() !== "part") return
|
|
||||||
const msg = message()
|
|
||||||
const value = item()
|
|
||||||
if (!msg || !value) return
|
|
||||||
return { msg, value }
|
|
||||||
})
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<Switch>
|
||||||
<Show when={kind() === "context" && parts().length > 0}>
|
<Match when={entryType() === "context"}>
|
||||||
<ContextToolGroup parts={parts()} busy={busy()} />
|
{(() => {
|
||||||
</Show>
|
const parts = createMemo(
|
||||||
<Show when={ready()}>
|
() => {
|
||||||
{(ready) => (
|
const entry = entryAccessor()
|
||||||
<Part
|
if (entry.type !== "context") return emptyTools
|
||||||
part={ready().value}
|
return entry.refs
|
||||||
message={ready().msg}
|
.map((ref) => part().get(ref.messageID)?.get(ref.partID))
|
||||||
showAssistantCopyPartID={props.showAssistantCopyPartID}
|
.filter((part): part is ToolPart => !!part && isContextGroupTool(part))
|
||||||
turnDurationMs={props.turnDurationMs}
|
},
|
||||||
defaultOpen={partDefaultOpen(ready().value, props.shellToolDefaultOpen, props.editToolDefaultOpen)}
|
emptyTools,
|
||||||
/>
|
{ equals: same },
|
||||||
)}
|
)
|
||||||
</Show>
|
const busy = createMemo(() => props.working && last() === entryAccessor().key)
|
||||||
</>
|
|
||||||
|
return (
|
||||||
|
<Show when={parts().length > 0}>
|
||||||
|
<ContextToolGroup parts={parts()} busy={busy()} />
|
||||||
|
</Show>
|
||||||
|
)
|
||||||
|
})()}
|
||||||
|
</Match>
|
||||||
|
<Match when={entryType() === "part"}>
|
||||||
|
{(() => {
|
||||||
|
const message = createMemo(() => {
|
||||||
|
const entry = entryAccessor()
|
||||||
|
if (entry.type !== "part") return
|
||||||
|
return msgs().get(entry.ref.messageID)
|
||||||
|
})
|
||||||
|
const item = createMemo(() => {
|
||||||
|
const entry = entryAccessor()
|
||||||
|
if (entry.type !== "part") return
|
||||||
|
return part().get(entry.ref.messageID)?.get(entry.ref.partID)
|
||||||
|
})
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Show when={message()}>
|
||||||
|
<Show when={item()}>
|
||||||
|
<Part
|
||||||
|
part={item()!}
|
||||||
|
message={message()!}
|
||||||
|
showAssistantCopyPartID={props.showAssistantCopyPartID}
|
||||||
|
turnDurationMs={props.turnDurationMs}
|
||||||
|
defaultOpen={partDefaultOpen(item()!, props.shellToolDefaultOpen, props.editToolDefaultOpen)}
|
||||||
|
/>
|
||||||
|
</Show>
|
||||||
|
</Show>
|
||||||
|
)
|
||||||
|
})()}
|
||||||
|
</Match>
|
||||||
|
</Switch>
|
||||||
)
|
)
|
||||||
}}
|
}}
|
||||||
</Index>
|
</Index>
|
||||||
|
|
@ -690,22 +688,25 @@ export function registerPartComponent(type: string, component: PartComponent) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function Message(props: MessageProps) {
|
export function Message(props: MessageProps) {
|
||||||
if (props.message.role === "user") {
|
return (
|
||||||
return <UserMessageDisplay message={props.message as UserMessage} parts={props.parts} actions={props.actions} />
|
<Switch>
|
||||||
}
|
<Match when={props.message.role === "user" && props.message}>
|
||||||
|
{(userMessage) => (
|
||||||
if (props.message.role === "assistant") {
|
<UserMessageDisplay message={userMessage() as UserMessage} parts={props.parts} actions={props.actions} />
|
||||||
return (
|
)}
|
||||||
<AssistantMessageDisplay
|
</Match>
|
||||||
message={props.message as AssistantMessage}
|
<Match when={props.message.role === "assistant" && props.message}>
|
||||||
parts={props.parts}
|
{(assistantMessage) => (
|
||||||
showAssistantCopyPartID={props.showAssistantCopyPartID}
|
<AssistantMessageDisplay
|
||||||
showReasoningSummaries={props.showReasoningSummaries}
|
message={assistantMessage() as AssistantMessage}
|
||||||
/>
|
parts={props.parts}
|
||||||
)
|
showAssistantCopyPartID={props.showAssistantCopyPartID}
|
||||||
}
|
showReasoningSummaries={props.showReasoningSummaries}
|
||||||
|
/>
|
||||||
return undefined
|
)}
|
||||||
|
</Match>
|
||||||
|
</Switch>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function AssistantMessageDisplay(props: {
|
export function AssistantMessageDisplay(props: {
|
||||||
|
|
@ -732,42 +733,52 @@ export function AssistantMessageDisplay(props: {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Index each={grouped()}>
|
<Index each={grouped()}>
|
||||||
{(entry) => {
|
{(entryAccessor) => {
|
||||||
const kind = createMemo(() => entry().type)
|
const entryType = createMemo(() => entryAccessor().type)
|
||||||
const parts = createMemo(
|
|
||||||
() => {
|
|
||||||
const value = entry()
|
|
||||||
if (value.type !== "context") return emptyTools
|
|
||||||
return value.refs
|
|
||||||
.map((ref) => part().get(ref.partID))
|
|
||||||
.filter((part): part is ToolPart => !!part && isContextGroupTool(part))
|
|
||||||
},
|
|
||||||
emptyTools,
|
|
||||||
{ equals: same },
|
|
||||||
)
|
|
||||||
const item = createMemo(() => {
|
|
||||||
const value = entry()
|
|
||||||
if (value.type !== "part") return
|
|
||||||
return part().get(value.ref.partID)
|
|
||||||
})
|
|
||||||
const ready = createMemo(() => {
|
|
||||||
if (kind() !== "part") return
|
|
||||||
const value = item()
|
|
||||||
if (!value) return
|
|
||||||
return value
|
|
||||||
})
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<Switch>
|
||||||
<Show when={kind() === "context" && parts().length > 0}>
|
<Match when={entryType() === "context"}>
|
||||||
<ContextToolGroup parts={parts()} />
|
{(() => {
|
||||||
</Show>
|
const parts = createMemo(
|
||||||
<Show when={ready()}>
|
() => {
|
||||||
{(ready) => (
|
const entry = entryAccessor()
|
||||||
<Part part={ready()} message={props.message} showAssistantCopyPartID={props.showAssistantCopyPartID} />
|
if (entry.type !== "context") return emptyTools
|
||||||
)}
|
return entry.refs
|
||||||
</Show>
|
.map((ref) => part().get(ref.partID))
|
||||||
</>
|
.filter((part): part is ToolPart => !!part && isContextGroupTool(part))
|
||||||
|
},
|
||||||
|
emptyTools,
|
||||||
|
{ equals: same },
|
||||||
|
)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Show when={parts().length > 0}>
|
||||||
|
<ContextToolGroup parts={parts()} />
|
||||||
|
</Show>
|
||||||
|
)
|
||||||
|
})()}
|
||||||
|
</Match>
|
||||||
|
<Match when={entryType() === "part"}>
|
||||||
|
{(() => {
|
||||||
|
const item = createMemo(() => {
|
||||||
|
const entry = entryAccessor()
|
||||||
|
if (entry.type !== "part") return
|
||||||
|
return part().get(entry.ref.partID)
|
||||||
|
})
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Show when={item()}>
|
||||||
|
<Part
|
||||||
|
part={item()!}
|
||||||
|
message={props.message}
|
||||||
|
showAssistantCopyPartID={props.showAssistantCopyPartID}
|
||||||
|
/>
|
||||||
|
</Show>
|
||||||
|
)
|
||||||
|
})()}
|
||||||
|
</Match>
|
||||||
|
</Switch>
|
||||||
)
|
)
|
||||||
}}
|
}}
|
||||||
</Index>
|
</Index>
|
||||||
|
|
@ -834,9 +845,11 @@ function ContextToolGroup(props: { parts: ToolPart[]; busy?: boolean }) {
|
||||||
<Collapsible.Content>
|
<Collapsible.Content>
|
||||||
<div data-component="context-tool-group-list">
|
<div data-component="context-tool-group-list">
|
||||||
<Index each={props.parts}>
|
<Index each={props.parts}>
|
||||||
{(part) => {
|
{(partAccessor) => {
|
||||||
const trigger = createMemo(() => contextToolTrigger(part(), i18n))
|
const trigger = createMemo(() => contextToolTrigger(partAccessor(), i18n))
|
||||||
const running = createMemo(() => part().state.status === "pending" || part().state.status === "running")
|
const running = createMemo(
|
||||||
|
() => partAccessor().state.status === "pending" || partAccessor().state.status === "running",
|
||||||
|
)
|
||||||
return (
|
return (
|
||||||
<div data-slot="context-tool-group-item">
|
<div data-slot="context-tool-group-item">
|
||||||
<div data-component="tool-trigger">
|
<div data-component="tool-trigger">
|
||||||
|
|
@ -874,7 +887,6 @@ export function UserMessageDisplay(props: { message: UserMessage; parts: PartTyp
|
||||||
const data = useData()
|
const data = useData()
|
||||||
const dialog = useDialog()
|
const dialog = useDialog()
|
||||||
const i18n = useI18n()
|
const i18n = useI18n()
|
||||||
const message = bindMessage(props.message)
|
|
||||||
const [state, setState] = createStore({
|
const [state, setState] = createStore({
|
||||||
copied: false,
|
copied: false,
|
||||||
busy: undefined as "fork" | "revert" | undefined,
|
busy: undefined as "fork" | "revert" | undefined,
|
||||||
|
|
@ -897,8 +909,8 @@ export function UserMessageDisplay(props: { message: UserMessage; parts: PartTyp
|
||||||
const agents = createMemo(() => (props.parts?.filter((p) => p.type === "agent") as AgentPart[]) ?? [])
|
const agents = createMemo(() => (props.parts?.filter((p) => p.type === "agent") as AgentPart[]) ?? [])
|
||||||
|
|
||||||
const model = createMemo(() => {
|
const model = createMemo(() => {
|
||||||
const providerID = message().model?.providerID
|
const providerID = props.message.model?.providerID
|
||||||
const modelID = message().model?.modelID
|
const modelID = props.message.model?.modelID
|
||||||
if (!providerID || !modelID) return ""
|
if (!providerID || !modelID) return ""
|
||||||
const match = data.store.provider?.all?.find((p) => p.id === providerID)
|
const match = data.store.provider?.all?.find((p) => p.id === providerID)
|
||||||
return match?.models?.[modelID]?.name ?? modelID
|
return match?.models?.[modelID]?.name ?? modelID
|
||||||
|
|
@ -906,13 +918,13 @@ export function UserMessageDisplay(props: { message: UserMessage; parts: PartTyp
|
||||||
const timefmt = createMemo(() => new Intl.DateTimeFormat(i18n.locale(), { timeStyle: "short" }))
|
const timefmt = createMemo(() => new Intl.DateTimeFormat(i18n.locale(), { timeStyle: "short" }))
|
||||||
|
|
||||||
const stamp = createMemo(() => {
|
const stamp = createMemo(() => {
|
||||||
const created = message().time?.created
|
const created = props.message.time?.created
|
||||||
if (typeof created !== "number") return ""
|
if (typeof created !== "number") return ""
|
||||||
return timefmt().format(created)
|
return timefmt().format(created)
|
||||||
})
|
})
|
||||||
|
|
||||||
const metaHead = createMemo(() => {
|
const metaHead = createMemo(() => {
|
||||||
const agent = message().agent
|
const agent = props.message.agent
|
||||||
const items = [agent ? agent[0]?.toUpperCase() + agent.slice(1) : "", model()]
|
const items = [agent ? agent[0]?.toUpperCase() + agent.slice(1) : "", model()]
|
||||||
return items.filter((x) => !!x).join("\u00A0\u00B7\u00A0")
|
return items.filter((x) => !!x).join("\u00A0\u00B7\u00A0")
|
||||||
})
|
})
|
||||||
|
|
@ -938,8 +950,8 @@ export function UserMessageDisplay(props: { message: UserMessage; parts: PartTyp
|
||||||
void Promise.resolve()
|
void Promise.resolve()
|
||||||
.then(() =>
|
.then(() =>
|
||||||
act({
|
act({
|
||||||
sessionID: message().sessionID,
|
sessionID: props.message.sessionID,
|
||||||
messageID: message().id,
|
messageID: props.message.id,
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
|
|
@ -1298,27 +1310,27 @@ PART_MAPPING["text"] = function TextPartDisplay(props) {
|
||||||
const i18n = useI18n()
|
const i18n = useI18n()
|
||||||
const numfmt = createMemo(() => new Intl.NumberFormat(i18n.locale()))
|
const numfmt = createMemo(() => new Intl.NumberFormat(i18n.locale()))
|
||||||
const part = () => props.part as TextPart
|
const part = () => props.part as TextPart
|
||||||
const message = bindMessage(props.message)
|
|
||||||
const interrupted = createMemo(
|
const interrupted = createMemo(
|
||||||
() => message().role === "assistant" && (message() as AssistantMessage).error?.name === "MessageAbortedError",
|
() =>
|
||||||
|
props.message.role === "assistant" && (props.message as AssistantMessage).error?.name === "MessageAbortedError",
|
||||||
)
|
)
|
||||||
|
|
||||||
const model = createMemo(() => {
|
const model = createMemo(() => {
|
||||||
const current = message()
|
if (props.message.role !== "assistant") return ""
|
||||||
if (current.role !== "assistant") return ""
|
const message = props.message as AssistantMessage
|
||||||
const match = data.store.provider?.all?.find((p) => p.id === current.providerID)
|
const match = data.store.provider?.all?.find((p) => p.id === message.providerID)
|
||||||
return match?.models?.[current.modelID]?.name ?? current.modelID
|
return match?.models?.[message.modelID]?.name ?? message.modelID
|
||||||
})
|
})
|
||||||
|
|
||||||
const duration = createMemo(() => {
|
const duration = createMemo(() => {
|
||||||
const current = message()
|
if (props.message.role !== "assistant") return ""
|
||||||
if (current.role !== "assistant") return ""
|
const message = props.message as AssistantMessage
|
||||||
const completed = current.time.completed
|
const completed = message.time.completed
|
||||||
const ms =
|
const ms =
|
||||||
typeof props.turnDurationMs === "number"
|
typeof props.turnDurationMs === "number"
|
||||||
? props.turnDurationMs
|
? props.turnDurationMs
|
||||||
: typeof completed === "number"
|
: typeof completed === "number"
|
||||||
? completed - current.time.created
|
? completed - message.time.created
|
||||||
: -1
|
: -1
|
||||||
if (!(ms >= 0)) return ""
|
if (!(ms >= 0)) return ""
|
||||||
const total = Math.round(ms / 1000)
|
const total = Math.round(ms / 1000)
|
||||||
|
|
@ -1332,9 +1344,8 @@ PART_MAPPING["text"] = function TextPartDisplay(props) {
|
||||||
})
|
})
|
||||||
|
|
||||||
const meta = createMemo(() => {
|
const meta = createMemo(() => {
|
||||||
const current = message()
|
if (props.message.role !== "assistant") return ""
|
||||||
if (current.role !== "assistant") return ""
|
const agent = (props.message as AssistantMessage).agent
|
||||||
const agent = current.agent
|
|
||||||
const items = [
|
const items = [
|
||||||
agent ? agent[0]?.toUpperCase() + agent.slice(1) : "",
|
agent ? agent[0]?.toUpperCase() + agent.slice(1) : "",
|
||||||
model(),
|
model(),
|
||||||
|
|
@ -1347,13 +1358,13 @@ PART_MAPPING["text"] = function TextPartDisplay(props) {
|
||||||
const displayText = () => (part().text ?? "").trim()
|
const displayText = () => (part().text ?? "").trim()
|
||||||
const throttledText = createThrottledValue(displayText)
|
const throttledText = createThrottledValue(displayText)
|
||||||
const isLastTextPart = createMemo(() => {
|
const isLastTextPart = createMemo(() => {
|
||||||
const last = (data.store.part?.[message().id] ?? [])
|
const last = (data.store.part?.[props.message.id] ?? [])
|
||||||
.filter((item): item is TextPart => item?.type === "text" && !!item.text?.trim())
|
.filter((item): item is TextPart => item?.type === "text" && !!item.text?.trim())
|
||||||
.at(-1)
|
.at(-1)
|
||||||
return last?.id === part().id
|
return last?.id === part().id
|
||||||
})
|
})
|
||||||
const showCopy = createMemo(() => {
|
const showCopy = createMemo(() => {
|
||||||
if (message().role !== "assistant") return isLastTextPart()
|
if (props.message.role !== "assistant") return isLastTextPart()
|
||||||
if (props.showAssistantCopyPartID === null) return false
|
if (props.showAssistantCopyPartID === null) return false
|
||||||
if (typeof props.showAssistantCopyPartID === "string") return props.showAssistantCopyPartID === part().id
|
if (typeof props.showAssistantCopyPartID === "string") return props.showAssistantCopyPartID === part().id
|
||||||
return isLastTextPart()
|
return isLastTextPart()
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue