import * as React from 'react'
import { useMutation } from '@tanstack/react-query'
import { useConfig } from '@/state/config'
import { ipc, isTauriHost } from '@/lib/api/desktop'
import { asEnvelope } from '@/lib/error'
import type { OutputFormat, TargetTool } from '@/lib/types/desktop'
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from '@/components/ui/card'
import { Button } from '@/components/ui/button'
import { Label } from '@/components/ui/label'
import { Textarea } from '@/components/ui/textarea'
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from '@/components/ui/select'
import { Badge } from '@/components/ui/badge'
import { ErrorBanner } from '@/components/error-banner'
import { Loader2, Sparkles } from 'lucide-react'
const TARGETS: TargetTool[] = ['claude', 'codex', 'opencode']
const FORMATS: OutputFormat[] = ['prompt', 'markdown', 'json']
export function ContextPage() {
const cfg = useConfig()
const [task, setTask] = React.useState('')
const [filesRaw, setFilesRaw] = React.useState('')
const [target, setTarget] = React.useState<TargetTool>('claude')
const [format, setFormat] = React.useState<OutputFormat>('prompt')
const runMutation = useMutation({
mutationFn: () => {
const files = filesRaw
.split(/[\n,]/)
.map((s) => s.trim())
.filter(Boolean)
return ipc.runContext({
config_path: cfg.configPath,
vault_root_override: cfg.vaultRoot.trim() || null,
cwd: cfg.cwd,
task,
files,
target,
format,
})
},
})
const result = runMutation.data ?? null
const loading = runMutation.isPending
const errorEnvelope = runMutation.error ? asEnvelope(runMutation.error) : null
return (
<div className="grid gap-4 lg:grid-cols-[1fr_1.2fr]">
<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2 text-base">
<Sparkles className="h-4 w-4" />
上下文注入
</CardTitle>
<CardDescription>
输入任务描述与相关文件,生成带路由解释的上下文包。
</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
<div className="grid gap-2">
<Label htmlFor="task">任务描述</Label>
<Textarea
id="task"
value={task}
onChange={(e) => setTask(e.target.value)}
placeholder="例如:实现 repo_path route 规划"
rows={4}
/>
</div>
<div className="grid gap-2">
<Label htmlFor="files">相关文件(逗号或换行分隔)</Label>
<Textarea
id="files"
value={filesRaw}
onChange={(e) => setFilesRaw(e.target.value)}
placeholder="src/engine/project_matcher.rs"
rows={3}
/>
</div>
<div className="grid grid-cols-2 gap-3">
<div className="grid gap-2">
<Label>目标</Label>
<Select value={target} onValueChange={(v) => setTarget(v as TargetTool)}>
<SelectTrigger>
<SelectValue />
</SelectTrigger>
<SelectContent>
{TARGETS.map((t) => (
<SelectItem key={t} value={t}>
{t}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
<div className="grid gap-2">
<Label>格式</Label>
<Select value={format} onValueChange={(v) => setFormat(v as OutputFormat)}>
<SelectTrigger>
<SelectValue />
</SelectTrigger>
<SelectContent>
{FORMATS.map((f) => (
<SelectItem key={f} value={f}>
{f}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
</div>
<div className="flex items-center gap-3 pt-2">
<Button
onClick={() => runMutation.mutate()}
disabled={loading || !task.trim() || !cfg.cwd.trim()}
>
{loading ? <Loader2 className="h-4 w-4 animate-spin" /> : null}
生成上下文
</Button>
{!isTauriHost() && (
<Badge variant="muted">
Bridge 未就绪(请在 Tauri 窗口内运行)
</Badge>
)}
</div>
{errorEnvelope && <ErrorBanner envelope={errorEnvelope} />}
</CardContent>
</Card>
<Card>
<CardHeader>
<CardTitle className="text-base">输出</CardTitle>
{result && (
<CardDescription>
format={result.used_format} · vault={result.used_vault_root}
</CardDescription>
)}
</CardHeader>
<CardContent className="space-y-4">
{!result && !loading && (
<p className="text-sm text-muted-foreground">尚未生成。</p>
)}
{result && (
<>
<div>
<Label className="mb-1 block">Rendered</Label>
<pre className="max-h-[420px] overflow-auto rounded-md border bg-muted/30 p-3 text-xs leading-relaxed">
{result.rendered}
</pre>
</div>
<div>
<Label className="mb-1 block">Explain</Label>
<pre className="max-h-[200px] overflow-auto rounded-md border bg-muted/30 p-3 text-xs leading-relaxed">
{result.explain}
</pre>
</div>
</>
)}
</CardContent>
</Card>
</div>
)
}