Files
socaichat/frontend/lib/api.ts
T
gongzhiyongandClaude Sonnet 4.6 71efdff800 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) <noreply@anthropic.com>
2026-04-08 17:34:23 +08:00

150 lines
4.7 KiB
TypeScript

const API_URL = process.env.NEXT_PUBLIC_API_URL ?? "https://soc-backend.azurewebsites.net";
// ── Conversations ────────────────────────────────────────────────────────────
export interface ConversationSummary {
id: string;
title: string;
created_at: string;
updated_at: string;
}
export interface ApiMessage {
id: string;
role: "human" | "ai";
content: string;
created_at: string;
}
export interface ConversationDetail extends ConversationSummary {
messages: ApiMessage[];
}
export async function fetchConversations(): Promise<ConversationSummary[]> {
const res = await fetch(`${API_URL}/api/conversations`);
if (!res.ok) throw new Error("Failed to fetch conversations");
return res.json();
}
export async function fetchConversation(id: string): Promise<ConversationDetail> {
const res = await fetch(`${API_URL}/api/conversations/${id}`);
if (!res.ok) throw new Error("Failed to fetch conversation");
return res.json();
}
export async function createConversation(title?: string): Promise<ConversationSummary> {
const res = await fetch(`${API_URL}/api/conversations`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ title: title ?? "New conversation" }),
});
if (!res.ok) throw new Error("Failed to create conversation");
return res.json();
}
export async function deleteConversation(id: string): Promise<void> {
const res = await fetch(`${API_URL}/api/conversations/${id}`, { method: "DELETE" });
if (!res.ok) throw new Error("Failed to delete conversation");
}
// ── Tickets ──────────────────────────────────────────────────────────────────
export interface TicketData {
id: string;
title: string;
status: "pending" | "processing" | "resolved";
priority: "P0" | "P1" | "P2" | "P3";
createdAt: string;
}
export async function fetchTickets(page = 1, pageSize = 20): Promise<TicketData[]> {
const res = await fetch(`${API_URL}/api/tickets?page=${page}&page_size=${pageSize}`);
if (!res.ok) throw new Error("Failed to fetch tickets");
return res.json();
}
export interface TicketSummaryData {
total: number;
by_status: { pending: number; processing: number; resolved: number };
by_priority: { P0: number; P1: number; P2: number; P3: number };
}
export async function fetchTicketSummary(): Promise<TicketSummaryData> {
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 {
type: "token" | "tool_start" | "tool_end" | "done";
content?: string;
tool?: string;
}
export function streamChat(
message: string,
conversationId: string,
tools: string[],
model: "flash" | "pro",
onEvent: (event: ChatStreamEvent) => void,
onError: (error: Error) => void,
onDone: () => void,
): AbortController {
const controller = new AbortController();
fetch(`${API_URL}/api/chat/stream`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ message, conversation_id: conversationId, tools, model }),
signal: controller.signal,
})
.then(async (res) => {
if (!res.ok) {
const text = await res.text().catch(() => "Unknown error");
throw new Error(`Chat stream failed (${res.status}): ${text}`);
}
const reader = res.body?.getReader();
if (!reader) throw new Error("No response body");
const decoder = new TextDecoder();
let buffer = "";
while (true) {
const { done, value } = await reader.read();
if (done) break;
buffer += decoder.decode(value, { stream: true });
const lines = buffer.split("\n");
buffer = lines.pop() ?? "";
for (const line of lines) {
const trimmed = line.trim();
if (!trimmed.startsWith("data: ")) continue;
const json = trimmed.slice(6);
if (!json) continue;
try {
const event: ChatStreamEvent = JSON.parse(json);
onEvent(event);
if (event.type === "done") {
onDone();
return;
}
} catch {
// skip malformed events
}
}
}
onDone();
})
.catch((err) => {
if (err.name !== "AbortError") {
onError(err);
}
});
return controller;
}