Browse Source

fix(app): task tool rendering

Adam 2 weeks ago
parent
commit
50f3e74d05

+ 9 - 0
packages/app/src/pages/directory-layout.tsx

@@ -54,6 +54,13 @@ export default function Layout(props: ParentProps) {
               navigate(`/${params.dir}/session/${sessionID}`)
             }
 
+            const sessionHref = (sessionID: string) => {
+              if (params.dir) return `/${params.dir}/session/${sessionID}`
+              return `/session/${sessionID}`
+            }
+
+            const syncSession = (sessionID: string) => sync.session.sync(sessionID)
+
             return (
               <DataProvider
                 data={sync.data}
@@ -62,6 +69,8 @@ export default function Layout(props: ParentProps) {
                 onQuestionReply={replyToQuestion}
                 onQuestionReject={rejectQuestion}
                 onNavigateToSession={navigateToSession}
+                onSessionHref={sessionHref}
+                onSyncSession={syncSession}
               >
                 <LocalProvider>{props.children}</LocalProvider>
               </DataProvider>

+ 70 - 32
packages/ui/src/components/message-part.tsx

@@ -877,6 +877,74 @@ ToolRegistry.register({
     const data = useData()
     const i18n = useI18n()
     const childSessionId = () => props.metadata.sessionId as string | undefined
+
+    const href = createMemo(() => {
+      const sessionId = childSessionId()
+      if (!sessionId) return
+
+      const direct = data.sessionHref?.(sessionId)
+      if (direct) return direct
+
+      if (typeof window === "undefined") return
+      const path = window.location.pathname
+      const idx = path.indexOf("/session")
+      if (idx === -1) return
+      return `${path.slice(0, idx)}/session/${sessionId}`
+    })
+
+    createEffect(() => {
+      const sessionId = childSessionId()
+      if (!sessionId) return
+      const sync = data.syncSession
+      if (!sync) return
+      Promise.resolve(sync(sessionId)).catch(() => undefined)
+    })
+
+    const handleLinkClick = (e: MouseEvent) => {
+      const sessionId = childSessionId()
+      const url = href()
+      if (!sessionId || !url) return
+
+      e.stopPropagation()
+
+      if (e.button !== 0 || e.metaKey || e.ctrlKey || e.shiftKey || e.altKey) return
+
+      const nav = data.navigateToSession
+      if (!nav || typeof window === "undefined") return
+
+      e.preventDefault()
+      const before = window.location.pathname + window.location.search + window.location.hash
+      nav(sessionId)
+      setTimeout(() => {
+        const after = window.location.pathname + window.location.search + window.location.hash
+        if (after === before) window.location.assign(url)
+      }, 50)
+    }
+
+    const trigger = () => (
+      <div data-slot="basic-tool-tool-info-structured">
+        <div data-slot="basic-tool-tool-info-main">
+          <span data-slot="basic-tool-tool-title" class="capitalize">
+            {i18n.t("ui.tool.agent", { type: props.input.subagent_type || props.tool })}
+          </span>
+          <Show when={props.input.description}>
+            <Switch>
+              <Match when={href()}>
+                {(url) => (
+                  <a data-slot="basic-tool-tool-subtitle" class="clickable" href={url()} onClick={handleLinkClick}>
+                    {props.input.description}
+                  </a>
+                )}
+              </Match>
+              <Match when={true}>
+                <span data-slot="basic-tool-tool-subtitle">{props.input.description}</span>
+              </Match>
+            </Switch>
+          </Show>
+        </div>
+      </div>
+    )
+
     const childToolParts = createMemo(() => {
       const sessionId = childSessionId()
       if (!sessionId) return []
@@ -924,13 +992,6 @@ ToolRegistry.register({
       })
     }
 
-    const handleSubtitleClick = () => {
-      const sessionId = childSessionId()
-      if (sessionId && data.navigateToSession) {
-        data.navigateToSession(sessionId)
-      }
-    }
-
     const renderChildToolPart = () => {
       const toolData = childToolPart()
       if (!toolData) return null
@@ -958,21 +1019,7 @@ ToolRegistry.register({
         <Switch>
           <Match when={childPermission()}>
             <>
-              <Show
-                when={childToolPart()}
-                fallback={
-                  <BasicTool
-                    icon="task"
-                    defaultOpen={true}
-                    trigger={{
-                      title: i18n.t("ui.tool.agent", { type: props.input.subagent_type || props.tool }),
-                      titleClass: "capitalize",
-                      subtitle: props.input.description,
-                    }}
-                    onSubtitleClick={handleSubtitleClick}
-                  />
-                }
-              >
+              <Show when={childToolPart()} fallback={<BasicTool icon="task" defaultOpen={true} trigger={trigger()} />}>
                 {renderChildToolPart()}
               </Show>
               <div data-component="permission-prompt">
@@ -991,16 +1038,7 @@ ToolRegistry.register({
             </>
           </Match>
           <Match when={true}>
-            <BasicTool
-              icon="task"
-              defaultOpen={true}
-              trigger={{
-                title: i18n.t("ui.tool.agent", { type: props.input.subagent_type || props.tool }),
-                titleClass: "capitalize",
-                subtitle: props.input.description,
-              }}
-              onSubtitleClick={handleSubtitleClick}
-            >
+            <BasicTool icon="task" defaultOpen={true} trigger={trigger()}>
               <div
                 ref={autoScroll.scrollRef}
                 onScroll={autoScroll.handleScroll}

+ 8 - 0
packages/ui/src/context/data.tsx

@@ -48,6 +48,10 @@ export type QuestionRejectFn = (input: { requestID: string }) => void
 
 export type NavigateToSessionFn = (sessionID: string) => void
 
+export type SessionHrefFn = (sessionID: string) => string
+
+export type SyncSessionFn = (sessionID: string) => void | Promise<void>
+
 export const { use: useData, provider: DataProvider } = createSimpleContext({
   name: "Data",
   init: (props: {
@@ -57,6 +61,8 @@ export const { use: useData, provider: DataProvider } = createSimpleContext({
     onQuestionReply?: QuestionReplyFn
     onQuestionReject?: QuestionRejectFn
     onNavigateToSession?: NavigateToSessionFn
+    onSessionHref?: SessionHrefFn
+    onSyncSession?: SyncSessionFn
   }) => {
     return {
       get store() {
@@ -69,6 +75,8 @@ export const { use: useData, provider: DataProvider } = createSimpleContext({
       replyToQuestion: props.onQuestionReply,
       rejectQuestion: props.onQuestionReject,
       navigateToSession: props.onNavigateToSession,
+      sessionHref: props.onSessionHref,
+      syncSession: props.onSyncSession,
     }
   },
 })