mirror of
https://github.com/temetro/temetro.git
synced 2026-08-11 11:09:04 +00:00
feat: clinic-wide AI (analytics/earnings/inventory + invoice-from-file),
real Live card, persistent chat history Analytics & earnings: - Analytics now carries real money computed from invoices (billed/paid/ outstanding + by-month); new Earnings section on the Analysis page drawn with the project's Bklit chart components (shared EarningsChart). AI agent reaches the whole clinic: - new read tools getClinicInfo / getAnalytics / listInventory render clinic, analytics (with a Bklit earnings chart) and inventory cards in chat - proposeInvoice turns an uploaded purchase/medication list into an invoice (new "invoice" action-preview kind → createInvoice); invoices/appointments auto-create/link a patient (ensurePatient) so they hit the Patients page Live card: - plots real data — patients checked in today — via GET /api/analytics/live (polled); value pill clamped and margins widened so nothing spills the card Persistent AI chat history (Claude-style): - ai_chat_threads + ai_chat_messages (migration 0016); per-user, org-scoped thread CRUD under /api/chat/threads - chat panel owns a thread id, loads /?thread=<id>, and auto-saves after each exchange; sidebar lists past chats (open/delete), "New chat" starts fresh Verified with backend typecheck + frontend tsc + next build. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,50 @@
|
||||
import {
|
||||
index,
|
||||
integer,
|
||||
jsonb,
|
||||
pgTable,
|
||||
text,
|
||||
timestamp,
|
||||
} from "drizzle-orm/pg-core";
|
||||
|
||||
import { organization, user } from "./auth.js";
|
||||
|
||||
// Persisted AI-chat threads (Claude-style history), per user within a clinic.
|
||||
// The thread id is client-generated (nanoid) so the frontend can start saving a
|
||||
// fresh chat without a round-trip. Messages store the full UIMessage `parts`
|
||||
// (text + custom record cards) as JSONB so a reopened thread renders exactly as
|
||||
// it did live.
|
||||
export const aiChatThreads = pgTable(
|
||||
"ai_chat_threads",
|
||||
{
|
||||
id: text("id").primaryKey(),
|
||||
organizationId: text("organization_id")
|
||||
.notNull()
|
||||
.references(() => organization.id, { onDelete: "cascade" }),
|
||||
userId: text("user_id")
|
||||
.notNull()
|
||||
.references(() => user.id, { onDelete: "cascade" }),
|
||||
title: text("title").notNull().default("New chat"),
|
||||
createdAt: timestamp("created_at").defaultNow().notNull(),
|
||||
updatedAt: timestamp("updated_at")
|
||||
.defaultNow()
|
||||
.$onUpdate(() => new Date())
|
||||
.notNull(),
|
||||
},
|
||||
(t) => [index("ai_threads_org_user_idx").on(t.organizationId, t.userId)],
|
||||
);
|
||||
|
||||
export const aiChatMessages = pgTable(
|
||||
"ai_chat_messages",
|
||||
{
|
||||
id: text("id").primaryKey(),
|
||||
threadId: text("thread_id")
|
||||
.notNull()
|
||||
.references(() => aiChatThreads.id, { onDelete: "cascade" }),
|
||||
position: integer("position").notNull(),
|
||||
role: text("role").notNull(),
|
||||
parts: jsonb("parts").notNull(),
|
||||
createdAt: timestamp("created_at").defaultNow().notNull(),
|
||||
},
|
||||
(t) => [index("ai_messages_thread_idx").on(t.threadId, t.position)],
|
||||
);
|
||||
@@ -11,3 +11,4 @@ export * from "./messaging.js";
|
||||
export * from "./notifications.js";
|
||||
export * from "./settings.js";
|
||||
export * from "./ai.js";
|
||||
export * from "./ai-chat.js";
|
||||
|
||||
Reference in New Issue
Block a user