This commit is contained in:
2026-06-09 09:36:48 +03:00
parent 8247b7116f
commit f0fda693d8
49 changed files with 5503 additions and 1 deletions
+132
View File
@@ -0,0 +1,132 @@
const API_BASE = import.meta.env.VITE_API_URL ?? "";
export interface ChatSession {
id: number;
title: string;
created_at: string;
updated_at: string;
}
export interface ChatMessage {
id: number;
role: string;
content: string;
created_at: string;
}
export interface SessionDetail extends ChatSession {
messages: ChatMessage[];
}
export interface PomodoroStatus {
status: string;
duration_min: number;
task_note: string;
elapsed_seconds: number;
remaining_seconds: number;
session_id: number | null;
started_at?: string | null;
finished_at?: string | null;
}
export interface PomodoroHistoryItem {
id: number;
status: string;
duration_min: number;
task_note: string;
result: string | null;
completed: boolean;
elapsed_seconds: number;
finished_at: string | null;
}
async function request<T>(path: string, options?: RequestInit): Promise<T> {
const response = await fetch(`${API_BASE}${path}`, options);
if (!response.ok) {
const text = await response.text();
throw new Error(text || response.statusText);
}
return response.json() as Promise<T>;
}
export const api = {
health: () => request<{ status: string }>("/api/v1/health"),
listSessions: () => request<ChatSession[]>("/api/v1/chat/sessions"),
createSession: (title = "Новый чат") =>
request<ChatSession>("/api/v1/chat/sessions", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ title }),
}),
getSession: (id: number) => request<SessionDetail>(`/api/v1/chat/sessions/${id}`),
deleteSession: (id: number) =>
request<{ ok: boolean }>(`/api/v1/chat/sessions/${id}`, { method: "DELETE" }),
sendMessage: async function* (sessionId: number, content: string) {
const response = await fetch(`${API_BASE}/api/v1/chat/sessions/${sessionId}/messages`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ content }),
});
if (!response.ok || !response.body) {
throw new Error("Failed to send message");
}
const reader = response.body.getReader();
const decoder = new TextDecoder();
let buffer = "";
while (true) {
const { done, value } = await reader.read();
if (done) break;
buffer += decoder.decode(value, { stream: true });
const parts = buffer.split("\n\n");
buffer = parts.pop() ?? "";
for (const part of parts) {
const lines = part.split("\n");
let event = "message";
let data = "";
for (const line of lines) {
if (line.startsWith("event: ")) event = line.slice(7);
if (line.startsWith("data: ")) data = line.slice(6);
}
if (data) {
yield { event, data: JSON.parse(data) };
}
}
}
},
pomodoroStatus: () => request<PomodoroStatus>("/api/v1/pomodoro/status"),
pomodoroStart: (duration_min: number, task_note: string) =>
request<PomodoroStatus>("/api/v1/pomodoro/start", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ duration_min, task_note }),
}),
pomodoroPause: () =>
request<PomodoroStatus>("/api/v1/pomodoro/pause", { method: "POST" }),
pomodoroResume: () =>
request<PomodoroStatus>("/api/v1/pomodoro/resume", { method: "POST" }),
pomodoroStop: (result: string, completed: boolean) =>
request<PomodoroStatus>("/api/v1/pomodoro/stop", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ result, completed }),
}),
pomodoroHistory: () => request<PomodoroHistoryItem[]>("/api/v1/pomodoro/history"),
};