refactor(web): enhance DiffView with modal preview and inline variant

This commit is contained in:
weishu
2025-12-18 22:56:00 +08:00
parent 4606ba23d9
commit 55b1a753ba
6 changed files with 79 additions and 32 deletions
+72 -1
View File
@@ -1,12 +1,83 @@
import { diffLines } from 'diff'
import { useMemo } from 'react'
import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, DialogTrigger } from '@/components/ui/dialog'
import { cn } from '@/lib/utils'
export function DiffView(props: {
oldString: string
newString: string
filePath?: string
variant?: 'preview' | 'inline'
}) {
const diff = diffLines(props.oldString, props.newString)
const variant = props.variant ?? 'preview'
const stats = useMemo(() => {
const oldChars = props.oldString.length
const newChars = props.newString.length
const oldLabel = `${oldChars.toLocaleString()} chars`
const newLabel = `${newChars.toLocaleString()} chars`
return { oldChars, newChars, label: `old: ${oldLabel} → new: ${newLabel}` }
}, [props.oldString.length, props.newString.length])
const title = props.filePath ? props.filePath : 'Diff'
const subtitle = props.filePath ? stats.label : `Diff • ${stats.label}`
const DiffInline = (
<DiffInlineView
oldString={props.oldString}
newString={props.newString}
filePath={props.filePath}
/>
)
if (variant === 'inline') {
return DiffInline
}
return (
<Dialog>
<DialogTrigger asChild>
<button type="button" className="w-full text-left">
<div className="overflow-hidden rounded-md border border-[var(--app-border)] bg-[var(--app-subtle-bg)] hover:bg-[var(--app-secondary-bg)] transition-colors">
{props.filePath ? (
<div className="border-b border-[var(--app-border)] bg-[var(--app-subtle-bg)] px-2 py-1 text-xs text-[var(--app-hint)] truncate">
{props.filePath}
</div>
) : null}
<div className="px-2 py-2">
<div className="flex items-center justify-between gap-3">
<div className="min-w-0 font-mono text-xs text-[var(--app-hint)] truncate">
{props.filePath ? stats.label : subtitle}
</div>
<div className="shrink-0 text-xs text-[var(--app-link)]">
View
</div>
</div>
</div>
</div>
</button>
</DialogTrigger>
<DialogContent className="max-w-4xl">
<DialogHeader>
<DialogTitle className="break-all">{title}</DialogTitle>
<DialogDescription className="font-mono break-all">
{stats.label}
</DialogDescription>
</DialogHeader>
<div className="mt-3 max-h-[75vh] overflow-auto">
{DiffInline}
</div>
</DialogContent>
</Dialog>
)
}
function DiffInlineView(props: {
oldString: string
newString: string
filePath?: string
}) {
const diff = useMemo(() => diffLines(props.oldString, props.newString), [props.oldString, props.newString])
return (
<div className="overflow-hidden rounded-md border border-[var(--app-border)] bg-[var(--app-subtle-bg)]">
+3 -28
View File
@@ -35,22 +35,6 @@ function countLines(text: string): number {
return text.split('\n').length
}
function isDocumentFilePath(filePath: string): boolean {
const lower = filePath.toLowerCase()
return (
lower.endsWith('.md')
|| lower.endsWith('.mdx')
|| lower.endsWith('.markdown')
|| lower.endsWith('.txt')
)
}
function shouldCollapseDocumentWrite(filePath: string | null): boolean {
if (!filePath) return false
if (!isDocumentFilePath(filePath)) return false
return true
}
function snakeToTitleWithSpaces(value: string): string {
return value
.split('_')
@@ -167,10 +151,7 @@ export const knownTools: Record<string, {
const file = getInputStringAny(opts.input, ['file_path', 'path'])
return file ? resolveDisplayPath(file, opts.metadata) : 'Edit file'
},
minimal: (opts) => {
const file = getInputStringAny(opts.input, ['file_path', 'path'])
return isDocumentFilePath(file ?? '')
}
minimal: true
},
MultiEdit: {
icon: () => <FileDiffIcon className={DEFAULT_ICON_CLASS} />,
@@ -182,10 +163,7 @@ export const knownTools: Record<string, {
const path = resolveDisplayPath(file, opts.metadata)
return count > 1 ? `${path} (${count} edits)` : path
},
minimal: (opts) => {
const file = getInputStringAny(opts.input, ['file_path', 'path'])
return isDocumentFilePath(file ?? '')
}
minimal: true
},
Write: {
icon: () => <FileDiffIcon className={DEFAULT_ICON_CLASS} />,
@@ -199,10 +177,7 @@ export const knownTools: Record<string, {
const lines = countLines(content)
return lines > 1 ? `${lines} lines` : `${content.length} chars`
},
minimal: (opts) => {
const file = getInputStringAny(opts.input, ['file_path', 'path'])
return shouldCollapseDocumentWrite(file)
}
minimal: true
},
WebFetch: {
icon: () => <GlobeIcon className={DEFAULT_ICON_CLASS} />,
@@ -67,6 +67,7 @@ function renderDiff(block: ToolViewProps['block'], showFileHeader: boolean) {
oldString={parsed.oldText}
newString={parsed.newText}
filePath={showFileHeader ? parsed.fileName : undefined}
variant={showFileHeader ? 'inline' : undefined}
/>
)
}
@@ -78,4 +79,3 @@ export function CodexDiffCompactView(props: ToolViewProps) {
export function CodexDiffFullView(props: ToolViewProps) {
return renderDiff(props.block, true)
}
@@ -17,7 +17,7 @@ export function EditView(props: ToolViewProps) {
<DiffView
oldString={oldString}
newString={newString}
variant="inline"
/>
)
}
@@ -53,6 +53,7 @@ export function MultiEditFullView(props: ToolViewProps) {
key={idx}
oldString={edit.old_string}
newString={edit.new_string}
variant="inline"
/>
))}
</div>
@@ -16,7 +16,7 @@ export function WriteView(props: ToolViewProps) {
<DiffView
oldString=""
newString={content}
variant="inline"
/>
)
}