Skip to content

Main → Prod#42

Merged
Sithumli merged 6 commits intoprodfrom
main
Apr 2, 2026
Merged

Main → Prod#42
Sithumli merged 6 commits intoprodfrom
main

Conversation

@Sithumli
Copy link
Copy Markdown
Owner

@Sithumli Sithumli commented Apr 2, 2026

No description provided.

@Sithumli Sithumli self-assigned this Apr 2, 2026
Copilot AI review requested due to automatic review settings April 2, 2026 05:26
@Sithumli Sithumli merged commit d9194aa into prod Apr 2, 2026
2 checks passed
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds an AI chat assistant feature (RAG over site content) to the DocuBase Astro template, including client UI, a chat page, an API endpoint, and indexing scripts to populate Upstash Vector.

Changes:

  • Introduces homepage “Ask Anything” entrypoint and a reusable floating chat widget UI.
  • Adds /chat/[uid] conversation page and /api/chat SSE streaming endpoint backed by Upstash Vector + Google Gemini.
  • Adds content indexing scripts (index-content, index-watch) plus new env vars and dependency updates.

Reviewed changes

Copilot reviewed 17 out of 19 changed files in this pull request and generated 10 comments.

Show a summary per file
File Description
template/src/types/index.ts Re-exports new chat-related types.
template/src/types/constants.ts Adds chat/indexing constants and roles.
template/src/types/components.ts Removes Hero button props from the public type.
template/src/types/chat.ts Adds chat + indexing-related TypeScript types.
template/src/pages/index.astro Adds the AskAnything section to the homepage.
template/src/pages/chat/[uid].astro Adds a dedicated chat page with streaming UI rendering.
template/src/pages/api/chat.ts Adds RAG + Gemini SSE chat endpoint.
template/src/components/Hero.astro Removes the CTA button from the hero component.
template/src/components/ChatWidget.astro Adds a floating modal chat widget with streaming updates.
template/src/components/AskAnything.astro Adds homepage form that navigates into chat.
scripts/index-watch.ts Adds a dev watcher to upsert/delete vectors on content changes.
scripts/index-content.ts Adds batch indexing with a local manifest to avoid re-uploading unchanged chunks.
README.md Documents the AI chat assistant setup and behavior.
package.json Adds dependencies + indexing scripts and bumps version to 1.2.0.
pnpm-lock.yaml Locks new dependencies for Gemini, Upstash Vector, Vercel adapter, tsx, etc.
astro.config.mjs Adds Vercel adapter configuration.
CHANGELOG.md Notes the new AI chat assistant release.
.gitignore Ignores generated index manifest and template dist/node_modules.
.env.example Adds required env vars for Upstash Vector + Gemini.
Files not reviewed (1)
  • pnpm-lock.yaml: Language not supported

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.


export default defineConfig({
site: 'https://docubase-docs.vercel.app',
output: 'static',
Copy link

Copilot AI Apr 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

output: 'static' is incompatible with the newly added non-prerendered routes (/api/chat, /chat/[uid]). With a static build those endpoints won't be deployed/served, even with the Vercel adapter configured. Switch to output: 'hybrid' (or server) to enable SSR for these routes, or remove the adapter + dynamic routes if static-only is intended.

Suggested change
output: 'static',
output: 'hybrid',

Copilot uses AI. Check for mistakes.
Comment on lines +40 to +59
function buildSystemPrompt(contexts: ChatContext[]): string {
const contextText = contexts
.map((ctx, i) => `[${i + 1}] ${ctx.title} - ${ctx.section}\nURL: ${ctx.url}`)
.join('\n\n');

return `You are a helpful documentation assistant for ${SITE_TITLE}, a documentation template built with Astro.
Your role is to answer questions based on the documentation content provided.

Guidelines:
- Be concise and helpful
- Reference specific documentation sections when relevant
- If the answer isn't in the provided context, say so honestly
- Include relevant URLs when they would be helpful
- Format responses using markdown for readability
- For code examples, use proper code blocks with language tags

Relevant documentation sections:
${contextText}

Answer the user's question based on the above context. If the question cannot be answered from the provided context, let them know and suggest they check the documentation directly.`;
Copy link

Copilot AI Apr 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

buildSystemPrompt() says it will answer based on provided documentation content, but the prompt only includes titles/sections/URLs and never includes ctx.content (the actual retrieved chunks). This effectively disables RAG and will cause hallucinations. Include the retrieved chunk text in the prompt (e.g., per context entry) or otherwise pass it to Gemini as grounded context.

Copilot uses AI. Check for mistakes.
Comment on lines +156 to +160
html = html.replace(/\[([^\]]+)\]\(([^)]+)\)/g, function(_, linkText, url) {
var safeUrl = sanitizeUrl(url);
if (!safeUrl) return linkText;
return '<a href="' + safeUrl + '" class="text-blue-600 dark:text-blue-400 underline hover:no-underline" rel="noopener noreferrer">' + linkText + '</a>';
});
Copy link

Copilot AI Apr 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

parseMarkdown() injects safeUrl directly into an href="..." attribute without escaping quotes. A crafted markdown link URL containing " can break out of the attribute and lead to XSS. Escape attribute-special characters (at least " and ') or build the anchor with DOM APIs (createElement('a') + setAttribute) after validating the protocol.

Copilot uses AI. Check for mistakes.
Comment on lines +159 to +164
// Links - with URL sanitization
html = html.replace(/\[([^\]]+)\]\(([^)]+)\)/g, function(_, linkText, url) {
var safeUrl = sanitizeUrl(url);
if (!safeUrl) return linkText;
return '<a href="' + safeUrl + '" class="text-blue-600 dark:text-blue-400 underline hover:no-underline" rel="noopener noreferrer">' + linkText + '</a>';
});
Copy link

Copilot AI Apr 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

parseMarkdown() injects safeUrl into href without escaping quotes, so a malicious URL containing " can break the attribute and execute script (XSS). Escaping "/' (or using DOM APIs to create/set the anchor element) is needed in addition to the protocol check.

Copilot uses AI. Check for mistakes.
Comment on lines +72 to +114
function init() {
if (window[INIT_FLAG]) return;
window[INIT_FLAG] = true;

var form = document.getElementById('ask-form');
var input = document.getElementById('ask-input');
var chips = document.querySelectorAll('.suggestion-chip');

if (form && input) {
form.addEventListener('submit', function(e) {
e.preventDefault();
navigateToChat(input.value);
});

input.addEventListener('keydown', function(e) {
if (e.key === 'Enter' && !e.shiftKey) {
e.preventDefault();
navigateToChat(input.value);
}
});

input.addEventListener('input', function() {
this.style.height = 'auto';
this.style.height = Math.min(this.scrollHeight, TEXTAREA_MAX_HEIGHT) + 'px';
});
}

chips.forEach(function(chip) {
chip.addEventListener('click', function() {
var text = chip.textContent.trim();
navigateToChat(text);
});
});
}

function reset() {
window[INIT_FLAG] = false;
init();
}

init();
document.addEventListener('astro:page-load', reset);
})();
Copy link

Copilot AI Apr 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

reset() clears the global init flag and then calls init(), which re-attaches event listeners on every astro:page-load. This will accumulate duplicate submit/keydown/input handlers over time. Prefer keeping the one-time INIT_FLAG guard and make the astro:page-load handler idempotent, or use an AbortController signal pattern (as in Search.astro) to remove and re-add listeners safely.

Copilot uses AI. Check for mistakes.
Comment on lines +312 to +318
function reset() {
window[INIT_FLAG] = false;
initChat();
}

initChat();
document.addEventListener('astro:page-load', reset);
Copy link

Copilot AI Apr 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

reset() sets window[INIT_FLAG] = false and then calls initChat(), which re-attaches DOM/global listeners on every astro:page-load (duplicates will accumulate). The existing codebase pattern is to keep the init guard and make the page-load init idempotent (or use an AbortController signal pattern like Search.astro) so listeners are not duplicated.

Suggested change
function reset() {
window[INIT_FLAG] = false;
initChat();
}
initChat();
document.addEventListener('astro:page-load', reset);
initChat();
document.addEventListener('astro:page-load', initChat);

Copilot uses AI. Check for mistakes.
Comment on lines +175 to +183
function watchDirectory(dir: string): void {
if (!fs.existsSync(dir)) return;

fs.watch(dir, { recursive: true }, (eventType, filename) => {
if (!filename) return;
const filePath = path.join(dir, filename);
handleFileChange(filePath, eventType);
});
}
Copy link

Copilot AI Apr 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fs.watch(..., { recursive: true }) is only fully supported on macOS/Windows; on Linux it is not recursive (and may throw depending on Node version). This makes the watcher unreliable in common CI/dev environments. Consider using a cross-platform watcher (e.g., chokidar) or explicitly document the platform limitation / implement manual directory recursion.

Copilot uses AI. Check for mistakes.
Comment on lines +300 to +307
3. That's it! Content is automatically indexed on every build.

### How It Works

- Content is automatically indexed when you run `pnpm run build`
- Users can ask questions via the chat widget or "Ask Anything" input
- The AI searches your docs and provides relevant answers with sources
- No manual indexing required - just write docs and deploy
Copy link

Copilot AI Apr 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The README claims content is "automatically indexed on every build" and specifically when running pnpm run build, but build does not run index-content (only build:with-index does). Update this section to match the actual scripts (or change the build script to include indexing) to avoid users deploying without a populated vector index.

Copilot uses AI. Check for mistakes.
Comment on lines +7 to +19
- 8cceec4: Add AI chat assistant with RAG-powered documentation search
- Add AskAnything component for homepage chat input
- Add ChatWidget floating chat button component
- Add dedicated chat page with conversation history
- Add chat API endpoint with Google Gemini integration
- Add Upstash Vector for semantic search
- Add index-content scripts for development and production
- Fix: XSS protection for AI-generated markdown links
- Fix: Proper SSE format for streaming responses
- Fix: Initialization guards for event listeners

- 8cceec4: AI chat assistant with RAG-powered documentation search

Copy link

Copilot AI Apr 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The 1.2.0 changelog entry repeats the same bullet twice (both keyed to 8cceec4). This looks like an accidental duplicate and makes the changelog noisy; remove the redundant line or replace it with distinct information.

Copilot uses AI. Check for mistakes.
Comment on lines +103 to +118
let result;
let lastError;

for (const modelName of CHAT.MODELS) {
try {
const model = genAI.getGenerativeModel({
model: modelName,
systemInstruction: systemPrompt,
});
const chat = model.startChat({ history: chatHistory });
result = await chat.sendMessageStream(message);
console.log(`Using model: ${modelName}`);
break;
} catch (error) {
lastError = error;
const errorMessage = error instanceof Error ? error.message : '';
Copy link

Copilot AI Apr 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lastError is assigned in the model fallback loop but never read. In strict TypeScript configurations (and many lint setups) this will fail the build due to an unused variable. Remove lastError or use it in the final 429 response / logging so failures are diagnosable.

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants