248 lines
6.3 KiB
TypeScript
248 lines
6.3 KiB
TypeScript
/**
|
|
* Cloudflare Worker Backend with Hono + TheSys C1
|
|
*
|
|
* File: backend/src/index.ts
|
|
*
|
|
* Features:
|
|
* - Hono routing
|
|
* - TheSys C1 API proxy
|
|
* - Streaming support
|
|
* - Static assets serving
|
|
* - CORS handling
|
|
*/
|
|
|
|
import { Hono } from "hono";
|
|
import { cors } from "hono/cors";
|
|
import { serveStatic } from "hono/cloudflare-workers";
|
|
|
|
type Bindings = {
|
|
THESYS_API_KEY: string;
|
|
ASSETS: Fetcher;
|
|
};
|
|
|
|
const app = new Hono<{ Bindings: Bindings }>();
|
|
|
|
// CORS middleware
|
|
app.use("/*", cors({
|
|
origin: "*",
|
|
allowMethods: ["GET", "POST", "OPTIONS"],
|
|
allowHeaders: ["Content-Type", "Authorization"],
|
|
}));
|
|
|
|
// ============================================================================
|
|
// Chat API Endpoint
|
|
// ============================================================================
|
|
|
|
app.post("/api/chat", async (c) => {
|
|
try {
|
|
const { prompt, previousC1Response } = await c.req.json();
|
|
|
|
if (!prompt || typeof prompt !== "string") {
|
|
return c.json({ error: "Invalid prompt" }, 400);
|
|
}
|
|
|
|
// Check API key binding
|
|
if (!c.env.THESYS_API_KEY) {
|
|
console.error("THESYS_API_KEY binding not found");
|
|
return c.json({ error: "Server configuration error" }, 500);
|
|
}
|
|
|
|
// Build messages
|
|
const messages = [
|
|
{
|
|
role: "system",
|
|
content: "You are a helpful AI assistant that generates interactive UI.",
|
|
},
|
|
{
|
|
role: "user",
|
|
content: prompt,
|
|
},
|
|
];
|
|
|
|
if (previousC1Response) {
|
|
messages.splice(1, 0, {
|
|
role: "assistant",
|
|
content: previousC1Response,
|
|
});
|
|
}
|
|
|
|
// Call TheSys C1 API
|
|
const response = await fetch(
|
|
"https://api.thesys.dev/v1/embed/chat/completions",
|
|
{
|
|
method: "POST",
|
|
headers: {
|
|
"Authorization": `Bearer ${c.env.THESYS_API_KEY}`,
|
|
"Content-Type": "application/json",
|
|
},
|
|
body: JSON.stringify({
|
|
model: "c1/openai/gpt-5/v-20250930",
|
|
messages,
|
|
stream: false, // Or handle streaming
|
|
temperature: 0.7,
|
|
max_tokens: 2000,
|
|
}),
|
|
}
|
|
);
|
|
|
|
if (!response.ok) {
|
|
const error = await response.text();
|
|
console.error("TheSys API Error:", error);
|
|
return c.json(
|
|
{ error: "Failed to get AI response" },
|
|
response.status
|
|
);
|
|
}
|
|
|
|
const data = await response.json();
|
|
|
|
return c.json({
|
|
response: data.choices[0]?.message?.content || "",
|
|
usage: data.usage,
|
|
});
|
|
} catch (error) {
|
|
console.error("Chat endpoint error:", error);
|
|
return c.json(
|
|
{ error: error instanceof Error ? error.message : "Internal error" },
|
|
500
|
|
);
|
|
}
|
|
});
|
|
|
|
// ============================================================================
|
|
// Streaming Chat Endpoint
|
|
// ============================================================================
|
|
|
|
app.post("/api/chat/stream", async (c) => {
|
|
try {
|
|
const { prompt } = await c.req.json();
|
|
|
|
const response = await fetch(
|
|
"https://api.thesys.dev/v1/embed/chat/completions",
|
|
{
|
|
method: "POST",
|
|
headers: {
|
|
"Authorization": `Bearer ${c.env.THESYS_API_KEY}`,
|
|
"Content-Type": "application/json",
|
|
},
|
|
body: JSON.stringify({
|
|
model: "c1/openai/gpt-5/v-20250930",
|
|
messages: [
|
|
{ role: "system", content: "You are a helpful assistant." },
|
|
{ role: "user", content: prompt },
|
|
],
|
|
stream: true,
|
|
}),
|
|
}
|
|
);
|
|
|
|
if (!response.ok) {
|
|
return c.json({ error: "Stream failed" }, response.status);
|
|
}
|
|
|
|
// Return the stream directly
|
|
return new Response(response.body, {
|
|
headers: {
|
|
"Content-Type": "text/event-stream",
|
|
"Cache-Control": "no-cache",
|
|
"Connection": "keep-alive",
|
|
},
|
|
});
|
|
} catch (error) {
|
|
console.error("Stream error:", error);
|
|
return c.json({ error: "Stream failed" }, 500);
|
|
}
|
|
});
|
|
|
|
// ============================================================================
|
|
// Health Check
|
|
// ============================================================================
|
|
|
|
app.get("/api/health", (c) => {
|
|
return c.json({
|
|
status: "ok",
|
|
timestamp: new Date().toISOString(),
|
|
});
|
|
});
|
|
|
|
// ============================================================================
|
|
// Serve Static Assets (Vite build output)
|
|
// ============================================================================
|
|
|
|
app.get("/*", serveStatic({ root: "./", mimes: {} }));
|
|
|
|
export default app;
|
|
|
|
/**
|
|
* Alternative: Using Workers AI directly (cheaper for some models)
|
|
*
|
|
* type Bindings = {
|
|
* AI: any; // Cloudflare AI binding
|
|
* };
|
|
*
|
|
* app.post("/api/chat", async (c) => {
|
|
* const { prompt } = await c.req.json();
|
|
*
|
|
* const aiResponse = await c.env.AI.run('@cf/meta/llama-3-8b-instruct', {
|
|
* messages: [
|
|
* { role: "system", content: "You are a helpful assistant." },
|
|
* { role: "user", content: prompt },
|
|
* ],
|
|
* });
|
|
*
|
|
* // Then optionally send to TheSys C1 for UI generation
|
|
* const c1Response = await fetch("https://api.thesys.dev/v1/embed/chat/completions", {
|
|
* method: "POST",
|
|
* headers: {
|
|
* "Authorization": `Bearer ${c.env.THESYS_API_KEY}`,
|
|
* "Content-Type": "application/json",
|
|
* },
|
|
* body: JSON.stringify({
|
|
* model: "c1/openai/gpt-5/v-20250930",
|
|
* messages: [
|
|
* {
|
|
* role: "system",
|
|
* content: "Generate a UI for this content: " + aiResponse.response,
|
|
* },
|
|
* ],
|
|
* }),
|
|
* });
|
|
*
|
|
* // ... return c1Response
|
|
* });
|
|
*/
|
|
|
|
/**
|
|
* Alternative: With D1 Database for message persistence
|
|
*
|
|
* type Bindings = {
|
|
* THESYS_API_KEY: string;
|
|
* DB: D1Database; // D1 binding
|
|
* };
|
|
*
|
|
* app.post("/api/chat", async (c) => {
|
|
* const { userId, threadId, prompt } = await c.req.json();
|
|
*
|
|
* // Save user message
|
|
* await c.env.DB.prepare(
|
|
* "INSERT INTO messages (thread_id, user_id, role, content) VALUES (?, ?, ?, ?)"
|
|
* )
|
|
* .bind(threadId, userId, "user", prompt)
|
|
* .run();
|
|
*
|
|
* // Get conversation history
|
|
* const { results } = await c.env.DB.prepare(
|
|
* "SELECT role, content FROM messages WHERE thread_id = ? ORDER BY created_at"
|
|
* )
|
|
* .bind(threadId)
|
|
* .all();
|
|
*
|
|
* const messages = [
|
|
* { role: "system", content: "You are a helpful assistant." },
|
|
* ...results,
|
|
* ];
|
|
*
|
|
* // Call TheSys API with full history...
|
|
* });
|
|
*/
|