Skip to content

Troubleshooting

Troubleshooting

Common integration issues and their solutions, organized by symptom.


CORS errors

Symptom: Browser console shows:

Access to fetch at 'https://knowledge-api.theeducationalequalityinstitute.org/api/chat'
from origin 'https://cockpit.theeducationalequalityinstitute.org' has been blocked
by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.

Cause: The host platform’s origin is not in the RAG Worker’s ALLOWED_ORIGINS.

Fix:

  1. Open teei-knowledge/rag-worker/wrangler.toml.
  2. Add the origin to the ALLOWED_ORIGINS variable:
[vars]
ALLOWED_ORIGINS = "https://theeducationalequalityinstitute.org,https://cockpit.theeducationalequalityinstitute.org,https://wbp.theeducationalequalityinstitute.org,https://admin.theeducationalequalityinstitute.org,https://docs.theeducationalequalityinstitute.org,https://your-new-origin.example.com"
  1. Redeploy the Worker:
Terminal window
cd teei-knowledge/rag-worker
wrangler deploy

For local development: The [env.dev] section uses ALLOWED_ORIGINS = "http://localhost:*", which matches all localhost ports. If you’re getting CORS errors locally, verify you’re running wrangler dev --env dev (not the production Worker).

Verification:

Terminal window
curl -I -X OPTIONS https://knowledge-api.theeducationalequalityinstitute.org/api/chat \
-H "Origin: https://cockpit.theeducationalequalityinstitute.org" \
-H "Access-Control-Request-Method: POST"
# → Access-Control-Allow-Origin: https://cockpit.theeducationalequalityinstitute.org

Widget not appearing

Symptom: Page loads without errors, but the chat FAB is not visible anywhere.

Cause A — Component not rendered: The ChatProvider/ChatWidget is conditionally excluded.

Check: Open the browser’s element inspector and search for .teei-chat-widget. If the element is absent from the DOM, the Astro template condition (isExcluded, session &&, etc.) is evaluating to false and the widget is not being rendered.

Fix: Add a temporary console.log in the layout frontmatter to verify the condition:

---
console.log('Widget excluded?', isExcluded, 'Path:', path);
---

Cause B — CSS not imported: The widget renders but is invisible because the stylesheet is missing.

Check: Search the page’s <head> for a stylesheet with chat-widget or teei-chat in the URL. If absent, the import is missing.

Fix: Ensure the styles are imported in the layout:

---
import '@teei/chat-widget/styles';
---

Or in a global CSS file:

@import '@teei/chat-widget/styles';

Cause C — Hydration not complete: client:idle waits for the browser to be idle before hydrating. On a very active page (many timers, animations), the widget may take a few seconds to appear.

Check: Open DevTools → Performance. Look for the teei-chat-widget DOM node. If it appears after 3–5 seconds, hydration is just delayed.

Fix: If the widget needs to appear immediately, change to client:load. This adds a small blocking cost but ensures instant render.

Cause D — Z-index stack buried: The widget renders but is hidden behind another element.

Check: In DevTools, find .teei-chat-fab and inspect its computed z-index. If another element has a higher z-index and covers the same screen position, the FAB is invisible.

Fix: Increase the widget’s z-index:

/* In platform CSS file */
.teei-chat-widget {
--teei-chat-z: 99999;
}

Auth not working / wrong role

Symptom: Widget appears but returns answers appropriate for a lower role than the user has. For example, a CSR manager sees only public content.

Cause A — Role prop not passed correctly: The userRole prop is hardcoded or the role mapping is wrong.

Check: In the browser DevTools → Network tab → filter for requests to /api/chat. Click a request and inspect the request body. Verify user_role has the expected value:

{
"query": "...",
"platform": "csr-cockpit",
"user_role": "manager", check this
"language": "en"
}

Fix: Trace the userRole value from the layout frontmatter through to the ChatProvider prop. If it’s wrong, fix the role mapping in the layout.

Cause B — Role map has wrong keys: The KNOWLEDGE_ROLE_MAP in the layout doesn’t handle all possible role values.

Fix: Add a fallback to the map:

const knowledgeRole = KNOWLEDGE_ROLE_MAP[managerRole] ?? 'employee'; // safe fallback

Log the value during development to confirm:

---
console.log('[Chat] managerRole:', managerRole, '→ knowledgeRole:', knowledgeRole);
---

Cause C — Content not ingested for the role: The user has the correct role but the vector store has no content tagged for that role. The widget returns “I don’t have information about that.”

Fix: Run the ingestion scripts and verify content is tagged correctly:

Terminal window
wrangler d1 execute teei-knowledge-db \
--command "SELECT role, COUNT(*) as count FROM chunks GROUP BY role"

Expected output shows rows for public, volunteer, employee, staff, manager, admin.


No answers returned

Symptom: Widget opens, accepts a question, shows loading spinner, then returns “I don’t have information about that” or an empty response for most questions.

Cause A — Vectorize has no data: The index is empty because ingestion has not been run.

Check:

Terminal window
wrangler vectorize list teei-knowledge-index

If the index exists but returns 0 vectors, run the ingestion scripts.

Cause B — Content tagged for wrong platform: Content exists in Vectorize but is tagged platform: admin and the query uses platform: website. The platform filter excludes it.

Check: Inspect D1 chunk tags:

Terminal window
wrangler d1 execute teei-knowledge-db \
--command "SELECT platform, role, COUNT(*) as count FROM chunks GROUP BY platform, role ORDER BY count DESC"

Verify the platform and role values match what the widget sends.

Cause C — Vectorize returns 0 similarity matches: The query vector is not similar enough to any stored vectors. This happens when:

  • The content is in English but the query is in Ukrainian (or vice versa) — bge-m3 is multilingual but similarity degrades across languages when only one language is indexed
  • The query is very different in phrasing from the stored content (jargon vs plain language)

Fix: Ingest content in the same language as the user’s query. bge-m3 handles cross-lingual retrieval reasonably well, but same-language retrieval is significantly better.

Cause D — AI Gateway returns an error: The gateway is misconfigured or the Anthropic API key is missing.

Check:

Terminal window
curl -X POST http://localhost:8787/api/chat \
-H "Content-Type: application/json" \
-d '{"query": "test", "platform": "website", "user_role": "public", "language": "en"}'

If the response includes "error": "...", check the Workers logs:

Terminal window
wrangler tail teei-knowledge-api

Slow responses

Symptom: Queries take 5–15 seconds before any streamed token appears.

Cause A — AI Gateway cache is cold: The first request for any given query generates a fresh embedding + vector search + Claude completion. Subsequent identical queries return from cache.

Expected first-response latency: 2–5 seconds (embedding + vector search + first Claude token).

Expected cached latency: < 500ms.

If first responses consistently exceed 10 seconds, the issue is elsewhere.

Cause B — AI Gateway caching is disabled: Check the AI Gateway configuration in the Cloudflare Dashboard (AI > AI Gateway > teei-knowledge > Settings). Cache must be enabled.

Cause C — Workers AI smart placement not active: The wrangler.toml includes [placement] mode = "smart" which routes the Worker to the Cloudflare datacenter nearest to the Workers AI inference cluster. Verify this is not overridden.

Cause D — Vectorize similarity search returning many low-quality results: If the vector store has many chunks with low similarity scores, the RAG Worker may be passing too much context to Claude, causing long completions.

Optimization: The RAG Worker queries Vectorize for the top-K results. If K is too large (e.g., 20 chunks), reduce it to 5–8 for faster responses with minimal quality loss.


Widget conflicts with other fixed-position elements

Symptom: The chat FAB overlaps with another UI element (annotation overlay, support widget, cookie banner, etc.).

Fix — Move the FAB:

/* In the platform's CSS file */
.teei-chat-widget .teei-chat-fab {
bottom: 88px; /* Push above a 64px-tall bottom bar */
}
.teei-chat-widget .teei-chat-panel {
bottom: 88px;
}

Or use position="bottom-left" on ChatProvider to move the widget to the opposite corner.

Fix — Lower the z-index:

.teei-chat-widget {
--teei-chat-z: 9990; /* Below annotation overlay at 9994 */
}

Note: lowering the z-index means modal overlays (DemoExitIntent at 9997, DemoContactDrawer at 9998) will cover the chat panel when open. This is usually acceptable behaviour.


TypeScript errors after installing the widget

Symptom: npx astro check or tsc reports errors from @teei/chat-widget.

Cause A — Package not built: The local package (file:../chat-widget) must be built before TypeScript can find the type declarations.

Fix:

Terminal window
cd teei-knowledge/chat-widget
pnpm build # Generates dist/index.d.ts
cd ../ # Back to platform project
pnpm install # Re-links the local package

Cause B — React version mismatch: The widget is compiled for React 18/19. If the platform uses a different React version (unlikely but possible), type mismatches occur.

Check: grep '"react"' package.json in both the platform and the widget.

Cause C — client:idle directive type error in Astro: Some older Astro type setups require explicit JSX type configuration for React components used with Astro directives.

Fix: Ensure tsconfig.json includes:

{
"compilerOptions": {
"jsx": "react-jsx",
"jsxImportSource": "react"
}
}

Widget crashes after page navigation (view transitions)

Symptom: Widget panel works on first page load, but after navigating to another page via Astro view transitions, the widget becomes unresponsive or throws React errors.

Cause: The React island unmounts and remounts on each navigation, losing state.

Fix: Add transition:persist to preserve the widget across navigations:

<ChatProvider transition:persist client:idle apiUrl={...} platform={...} userRole={...}>
<ChatWidget />
</ChatProvider>

transition:persist tells Astro’s view transition system to keep the DOM node in place rather than destroying and recreating it.