Refactor coach to plain Appwrite storage with integrated overview UI.

Remove client-side encryption, migrate coach_chats schema, fix the Ollama proxy, and embed coach on overview alongside the dedicated tab.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Ned Halksworth
2026-05-23 20:25:21 +01:00
parent d312321ffa
commit dc9fbf496d
11 changed files with 1182 additions and 958 deletions
+97 -1
View File
@@ -1,6 +1,10 @@
import react from "@vitejs/plugin-react";
import type { IncomingMessage, ServerResponse } from "node:http";
import type { Plugin } from "vite";
import { defineConfig, loadEnv } from "vite";
const DEFAULT_MODEL = "deepseek-v4-pro:cloud";
export default defineConfig(({ mode }) => {
const env = loadEnv(mode, process.cwd(), "");
const ollamaProxy = {
@@ -17,7 +21,7 @@ export default defineConfig(({ mode }) => {
};
return {
plugins: [react()],
plugins: [react(), ollamaProxyPlugin(env)],
server: {
proxy: {
"/api/ollama-chat": ollamaProxy,
@@ -42,3 +46,95 @@ export default defineConfig(({ mode }) => {
},
};
});
function ollamaProxyPlugin(env: Record<string, string>): Plugin {
return {
name: "ollama-proxy",
configureServer(server) {
server.middlewares.use("/api/ollama-chat", createOllamaHandler(env));
},
configurePreviewServer(server) {
server.middlewares.use("/api/ollama-chat", createOllamaHandler(env));
},
};
}
function createOllamaHandler(env: Record<string, string>) {
return (req: IncomingMessage, res: ServerResponse) => {
res.setHeader("Access-Control-Allow-Origin", "*");
res.setHeader("Access-Control-Allow-Methods", "POST, OPTIONS");
res.setHeader("Access-Control-Allow-Headers", "Content-Type, Authorization");
if (req.method === "OPTIONS") {
res.statusCode = 204;
res.end();
return;
}
if (req.method !== "POST") {
res.statusCode = 405;
res.setHeader("Content-Type", "text/plain; charset=utf-8");
res.end("Method not allowed");
return;
}
void handleOllamaProxy(req, res, env);
};
}
async function handleOllamaProxy(req: IncomingMessage, res: ServerResponse, env: Record<string, string>) {
const apiKey = env.OLLAMA_API_KEY;
if (!apiKey) {
res.statusCode = 500;
res.setHeader("Content-Type", "text/plain; charset=utf-8");
res.end("OLLAMA_API_KEY is not configured on the server.");
return;
}
try {
const payload = await readJsonBody(req);
const upstream = await fetch("https://ollama.com/api/chat", {
method: "POST",
headers: {
Authorization: `Bearer ${apiKey}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
...payload,
model: payload.model || env.OLLAMA_MODEL || DEFAULT_MODEL,
stream: payload.stream !== false,
}),
});
res.statusCode = upstream.status;
res.setHeader("Content-Type", upstream.headers.get("content-type") || "application/x-ndjson");
if (!upstream.ok) {
res.end(await upstream.text());
return;
}
if (!upstream.body) {
res.end();
return;
}
const reader = upstream.body.getReader();
while (true) {
const { done, value } = await reader.read();
if (done) break;
res.write(Buffer.from(value));
}
res.end();
} catch (error) {
res.statusCode = 500;
res.setHeader("Content-Type", "text/plain; charset=utf-8");
res.end(error instanceof Error ? error.message : "Ollama proxy failed.");
}
}
async function readJsonBody(req: IncomingMessage) {
let raw = "";
for await (const chunk of req) raw += chunk;
return raw ? (JSON.parse(raw) as Record<string, unknown>) : {};
}