|
| 1 | +import { TextAttributes } from "@opentui/core" |
| 2 | +import { createMemo, createSignal, For } from "solid-js" |
| 3 | +import { InstallationChannel, InstallationVersion } from "@opencode-ai/core/installation/version" |
| 4 | +import { useTheme } from "../context/theme" |
| 5 | +import { useDialog } from "../ui/dialog" |
| 6 | +import { useRoute } from "../context/route" |
| 7 | +import { useLocal } from "../context/local" |
| 8 | +import { useClipboard } from "../context/clipboard" |
| 9 | +import { useToast } from "../ui/toast" |
| 10 | +import { useBindings } from "../keymap" |
| 11 | +import { describeOS, describeTerminal } from "../util/system" |
| 12 | + |
| 13 | +export function DialogDebug() { |
| 14 | + const { theme } = useTheme() |
| 15 | + const dialog = useDialog() |
| 16 | + const route = useRoute() |
| 17 | + const local = useLocal() |
| 18 | + const clipboard = useClipboard() |
| 19 | + const toast = useToast() |
| 20 | + const [copied, setCopied] = createSignal(false) |
| 21 | + |
| 22 | + dialog.setSize("large") |
| 23 | + |
| 24 | + const entries = createMemo(() => { |
| 25 | + const model = local.model.current() |
| 26 | + return [ |
| 27 | + { label: "Version", value: `${InstallationVersion} (${InstallationChannel})` }, |
| 28 | + { label: "Date", value: new Date().toISOString() }, |
| 29 | + { label: "OS", value: describeOS() }, |
| 30 | + { label: "Terminal", value: describeTerminal() }, |
| 31 | + { label: "Session ID", value: route.data.type === "session" ? route.data.sessionID : "n/a" }, |
| 32 | + { label: "Model", value: model ? `${model.providerID}/${model.modelID}` : "n/a" }, |
| 33 | + ] |
| 34 | + }) |
| 35 | + |
| 36 | + const copy = () => { |
| 37 | + const text = entries() |
| 38 | + .map((entry) => `${entry.label}: ${entry.value}`) |
| 39 | + .join("\n") |
| 40 | + void clipboard |
| 41 | + .write?.(text) |
| 42 | + .then(() => { |
| 43 | + setCopied(true) |
| 44 | + toast.show({ message: "Debug info copied to clipboard", variant: "info" }) |
| 45 | + }) |
| 46 | + .catch(toast.error) |
| 47 | + } |
| 48 | + |
| 49 | + useBindings(() => ({ |
| 50 | + bindings: [{ key: "return", desc: "Copy debug info", group: "Dialog", cmd: copy }], |
| 51 | + })) |
| 52 | + |
| 53 | + return ( |
| 54 | + <box paddingLeft={2} paddingRight={2} gap={1} paddingBottom={1}> |
| 55 | + <box flexDirection="row" justifyContent="space-between"> |
| 56 | + <text fg={theme.text} attributes={TextAttributes.BOLD}> |
| 57 | + Debug |
| 58 | + </text> |
| 59 | + <text fg={theme.textMuted} onMouseUp={() => dialog.clear()}> |
| 60 | + esc |
| 61 | + </text> |
| 62 | + </box> |
| 63 | + {/* No click-to-copy here: releasing a mouse selection must trigger the |
| 64 | + global copy-on-select so users can copy a single value, e.g. the session id. */} |
| 65 | + <box> |
| 66 | + <For each={entries()}> |
| 67 | + {(entry) => ( |
| 68 | + <box flexDirection="row" gap={1}> |
| 69 | + <text flexShrink={0} fg={theme.textMuted}> |
| 70 | + {entry.label.padEnd(10)} |
| 71 | + </text> |
| 72 | + <text fg={theme.text} wrapMode="word"> |
| 73 | + {entry.value} |
| 74 | + </text> |
| 75 | + </box> |
| 76 | + )} |
| 77 | + </For> |
| 78 | + </box> |
| 79 | + <box flexDirection="row" justifyContent="space-between"> |
| 80 | + <text fg={theme.textMuted}>Share this when reporting an issue.</text> |
| 81 | + <text onMouseUp={copy}> |
| 82 | + <span style={{ fg: copied() ? theme.success : theme.text }}> |
| 83 | + <b>{copied() ? "✓ copied" : "copy"}</b>{" "} |
| 84 | + </span> |
| 85 | + <span style={{ fg: theme.textMuted }}>enter</span> |
| 86 | + </text> |
| 87 | + </box> |
| 88 | + </box> |
| 89 | + ) |
| 90 | +} |
0 commit comments