"use client"; import { useState, useEffect, useCallback } from "react"; import { Button } from "@/components/ui/button"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Label } from "@/components/ui/label"; import { Input } from "@/components/ui/input"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@/components/ui/select"; import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter, DialogTrigger, } from "@/components/ui/dialog"; import ReactMarkdown from "react-markdown"; import { Wand2, Save, FileSpreadsheet, FileDown, Loader2, Plus, Search, CloudUpload } from "lucide-react"; import { toast } from "sonner"; import { customersApi, projectsApi, templatesApi, pushProjectToCloud, downloadFile, downloadFileAsBlob, type CustomerRead, type QuoteGenerateResponse, type TemplateInfo, } from "@/lib/api/client"; export default function WorkspacePage() { const [customers, setCustomers] = useState([]); const [customerId, setCustomerId] = useState(""); const [rawText, setRawText] = useState(""); const [solutionMd, setSolutionMd] = useState(""); const [projectId, setProjectId] = useState(null); const [lastQuote, setLastQuote] = useState(null); const [analyzing, setAnalyzing] = useState(false); const [saving, setSaving] = useState(false); const [generatingQuote, setGeneratingQuote] = useState(false); const [addCustomerOpen, setAddCustomerOpen] = useState(false); const [newCustomerName, setNewCustomerName] = useState(""); const [newCustomerContact, setNewCustomerContact] = useState(""); const [newCustomerTags, setNewCustomerTags] = useState(""); const [addingCustomer, setAddingCustomer] = useState(false); const [quoteTemplates, setQuoteTemplates] = useState([]); const [selectedQuoteTemplate, setSelectedQuoteTemplate] = useState(""); const [customerSearch, setCustomerSearch] = useState(""); const [pushToCloudLoading, setPushToCloudLoading] = useState(false); const loadCustomers = useCallback(async (search?: string) => { try { const list = await customersApi.list(search?.trim() ? { q: search.trim() } : undefined); setCustomers(list); if (list.length > 0 && !customerId) setCustomerId(String(list[0].id)); } catch (e) { toast.error("加载客户列表失败"); } }, [customerId]); useEffect(() => { loadCustomers(customerSearch); }, [loadCustomers, customerSearch]); const loadQuoteTemplates = useCallback(async () => { try { const list = await templatesApi.list(); setQuoteTemplates(list.filter((t) => t.type === "excel")); } catch { // ignore } }, []); useEffect(() => { loadQuoteTemplates(); }, [loadQuoteTemplates]); const handleAddCustomer = async (e: React.FormEvent) => { e.preventDefault(); const name = newCustomerName.trim(); if (!name) { toast.error("请填写客户名称"); return; } setAddingCustomer(true); try { const created = await customersApi.create({ name, contact_info: newCustomerContact.trim() || null, tags: newCustomerTags.trim() || null, }); toast.success("客户已添加"); setAddCustomerOpen(false); setNewCustomerName(""); setNewCustomerContact(""); setNewCustomerTags(""); await loadCustomers(customerSearch); setCustomerId(String(created.id)); } catch (err) { const msg = err instanceof Error ? err.message : "添加失败"; toast.error(msg); } finally { setAddingCustomer(false); } }; const handleAnalyze = async () => { if (!customerId || !rawText.trim()) { toast.error("请选择客户并输入原始需求"); return; } setAnalyzing(true); try { const res = await projectsApi.analyze({ customer_id: Number(customerId), raw_text: rawText.trim(), }); setSolutionMd(res.ai_solution_md); setProjectId(res.project_id); setLastQuote(null); toast.success("方案已生成,可在右侧编辑"); } catch (e: unknown) { const msg = e instanceof Error ? e.message : "AI 解析失败"; toast.error(msg); } finally { setAnalyzing(false); } }; const handlePushToCloud = async (platform: "feishu" | "yuque" | "tencent") => { if (projectId == null) return; const md = solutionMd?.trim() || ""; if (!md) { toast.error("请先在编辑器中填写方案内容后再推送"); return; } setPushToCloudLoading(true); try { const res = await pushProjectToCloud(projectId, { platform, body_md: md, }); toast.success("已推送到云文档", { action: res.url ? { label: "打开链接", onClick: () => window.open(res.url, "_blank"), } : undefined, }); } catch (e) { toast.error(e instanceof Error ? e.message : "推送失败"); } finally { setPushToCloudLoading(false); } }; const handleSaveToArchive = async () => { if (projectId == null) { toast.error("请先进行 AI 解析"); return; } setSaving(true); try { await projectsApi.update(projectId, { ai_solution_md: solutionMd }); toast.success("已保存到项目档案"); } catch (e: unknown) { const msg = e instanceof Error ? e.message : "保存失败"; toast.error(msg); } finally { setSaving(false); } }; const handleDraftQuote = async () => { if (projectId == null) { toast.error("请先进行 AI 解析并保存"); return; } setGeneratingQuote(true); try { const res = await projectsApi.generateQuote( projectId, selectedQuoteTemplate || undefined ); setLastQuote(res); toast.success("报价单已生成"); downloadFile(res.excel_path, `quote_project_${projectId}.xlsx`); } catch (e: unknown) { const msg = e instanceof Error ? e.message : "生成报价失败"; toast.error(msg); } finally { setGeneratingQuote(false); } }; const handleExportPdf = async () => { if (lastQuote?.pdf_path) { downloadFile(lastQuote.pdf_path, `quote_project_${projectId}.pdf`); toast.success("PDF 已下载"); return; } if (projectId == null) { toast.error("请先进行 AI 解析"); return; } setGeneratingQuote(true); try { const res = await projectsApi.generateQuote( projectId, selectedQuoteTemplate || undefined ); setLastQuote(res); await downloadFileAsBlob(res.pdf_path, `quote_project_${projectId}.pdf`); toast.success("PDF 已下载"); } catch (e: unknown) { const msg = e instanceof Error ? e.message : "生成 PDF 失败"; toast.error(msg); } finally { setGeneratingQuote(false); } }; return (
{/* Left Panel — 40% */}
原始需求
setCustomerSearch(e.target.value)} className="pl-8 h-9" />
新建客户
setNewCustomerName(e.target.value)} placeholder="必填" />
setNewCustomerContact(e.target.value)} placeholder="选填" />
setNewCustomerTags(e.target.value)} placeholder="如:重点客户, 已签约" />