From 34bf1d88fa9e0dd04278d42b5b31433497714773 Mon Sep 17 00:00:00 2001 From: anurag629 Date: Sun, 25 Oct 2026 23:21:51 +0530 Subject: [PATCH] feat(website): run the docs chat on D1 and sessions when they're configured --- pnpm-lock.yaml | 4 ++++ website/astro.config.mjs | 1 + website/package.json | 1 + website/src/cloudflare-workers.d.ts | 4 ++++ website/src/pages/api/chat.ts | 17 +++++++++++++++++ website/src/pages/api/chat/session.ts | 5 +++++ 6 files changed, 32 insertions(+) create mode 100644 website/src/cloudflare-workers.d.ts create mode 100644 website/src/pages/api/chat/session.ts diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ecce273..d16c5b1 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -205,6 +205,9 @@ importers: '@chatcops/server': specifier: workspace:^ version: link:../packages/server + '@chatcops/store-cloudflare': + specifier: workspace:^ + version: link:../packages/store-cloudflare '@chatcops/widget': specifier: workspace:^ version: link:../packages/widget @@ -9054,6 +9057,7 @@ snapshots: undici-types@7.18.2: optional: true + undici@7.28.0: {} undici@7.29.1: {} diff --git a/website/astro.config.mjs b/website/astro.config.mjs index 7a0cabf..926f216 100644 --- a/website/astro.config.mjs +++ b/website/astro.config.mjs @@ -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 }), diff --git a/website/package.json b/website/package.json index d0ed615..a0b014e 100644 --- a/website/package.json +++ b/website/package.json @@ -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", diff --git a/website/src/cloudflare-workers.d.ts b/website/src/cloudflare-workers.d.ts new file mode 100644 index 0000000..b27aebf --- /dev/null +++ b/website/src/cloudflare-workers.d.ts @@ -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; +} diff --git a/website/src/pages/api/chat.ts b/website/src/pages/api/chat.ts index a4972d7..025bac8 100644 --- a/website/src/pages/api/chat.ts +++ b/website/src/pages/api/chat.ts @@ -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; @@ -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 | undefined; function getChat() { chat ??= chatcopsCloudflareHandler({ @@ -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); diff --git a/website/src/pages/api/chat/session.ts b/website/src/pages/api/chat/session.ts new file mode 100644 index 0000000..79caab6 --- /dev/null +++ b/website/src/pages/api/chat/session.ts @@ -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;