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:
+97
-1
@@ -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>) : {};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user