fix: show model display name in message footer and transcript (#20539)
This commit is contained in:
@@ -21,7 +21,15 @@ import { Spinner } from "@tui/component/spinner"
|
||||
import { selectedForeground, useTheme } from "@tui/context/theme"
|
||||
import { BoxRenderable, ScrollBoxRenderable, addDefaultParsers, TextAttributes, RGBA } from "@opentui/core"
|
||||
import { Prompt, type PromptRef } from "@tui/component/prompt"
|
||||
import type { AssistantMessage, Part, ToolPart, UserMessage, TextPart, ReasoningPart } from "@opencode-ai/sdk/v2"
|
||||
import type {
|
||||
AssistantMessage,
|
||||
Part,
|
||||
Provider,
|
||||
ToolPart,
|
||||
UserMessage,
|
||||
TextPart,
|
||||
ReasoningPart,
|
||||
} from "@opencode-ai/sdk/v2"
|
||||
import { useLocal } from "@tui/context/local"
|
||||
import { Locale } from "@/util/locale"
|
||||
import type { Tool } from "@/tool/tool"
|
||||
@@ -69,6 +77,7 @@ import { Global } from "@/global"
|
||||
import { PermissionPrompt } from "./permission"
|
||||
import { QuestionPrompt } from "./question"
|
||||
import { DialogExportOptions } from "../../ui/dialog-export-options"
|
||||
import * as Model from "../../util/model"
|
||||
import { formatTranscript } from "../../util/transcript"
|
||||
import { UI } from "@/cli/ui.ts"
|
||||
import { useTuiConfig } from "../../context/tui-config"
|
||||
@@ -85,6 +94,7 @@ const context = createContext<{
|
||||
showDetails: () => boolean
|
||||
showGenericToolOutput: () => boolean
|
||||
diffWrapMode: () => "word" | "none"
|
||||
providers: () => ReadonlyMap<string, Provider>
|
||||
sync: ReturnType<typeof useSync>
|
||||
tui: ReturnType<typeof useTuiConfig>
|
||||
}>()
|
||||
@@ -150,6 +160,7 @@ export function Session() {
|
||||
})
|
||||
const showTimestamps = createMemo(() => timestamps() === "show")
|
||||
const contentWidth = createMemo(() => dimensions().width - (sidebarVisible() ? 42 : 0) - 4)
|
||||
const providers = createMemo(() => Model.index(sync.data.provider))
|
||||
|
||||
const scrollAcceleration = createMemo(() => getScrollAcceleration(tuiConfig))
|
||||
|
||||
@@ -814,6 +825,7 @@ export function Session() {
|
||||
thinking: showThinking(),
|
||||
toolDetails: showDetails(),
|
||||
assistantMetadata: showAssistantMetadata(),
|
||||
providers: sync.data.provider,
|
||||
},
|
||||
)
|
||||
await Clipboard.copy(transcript)
|
||||
@@ -858,6 +870,7 @@ export function Session() {
|
||||
thinking: options.thinking,
|
||||
toolDetails: options.toolDetails,
|
||||
assistantMetadata: options.assistantMetadata,
|
||||
providers: sync.data.provider,
|
||||
},
|
||||
)
|
||||
|
||||
@@ -1003,6 +1016,7 @@ export function Session() {
|
||||
showDetails,
|
||||
showGenericToolOutput,
|
||||
diffWrapMode,
|
||||
providers,
|
||||
sync,
|
||||
tui: tuiConfig,
|
||||
}}
|
||||
@@ -1287,10 +1301,12 @@ function UserMessage(props: {
|
||||
}
|
||||
|
||||
function AssistantMessage(props: { message: AssistantMessage; parts: Part[]; last: boolean }) {
|
||||
const ctx = use()
|
||||
const local = useLocal()
|
||||
const { theme } = useTheme()
|
||||
const sync = useSync()
|
||||
const messages = createMemo(() => sync.data.message[props.message.sessionID] ?? [])
|
||||
const model = createMemo(() => Model.name(ctx.providers(), props.message.providerID, props.message.modelID))
|
||||
|
||||
const final = createMemo(() => {
|
||||
return props.message.finish && !["tool-calls", "unknown"].includes(props.message.finish)
|
||||
@@ -1360,7 +1376,7 @@ function AssistantMessage(props: { message: AssistantMessage; parts: Part[]; las
|
||||
▣{" "}
|
||||
</span>{" "}
|
||||
<span style={{ fg: theme.text }}>{Locale.titlecase(props.message.mode)}</span>
|
||||
<span style={{ fg: theme.textMuted }}> · {props.message.modelID}</span>
|
||||
<span style={{ fg: theme.textMuted }}> · {model()}</span>
|
||||
<Show when={duration()}>
|
||||
<span style={{ fg: theme.textMuted }}> · {Locale.duration(duration())}</span>
|
||||
</Show>
|
||||
|
||||
23
packages/opencode/src/cli/cmd/tui/util/model.ts
Normal file
23
packages/opencode/src/cli/cmd/tui/util/model.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import type { Provider } from "@opencode-ai/sdk/v2"
|
||||
|
||||
export function index(list: Provider[] | undefined) {
|
||||
return new Map((list ?? []).map((item) => [item.id, item] as const))
|
||||
}
|
||||
|
||||
export function get(list: Provider[] | ReadonlyMap<string, Provider> | undefined, providerID: string, modelID: string) {
|
||||
const provider =
|
||||
list instanceof Map
|
||||
? list.get(providerID)
|
||||
: Array.isArray(list)
|
||||
? list.find((item) => item.id === providerID)
|
||||
: undefined
|
||||
return provider?.models[modelID]
|
||||
}
|
||||
|
||||
export function name(
|
||||
list: Provider[] | ReadonlyMap<string, Provider> | undefined,
|
||||
providerID: string,
|
||||
modelID: string,
|
||||
) {
|
||||
return get(list, providerID, modelID)?.name ?? modelID
|
||||
}
|
||||
@@ -1,10 +1,12 @@
|
||||
import type { AssistantMessage, Part, UserMessage } from "@opencode-ai/sdk/v2"
|
||||
import type { AssistantMessage, Part, Provider, UserMessage } from "@opencode-ai/sdk/v2"
|
||||
import { Locale } from "@/util/locale"
|
||||
import * as Model from "./model"
|
||||
|
||||
export type TranscriptOptions = {
|
||||
thinking: boolean
|
||||
toolDetails: boolean
|
||||
assistantMetadata: boolean
|
||||
providers?: Provider[]
|
||||
}
|
||||
|
||||
export type SessionInfo = {
|
||||
@@ -26,6 +28,7 @@ export function formatTranscript(
|
||||
messages: MessageWithParts[],
|
||||
options: TranscriptOptions,
|
||||
): string {
|
||||
const providers = Model.index(options.providers)
|
||||
let transcript = `# ${session.title}\n\n`
|
||||
transcript += `**Session ID:** ${session.id}\n`
|
||||
transcript += `**Created:** ${new Date(session.time.created).toLocaleString()}\n`
|
||||
@@ -33,20 +36,25 @@ export function formatTranscript(
|
||||
transcript += `---\n\n`
|
||||
|
||||
for (const msg of messages) {
|
||||
transcript += formatMessage(msg.info, msg.parts, options)
|
||||
transcript += formatMessage(msg.info, msg.parts, options, providers)
|
||||
transcript += `---\n\n`
|
||||
}
|
||||
|
||||
return transcript
|
||||
}
|
||||
|
||||
export function formatMessage(msg: UserMessage | AssistantMessage, parts: Part[], options: TranscriptOptions): string {
|
||||
export function formatMessage(
|
||||
msg: UserMessage | AssistantMessage,
|
||||
parts: Part[],
|
||||
options: TranscriptOptions,
|
||||
providers?: Provider[] | ReadonlyMap<string, Provider>,
|
||||
): string {
|
||||
let result = ""
|
||||
|
||||
if (msg.role === "user") {
|
||||
result += `## User\n\n`
|
||||
} else {
|
||||
result += formatAssistantHeader(msg, options.assistantMetadata)
|
||||
result += formatAssistantHeader(msg, options.assistantMetadata, providers ?? options.providers)
|
||||
}
|
||||
|
||||
for (const part of parts) {
|
||||
@@ -56,7 +64,11 @@ export function formatMessage(msg: UserMessage | AssistantMessage, parts: Part[]
|
||||
return result
|
||||
}
|
||||
|
||||
export function formatAssistantHeader(msg: AssistantMessage, includeMetadata: boolean): string {
|
||||
export function formatAssistantHeader(
|
||||
msg: AssistantMessage,
|
||||
includeMetadata: boolean,
|
||||
providers?: Provider[] | ReadonlyMap<string, Provider>,
|
||||
): string {
|
||||
if (!includeMetadata) {
|
||||
return `## Assistant\n\n`
|
||||
}
|
||||
@@ -64,7 +76,9 @@ export function formatAssistantHeader(msg: AssistantMessage, includeMetadata: bo
|
||||
const duration =
|
||||
msg.time.completed && msg.time.created ? ((msg.time.completed - msg.time.created) / 1000).toFixed(1) + "s" : ""
|
||||
|
||||
return `## Assistant (${Locale.titlecase(msg.agent)} · ${msg.modelID}${duration ? ` · ${duration}` : ""})\n\n`
|
||||
const modelName = Model.name(providers, msg.providerID, msg.modelID)
|
||||
|
||||
return `## Assistant (${Locale.titlecase(msg.agent)} · ${modelName}${duration ? ` · ${duration}` : ""})\n\n`
|
||||
}
|
||||
|
||||
export function formatPart(part: Part, options: TranscriptOptions): string {
|
||||
|
||||
Reference in New Issue
Block a user