cc-haha/desktop/src/pages/ToolInspection.tsx
程序员阿江(Relakkes) 993b96cd39 Stabilize the desktop transcript so long agent sessions stay readable
The desktop app now keeps the composer stable while turns are active,
reduces low-signal tool noise in the transcript, restores project context
under the composer after session creation, and relies on the CLI's own
permission requests instead of injecting broader desktop-side Bash asks.

This also brings in the supporting desktop app source tree and the server
routes/session metadata needed for git info, filesystem browsing, session
resume, slash commands, and SDK-backed permission bridging so the UI can
operate as a coherent feature instead of a partial patch.

Constraint: Desktop transcript needs to stay usable during long multi-tool sessions without hiding file-change diffs
Constraint: Permission prompts must mirror CLI behavior closely enough that read-only commands do not get desktop-only prompts
Rejected: Keep rendering Read/Bash bodies inline | too noisy and unlike the intended transcript model
Rejected: Commit only the touched desktop files | would leave the newly introduced desktop app incomplete in git history
Confidence: medium
Scope-risk: broad
Reversibility: messy
Directive: Treat non-writing tools as summary-first transcript events; do not re-expand them by default without validating the UX against long sessions
Tested: cd desktop && bun run lint
Tested: cd desktop && bun run test -- --run
Tested: bun test src/server/__tests__/conversations.test.ts
Not-tested: Manual visual regression against the exact screenshots in a live desktop session
Not-tested: Full root TypeScript check (repository still has unrelated extracted-native parse failures)
2026-04-06 20:37:44 +08:00

236 lines
12 KiB
TypeScript

import { useState } from 'react'
import { mockToolInspection } from '../mocks/data'
export function ToolInspection() {
const [activeDiffTab, setActiveDiffTab] = useState<'split' | 'unified'>('split')
const { toolType, toolName, description, filePath, dryRunStatus, linesChanged, diffLines } =
mockToolInspection
return (
<div className="flex-1 flex flex-col overflow-hidden bg-[var(--color-surface)]">
{/* Separator */}
<div className="h-px w-full bg-[var(--color-surface-container)]" />
{/* Content */}
<main className="flex-1 overflow-y-auto p-8 max-w-6xl mx-auto w-full">
<div className="flex flex-col gap-6">
{/* ── Title row + action buttons ─────────────────── */}
<div className="flex items-start justify-between">
<div className="space-y-1">
<div className="flex items-center gap-2">
<span className="px-2 py-0.5 bg-[var(--color-primary-fixed)] text-[#75331C] text-[10px] font-bold rounded uppercase tracking-widest">
{toolType}
</span>
<h1 className="font-[var(--font-headline)] font-extrabold text-2xl text-[var(--color-on-surface)] tracking-tight">
{toolName}
</h1>
</div>
<p className="text-[var(--color-on-surface-variant)] font-medium">{description}</p>
</div>
<div className="flex gap-2">
<button className="px-4 py-2 bg-[var(--color-surface-container-high)] rounded-lg text-sm font-semibold hover:bg-[var(--color-surface-variant)] transition-all">
Revert Change
</button>
<button className="px-4 py-2 bg-[var(--color-primary)] text-[var(--color-on-primary)] rounded-lg text-sm font-semibold shadow-sm hover:opacity-90 transition-all">
Apply to All
</button>
</div>
</div>
{/* ── Metadata cards ─────────────────────────────── */}
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
<div className="bg-[var(--color-surface-container-low)] rounded-xl p-4 flex flex-col gap-1">
<span className="text-[10px] font-bold text-[var(--color-outline)] uppercase tracking-wider">
Target File
</span>
<div className="flex items-center gap-2 text-[var(--color-on-surface)]">
<span className="material-symbols-outlined text-[18px]">description</span>
<span className="font-[var(--font-mono)] text-sm">{filePath}</span>
</div>
</div>
<div className="bg-[var(--color-surface-container-low)] rounded-xl p-4 flex flex-col gap-1">
<span className="text-[10px] font-bold text-[var(--color-outline)] uppercase tracking-wider">
Status
</span>
<div className="flex items-center gap-2 text-[var(--color-tertiary)]">
<span
className="material-symbols-outlined text-[18px]"
style={{ fontVariationSettings: "'FILL' 1" }}
>
check_circle
</span>
<span className="font-semibold text-sm">{dryRunStatus}</span>
</div>
</div>
<div className="bg-[var(--color-surface-container-low)] rounded-xl p-4 flex flex-col gap-1">
<span className="text-[10px] font-bold text-[var(--color-outline)] uppercase tracking-wider">
Lines Modified
</span>
<div className="flex items-center gap-2 text-[var(--color-on-surface)]">
<span className="material-symbols-outlined text-[18px]">edit_note</span>
<span className="font-semibold text-sm">
+{linesChanged.added} / -{linesChanged.removed} lines
</span>
</div>
</div>
</div>
{/* ── Diff Viewer ────────────────────────────────── */}
<div className="bg-[var(--color-surface-dim)] rounded-xl overflow-hidden border border-[var(--color-outline-variant)]/20 shadow-sm">
<div className="px-4 py-2.5 bg-[var(--color-surface-container-high)] flex items-center justify-between border-b border-[var(--color-outline-variant)]/20">
<div className="flex items-center gap-3">
<div className="flex gap-1.5">
<div className="w-2.5 h-2.5 rounded-full bg-[var(--color-error)] opacity-30" />
<div className="w-2.5 h-2.5 rounded-full bg-[var(--color-primary-fixed-dim)]" />
<div className="w-2.5 h-2.5 rounded-full bg-[var(--color-tertiary-container)] opacity-30" />
</div>
<span className="font-[var(--font-mono)] text-xs text-[var(--color-outline)] px-2 border-l border-[var(--color-outline-variant)]/30">
{filePath} Diff View
</span>
</div>
<div className="flex items-center gap-4">
<span className="text-[11px] text-[var(--color-outline)] font-medium">
L{diffLines[0]?.lineNo ?? 1} L{diffLines[diffLines.length - 1]?.lineNo ?? 1}
</span>
<div className="flex bg-[var(--color-surface-container-low)] rounded p-0.5">
<button
onClick={() => setActiveDiffTab('split')}
className={`px-2 py-1 text-[10px] font-bold uppercase ${
activeDiffTab === 'split'
? 'bg-[var(--color-surface)] rounded shadow-sm text-[var(--color-on-surface)]'
: 'text-[var(--color-outline)]'
}`}
>
Split
</button>
<button
onClick={() => setActiveDiffTab('unified')}
className={`px-2 py-1 text-[10px] font-bold uppercase ${
activeDiffTab === 'unified'
? 'bg-[var(--color-surface)] rounded shadow-sm text-[var(--color-on-surface)]'
: 'text-[var(--color-outline)]'
}`}
>
Unified
</button>
</div>
</div>
</div>
<div className="font-[var(--font-mono)] text-[13px] leading-relaxed p-4 overflow-x-auto whitespace-pre">
{diffLines.map((line, idx) => {
const isAdded = line.type === 'added'
const isRemoved = line.type === 'removed'
let rowBg = ''
if (isAdded) rowBg = 'bg-[rgba(103,123,78,0.15)]'
else if (isRemoved) rowBg = 'bg-[rgba(143,72,47,0.15)]'
let lineNoColor = 'text-[var(--color-outline)] opacity-40'
if (isAdded) lineNoColor = 'text-[var(--color-tertiary)] opacity-40'
else if (isRemoved) lineNoColor = 'text-[var(--color-error)] opacity-40'
const prefix = isAdded ? '+ ' : isRemoved ? '- ' : ' '
return (
<div key={idx} className={`flex w-full ${rowBg}`}>
<span
className={`w-10 flex-shrink-0 text-right pr-4 select-none ${lineNoColor}`}
>
{line.lineNo}
</span>
<span className="text-[var(--color-on-surface-variant)]">
{prefix}
{line.content}
</span>
</div>
)
})}
</div>
</div>
{/* ── Implementation Context ─────────────────────── */}
<div className="p-6 bg-[var(--color-surface-container-lowest)] rounded-2xl border border-[var(--color-outline-variant)]/10">
<h3 className="font-[var(--font-headline)] font-bold text-sm text-[var(--color-on-surface)] mb-4">
Implementation Context
</h3>
<div className="grid grid-cols-1 lg:grid-cols-2 gap-8">
<div className="space-y-4">
<div className="flex items-start gap-3">
<div className="mt-1 w-6 h-6 rounded bg-[var(--color-primary-fixed)] flex items-center justify-center">
<span className="material-symbols-outlined text-[14px] text-[#75331C]">
psychology
</span>
</div>
<div>
<p className="text-xs font-bold uppercase tracking-widest text-[var(--color-outline)] mb-1">
Reasoning
</p>
<p className="text-sm text-[var(--color-on-surface-variant)] leading-relaxed">
The <code className="font-[var(--font-mono)]">legacyAuthService</code> was
deprecated in RFC-204. The new SDK provides automatic session refresh and
better error typing. This migration ensures the login flow is compliant with
the new security standards.
</p>
</div>
</div>
<div className="flex items-start gap-3">
<div className="mt-1 w-6 h-6 rounded bg-[#D4EAB4] flex items-center justify-center">
<span className="material-symbols-outlined text-[14px] text-[#3B4C24]">
science
</span>
</div>
<div>
<p className="text-xs font-bold uppercase tracking-widest text-[var(--color-outline)] mb-1">
Impact Analysis
</p>
<p className="text-sm text-[var(--color-on-surface-variant)] leading-relaxed">
No changes needed in calling components. The interface remains compatible but
internal state management is improved.
</p>
</div>
</div>
</div>
<div className="flex items-center justify-center">
<div className="w-full h-32 rounded-xl bg-[var(--color-surface-container)] relative overflow-hidden group">
<div className="absolute inset-0 bg-gradient-to-br from-[var(--color-primary)]/5 to-[var(--color-secondary)]/5" />
<div className="absolute inset-0 flex items-center justify-center gap-4">
<div className="flex flex-col items-center gap-1">
<div className="p-2 bg-[var(--color-surface)] rounded-lg shadow-sm">
<span className="material-symbols-outlined text-[var(--color-outline)]">
description
</span>
</div>
<span className="text-[9px] font-bold text-[var(--color-outline)]">
auth.ts
</span>
</div>
<span className="material-symbols-outlined text-[var(--color-outline)] animate-pulse">
keyboard_double_arrow_right
</span>
<div className="flex flex-col items-center gap-1">
<div className="p-2 bg-[var(--color-surface)] rounded-lg shadow-sm border border-[var(--color-tertiary)]/20">
<span className="material-symbols-outlined text-[var(--color-tertiary)]">
check_circle
</span>
</div>
<span className="text-[9px] font-bold text-[var(--color-tertiary)]">
Verified
</span>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</main>
</div>
)
}