|
|
@@ -1524,6 +1524,8 @@ const PART_MAPPING = {
|
|
|
reasoning: ReasoningPart,
|
|
|
}
|
|
|
|
|
|
+const INLINE_TOOL_ICON_WIDTH = 2
|
|
|
+
|
|
|
function ReasoningPart(props: { last: boolean; part: ReasoningPart; message: AssistantMessage }) {
|
|
|
const { theme } = useTheme()
|
|
|
const ctx = use()
|
|
|
@@ -1789,12 +1791,12 @@ function InlineTool(props: {
|
|
|
part: ToolPart
|
|
|
onClick?: () => void
|
|
|
}) {
|
|
|
- const [margin, setMargin] = createSignal(0)
|
|
|
const { theme } = useTheme()
|
|
|
const ctx = use()
|
|
|
const sync = useSync()
|
|
|
const renderer = useRenderer()
|
|
|
const [hover, setHover] = createSignal(false)
|
|
|
+ const [errorExpanded, setErrorExpanded] = createSignal(false)
|
|
|
|
|
|
const permission = createMemo(() => {
|
|
|
const callID = sync.data.permission[ctx.sessionID]?.at(0)?.tool?.callID
|
|
|
@@ -1802,14 +1804,6 @@ function InlineTool(props: {
|
|
|
return callID === props.part.callID
|
|
|
})
|
|
|
|
|
|
- const fg = createMemo(() => {
|
|
|
- if (props.color) return props.color
|
|
|
- if (permission()) return theme.warning
|
|
|
- if (hover() && props.onClick) return theme.text
|
|
|
- if (props.complete) return theme.textMuted
|
|
|
- return theme.text
|
|
|
- })
|
|
|
-
|
|
|
const error = createMemo(() => (props.part.state.status === "error" ? props.part.state.error : undefined))
|
|
|
|
|
|
const denied = createMemo(
|
|
|
@@ -1820,53 +1814,134 @@ function InlineTool(props: {
|
|
|
error()?.includes("user dismissed"),
|
|
|
)
|
|
|
|
|
|
+ const failed = createMemo(() => Boolean(error() && !denied()))
|
|
|
+ const clickable = createMemo(() => Boolean(props.onClick || failed()))
|
|
|
+ const fg = createMemo(() => {
|
|
|
+ if (props.color) return props.color
|
|
|
+ if (permission()) return theme.warning
|
|
|
+ if (failed()) return theme.error
|
|
|
+ if (hover() && props.onClick) return theme.text
|
|
|
+ if (props.complete) return theme.textMuted
|
|
|
+ return theme.text
|
|
|
+ })
|
|
|
+
|
|
|
return (
|
|
|
- <box
|
|
|
- marginTop={margin()}
|
|
|
- paddingLeft={3}
|
|
|
- onMouseOver={() => props.onClick && setHover(true)}
|
|
|
+ <InlineToolRow
|
|
|
+ icon={props.icon}
|
|
|
+ iconColor={props.iconColor}
|
|
|
+ color={fg()}
|
|
|
+ errorColor={theme.error}
|
|
|
+ failed={failed()}
|
|
|
+ denied={Boolean(denied())}
|
|
|
+ error={error()}
|
|
|
+ errorExpanded={errorExpanded()}
|
|
|
+ complete={props.complete}
|
|
|
+ pending={props.pending}
|
|
|
+ spinner={props.spinner}
|
|
|
+ separateAfter={(id) =>
|
|
|
+ sync.data.message[ctx.sessionID]?.some((message) => message.role === "user" && message.id === id) ?? false
|
|
|
+ }
|
|
|
+ onMouseOver={() => clickable() && setHover(true)}
|
|
|
onMouseOut={() => setHover(false)}
|
|
|
onMouseUp={() => {
|
|
|
if (renderer.getSelection()?.getSelectedText()) return
|
|
|
+ if (failed()) {
|
|
|
+ setErrorExpanded((value) => !value)
|
|
|
+ return
|
|
|
+ }
|
|
|
props.onClick?.()
|
|
|
}}
|
|
|
+ >
|
|
|
+ {props.children}
|
|
|
+ </InlineToolRow>
|
|
|
+ )
|
|
|
+}
|
|
|
+
|
|
|
+export function InlineToolRow(props: {
|
|
|
+ icon: string
|
|
|
+ iconColor?: RGBA
|
|
|
+ color?: RGBA
|
|
|
+ errorColor?: RGBA
|
|
|
+ failed?: boolean
|
|
|
+ denied?: boolean
|
|
|
+ error?: string
|
|
|
+ errorExpanded?: boolean
|
|
|
+ complete: any
|
|
|
+ pending: string
|
|
|
+ spinner?: boolean
|
|
|
+ children: JSX.Element
|
|
|
+ separateAfter?: (id: string | undefined) => boolean
|
|
|
+ onMouseOver?: () => void
|
|
|
+ onMouseOut?: () => void
|
|
|
+ onMouseUp?: () => void
|
|
|
+}) {
|
|
|
+ const [margin, setMargin] = createSignal(0)
|
|
|
+
|
|
|
+ return (
|
|
|
+ <box
|
|
|
+ marginTop={margin()}
|
|
|
+ paddingLeft={3}
|
|
|
+ onMouseOver={props.onMouseOver}
|
|
|
+ onMouseOut={props.onMouseOut}
|
|
|
+ onMouseUp={props.onMouseUp}
|
|
|
renderBefore={function () {
|
|
|
const el = this as BoxRenderable
|
|
|
const parent = el.parent
|
|
|
if (!parent) {
|
|
|
return
|
|
|
}
|
|
|
- if (el.height > 1) {
|
|
|
- setMargin(1)
|
|
|
- return
|
|
|
- }
|
|
|
const children = parent.getChildren()
|
|
|
const index = children.indexOf(el)
|
|
|
const previous = children[index - 1]
|
|
|
- if (!previous) {
|
|
|
- setMargin(0)
|
|
|
- return
|
|
|
- }
|
|
|
- if (previous.height > 1 || previous.id.startsWith("text-")) {
|
|
|
- setMargin(1)
|
|
|
- return
|
|
|
- }
|
|
|
+ setMargin(
|
|
|
+ previous?.id.startsWith("text-") ||
|
|
|
+ previous?.id.startsWith("tool-block-") ||
|
|
|
+ props.separateAfter?.(previous?.id)
|
|
|
+ ? 1
|
|
|
+ : 0,
|
|
|
+ )
|
|
|
}}
|
|
|
>
|
|
|
<Switch>
|
|
|
<Match when={props.spinner}>
|
|
|
- <Spinner color={fg()} children={props.children} />
|
|
|
+ <Spinner color={props.color} children={props.children} />
|
|
|
</Match>
|
|
|
<Match when={true}>
|
|
|
- <text paddingLeft={3} fg={fg()} attributes={denied() ? TextAttributes.STRIKETHROUGH : undefined}>
|
|
|
- <Show fallback={<>~ {props.pending}</>} when={props.complete}>
|
|
|
- <span style={{ fg: props.iconColor }}>{props.icon}</span> {props.children}
|
|
|
- </Show>
|
|
|
- </text>
|
|
|
+ <Show
|
|
|
+ fallback={
|
|
|
+ <text
|
|
|
+ paddingLeft={3}
|
|
|
+ fg={props.color}
|
|
|
+ attributes={props.denied ? TextAttributes.STRIKETHROUGH : undefined}
|
|
|
+ >
|
|
|
+ ~ {props.pending}
|
|
|
+ </text>
|
|
|
+ }
|
|
|
+ when={props.complete}
|
|
|
+ >
|
|
|
+ <box flexDirection="row">
|
|
|
+ <text
|
|
|
+ width={INLINE_TOOL_ICON_WIDTH}
|
|
|
+ fg={props.failed ? props.errorColor : (props.iconColor ?? props.color)}
|
|
|
+ attributes={props.denied ? TextAttributes.STRIKETHROUGH : undefined}
|
|
|
+ >
|
|
|
+ {props.icon}
|
|
|
+ </text>
|
|
|
+ <text
|
|
|
+ flexGrow={1}
|
|
|
+ fg={props.failed ? props.errorColor : props.color}
|
|
|
+ attributes={props.denied ? TextAttributes.STRIKETHROUGH : undefined}
|
|
|
+ >
|
|
|
+ {props.children}
|
|
|
+ </text>
|
|
|
+ </box>
|
|
|
+ </Show>
|
|
|
</Match>
|
|
|
</Switch>
|
|
|
- <Show when={error() && !denied()}>
|
|
|
- <text fg={theme.error}>{error()}</text>
|
|
|
+ <Show when={props.failed && props.errorExpanded}>
|
|
|
+ <box paddingLeft={INLINE_TOOL_ICON_WIDTH}>
|
|
|
+ <text fg={props.errorColor}>{props.error}</text>
|
|
|
+ </box>
|
|
|
</Show>
|
|
|
</box>
|
|
|
)
|
|
|
@@ -1885,6 +1960,7 @@ function BlockTool(props: {
|
|
|
const error = createMemo(() => (props.part?.state.status === "error" ? props.part.state.error : undefined))
|
|
|
return (
|
|
|
<box
|
|
|
+ id={props.part ? "tool-block-" + props.part.id : undefined}
|
|
|
border={["left"]}
|
|
|
paddingTop={1}
|
|
|
paddingBottom={1}
|