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 {