Adam 3 месяцев назад
Родитель
Сommit
0ff73ed8a6
2 измененных файлов с 56 добавлено и 19 удалено
  1. 39 11
      packages/desktop/src/components/message.tsx
  2. 17 8
      packages/desktop/src/pages/index.tsx

+ 39 - 11
packages/desktop/src/components/assistant-message.tsx → packages/desktop/src/components/message.tsx

@@ -1,4 +1,4 @@
-import type { Part, AssistantMessage, ReasoningPart, TextPart, ToolPart, Message } from "@opencode-ai/sdk"
+import type { Part, ReasoningPart, TextPart, ToolPart, Message, AssistantMessage, UserMessage } from "@opencode-ai/sdk"
 import { children, Component, createMemo, For, Match, Show, Switch, type JSX } from "solid-js"
 import { Dynamic } from "solid-js/web"
 import { Markdown } from "./markdown"
@@ -17,7 +17,20 @@ import type { WriteTool } from "opencode/tool/write"
 import type { TodoWriteTool } from "opencode/tool/todo"
 import { DiffChanges } from "./diff-changes"
 
-export function AssistantMessage(props: { message: AssistantMessage; parts: Part[] }) {
+export function Message(props: { message: Message; parts: Part[] }) {
+  return (
+    <Switch>
+      <Match when={props.message.role === "user" && props.message}>
+        {(userMessage) => <UserMessage message={userMessage()} parts={props.parts} />}
+      </Match>
+      <Match when={props.message.role === "assistant" && props.message}>
+        {(assistantMessage) => <AssistantMessage message={assistantMessage()} parts={props.parts} />}
+      </Match>
+    </Switch>
+  )
+}
+
+function AssistantMessage(props: { message: AssistantMessage; parts: Part[] }) {
   const filteredParts = createMemo(() => {
     return props.parts?.filter((x) => {
       if (x.type === "reasoning") return false
@@ -31,11 +44,26 @@ export function AssistantMessage(props: { message: AssistantMessage; parts: Part
   )
 }
 
-export function Part(props: { part: Part; message: Message; readonly?: boolean }) {
+function UserMessage(props: { message: UserMessage; parts: Part[] }) {
+  const text = createMemo(() =>
+    props.parts
+      ?.filter((p) => p.type === "text" && !p.synthetic)
+      ?.map((p) => (p as TextPart).text)
+      ?.join(""),
+  )
+  return <div class="text-12-regular text-text-base line-clamp-3">{text()}</div>
+}
+
+export function Part(props: { part: Part; message: Message; hideDetails?: boolean }) {
   const component = createMemo(() => PART_MAPPING[props.part.type as keyof typeof PART_MAPPING])
   return (
     <Show when={component()}>
-      <Dynamic component={component()} part={props.part as any} message={props.message} readonly={props.readonly} />
+      <Dynamic
+        component={component()}
+        part={props.part as any}
+        message={props.message}
+        hideDetails={props.hideDetails}
+      />
     </Show>
   )
 }
@@ -62,7 +90,7 @@ function TextPart(props: { part: TextPart; message: Message }) {
   )
 }
 
-function ToolPart(props: { part: ToolPart; message: Message; readonly?: boolean }) {
+function ToolPart(props: { part: ToolPart; message: Message; hideDetails?: boolean }) {
   const component = createMemo(() => {
     const render = ToolRegistry.render(props.part.tool) ?? GenericTool
     const metadata = props.part.state.status === "pending" ? {} : (props.part.state.metadata ?? {})
@@ -75,7 +103,7 @@ function ToolPart(props: { part: ToolPart; message: Message; readonly?: boolean
         tool={props.part.tool}
         metadata={metadata}
         output={props.part.state.status === "completed" ? props.part.state.output : undefined}
-        readonly={props.readonly}
+        hideDetails={props.hideDetails}
       />
     )
   })
@@ -101,7 +129,7 @@ function BasicTool(props: {
   icon: IconProps["name"]
   trigger: TriggerTitle | JSX.Element
   children?: JSX.Element
-  readonly?: boolean
+  hideDetails?: boolean
 }) {
   const resolved = children(() => props.children)
   return (
@@ -157,12 +185,12 @@ function BasicTool(props: {
               </Switch>
             </div>
           </div>
-          <Show when={resolved() && !props.readonly}>
+          <Show when={resolved() && !props.hideDetails}>
             <Collapsible.Arrow />
           </Show>
         </div>
       </Collapsible.Trigger>
-      <Show when={resolved() && !props.readonly}>
+      <Show when={resolved() && !props.hideDetails}>
         <Collapsible.Content>{resolved()}</Collapsible.Content>
       </Show>
     </Collapsible>
@@ -173,7 +201,7 @@ function BasicTool(props: {
 }
 
 function GenericTool(props: ToolProps<any>) {
-  return <BasicTool icon="mcp" trigger={{ title: props.tool }} readonly={props.readonly} />
+  return <BasicTool icon="mcp" trigger={{ title: props.tool }} hideDetails={props.hideDetails} />
 }
 
 type ToolProps<T extends Tool.Info> = {
@@ -181,7 +209,7 @@ type ToolProps<T extends Tool.Info> = {
   metadata: Partial<Tool.InferMetadata<T>>
   tool: string
   output?: string
-  readonly?: boolean
+  hideDetails?: boolean
 }
 
 const ToolRegistry = (() => {

+ 17 - 8
packages/desktop/src/pages/index.tsx

@@ -33,7 +33,7 @@ import { Code } from "@/components/code"
 import { useSync } from "@/context/sync"
 import { useSDK } from "@/context/sdk"
 import { ProgressCircle } from "@/components/progress-circle"
-import { AssistantMessage, Part } from "@/components/assistant-message"
+import { Message, Part } from "@/components/message"
 import { type AssistantMessage as AssistantMessageType } from "@opencode-ai/sdk"
 import { DiffChanges } from "@/components/diff-changes"
 
@@ -198,6 +198,7 @@ export default function Page() {
     }
     if (!session) return
 
+    local.session.setActive(session.id)
     const toAbsolutePath = (path: string) => (path.startsWith("/") ? path : sync.absolute(path))
 
     const text = parts.map((part) => part.content).join("")
@@ -259,7 +260,6 @@ export default function Page() {
         ],
       },
     })
-    local.session.setActive(session.id)
   }
 
   const handleNewSession = () => {
@@ -639,8 +639,9 @@ export default function Page() {
                               <For each={local.session.userMessages()}>
                                 {(message) => {
                                   const [expanded, setExpanded] = createSignal(false)
-                                  const title = createMemo(() => message.summary?.title)
+                                  const parts = createMemo(() => sync.data.part[message.id])
                                   const prompt = createMemo(() => local.session.getMessageText(message))
+                                  const title = createMemo(() => message.summary?.title)
                                   const summary = createMemo(() => message.summary?.body)
                                   const assistantMessages = createMemo(() => {
                                     return sync.data.message[activeSession().id]?.filter(
@@ -665,7 +666,9 @@ export default function Page() {
                                         </h1>
                                       </div>
                                       <Show when={title}>
-                                        <div class="-mt-8 text-12-regular text-text-base line-clamp-3">{prompt()}</div>
+                                        <div class="-mt-8">
+                                          <Message message={message} parts={parts()} />
+                                        </div>
                                       </Show>
                                       {/* Response */}
                                       <div class="w-full flex flex-col gap-2">
@@ -686,7 +689,7 @@ export default function Page() {
                                               <For each={assistantMessages()}>
                                                 {(assistantMessage) => {
                                                   const parts = createMemo(() => sync.data.part[assistantMessage.id])
-                                                  return <AssistantMessage message={assistantMessage} parts={parts()} />
+                                                  return <Message message={assistantMessage} parts={parts()} />
                                                 }}
                                               </For>
                                             </div>
@@ -722,7 +725,9 @@ export default function Page() {
                                                       const lastTextPart = createMemo(() =>
                                                         sync.data.part[last().id].findLast((p) => p.type === "text"),
                                                       )
-                                                      return <Part message={last()} part={lastTextPart()!} readonly />
+                                                      return (
+                                                        <Part message={last()} part={lastTextPart()!} hideDetails />
+                                                      )
                                                     }}
                                                   </Match>
                                                   <Match when={lastMessageWithReasoning()}>
@@ -733,7 +738,11 @@ export default function Page() {
                                                         ),
                                                       )
                                                       return (
-                                                        <Part message={last()} part={lastReasoningPart()!} readonly />
+                                                        <Part
+                                                          message={last()}
+                                                          part={lastReasoningPart()!}
+                                                          hideDetails
+                                                        />
                                                       )
                                                     }}
                                                   </Match>
@@ -745,7 +754,7 @@ export default function Page() {
                                                         (p) => p.type === "tool" && p.state.status === "completed",
                                                       ),
                                                     )
-                                                    return <Part message={last()} part={lastToolPart()!} readonly />
+                                                    return <Part message={last()} part={lastToolPart()!} hideDetails />
                                                   }}
                                                 </Show>
                                               </div>