Website Assistant Chatbot Build Plan
Website Assistant Chatbot Build Plan Context You are helping me build a website assistant chatbot for my existing personal portfolio website. Important context: My website already
Source: content/obsidian/website-assistant-chatbot-build-plan.md
Website Assistant Chatbot Build Plan
Context
You are helping me build a website assistant chatbot for my existing personal portfolio website.
Important context:
- My website already exists.
- Do not rebuild the whole website.
- Do not change the global design system unless necessary.
- My website uses Next.js App Router, TypeScript, Tailwind CSS, and shadcn/ui.
- The chatbot should use OpenRouter API for chat responses.
- Later, the chatbot will read my Obsidian Markdown notes and answer based on them.
- Build this step by step.
- Before changing important structure, ask me first.
- If you need an API key, environment variable, model choice, Supabase setting, or design opinion, ask me.
- Do not guess private values.
- Do not expose API keys in frontend code.
Main Goal
Create a floating website assistant chatbot that can eventually answer questions about me, my projects, services, study, achievements, portfolio, and website content.
The final architecture should be:
Obsidian Markdown notes
-> Ingestion script
-> Embeddings
-> Supabase vector database
-> /api/chat retrieval
-> OpenRouter API
-> Chatbot UI on website
Build it in safe stages.
Stage 0: Inspect Project First
Before writing code:
- Inspect the current project structure.
- Identify:
- Next.js version and App Router structure
- Existing
app/layout.tsx - Existing
componentsfolder - Existing shadcn/ui components
- Existing Tailwind config
- Existing environment variable pattern
- Do not rename or move existing files unless necessary.
- Tell me what you found before making major changes.
Stage 1: Build Chatbot UI Only
Build UI only first.
No OpenRouter API yet. No Supabase yet. No Obsidian ingestion yet.
Create these files:
components/chatbot/ChatbotWidget.tsx
components/chatbot/ChatbotButton.tsx
components/chatbot/ChatbotWindow.tsx
components/chatbot/ChatMessage.tsx
components/chatbot/ChatInput.tsx
Use:
- React
- TypeScript
- Tailwind CSS
- shadcn/ui
- lucide-react
Use shadcn components where suitable:
- Button
- Card
- CardHeader
- CardContent
- CardFooter
- Avatar
- AvatarFallback
- ScrollArea
- Textarea
- Badge
- Separator
If components are missing, ask me before installing them, or suggest this command:
npx shadcn@latest add button card avatar scroll-area textarea badge separator
UI Behaviour
- Show floating circular chatbot button at bottom-right.
- Clicking the button opens chatbot window.
- Chatbot window appears bottom-right on desktop.
- On mobile, chatbot window should be almost full width.
- User can type message.
- Press Enter to send.
- Shift + Enter creates new line.
- Empty messages cannot be sent.
- Show fake loading state.
- Return fake response.
- Show suggested question chips before first user message.
- Clicking a suggested chip sends that question.
- Auto-scroll to latest message.
- Close button closes the window.
Assistant name:
Max AI Assistant
Welcome message:
Hi, I’m Max’s website assistant. You can ask me about Max’s projects, skills, services, study, and portfolio.
Suggested questions:
- Who is Max Hoang?
- What AI projects has Max built?
- What services does Max offer?
- What awards has Max won?
- How can I contact Max?
Fake response:
Thanks for your question. This is a UI demo for now. In the next step, I will connect this chatbot to Max’s Obsidian notes and website content.
Desktop Style
- Floating button:
fixed bottom-6 right-6 z-50 - Button size:
h-14 w-14 rounded-full - Chat window:
fixed bottom-24 right-6 z-50 - Width:
380px - Height:
560px rounded-2xlshadow-2xlborder
Mobile Style
- Button:
bottom-4 right-4 - Window:
bottom-20 left-3 right-3 - Width:
calc(100vw - 24px) - Height:
75vh
Use theme tokens:
bg-backgroundbg-cardtext-card-foregroundbg-mutedtext-muted-foregroundbg-primarytext-primary-foregroundborder-border
Do not hard-code many colours.
Add <ChatbotWidget /> to app/layout.tsx so it appears on every page.
Important:
- Do not break the existing layout.
- If
app/layout.tsxhas custom providers, keep them unchanged.
Stage 2: Add OpenRouter Backend Route
After UI is working, add OpenRouter connection.
Create:
app/api/chat/route.ts
This route should:
- Accept POST request with:
{
"message": "user message"
}
- Validate message is not empty.
- Read environment variables:
OPENROUTER_API_KEYOPENROUTER_MODEL
- If missing API key, return a clear error:
OpenRouter API key is not configured.
- Call OpenRouter chat completions endpoint:
https://openrouter.ai/api/v1/chat/completions
- Use Authorization Bearer token.
- Use a safe default model if
OPENROUTER_MODELis missing:
openrouter/free
- Return:
{
"answer": "...",
"sources": []
}
Important:
- API key must only be used in server route.
- Never expose
OPENROUTER_API_KEYto frontend. - Do not use
NEXT_PUBLICfor the API key. - Add basic error handling.
- Add request timeout if reasonable.
- Keep code simple.
Environment variables:
OPENROUTER_API_KEY=
OPENROUTER_MODEL=openrouter/free
Optional OpenRouter headers:
HTTP-Referer: my website URL
X-Title: Max Hoang Website Assistant
If website URL is unknown, ask me.
System prompt for basic stage:
You are Max Hoang’s website assistant. Answer in a clear, friendly, professional way. Keep answers short. If you do not know something, say you do not have enough information yet. This chatbot will later connect to Max’s website and Obsidian notes.
Stage 3: Connect UI To /api/chat
Update the chatbot UI.
Replace fake response with real call to:
POST /api/chat
Request:
{
"message": "userMessage"
}
Response:
{
"answer": "...",
"sources": []
}
Keep loading state. Keep error state.
If API fails, show:
Sorry, I could not answer right now. Please try again later.
Do not remove the demo UI features. Keep suggested question chips.
Stage 4: Prepare Obsidian Content Structure
Create folder:
content/obsidian/
Add example file only:
content/obsidian/about-max.example.md
Example content:
---
title: About Max Hoang
publish: true
category: about
tags:
- portfolio
- about
slug: about-max
---
# About Max Hoang
This is an example public note for the chatbot. Replace this with real Obsidian content later.
Important privacy rule:
Only process notes with publish: true.
Ignore folders:
- private
- journal
- family
- finance
- visa
- passwords
- health
- archive
- templates
Do not import my whole Obsidian vault yet. Ask me before adding real notes.
Stage 5: Add Supabase Schema For Future RAG
Only do this when I confirm Supabase is ready.
Create:
supabase/schema.sql
Use pgvector.
Schema:
create extension if not exists vector;
create table if not exists documents (
id uuid primary key default gen_random_uuid(),
title text,
source_path text not null,
slug text,
category text,
tags text[],
content text not null,
chunk_index int not null,
embedding vector(1536),
publish boolean default true,
updated_at timestamptz default now()
);
create index if not exists documents_embedding_idx
on documents
using ivfflat (embedding vector_cosine_ops)
with (lists = 100);
create index if not exists documents_source_path_idx
on documents(source_path);
create or replace function match_documents (
query_embedding vector(1536),
match_count int default 8,
match_threshold float default 0.75
)
returns table (
id uuid,
title text,
source_path text,
slug text,
category text,
tags text[],
content text,
similarity float
)
language sql stable
as $$
select
documents.id,
documents.title,
documents.source_path,
documents.slug,
documents.category,
documents.tags,
documents.content,
1 - (documents.embedding <=> query_embedding) as similarity
from documents
where 1 - (documents.embedding <=> query_embedding) > match_threshold
order by documents.embedding <=> query_embedding
limit match_count;
$$;
Ask me before applying this to Supabase.
Stage 6: Add Ingestion Script
Only do this after I confirm Supabase keys and embedding provider.
Create:
scripts/ingest-notes.ts
The script should:
- Read Markdown files from
content/obsidian. - Ignore private folders.
- Parse frontmatter.
- Only process
publish: true. - Remove frontmatter.
- Convert Obsidian links:
Note Name->Note Name- `` -> remove for now
- Split content into chunks.
- Create embeddings.
- Delete old chunks for same
source_path. - Insert new chunks into Supabase
documentstable. - Log indexed files and chunks.
Use package:
gray-matter
Use package:
@supabase/supabase-js
Use embedding provider:
OpenAI text-embedding-3-small
Environment variables:
NEXT_PUBLIC_SUPABASE_URL=
SUPABASE_SERVICE_ROLE_KEY=
OPENAI_API_KEY=
EMBEDDING_MODEL=text-embedding-3-small
Add script to package.json:
{
"ingest": "tsx scripts/ingest-notes.ts"
}
Ask me before installing dependencies.
Stage 7: Add RAG Retrieval
Only do this after ingestion works.
Create:
lib/retrieval.ts
lib/prompt.ts
lib/embeddings.ts
Retrieval flow:
- Take user message.
- Create embedding for query.
- Call Supabase RPC
match_documents. - Get top relevant chunks.
- If no chunks are found, return no-context response.
- Build context.
- Send context + question to OpenRouter.
- Return answer + source list.
RAG system prompt:
You are Max Hoang’s website assistant.
You answer questions about Max, his projects, skills, study, work, services, blog content, and public portfolio information.
You must answer only using the provided context from Max's website and Obsidian notes.
Rules:
- Do not invent facts.
- If the answer is not in the context, say you do not have enough information in Max’s public notes.
- Keep answers clear, friendly, and professional.
- Use simple language.
- Do not reveal private or hidden information.
- Do not answer as if you are Max unless specifically asked to draft text for him.
- If a visitor asks about hiring Max, explain his relevant services and suggest contacting him through the website.
Update app/api/chat/route.ts:
- First retrieve relevant context.
- If context exists, call OpenRouter with context.
- If no context exists, return:
I do not have enough information in Max’s public notes to answer that yet.
- Return sources.
Stage 8: Add GitHub Actions Auto-Ingest
Only do this after manual ingestion works.
Create:
.github/workflows/ingest.yml
Workflow:
- Trigger on push to
content/obsidian/*/.md - Install dependencies
- Run
npm run ingest - Use GitHub secrets for API keys
Ask me before creating workflow if deployment structure is unclear.
Safety Rules For Codex
- Work in small steps.
- Do not refactor unrelated files.
- Do not delete existing components.
- Do not rename existing routes.
- Do not change global CSS unless necessary.
- Do not overwrite
app/layout.tsx. Only addChatbotWidgetcarefully. - Do not expose API keys in frontend.
- Do not commit
.env.local. - Update
.env.exampleonly. - Ask me before installing packages.
- Ask me before adding Supabase.
- Ask me before changing database schema.
- Ask me before importing real Obsidian notes.
- After each stage, run:
npm run lintnpm run build
- If build fails, fix only related errors.
- Explain what changed after each stage.
First Task For Codex
Start with Stage 0 and Stage 1 only.
Do not connect OpenRouter yet.
Inspect the project, then create the shadcn chatbot UI.
After Stage 1 is working, stop and ask me before moving to Stage 2.
