From 71efdff80062ae3a78dba26cd882e7762cf7044f Mon Sep 17 00:00:00 2001 From: gongzhiyong Date: Wed, 8 Apr 2026 17:34:23 +0800 Subject: [PATCH] Switch TicketSummary to use /api/tickets/summary endpoint - Add fetchTicketSummary() and TicketSummaryData type to lib/api.ts - Change TicketSummary props from tickets[] array to summary object with total, by_status, and by_priority fields - Update GeminiChat to fetch summary data instead of ticket list - TicketSummary now renders status and priority breakdowns from the summary endpoint instead of computing from raw ticket data Co-Authored-By: Claude Sonnet 4.6 (1M context) --- .../components/gemini/ExtensionsPanel.tsx | 78 +++++++++---------- frontend/components/gemini/GeminiChat.tsx | 20 ++--- frontend/lib/api.ts | 12 +++ 3 files changed, 61 insertions(+), 49 deletions(-) diff --git a/frontend/components/gemini/ExtensionsPanel.tsx b/frontend/components/gemini/ExtensionsPanel.tsx index 3f0d62d..5b1c413 100644 --- a/frontend/components/gemini/ExtensionsPanel.tsx +++ b/frontend/components/gemini/ExtensionsPanel.tsx @@ -251,30 +251,28 @@ export function ExtensionsPanel({ // 工单摘要组件 - 显示在对话区域 interface TicketSummaryProps { - tickets: { - id: string; - title: string; - status: "pending" | "processing" | "resolved"; - priority: "P0" | "P1" | "P2" | "P3"; - createdAt: string; - }[]; + summary: { + total: number; + by_status: { pending: number; processing: number; resolved: number }; + by_priority: { P0: number; P1: number; P2: number; P3: number }; + }; } -export function TicketSummary({ tickets }: TicketSummaryProps) { - const statusMap = { - pending: { label: "待处理", color: "text-yellow-400 bg-yellow-900/30" }, - processing: { label: "处理中", color: "text-blue-400 bg-blue-900/30" }, - resolved: { label: "已解决", color: "text-green-400 bg-green-900/30" }, - }; +export function TicketSummary({ summary }: TicketSummaryProps) { + const statusEntries: { key: keyof typeof summary.by_status; label: string; color: string }[] = [ + { key: "pending", label: "待处理", color: "text-yellow-400 bg-yellow-900/30" }, + { key: "processing", label: "处理中", color: "text-blue-400 bg-blue-900/30" }, + { key: "resolved", label: "已解决", color: "text-green-400 bg-green-900/30" }, + ]; - const priorityColors = { - P0: "text-red-400", - P1: "text-orange-400", - P2: "text-yellow-400", - P3: "text-[var(--gem-text-muted)]", - }; + const priorityEntries: { key: keyof typeof summary.by_priority; color: string }[] = [ + { key: "P0", color: "text-red-400" }, + { key: "P1", color: "text-orange-400" }, + { key: "P2", color: "text-yellow-400" }, + { key: "P3", color: "text-[var(--gem-text-muted)]" }, + ]; - if (tickets.length === 0) return null; + if (summary.total === 0) return null; return (
@@ -282,39 +280,41 @@ export function TicketSummary({ tickets }: TicketSummaryProps) {
工单摘要 - ({tickets.length} 条待关注) + ({summary.total} 条待关注)
- {tickets.slice(0, 5).map((ticket) => ( + {statusEntries.map(({ key, label, color }) => (
- - {ticket.priority} - - - {ticket.title} - - {statusMap[ticket.status].label} + {label} + + + {summary.by_status[key]} 条 + +
+ ))} + {priorityEntries.map(({ key, color }) => ( +
+ + {key} + + + {summary.by_priority[key]} 条 - {ticket.createdAt}
))}
- {tickets.length > 5 && ( -
- -
- )}
); diff --git a/frontend/components/gemini/GeminiChat.tsx b/frontend/components/gemini/GeminiChat.tsx index 8cb397d..11ae6d3 100644 --- a/frontend/components/gemini/GeminiChat.tsx +++ b/frontend/components/gemini/GeminiChat.tsx @@ -13,9 +13,9 @@ import { cn } from "@/lib/utils"; import { fetchConversations, fetchConversation, - fetchTickets, + fetchTicketSummary, streamChat, - type TicketData, + type TicketSummaryData, } from "@/lib/api"; // ── Types ──────────────────────────────────────────────────────────────────── @@ -70,7 +70,7 @@ export function GeminiChat() { const [isLoading, setIsLoading] = useState(false); const [extensionsPanelOpen, setExtensionsPanelOpen] = useState(false); const [extensions, setExtensions] = useState(INITIAL_EXTENSIONS); - const [tickets, setTickets] = useState([]); + const [ticketSummary, setTicketSummary] = useState(null); const [activeTools, setActiveTools] = useState>(new Set()); const [selectedModel, setSelectedModel] = useState<"flash" | "pro">("pro"); const messagesEndRef = useRef(null); @@ -95,14 +95,14 @@ export function GeminiChat() { }); }, []); - // Load tickets when ticket extension connects + // Load ticket summary when ticket extension connects useEffect(() => { if (ticketSystemConnected) { - fetchTickets() - .then(setTickets) - .catch(() => setTickets([])); + fetchTicketSummary() + .then(setTicketSummary) + .catch(() => setTicketSummary(null)); } else { - setTickets([]); + setTicketSummary(null); } }, [ticketSystemConnected]); @@ -348,12 +348,12 @@ export function GeminiChat() { <> {/* 显示工单摘要(仅当工单系统已连接) */} - {ticketSystemConnected && } + {ticketSystemConnected && ticketSummary && } ) : (
{/* 对话中也显示工单摘要 */} - {ticketSystemConnected && } + {ticketSystemConnected && ticketSummary && } {messages.map((msg) => { // Hide empty assistant message while streaming — the typing indicator covers it if (msg.role === "assistant" && msg.content === "" && isLoading) { diff --git a/frontend/lib/api.ts b/frontend/lib/api.ts index 862d0e4..9c571fb 100644 --- a/frontend/lib/api.ts +++ b/frontend/lib/api.ts @@ -63,6 +63,18 @@ export async function fetchTickets(page = 1, pageSize = 20): Promise { + const res = await fetch(`${API_URL}/api/tickets/summary`); + if (!res.ok) throw new Error("Failed to fetch ticket summary"); + return res.json(); +} + // ── SSE Chat Stream ────────────────────────────────────────────────────────── export interface ChatStreamEvent {