var __defProp = Object.defineProperty; var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
// api.js
var CEREBRAS_MODEL = "gpt-oss-120b";
var CEREBRAS_API_URL = "https://api.cerebras.ai/v1/chat/completions";
var MAX_HISTORY_LENGTH = 50;
var HISTORY_TTL = 60 * 60 * 24 * 30;
async function handleChatRequest(request, env) {
try {
const { message, sessionId = "default" } = await request.json();
if (!message || typeof message !== "string" || message.trim().length === 0) {
return jsonResponse({ error: "Invalid message" }, 400);
}
let history = [];
if (env.AI_KV) {
const historyKey = chat:${sessionId};
const storedHistory = await env.AI_KV.get(historyKey);
if (storedHistory) {
try {
history = JSON.parse(storedHistory);
} catch (e) {
console.error("Failed to parse history:", e);
history = [];
}
}
}
const messages = [
{
role: "system",
content: You are a helpful AI assistant powered by Cerebras GPT-OSS-120B. Format your responses using markdown. Be concise yet thorough. If asked to generate code, use proper syntax highlighting. If asked to create HTML, provide clean, modern code.
},
...history.slice(-20).map(([role, content]) => ({ role, content })),
{ role: "user", content: message }
];
let reply;
if (!env.CEREBRAS_API_KEY) {
reply = `\u{1F916} Demo Mode Response
I received your message: "${message}"
To enable real AI responses:
CEREBRAS_API_KEY\\Features available:
; } else { try { const response = await fetch(CEREBRAS_API_URL, { method: "POST", headers: { "Content-Type": "application/json", "Authorization": Bearer ${env.CEREBRAS_API_KEY} }, body: JSON.stringify({ model: CEREBRAS_MODEL, messages, temperature: 0.7, max_tokens: 2e3, top_p: 0.95, stream: false }) }); if (!response.ok) { const errorText = await response.text(); console.error("Cerebras API error:", errorText); throw new Error(API returned ${response.status}: ${errorText.substring(0, 100)}); } const data = await response.json(); reply = data.choices?.[0]?.message?.content || "No response generated"; } catch (apiError) { console.error("API call failed:", apiError); reply = \u26A0\uFE0F API ErrorFailed to get response from Cerebras API. Error: ${apiError.message}
Please check your API key and try again.; } } const newHistory = [...history, ["user", message], ["assistant", reply]]; if (newHistory.length > MAX_HISTORY_LENGTH * 2) { newHistory.splice(0, newHistory.length - MAX_HISTORY_LENGTH * 2); } if (env.AI_KV) { const historyKey = chat:${sessionId}; await env.AI_KV.put(historyKey, JSON.stringify(newHistory), { expirationTtl: HISTORY_TTL }); await updateSessionMetadata(env, sessionId, newHistory.length / 2); } return jsonResponse({ reply, sessionId, timestamp: (/* @__PURE__ */ new Date()).toISOString(), messageCount: newHistory.length / 2 }); } catch (error) { console.error("Chat request error:", error); return jsonResponse({ error: "Failed to process chat request", details: error.message }, 500); } } __name(handleChatRequest, "handleChatRequest"); async function handleHistoryRequest(request, env) { try { const url = new URL(request.url); const sessionId = url.searchParams.get("session") || "default"; if (!env.AI_KV) { return jsonResponse({ history: [], sessionId }); } const historyKey = chat:${sessionId}; const storedHistory = await env.AI_KV.get(historyKey); let history = []; if (storedHistory) { try { history = JSON.parse(storedHistory); } catch (e) { console.error("Failed to parse history:", e); } } return jsonResponse({ history, sessionId, messageCount: history.length / 2 }); } catch (error) { console.error("History request error:", error); return jsonResponse({ error: "Failed to retrieve history", history: [], sessionId: "default" }, 500); } } __name(handleHistoryRequest, "handleHistoryRequest"); async function handleClearHistory(request, env) { try { const { sessionId = "default" } = await request.json(); if (!env.AI_KV) { return jsonResponse({ success: true }); } const historyKey = chat:${sessionId}`;
await env.AI_KV.delete(historyKey);
await updateSessionMetadata(env, sessionId, 0);
return jsonResponse({
success: true,
sessionId,
message: "History cleared successfully"
});
} catch (error) {
console.error("Clear history error:", error);
return jsonResponse({
error: "Failed to clear history",
success: false
}, 500);
}
}
__name(handleClearHistory, "handleClearHistory");
async function updateSessionMetadata(env, sessionId, messageCount) {
if (!env.AI_KV) return;
try {
const sessionsKey = "sessions:list";
const sessionsData = await env.AI_KV.get(sessionsKey);
let sessions = sessionsData ? JSON.parse(sessionsData) : [];
const sessionIndex = sessions.findIndex((s) => s.id === sessionId);
if (sessionIndex >= 0) {
sessions[sessionIndex].lastUsed = Date.now();
sessions[sessionIndex].messageCount = messageCount;
} else if (sessionId === "default") {
sessions.push({
id: "default",
name: "Default Chat",
created: Date.now(),
lastUsed: Date.now(),
messageCount
});
}
await env.AI_KV.put(sessionsKey, JSON.stringify(sessions), {
expirationTtl: HISTORY_TTL
});
} catch (e) {
console.error("Failed to update session metadata:", e);
}
}
__name(updateSessionMetadata, "updateSessionMetadata");
function jsonResponse(data, status = 200) {
return new Response(JSON.stringify(data), {
status,
headers: {
"Content-Type": "application/json",
"Cache-Control": "no-cache"
}
});
}
__name(jsonResponse, "jsonResponse");
// sessions.js
var MAX_SESSIONS = 20;
var SESSION_TTL = 60 * 60 * 24 * 30;
async function handleSessionsList(request, env) {
try {
const userId = request.user?.userId || "default";
if (!env.AI_KV) {
return jsonResponse2({
sessions: []
});
}
const sessionsKey = userId !== "default" ? sessions:user:${userId} : "sessions:list";
const sessionsData = await env.AI_KV.get(sessionsKey);
let sessions = [];
if (sessionsData) {
try {
sessions = JSON.parse(sessionsData);
} catch (e) {
console.error("Failed to parse sessions:", e);
}
}
sessions.sort((a, b) => {
return (b.lastUsed || 0) - (a.lastUsed || 0);
});
return jsonResponse2({ sessions });
} catch (error) {
console.error("List sessions error:", error);
return jsonResponse2({
error: "Failed to list sessions",
sessions: []
// Return empty array on error, no default session
}, 500);
}
}
__name(handleSessionsList, "handleSessionsList");
async function handleNewSession(request, env) {
try {
const { name } = await request.json();
if (!name || typeof name !== "string" || name.trim().length === 0) {
return jsonResponse2({ error: "Session name is required" }, 400);
}
const sessionId = session_${Date.now()}_${Math.random().toString(36).substr(2, 9)};
const session = {
id: sessionId,
name: name.trim(),
created: Date.now(),
lastUsed: Date.now(),
messageCount: 0
};
if (!env.AI_KV) {
return jsonResponse2({ session });
}
const userId = request.user?.userId || "default";
const sessionsKey = userId !== "default" ? sessions:user:${userId} : "sessions:list";
const sessionsData = await env.AI_KV.get(sessionsKey);
let sessions = sessionsData ? JSON.parse(sessionsData) : [];
sessions.push(session);
if (sessions.length > MAX_SESSIONS) {
sessions.sort((a, b) => {
return (b.lastUsed || 0) - (a.lastUsed || 0);
});
const toRemove = sessions.slice(MAX_SESSIONS);
for (const oldSession of toRemove) {
await env.AI_KV.delete(chat:${oldSession.id});
}
sessions = sessions.slice(0, MAX_SESSIONS);
}
await env.AI_KV.put(sessionsKey, JSON.stringify(sessions), {
expirationTtl: SESSION_TTL
});
return jsonResponse2({
session,
message: "Session created successfully"
});
} catch (error) {
console.error("New session error:", error);
return jsonResponse2({
error: "Failed to create session",
details: error.message
}, 500);
}
}
__name(handleNewSession, "handleNewSession");
async function handleDeleteSession(request, env) {
try {
const { sessionId } = await request.json();
if (!sessionId) {
return jsonResponse2({ error: "Session ID is required" }, 400);
}
if (!env.AI_KV) {
return jsonResponse2({ success: true });
}
const userId = request.user?.userId || "default";
const sessionsKey = userId !== "default" ? sessions:user:${userId} : "sessions:list";
const sessionsData = await env.AI_KV.get(sessionsKey);
let sessions = sessionsData ? JSON.parse(sessionsData) : [];
sessions = sessions.filter((s) => s.id !== sessionId);
await env.AI_KV.delete(chat:${sessionId});
await env.AI_KV.put(sessionsKey, JSON.stringify(sessions), {
expirationTtl: SESSION_TTL
});
return jsonResponse2({
success: true,
message: "Session deleted successfully"
});
} catch (error) {
console.error("Delete session error:", error);
return jsonResponse2({
error: "Failed to delete session",
details: error.message
}, 500);
}
}
__name(handleDeleteSession, "handleDeleteSession");
async function handleSwitchSession(request, env) {
try {
const { sessionId } = await request.json();
if (!sessionId) {
return jsonResponse2({ error: "Session ID is required" }, 400);
}
if (env.AI_KV) {
const userId = request.user?.userId || "default";
const sessionsKey = userId !== "default" ? sessions:user:${userId} : "sessions:list";
const sessionsData = await env.AI_KV.get(sessionsKey);
if (sessionsData) {
let sessions = JSON.parse(sessionsData);
const sessionIndex = sessions.findIndex((s) => s.id === sessionId);
if (sessionIndex >= 0) {
sessions[sessionIndex].lastUsed = Date.now();
await env.AI_KV.put(sessionsKey, JSON.stringify(sessions), {
expirationTtl: SESSION_TTL
});
}
}
}
return jsonResponse2({
success: true,
sessionId,
message: "Switched to session successfully"
});
} catch (error) {
console.error("Switch session error:", error);
return jsonResponse2({
error: "Failed to switch session",
details: error.message
}, 500);
}
}
__name(handleSwitchSession, "handleSwitchSession");
function jsonResponse2(data, status = 200) {
return new Response(JSON.stringify(data), {
status,
headers: {
"Content-Type": "application/json",
"Cache-Control": "no-cache"
}
});
}
__name(jsonResponse2, "jsonResponse");
// ui.js
function generateHTML(env) {
const hasAuth = !!env.AUTH_PASSWORD;
const hasD1Auth = !!env.DB;
const html = <!DOCTYPE html> <html lang="en" class="dark"> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>AI Chat Assistant</title> <script src="<https://cdn.tailwindcss.com>"><\\/script> <script src="<https://cdn.jsdelivr.net/npm/marked/marked.min.js>"><\\/script> <script src="<https://cdn.jsdelivr.net/npm/dompurify@3/dist/purify.min.js>"><\\/script> <style> .dark body { background: linear-gradient(135deg, #0a0a0f 0%, #1a0f2e 100%); color: #e1e7ef; } .dark .glass { background: rgba(17, 17, 27, 0.8); backdrop-filter: blur(10px); border-color: #1f1f2e; } .dark .message-user { background: linear-gradient(135deg, #9333ea, #7e22ce); } .dark .message-assistant { background: #11111b; border: 1px solid #1f1f2e; } .dark .bg-card { background: #11111b; } .dark .border-border { border-color: #1f1f2e; } .dark .text-muted { color: #71717a; } .dark .session-item:hover { background: #1a1a24; } .dark .session-item.active { background: linear-gradient(135deg, #9333ea20, #7e22ce20); border-left: 3px solid #9333ea; } .light body { background: linear-gradient(135deg, #f3f4f6 0%, #e5e7eb 100%); color: #111827; } .light .glass { background: rgba(255, 255, 255, 0.8); backdrop-filter: blur(10px); border-color: #e5e7eb; } .light .message-user { background: linear-gradient(135deg, #9333ea, #7e22ce); color: white; } .light .message-assistant { background: white; border: 1px solid #e5e7eb; color: #111827; } .light .bg-card { background: white; color: #111827; } .light .border-border { border-color: #e5e7eb; } .light .text-muted { color: #6b7280; } .light input { background: white; color: #111827; border-color: #e5e7eb; } .light .session-item:hover { background: #f9fafb; } .light .session-item.active { background: linear-gradient(135deg, #9333ea10, #7e22ce10); border-left: 3px solid #9333ea; } .glass { backdrop-filter: blur(10px); transition: all 0.3s; } .prose { color: inherit; max-width: none; } /* Dark mode styles for code and tables */ .dark .prose pre { background: #1a1a24; border: 1px solid #2a2a3a; border-radius: 0.5rem; padding: 1rem; overflow-x: auto; color: #e1e7ef; } .dark .prose pre code { background: transparent; color: #e1e7ef; padding: 0; } .dark .prose code { background: #2a2a3a; color: #9ca3af; padding: 0.125rem 0.375rem; border-radius: 0.25rem; font-size: 0.875rem; } .dark .prose blockquote { border-left: 3px solid #9333ea; padding-left: 1rem; opacity: 0.9; color: #9ca3af; } .dark .prose table { border-collapse: collapse; margin: 1rem 0; width: 100%; } .dark .prose th, .dark .prose td { border: 1px solid #2a2a3a; padding: 0.5rem; color: #e1e7ef; } .dark .prose th { background: #1a1a24; font-weight: 600; color: #f3f4f6; } .dark .prose td { background: #11111b; } /* Light mode styles for code and tables */ .light .prose pre { background: #f3f4f6; border: 1px solid #d1d5db; border-radius: 0.5rem; padding: 1rem; overflow-x: auto; color: #111827; } .light .prose pre code { background: transparent; color: #111827; padding: 0; } .light .prose code { background: #e5e7eb; color: #4b5563; padding: 0.125rem 0.375rem; border-radius: 0.25rem; font-size: 0.875rem; } .light .prose blockquote { border-left: 3px solid #9333ea; padding-left: 1rem; opacity: 0.9; color: #4b5563; } .light .prose table { border-collapse: collapse; margin: 1rem 0; width: 100%; } .light .prose th, .light .prose td { border: 1px solid #d1d5db; padding: 0.5rem; color: #111827; } .light .prose th { background: #e5e7eb; font-weight: 600; color: #111827; } .light .prose td { background: white; } /* Common styles */ .prose h1, .prose h2, .prose h3 { font-weight: 600; margin-top: 1.5rem; margin-bottom: 0.75rem; } .prose ul, .prose ol { margin: 0.75rem 0; padding-left: 1.5rem; } ::-webkit-scrollbar { width: 8px; } ::-webkit-scrollbar-track { background: transparent; } ::-webkit-scrollbar-thumb { background: #4b5563; border-radius: 4px; } .session-item { padding: 0.5rem; margin-bottom: 0.25rem; border-radius: 0.375rem; cursor: pointer; transition: all 0.2s; position: relative; padding-left: 0.75rem; } </style> </head> <body class="min-h-screen p-4"> <div class="max-w-7xl mx-auto"> <div class="glass rounded-xl p-4 mb-4 border border-border"> <div class="flex justify-between items-center"> <div> <h1 class="text-2xl font-bold bg-gradient-to-r from-purple-400 to-purple-600 bg-clip-text text-transparent">AI Chat Assistant</h1> <p class="text-sm text-muted">Powered by Cerebras GPT-OSS-120B</p> </div> <div class="flex gap-2"> <button id="themeBtn" class="px-3 py-2 rounded-lg bg-card border border-border text-sm hover:opacity-80"> <span id="themeIcon">\\u{1F319}</span> Theme </button> <button id="scrollBtn" class="px-3 py-2 rounded-lg bg-card border border-border text-sm hover:opacity-80"> <span id="scrollIcon">\\u{1F4CC}</span> <span id="scrollText">Auto-Scroll: ON</span> </button> + (hasD1Auth ? <button id="signOutBtn" class="px-3 py-2 rounded-lg bg-red-600 hover:bg-red-700 text-white text-sm transition-colors"> <span>\\u{1F6AA}</span> Sign Out </button> : hasAuth ? '<form method="POST" action="/logout"><button class="px-4 py-2 rounded-lg bg-card hover:bg-card-hover border border-border text-sm">Sign Out</button></form>' : "") + `
</div>
</div>
</div>
<div class="grid grid-cols-1 lg:grid-cols-5 gap-4">
<div class="lg:col-span-1">
<div class="glass rounded-xl p-4 border border-border">
<div class="flex justify-between items-center mb-3">
<h3 class="font-semibold text-sm">Sessions</h3>
<button id="newSessionBtn" class="text-purple-500 hover:text-purple-400 text-sm">+ New</button>
</div>
<div id="sessionsList" class="max-h-96 overflow-y-auto"></div>
</div>
</div>
<div class="lg:col-span-4">
<div class="glass rounded-xl border border-border" style="height: calc(100vh - 10rem);">
<div id="sessionInfo" class="border-b border-border px-4 py-2 text-sm text-muted">No session selected</div>
<div id="messages" class="overflow-y-auto p-4" style="height: calc(100% - 8rem);">
<div class="text-center text-muted py-8">Start a conversation...</div>
</div>
<div class="border-t border-border p-4">
<div class="flex gap-2">
<input id="messageInput" type="text" placeholder="Type your message..."
class="flex-1 p-3 rounded-lg bg-card border border-border focus:border-purple-500 outline-none">
<button id="sendBtn"
class="px-6 py-3 rounded-lg bg-gradient-to-r from-purple-600 to-purple-700 hover:from-purple-700 hover:to-purple-800 font-semibold text-white">
Send
</button>
</div>
<div class="flex justify-between mt-2">
<button id="clearBtn" class="text-xs text-muted hover:text-purple-500">Clear Session</button>
<span id="status" class="text-xs text-muted"></span>
</div>
</div>
</div>
</div>
</div>
</div>
<script> console.log("Script starting to execute"); console.error("DEBUG: Script loaded successfully");