UBOT is an intelligent platform that lets you clone your professional identity into an AI chatbot. It digests your Resume (PDF) and GitHub profile to create a conversational agent that speaks for you, answers recruiters' questions, and showcases your skillsβ24/7.
UBOT Terminal Interface
classDiagram
direction TB
%% ββ DATA MODELS (src/lib/types.ts) ββ
class PortfolioData {
<<interface>>
+name : string
+role : string
+bio : string
+skills : string[]
+github : string
+socials? : Socials
}
class Profile {
<<interface>>
+id : string
+username : string
+portfolio_data : PortfolioData
+created_at : string
}
class ChatMessage {
<<type>>
+role : "user" | "assistant" | "system"
+content : string
}
class ContactEmailData {
<<interface>>
+name : string
+email : string
+subject : string
+message : string
}
Profile *-- PortfolioData : contains
%% ββ DATABASE TABLES (Supabase) ββ
class ProfilesTable {
<<Supabase Table>>
+id : bigint
+user_id : uuid
+username : string
+portfolio_data : jsonb
+created_at : timestamp
}
class DocumentsTable {
<<Supabase Table>>
+id : bigint
+user_id : uuid
+content : text
+metadata : jsonb
+embedding : vector~3072~
}
class MatchDocuments {
<<Supabase RPC>>
+query_embedding : vector~3072~
+match_threshold : float
+match_count : int
+filter_user_id : uuid
returns(id, content, metadata, similarity)
}
ProfilesTable "1" --> "0..*" DocumentsTable : user_id
DocumentsTable ..> MatchDocuments : queried via
%% ββ LIBRARY / SERVICES (src/lib/) ββ
class AIProvider {
<<module: ai-provider.ts>>
+MODELS : FREE_MODELS[], PRO
-GOOGLE_KEYS : string[]
-openrouter : OpenAI client
+withRetry~T~(fn, retries) T
+generateEmbedding(text) number[]
+generateEmbeddings(texts) number[][]
}
class SupabaseBrowserClient {
<<module: supabase.ts>>
+supabase : SupabaseClient
}
class SupabaseServerClient {
<<module: supabase-server.ts>>
+createClient() SupabaseClient
}
class EmailService {
<<module: email.ts>>
+sendContactEmail(data) void
+sendContactConfirmationEmail(data) void
}
class EnvValidator {
<<module: env.ts>>
+validateEnv() bool
}
EmailService ..> ContactEmailData : uses
%% ββ MIDDLEWARE (src/middleware.ts) ββ
class Middleware {
<<middleware.ts>>
+middleware(request) Response
#protects /dashboard
#protects /api/ingest
#protects /api/profile
}
Middleware ..> SupabaseServerClient : creates client
%% ββ API ROUTES (src/app/api/) ββ
class ChatAPI {
<<api/chat/[username]>>
+POST(req, params) StreamingResponse
+OPTIONS() Response
}
class IngestAPI {
<<api/ingest>>
+POST(req) Response
+OPTIONS() Response
}
class ProfileAPI {
<<api/profile>>
+GET(req) Response
+DELETE(req) Response
}
class CheckUsernameAPI {
<<api/check-username>>
+GET(req) Response
}
class ContactAPI {
<<api/contact>>
+POST(req) Response
}
class AuthCallbackAPI {
<<api/auth/callback>>
+GET(request) Redirect
}
ChatAPI ..> AIProvider : withRetry, generateEmbedding
ChatAPI ..> ChatMessage : uses
ChatAPI ..> ProfilesTable : queries
ChatAPI ..> DocumentsTable : vector search
IngestAPI ..> AIProvider : withRetry, generateEmbeddings
IngestAPI ..> ProfilesTable : upserts
IngestAPI ..> DocumentsTable : inserts
ProfileAPI ..> ProfilesTable : queries / deletes
CheckUsernameAPI ..> ProfilesTable : queries
ContactAPI ..> EmailService : sends email
AuthCallbackAPI ..> SupabaseServerClient : creates client
%% ββ PAGE COMPONENTS (src/app/) ββ
class RootLayout {
<<layout.tsx>>
+children : ReactNode
}
class LandingPage {
<<page.tsx>>
-session : Session
}
class DashboardPage {
<<dashboard/page.tsx>>
-user : User
-username : string
-existingProfile : Profile
+handleGenerate(e)
+handleDelete()
+handleLogout()
}
class PublicBotPage {
<<chat/[username]/page.tsx>>
-profile : Profile
+renderMarkdown(text)
+handleCustomSubmit(e)
}
class Navbar {
<<components/Navbar.tsx>>
-user : SupabaseUser
+navLinks : NavLink[]
}
RootLayout *-- Navbar : renders
RootLayout ..> EnvValidator : validateEnv
LandingPage ..> SupabaseBrowserClient : auth state
DashboardPage ..> SupabaseBrowserClient : auth state
DashboardPage ..> IngestAPI : POST
DashboardPage ..> ProfileAPI : GET / DELETE
DashboardPage ..> CheckUsernameAPI : GET
PublicBotPage ..> SupabaseBrowserClient : fetch profile
PublicBotPage ..> ChatAPI : streaming chat
Navbar ..> SupabaseBrowserClient : auth state
- Input: You upload a PDF Resume and optionally provide a GitHub username.
- Parsing:
pdf-parseextracts text from your resume.- GitHub API fetches your profile, pinned repositories, and bio.
- Persona Generation:
- OpenRouter (rotating through free models like
Step-3.5-FlashandQwen 3) analyzes this raw data to build a structured JSON profile.
- OpenRouter (rotating through free models like
- Vector Embeddings (RAG):
- Google Gemini Embeddings: We use
gemini-embedding-001(3072 dimensions) for high-accuracy semantic search. - Key Rotation: To bypass free-tier quota limits, the system automatically rotates through up to 5 Google API keys.
- Vectors are stored in Supabase (PostgreSQL +
pgvector).
- Google Gemini Embeddings: We use
- Source Attribution:
- All ingested data is transparently tagged (e.g.,
[Source: Resume],[Source: GitHub]) so the bot knows where its knowledge comes from.
- All ingested data is transparently tagged (e.g.,
- Retrieval Augmented Generation (RAG):
- When a user asks a question, we generate a 3072-dim embedding for their query using rotated Google keys.
- We perform a semantic search in Supabase retrieving up to 10 context chunks for breadth.
- Response Generation:
- The relevant context is fed into OpenRouter, which answers in the first person ("I built...", "My experience...").
- The system includes model rotation to ensure responses are delivered even if specific free models are timing out.
- Markdown Rendering:
- AI responses render bold, italic,
inline code, bullet points, headings, and links as formatted text β not raw markdown.
- AI responses render bold, italic,
| Component | Technology | Description |
|---|---|---|
| Framework | Next.js 16 | App Router, Server Components. |
| LLM Inference | OpenRouter | Rotating free models (Step-3.5-Flash, Qwen 3, DeepSeek) for high reliability. |
| Embeddings | Google AI | gemini-embedding-001 (3072 dims) with multi-key rotation. |
| Database | Supabase | PostgreSQL + pgvector for storage and semantic search. |
| Styling | Tailwind CSS v4 | Terminal/hacker glassmorphism aesthetic with CRT overlay effect. |
| Auth | Supabase Auth | Email/password and Google OAuth with session management. |
| Resend | Transactional emails for contact forms. |
UBOT follows industry-standard UI/UX best practices:
- Focus States: Visible green focus rings on all interactive elements via
focus-visible. - Keyboard Navigation: Skip-to-content link,
aria-expandedon toggles,role="menu"on mobile nav. - Forms: All labels paired with inputs (
htmlFor/id), required-field asterisks,autoCompleteattributes. - Semantic HTML:
<section>,<aside>,<main>,role="log",role="alert",aria-liveregions. - Error States: Icon + colored background + actionable text (not color-only).
- Typography: 16px body base, 1.6 line-height, minimum 14px for all visible text.
- Tap Targets: All buttons and links meet 44Γ44px minimum for mobile.
- External Links:
rel="noopener noreferrer"on all external links.
- Node.js 20+
- Supabase Account (with
vectorextension enabled) - OpenRouter API Key
- Google AI Studio Keys (Free tier works! Get multiple for rotation)
-
Clone & Install
git clone https://github.com/Trishix/ubot.git cd ubot npm install -
Environment Setup Create
.env.local:# Supabase NEXT_PUBLIC_SUPABASE_URL=... NEXT_PUBLIC_SUPABASE_ANON_KEY=... SUPABASE_SERVICE_ROLE_KEY=... # OpenRouter (Chat & Persona) OPENROUTER_API_KEY=... NEXT_PUBLIC_SITE_URL=https://your-domain.vercel.app # Google (Embeddings Key Rotation) FREE_API_KEY_1=... FREE_API_KEY_2=... FREE_API_KEY_3=... FREE_API_KEY_4=... FREE_API_KEY_5=... # Resend (Contact Form) RESEND_API_KEY=...
-
Database Setup (SQL) Run this in your Supabase SQL Editor:
-- Enable Vector Extension create extension if not exists vector; -- Documents Table for RAG create table documents ( id bigserial primary key, content text, metadata jsonb, embedding vector(3072), -- Matches gemini-embedding-001 dimensions user_id uuid references auth.users not null ); -- Search Function create or replace function match_documents ( query_embedding vector(3072), match_threshold float, match_count int, filter_user_id uuid ) returns table ( id bigint, content text, metadata jsonb, similarity float ) language plpgsql stable as $$ begin return query select id, content, metadata, 1 - (documents.embedding <=> query_embedding) as similarity from documents where 1 - (documents.embedding <=> query_embedding) > match_threshold and user_id = filter_user_id order by documents.embedding <=> query_embedding limit match_count; end; $$;
-
Run Development Server
npm run dev
MIT License. Built for the future of work.

