added RAG, Multiuser, TG bot

This commit is contained in:
2026-06-13 20:20:56 +00:00
parent 66e1b0e29e
commit c8a9429bed
142 changed files with 19901 additions and 8790 deletions
+821 -555
View File
File diff suppressed because it is too large Load Diff
+555
View File
@@ -0,0 +1,555 @@
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;
tool_calls_json?: string | null;
created_at: string;
}
export interface SessionDetail extends ChatSession {
messages: ChatMessage[];
}
export interface PomodoroCycle {
completed_work_sessions: number;
sessions_until_long_break: number;
task_note: string;
work_duration_min: number;
short_break_min: number;
long_break_min: number;
auto_advance: boolean;
chat_notify_seq: number;
}
export interface PomodoroStatus {
status: string;
phase: 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;
cycle: PomodoroCycle;
}
export interface CharacterCardData {
name: string;
description: string;
personality: string;
scenario: string;
first_mes: string;
mes_example: string;
system_prompt: string;
post_history_instructions: string;
tags: string[];
creator: string;
creator_notes: string;
alternate_greetings: string[];
character_version: string;
}
export interface CharacterCardV2 {
spec: string;
spec_version: string;
data: CharacterCardData;
}
export interface UserProfile {
name?: string;
age?: string;
timezone?: string;
language?: string;
notes?: string;
}
export interface MemoryFact {
id: number;
category: string;
content: string;
importance: number;
source?: string;
updated_at?: string | null;
}
export interface FitnessComputed {
bmr: number;
tdee: number;
bmi: number;
}
export interface FitnessProfile {
sex?: string;
age?: number;
height_cm?: number;
weight_kg?: number;
activity_level?: string;
goal?: string;
target_weight_kg?: number | null;
weekly_workouts?: number;
calorie_target?: number;
protein_g?: number;
fat_g?: number;
carbs_g?: number;
water_l?: number;
computed?: FitnessComputed;
}
export interface FoodLogItem {
id: number;
meal_type: string;
description: string;
calories: number;
protein_g: number;
fat_g: number;
carbs_g: number;
estimated: boolean;
logged_at?: string;
}
export interface WaterLogItem {
id: number;
amount_ml: number;
logged_at?: string;
}
export interface WorkoutLogItem {
id: number;
title: string;
notes?: string;
duration_min?: number | null;
exercises?: unknown[];
logged_at?: string;
}
export interface FitnessDailySummary {
date: string;
totals: {
calories: number;
protein_g: number;
fat_g: number;
carbs_g: number;
water_ml: number;
};
targets: {
calories: number;
protein_g: number;
fat_g: number;
carbs_g: number;
water_ml: number;
};
meals: FoodLogItem[];
water: WaterLogItem[];
workouts: WorkoutLogItem[];
}
export interface BodyMetric {
id: number;
weight_kg: number;
recorded_at?: string;
}
export interface FitnessReminder {
id: number;
kind: string;
hour: number;
minute: number;
interval_hours?: number | null;
enabled: boolean;
}
export interface FitnessDayOverview {
date: string;
has_data: boolean;
totals: FitnessDailySummary["totals"];
targets: FitnessDailySummary["targets"];
meal_count: number;
workout_count: number;
}
export interface FitnessHistory {
start_date: string;
end_date: string;
days: number;
summaries: FitnessDayOverview[];
}
export interface FitnessSnapshot {
profile: FitnessProfile | null;
today: FitnessDailySummary;
history?: FitnessHistory;
body_metrics: BodyMetric[];
reminders: FitnessReminder[];
}
export interface MemorySnapshot {
profile: UserProfile;
facts: MemoryFact[];
session_summary?: string;
total_facts: number;
}
export interface PomodoroHistoryItem {
id: number;
status: string;
phase: 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) {
const detail = await response.text().catch(() => "");
throw new Error(detail || `Ошибка отправки (${response.status})`);
}
const reader = response.body.getReader();
const decoder = new TextDecoder();
let buffer = "";
const flushParts = function* (parts: string[]) {
for (const part of parts) {
if (!part.trim()) continue;
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) };
}
}
};
try {
while (true) {
let done = false;
let value: Uint8Array | undefined;
try {
({ done, value } = await reader.read());
} catch {
throw new Error(
"Соединение прервалось (таймаут прокси). Обновите чат — ответ мог уже сохраниться.",
);
}
if (value) {
buffer += decoder.decode(value, { stream: !done });
}
const parts = buffer.split("\n\n");
buffer = parts.pop() ?? "";
yield* flushParts(parts);
if (done) {
if (buffer.trim()) {
yield* flushParts([buffer]);
}
break;
}
}
} finally {
reader.releaseLock();
}
},
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"),
pomodoroResetCycle: (clear_task = false) =>
request<PomodoroStatus>(`/api/v1/pomodoro/cycle/reset?clear_task=${clear_task}`, {
method: "POST",
}),
pomodoroSkip: () =>
request<PomodoroStatus>("/api/v1/pomodoro/skip", { method: "POST" }),
pomodoroStartShortBreak: (duration_min?: number) =>
request<PomodoroStatus>(
`/api/v1/pomodoro/break/short/start${duration_min ? `?duration_min=${duration_min}` : ""}`,
{ method: "POST" }
),
pomodoroStartLongBreak: (duration_min?: number) =>
request<PomodoroStatus>(
`/api/v1/pomodoro/break/long/start${duration_min ? `?duration_min=${duration_min}` : ""}`,
{ method: "POST" }
),
getCharacter: () => request<CharacterCardV2>("/api/v1/character"),
saveCharacter: (card: CharacterCardV2) =>
request<CharacterCardV2>("/api/v1/character", {
method: "PUT",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(card),
}),
getMemorySnapshot: (sessionId?: number) =>
request<MemorySnapshot>(
`/api/v1/memory${sessionId ? `?session_id=${sessionId}` : ""}`
),
updateProfile: (updates: UserProfile) =>
request<{ ok: boolean; profile: UserProfile }>("/api/v1/profile", {
method: "PUT",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ updates }),
}),
createMemoryFact: (payload: {
content: string;
category?: string;
importance?: number;
session_id?: number;
}) =>
request<{ ok: boolean; memory_id: number }>("/api/v1/memory/facts", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(payload),
}),
forgetMemoryFact: (id: number) =>
request<{ ok: boolean }>(`/api/v1/memory/facts/${id}`, { method: "DELETE" }),
getFitnessSnapshot: () => request<FitnessSnapshot>("/api/v1/fitness"),
getFitnessSummary: (day?: string) =>
request<FitnessDailySummary>(
`/api/v1/fitness/summary${day ? `?day=${encodeURIComponent(day)}` : ""}`
),
getFitnessHistory: (days = 7, end?: string) => {
const params = new URLSearchParams({ days: String(days) });
if (end) params.set("end", end);
return request<FitnessHistory>(`/api/v1/fitness/history?${params}`);
},
updateFitnessProfile: (updates: Partial<FitnessProfile>) =>
request<{ ok: boolean; profile: FitnessProfile }>("/api/v1/fitness/profile", {
method: "PUT",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(updates),
}),
deleteFitnessMeal: (id: number) =>
request<{ ok: boolean }>(`/api/v1/fitness/meals/${id}`, { method: "DELETE" }),
deleteFitnessWater: (id: number) =>
request<{ ok: boolean }>(`/api/v1/fitness/water/${id}`, { method: "DELETE" }),
updateFitnessReminder: (
kind: string,
updates: { enabled?: boolean; hour?: number; minute?: number; interval_hours?: number }
) =>
request<{ ok: boolean }>(`/api/v1/fitness/reminders/${kind}`, {
method: "PUT",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(updates),
}),
getShoppingSnapshot: () => request<ShoppingSnapshot>("/api/v1/shopping"),
createShoppingList: (name: string) =>
request<{ ok: boolean; list: ShoppingList }>("/api/v1/shopping/lists", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ name }),
}),
renameShoppingList: (listId: number, name: string) =>
request<{ ok: boolean; list: ShoppingList }>(`/api/v1/shopping/lists/${listId}`, {
method: "PATCH",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ name }),
}),
deleteShoppingList: (listId: number) =>
request<{ ok: boolean }>(`/api/v1/shopping/lists/${listId}`, { method: "DELETE" }),
addShoppingItems: (payload: {
list_id?: number;
list_name?: string;
items: { text: string; quantity?: number; unit?: string }[];
}) =>
request<{ ok: boolean }>("/api/v1/shopping/items", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(payload),
}),
setShoppingItemChecked: (itemId: number, checked: boolean) =>
request<{ ok: boolean }>(`/api/v1/shopping/items/${itemId}`, {
method: "PATCH",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ checked }),
}),
removeShoppingItem: (itemId: number) =>
request<{ ok: boolean }>(`/api/v1/shopping/items/${itemId}`, { method: "DELETE" }),
clearShoppingChecked: (listId: number) =>
request<{ ok: boolean }>(`/api/v1/shopping/lists/${listId}/clear-checked`, {
method: "POST",
}),
getRemindersSnapshot: () => request<RemindersSnapshot>("/api/v1/reminders"),
getRemindersCalendar: (year: number, month: number) =>
request<RemindersCalendar>(`/api/v1/reminders/calendar?year=${year}&month=${month}`),
createReminder: (payload: ReminderCreatePayload) =>
request<{ ok: boolean; reminder: Reminder }>("/api/v1/reminders", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(payload),
}),
updateReminder: (id: number, payload: Partial<ReminderCreatePayload> & { enabled?: boolean }) =>
request<{ ok: boolean; reminder: Reminder }>(`/api/v1/reminders/${id}`, {
method: "PATCH",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(payload),
}),
deleteReminder: (id: number) =>
request<{ ok: boolean }>(`/api/v1/reminders/${id}`, { method: "DELETE" }),
completeReminder: (id: number) =>
request<{ ok: boolean; reminder: Reminder }>(`/api/v1/reminders/${id}/complete`, {
method: "POST",
}),
};
export interface ShoppingListItem {
id: number;
list_id: number;
text: string;
quantity: number | null;
unit: string;
checked: boolean;
sort_order: number;
}
export interface ShoppingList {
id: number;
name: string;
sort_order: number;
item_count: number;
unchecked_count: number;
items?: ShoppingListItem[];
}
export interface ShoppingSnapshot {
lists: ShoppingList[];
list_count: number;
total_items: number;
unchecked_items: number;
}
export interface Reminder {
id: number;
title: string;
notes: string;
due_at: string;
due_at_local: string;
all_day: boolean;
recurrence: string;
enabled: boolean;
completed_at: string | null;
timezone: string;
created_at: string | null;
}
export interface RemindersSnapshot {
notify_seq: number;
upcoming: Reminder[];
upcoming_count: number;
timezone: string;
}
export interface RemindersCalendar {
year: number;
month: number;
timezone: string;
reminders: Reminder[];
}
export interface ReminderCreatePayload {
title: string;
due_at: string;
notes?: string;
all_day?: boolean;
recurrence?: string;
}