feat(ai-analysis): 添加详情页智能分析入口
Some checks failed
Deploy Production / Build, push, deploy, verify (push) Has been cancelled

This commit is contained in:
2026-07-09 09:42:58 +08:00
parent 6d2999085e
commit 1e6fb0c7aa
6 changed files with 231 additions and 4 deletions

View File

@@ -0,0 +1,107 @@
'use client';
import { type FormEvent, useState } from 'react';
import type { AnalysisRequest, AnalysisResponse } from '@ftb/shared';
import { Loader2, Send, X } from 'lucide-react';
import { requestAnalysis } from '@/lib/analysis-api';
import { AnalysisResultBlock } from './AnalysisResultBlock';
export function AnalysisContextDrawer({
open,
title,
context,
permissions,
onClose,
}: {
open: boolean;
title: string;
context: NonNullable<AnalysisRequest['context']>;
permissions: string[];
onClose: () => void;
}) {
const [question, setQuestion] = useState('');
const [response, setResponse] = useState<AnalysisResponse | null>(null);
const [loading, setLoading] = useState(false);
if (!open) return null;
async function ask(prompt: string) {
const value = prompt.trim();
if (!value || loading) return;
setLoading(true);
try {
const result = await requestAnalysis({ question: value, context }, permissions);
setResponse(result);
setQuestion('');
} catch {
setResponse({
ok: false,
code: 'AI_UNAVAILABLE',
message: '业务分析暂不可用,请稍后再试。',
});
} finally {
setLoading(false);
}
}
function submit(event: FormEvent<HTMLFormElement>) {
event.preventDefault();
void ask(question);
}
return (
<div className="fixed inset-0 z-50 flex justify-end bg-black/40">
<aside className="flex h-full w-full max-w-2xl flex-col border-l border-white/60 bg-[#f8fafc]/90 shadow-2xl backdrop-blur-xl">
<header className="flex h-14 shrink-0 items-center justify-between border-b border-white/70 px-5">
<div className="min-w-0">
<p className="truncate text-[13px] font-semibold text-[#0f172a]">{title}</p>
<p className="mt-0.5 text-[11px] text-[#64748b]"></p>
</div>
<button
type="button"
onClick={onClose}
className="ml-3 rounded-full p-2 text-[#64748b] transition-colors hover:bg-white hover:text-[#0f172a]"
title="关闭"
>
<X className="h-4 w-4" />
</button>
</header>
<div className="min-h-0 flex-1 space-y-4 overflow-y-auto p-5">
<form
onSubmit={submit}
className="rounded-[28px] border border-white/70 bg-white/80 p-3 shadow-[0_18px_60px_rgba(15,23,42,0.08)] backdrop-blur-xl"
>
<textarea
value={question}
onChange={(event) => setQuestion(event.target.value)}
rows={3}
className="w-full resize-none bg-transparent px-2 py-2 text-[14px] leading-6 text-[#0f172a] outline-none placeholder:text-[#94a3b8]"
placeholder="例如:这个版本风险怎么样?需求完成趋势怎么样?"
/>
<div className="flex items-center justify-between gap-3 px-1">
<span className="text-[11px] text-[#64748b]"></span>
<button
type="submit"
disabled={loading || !question.trim()}
className="inline-flex h-8 shrink-0 items-center gap-1.5 rounded-full bg-[#0f172a] px-4 text-[13px] font-medium text-white transition-colors hover:bg-[#1e293b] disabled:cursor-not-allowed disabled:bg-[#cbd5e1]"
>
{loading ? <Loader2 className="h-3.5 w-3.5 animate-spin" /> : <Send className="h-3.5 w-3.5" />}
{loading ? '分析中' : '开始分析'}
</button>
</div>
</form>
{response ? (
<AnalysisResultBlock response={response} onAsk={(prompt) => void ask(prompt)} />
) : (
<div className="rounded-[28px] border border-dashed border-[#dbe3ef] bg-white/55 p-6 text-[13px] leading-7 text-[#64748b]">
</div>
)}
</div>
</aside>
</div>
);
}