Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions website/astro.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ export default defineConfig({
// `undefined`.
schema: {
OPENAI_API_KEY: envField.string({ context: 'server', access: 'secret', optional: true }),
CHATCOPS_SESSION_SECRET: envField.string({ context: 'server', access: 'secret', optional: true }),
UPSTASH_REDIS_REST_URL: envField.string({ context: 'server', access: 'secret', optional: true }),
UPSTASH_REDIS_REST_TOKEN: envField.string({ context: 'server', access: 'secret', optional: true }),
VISITOR_HASH_SALT: envField.string({ context: 'server', access: 'secret', optional: true }),
Expand Down
1 change: 1 addition & 0 deletions website/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
"@astrojs/starlight": "^0.42.5",
"@chatcops/core": "workspace:^",
"@chatcops/server": "workspace:^",
"@chatcops/store-cloudflare": "workspace:^",
"@chatcops/widget": "workspace:^",
"@tailwindcss/vite": "^4.3.3",
"astro": "^7.3.5",
Expand Down
4 changes: 4 additions & 0 deletions website/src/cloudflare-workers.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
// The Worker's bindings, read in src/pages/api/chat.ts. Only what the site uses.
declare module 'cloudflare:workers' {
export const env: Record<string, unknown>;
}
17 changes: 17 additions & 0 deletions website/src/pages/api/chat.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import type { APIRoute } from 'astro';
import { getSecret } from 'astro:env/server';
import { env } from 'cloudflare:workers';
import { chatcopsCloudflareHandler } from '@chatcops/server/cloudflare';
import { FAQKnowledgeSource } from '@chatcops/core';
import { cloudflareStorage, type D1Database } from '@chatcops/store-cloudflare';

export const prerender = false;

Expand Down Expand Up @@ -50,6 +52,10 @@ const faq = new FAQKnowledgeSource([

// Built on the first request rather than at import: the API key is a Worker
// secret that only exists inside a request.
//
// With the CHATCOPS_DB binding (wrangler.jsonc), conversations, limits and
// turn locks live in D1 and hold across every isolate; without it, in memory.
// With the CHATCOPS_SESSION_SECRET secret, the chat needs session tokens.
let chat: ReturnType<typeof chatcopsCloudflareHandler> | undefined;
function getChat() {
chat ??= chatcopsCloudflareHandler({
Expand Down Expand Up @@ -78,10 +84,21 @@ Be concise, helpful, and friendly. Use markdown formatting. If you don't know so
request.headers.get('cf-connecting-ip') ??
(import.meta.env.DEV ? request.headers.get('x-forwarded-for')?.split(',')[0]?.trim() : undefined),
rateLimit: { maxRequests: 10, windowMs: 60_000 },
...storageOptions(),
});
return chat;
}

function storageOptions() {
const db = (env as { CHATCOPS_DB?: D1Database }).CHATCOPS_DB;
const sessionSecret = getSecret('CHATCOPS_SESSION_SECRET');
return {
...(db ? { storage: cloudflareStorage({ d1: db }), retention: { conversationsDays: 30 } } : {}),
// Widgets cached from before 0.6 don't send tokens yet.
...(sessionSecret ? { sessionSecrets: [sessionSecret], requireSession: false } : {}),
};
}

export const POST: APIRoute = ({ request, locals }) => getChat()(request, undefined, locals.cfContext);

export const OPTIONS: APIRoute = ({ request, locals }) => getChat()(request, undefined, locals.cfContext);
5 changes: 5 additions & 0 deletions website/src/pages/api/chat/session.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
// The widget asks here for a session token when CHATCOPS_SESSION_SECRET is set.
// The chat handler answers both paths.
export { OPTIONS, POST } from '../chat';

export const prerender = false;
Loading