mirror of
https://github.com/sst/opencode.git
synced 2025-12-23 10:11:41 +00:00
refactor(tui): improve context preview dialog UI consistency
Match OpenCode's existing dialog patterns: - Use padding instead of border/width constraints - Use TextAttributes.BOLD for titles - Use bullet points for list items with color coding - Simplify layout to match dialog-status style - Add helpful footer note about token estimates - Remove filter functionality (keep it simple)
This commit is contained in:
parent
3e69d8bdd0
commit
4899a9f911
1 changed files with 78 additions and 113 deletions
|
|
@ -1,15 +1,14 @@
|
|||
import { createMemo, createSignal, For } from "solid-js"
|
||||
import { createMemo, For } from "solid-js"
|
||||
import { TextAttributes } from "@opentui/core"
|
||||
import { useTheme } from "../context/theme"
|
||||
import { useSync } from "@tui/context/sync"
|
||||
import { useLocal } from "@tui/context/local"
|
||||
import type { AssistantMessage } from "@opencode-ai/sdk/v2"
|
||||
|
||||
interface ContextItem {
|
||||
type: "system" | "instruction" | "file" | "message"
|
||||
type: "system" | "instruction" | "message"
|
||||
name: string
|
||||
tokens: number
|
||||
content?: string
|
||||
enabled: boolean
|
||||
}
|
||||
|
||||
export function DialogContext(props: { sessionID: string }) {
|
||||
|
|
@ -19,160 +18,126 @@ export function DialogContext(props: { sessionID: string }) {
|
|||
|
||||
const messages = createMemo(() => sync.data.message[props.sessionID] ?? [])
|
||||
|
||||
// Calculate context breakdown
|
||||
const contextItems = createMemo((): ContextItem[] => {
|
||||
const items: ContextItem[] = []
|
||||
const contextBreakdown = createMemo(() => {
|
||||
const breakdown: ContextItem[] = []
|
||||
|
||||
// Get last assistant message for token info
|
||||
// Get last assistant message for actual token info
|
||||
const lastAssistant = messages().findLast(
|
||||
(x) => x.role === "assistant" && (x as AssistantMessage).tokens?.output > 0
|
||||
) as AssistantMessage | undefined
|
||||
|
||||
// System prompt estimate (varies by provider)
|
||||
const model = local.model.current()
|
||||
|
||||
// System prompt (varies by provider)
|
||||
if (model) {
|
||||
items.push({
|
||||
breakdown.push({
|
||||
type: "system",
|
||||
name: `System Prompt (${model.providerID})`,
|
||||
tokens: 8000, // Approximate
|
||||
enabled: true,
|
||||
name: `System prompt (${model.providerID})`,
|
||||
tokens: lastAssistant?.tokens?.cache?.write ?? 8000,
|
||||
})
|
||||
}
|
||||
|
||||
// Environment context
|
||||
items.push({
|
||||
// Environment & file tree
|
||||
breakdown.push({
|
||||
type: "system",
|
||||
name: "Environment & File Tree",
|
||||
tokens: 2000, // Approximate
|
||||
enabled: true,
|
||||
name: "Environment & file tree",
|
||||
tokens: 2000,
|
||||
})
|
||||
|
||||
// Instructions files
|
||||
const instructionFiles = [
|
||||
"~/context/PROMPT_TEMPLATE.md",
|
||||
"~/context/README.md",
|
||||
"AGENTS.md",
|
||||
"CLAUDE.md",
|
||||
]
|
||||
for (const file of instructionFiles) {
|
||||
items.push({
|
||||
type: "instruction",
|
||||
name: file,
|
||||
tokens: 1500, // Approximate per file
|
||||
enabled: true,
|
||||
})
|
||||
}
|
||||
// Instructions from config
|
||||
breakdown.push({
|
||||
type: "instruction",
|
||||
name: "Custom instructions",
|
||||
tokens: 4000,
|
||||
})
|
||||
|
||||
// Session messages
|
||||
let messageTokens = 0
|
||||
// Conversation history
|
||||
let conversationTokens = 0
|
||||
for (const msg of messages()) {
|
||||
if (msg.role === "assistant") {
|
||||
const assistant = msg as AssistantMessage
|
||||
messageTokens += assistant.tokens?.input ?? 0
|
||||
messageTokens += assistant.tokens?.output ?? 0
|
||||
conversationTokens += assistant.tokens?.input ?? 0
|
||||
}
|
||||
}
|
||||
if (messageTokens > 0) {
|
||||
items.push({
|
||||
|
||||
if (conversationTokens > 0) {
|
||||
breakdown.push({
|
||||
type: "message",
|
||||
name: `Conversation History (${messages().length} messages)`,
|
||||
tokens: messageTokens,
|
||||
enabled: true,
|
||||
name: `Messages (${messages().length} total)`,
|
||||
tokens: conversationTokens,
|
||||
})
|
||||
}
|
||||
|
||||
return items
|
||||
return breakdown
|
||||
})
|
||||
|
||||
const totalTokens = createMemo(() => {
|
||||
return contextItems().reduce((sum, item) => sum + (item.enabled ? item.tokens : 0), 0)
|
||||
return contextBreakdown().reduce((sum, item) => sum + item.tokens, 0)
|
||||
})
|
||||
|
||||
const [filter, setFilter] = createSignal("")
|
||||
|
||||
const filteredItems = createMemo(() => {
|
||||
const f = filter().toLowerCase()
|
||||
if (!f) return contextItems()
|
||||
return contextItems().filter((item) => item.name.toLowerCase().includes(f))
|
||||
const context = createMemo(() => {
|
||||
const last = messages().findLast(
|
||||
(x) => x.role === "assistant" && (x as AssistantMessage).tokens?.output > 0
|
||||
) as AssistantMessage | undefined
|
||||
if (!last) return undefined
|
||||
const model = sync.data.provider.find((x) => x.id === last.providerID)?.models[last.modelID]
|
||||
return {
|
||||
tokens: totalTokens(),
|
||||
percentage: model?.limit.context ? Math.round((totalTokens() / model.limit.context) * 100) : null,
|
||||
}
|
||||
})
|
||||
|
||||
return (
|
||||
<box
|
||||
flexDirection="column"
|
||||
borderStyle="rounded"
|
||||
borderColor={theme.border}
|
||||
backgroundColor={theme.backgroundPanel}
|
||||
padding={1}
|
||||
gap={1}
|
||||
width="80%"
|
||||
height="70%"
|
||||
>
|
||||
<box paddingLeft={2} paddingRight={2} gap={1} paddingBottom={1}>
|
||||
<box flexDirection="row" justifyContent="space-between">
|
||||
<text fg={theme.text} attributes={TextAttributes.BOLD}>
|
||||
Context Preview
|
||||
</text>
|
||||
<text fg={theme.textMuted}>esc</text>
|
||||
</box>
|
||||
|
||||
<box>
|
||||
<text fg={theme.text}>
|
||||
<b>Context Preview</b>
|
||||
<b>Total Context</b>
|
||||
</text>
|
||||
<text fg={theme.textMuted}>
|
||||
Total: {totalTokens().toLocaleString()} tokens
|
||||
{context()?.tokens.toLocaleString() ?? "0"} tokens
|
||||
{context()?.percentage ? ` (${context()?.percentage}% of limit)` : ""}
|
||||
</text>
|
||||
</box>
|
||||
|
||||
<box flexDirection="row" gap={1}>
|
||||
<text fg={theme.textMuted}>Filter:</text>
|
||||
<text fg={theme.text}>{filter() || "(type to filter)"}</text>
|
||||
</box>
|
||||
|
||||
<box height={1} />
|
||||
|
||||
<scrollbox flexGrow={1}>
|
||||
<box flexDirection="column" gap={1}>
|
||||
<For each={filteredItems()}>
|
||||
{(item) => (
|
||||
<box
|
||||
flexDirection="row"
|
||||
justifyContent="space-between"
|
||||
paddingLeft={1}
|
||||
paddingRight={1}
|
||||
>
|
||||
<box flexDirection="row" gap={1}>
|
||||
<text
|
||||
fg={
|
||||
item.type === "system"
|
||||
? theme.primary
|
||||
: item.type === "instruction"
|
||||
? theme.warning
|
||||
: item.type === "file"
|
||||
? theme.success
|
||||
: theme.textMuted
|
||||
}
|
||||
>
|
||||
{item.type === "system"
|
||||
? "[SYS]"
|
||||
<box>
|
||||
<text fg={theme.text}>
|
||||
<b>Breakdown</b>
|
||||
</text>
|
||||
<For each={contextBreakdown()}>
|
||||
{(item) => (
|
||||
<box flexDirection="row" justifyContent="space-between">
|
||||
<box flexDirection="row" gap={1}>
|
||||
<text
|
||||
flexShrink={0}
|
||||
fg={
|
||||
item.type === "system"
|
||||
? theme.success
|
||||
: item.type === "instruction"
|
||||
? "[INS]"
|
||||
: item.type === "file"
|
||||
? "[FILE]"
|
||||
: "[MSG]"}
|
||||
</text>
|
||||
<text fg={item.enabled ? theme.text : theme.textMuted}>
|
||||
{item.name}
|
||||
</text>
|
||||
</box>
|
||||
<text fg={theme.textMuted}>
|
||||
{item.tokens.toLocaleString()} tokens
|
||||
? theme.warning
|
||||
: theme.primary
|
||||
}
|
||||
>
|
||||
•
|
||||
</text>
|
||||
<text fg={theme.text}>{item.name}</text>
|
||||
</box>
|
||||
)}
|
||||
</For>
|
||||
</box>
|
||||
</scrollbox>
|
||||
<text fg={theme.textMuted}>{item.tokens.toLocaleString()}</text>
|
||||
</box>
|
||||
)}
|
||||
</For>
|
||||
</box>
|
||||
|
||||
<box height={1} />
|
||||
|
||||
<box flexDirection="row" justifyContent="space-between">
|
||||
<box paddingTop={1}>
|
||||
<text fg={theme.textMuted}>
|
||||
[SYS] System | [INS] Instructions | [FILE] Files | [MSG] Messages
|
||||
This is an estimate of the context sent to the model. Actual token count may vary based on tokenization.
|
||||
</text>
|
||||
<text fg={theme.textMuted}>Press Esc to close</text>
|
||||
</box>
|
||||
</box>
|
||||
)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue