mirror of
https://github.com/wu736139669/hapi.git
synced 2026-08-05 06:24:37 +00:00
refactor(web): enhance DiffView with modal preview and inline variant
This commit is contained in:
@@ -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)]">
|
||||
|
||||
@@ -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"
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user