SaaS / Show HN: Hyper, the self driving company brain

Show HN: Hyper, the self driving company brain

bootstrapped
Le cerveau autonome de votre entreprise
👥 Voir les clients
MRR
0€/m
Clients payants
0
Trials
0
Churn
0%
Fiche produit
ICP
Knowledge worker & équipe B2B en croissance
Problème résolu
Les équipes perdent des heures chaque semaine à chercher des informations dispersées dans Notion, Slack, emails et réunions, sans jamais capitaliser s
Pricing
Hyper
49
Pro
129
Business
299
Trial : 14 jours gratuits
Mettre à jour les métriques
Docs générés automatiquement
SPEC.mdDESIGN.mdMARKETING.md

Hyper — Spécification Technique MVP


1. Vue d'ensemble

Concept : Hyper est un SaaS B2B qui centralise toute la connaissance d'une entreprise (Notion, Slack, Google Drive, emails, réunions) dans un cerveau d'entreprise unifié, interrogeable en langage naturel via un agent IA agentique qui peut répondre, résumer et créer des tâches automatiquement. Valeur ajoutée principale : Zéro friction pour retrouver une information — l'IA cherche, synthétise et agit à la place du collaborateur, capitalisant la connaissance collective même après les départs. Ce qu'on build en MVP (scope réaliste 2-4 semaines) :
  • Authentification + onboarding équipe
  • Connexion à 2 sources max : Notion (OAuth) + upload de fichiers (PDF, DOCX, TXT)
  • Pipeline d'indexation : chunking → embeddings → stockage vectoriel (pgvector)
  • Interface de chat IA avec RAG (Retrieval-Augmented Generation) + citations de sources
  • Agent simple : réponse avec sources + résumé de document à la demande
  • Gestion d'équipe (invitations membres) + contrôle d'accès par workspace
  • Billing Stripe (trial 14j → plan payant)
  • Landing page + emails transactionnels


2. Stack technique

Core

| Couche | Technologie |

|---|---|

| Framework | Next.js 15 (App Router) + TypeScript strict |

| Backend / Auth / DB | Supabase (PostgreSQL + pgvector + Auth + Storage) |

| Billing | Stripe (Checkout + Customer Portal + Webhooks) |

| Emails | Resend + React Email |

| IA / LLM | OpenAI API (GPT-4o pour chat, text-embedding-3-small pour embeddings) |

| Vector Store | pgvector (extension Supabase) |

Dépendances NPM spécifiques

{

"dependencies": {

"@supabase/supabase-js": "^2.45.0",

"@supabase/ssr": "^0.5.0",

"openai": "^4.67.0",

"ai": "^3.4.0",

"stripe": "^17.0.0",

"@stripe/stripe-js": "^4.0.0",

"resend": "^4.0.0",

"@react-email/components": "^0.0.25",

"notion-client": "^6.16.0",

"@notionhq/client": "^2.2.15",

"pdf-parse": "^1.1.1",

"mammoth": "^1.8.0",

"langchain": "^0.3.0",

"@langchain/openai": "^0.3.0",

"@langchain/community": "^0.3.0",

"zod": "^3.23.0",

"zustand": "^5.0.0",

"react-dropzone": "^14.3.0",

"react-markdown": "^9.0.0",

"remark-gfm": "^4.0.0",

"react-syntax-highlighter": "^15.6.0",

"date-fns": "^4.0.0",

"nanoid": "^5.0.0",

"sharp": "^0.33.0",

"cheerio": "^1.0.0",

"p-limit": "^6.1.0",

"tiktoken": "^1.0.17"

},

"devDependencies": {

"tailwindcss": "^3.4.0",

"@tailwindcss/typography": "^0.5.0",

"shadcn-ui": "latest",

"lucide-react": "^0.460.0"

}

}

UI

  • shadcn/ui (composants accessibles, thème custom)
  • Tailwind CSS v3
  • Lucide React (icônes)


3. Modèle de données Supabase

Extensions requises

CREATE EXTENSION IF NOT EXISTS "uuid-ossp";

CREATE EXTENSION IF NOT EXISTS "vector";

CREATE EXTENSION IF NOT EXISTS "pg_trgm"; -- recherche texte floue


Table : workspaces

CREATE TABLE workspaces (

id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),

name TEXT NOT NULL,

slug TEXT NOT NULL UNIQUE,

logo_url TEXT,

owner_id UUID NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE,

plan TEXT NOT NULL DEFAULT 'trial' CHECK (plan IN ('trial', 'starter', 'pro', 'enterprise')),

trial_ends_at TIMESTAMPTZ NOT NULL DEFAULT (NOW() + INTERVAL '14 days'),

stripe_customer_id TEXT UNIQUE,

stripe_subscription_id TEXT UNIQUE,

max_members INT NOT NULL DEFAULT 3,

max_documents INT NOT NULL DEFAULT 100,

settings JSONB NOT NULL DEFAULT '{}',

created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),

updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()

);

CREATE INDEX idx_workspaces_owner ON workspaces(owner_id);

CREATE INDEX idx_workspaces_slug ON workspaces(slug);

CREATE INDEX idx_workspaces_stripe_customer ON workspaces(stripe_customer_id);


Table : workspace_members

CREATE TABLE workspace_members (

id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),

workspace_id UUID NOT NULL REFERENCES workspaces(id) ON DELETE CASCADE,

user_id UUID NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE,

role TEXT NOT NULL DEFAULT 'member' CHECK (role IN ('owner', 'admin', 'member', 'viewer')),

invited_by UUID REFERENCES auth.users(id),

joined_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),

UNIQUE(workspace_id, user_id)

);

CREATE INDEX idx_wm_workspace ON workspace_members(workspace_id);

CREATE INDEX idx_wm_user ON workspace_members(user_id);


Table : integrations

CREATE TABLE integrations (

id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),

workspace_id UUID NOT NULL REFERENCES workspaces(id) ON DELETE CASCADE,

provider TEXT NOT NULL CHECK (provider IN ('notion', 'google_drive', 'slack', 'confluence', 'upload')),

status TEXT NOT NULL DEFAULT 'active' CHECK (status IN ('active', 'error', 'paused', 'disconnected')),

access_token TEXT, -- chiffré avec pgcrypto en prod

refresh_token TEXT, -- chiffré

token_expires_at TIMESTAMPTZ,

external_id TEXT, -- ID workspace/team chez le provider

metadata JSONB NOT NULL DEFAULT '{}', -- config spécifique provider

last_sync_at TIMESTAMPTZ,

sync_error TEXT,

created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),

updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),

UNIQUE(workspace_id, provider)

);

CREATE INDEX idx_integrations_workspace ON integrations(workspace_id);

CREATE INDEX idx_integrations_provider ON integrations(provider);


Table : documents

CREATE TABLE documents (

id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),

workspace_id UUID NOT NULL REFERENCES workspaces(id) ON DELETE CASCADE,

integration_id UUID REFERENCES integrations(id) ON DELETE SET NULL,

title TEXT NOT NULL,

source_type TEXT NOT NULL CHECK (source_type IN ('notion', 'google_drive', 'slack', 'upload', 'confluence', 'manual')),

source_url TEXT,

external_id TEXT, -- ID du doc chez le provider

file_path TEXT, -- chemin Supabase Storage si upload

mime_type TEXT,

raw_content TEXT, -- texte extrait brut (stocké pour re-indexation)

summary TEXT, -- résumé généré par IA

status TEXT NOT NULL DEFAULT 'pending' CHECK (status IN ('pending', 'processing', 'indexed', 'error')),

index_error TEXT,

word_count INT DEFAULT 0,

chunk_count INT DEFAULT 0,

metadata JSONB NOT NULL DEFAULT '{}',

indexed_at TIMESTAMPTZ,

created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),

updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()

);

CREATE INDEX idx_documents_workspace ON documents(workspace_id);

CREATE INDEX idx_documents_status ON documents(status);

CREATE INDEX idx_documents_source_type ON documents(source_type);

CREATE INDEX idx_documents_external_id ON documents(external_id);

-- Recherche full-text sur le titre

CREATE INDEX idx_documents_title_trgm ON documents USING gin(title gin_trgm_ops);


Table : document_chunks

CREATE TABLE document_chunks (

id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),

document_id UUID NOT NULL REFERENCES documents(id) ON DELETE CASCADE,

workspace_id UUID NOT NULL REFERENCES workspaces(id) ON DELETE CASCADE,

chunk_index INT NOT NULL,

content TEXT NOT NULL,

token_count INT NOT NULL DEFAULT 0,

embedding VECTOR(1536), -- text-embedding-3-small = 1536 dims

metadata JSONB NOT NULL DEFAULT '{}', -- page, heading, section, etc.

created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()

);

-- Index HNSW pour recherche vectorielle (pgvector)

CREATE INDEX idx_chunks_embedding ON document_chunks

USING hnsw (embedding vector_cosine_ops)

WITH (m = 16, ef_construction = 64);

CREATE INDEX idx_chunks_document ON document_chunks(document_id);

CREATE INDEX idx_chunks_workspace ON document_chunks(workspace_id);

-- Full-text search sur le contenu

CREATE INDEX idx_chunks_content_trgm ON document_chunks USING gin(content gin_trgm_ops);


Table : conversations

CREATE TABLE conversations (

id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),

workspace_id UUID NOT NULL REFERENCES workspaces(id) ON DELETE CASCADE,

user_id UUID NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE,

title TEXT, -- généré automatiquement depuis le 1er message

is_pinned BOOLEAN NOT NULL DEFAULT false,

metadata JSONB NOT NULL DEFAULT '{}',

created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),

updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()

);

CREATE INDEX idx_conversations_workspace ON conversations(workspace_id);

CREATE INDEX idx_conversations_user ON conversations(user_id);


Table : messages

CREATE TABLE messages (

id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),

conversation_id UUID NOT NULL REFERENCES conversations(id) ON DELETE CASCADE,

workspace_id UUID NOT NULL REFERENCES workspaces(id) ON DELETE CASCADE,

role TEXT NOT NULL CHECK (role IN ('user', 'assistant', 'system')),

content TEXT NOT NULL,

sources JSONB DEFAULT '[]', -- [{chunk_id, document_id, title, url, score}]

tokens_used INT DEFAULT 0,

model TEXT DEFAULT 'gpt-4o',

latency_ms INT,

feedback TEXT CHECK (feedback IN ('positive', 'negative', NULL)),

created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()

);

CREATE INDEX idx_messages_conversation ON messages(conversation_id);

CREATE INDEX idx_messages_workspace ON messages(workspace_id);

CREATE INDEX idx_messages_created ON messages(created_at DESC);


Table : invitations

CREATE TABLE invitations (

id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),

workspace_id UUID NOT NULL REFERENCES workspaces(id) ON DELETE CASCADE,

email TEXT NOT NULL,

role TEXT NOT NULL DEFAULT 'member' CHECK (role IN ('admin', 'member', 'viewer')),

token TEXT NOT NULL UNIQUE DEFAULT encode(gen_random_bytes(32), 'hex'),

invited_by UUID NOT NULL REFERENCES auth.users(id),

accepted_at TIMESTAMPTZ,

expires_at TIMESTAMPTZ NOT NULL DEFAULT (NOW() + INTERVAL '7 days'),

created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()

);

CREATE INDEX idx_invitations_workspace ON invitations(workspace_id);

CREATE INDEX idx_invitations_email ON invitations(email);

CREATE INDEX idx_invitations_token ON invitations(token);


Table : sync_jobs

CREATE TABLE sync_jobs (

id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),

workspace_id UUID NOT NULL REFERENCES workspaces(id) ON DELETE CASCADE,

integration_id UUID NOT NULL REFERENCES integrations(id) ON DELETE CASCADE,

status TEXT NOT NULL DEFAULT 'pending' CHECK (status IN ('pending', 'running', 'completed', 'failed')),

triggered_by TEXT NOT NULL DEFAULT 'manual' CHECK (triggered_by IN ('manual', 'scheduled', 'webhook')),

documents_found INT DEFAULT 0,

documents_indexed INT DEFAULT 0,

documents_failed INT DEFAULT 0,

error_log TEXT,

started_at TIMESTAMPTZ,

completed_at TIMESTAMPTZ,

created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()

);

CREATE INDEX idx_sync_jobs_workspace ON sync_jobs(workspace_id);

CREATE INDEX idx_sync_jobs_integration ON sync_jobs(integration_id);

CREATE INDEX idx_sync_jobs_status ON sync_jobs(status);


Table : usage_events

CREATE TABLE usage_events (

id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),

workspace_id UUID NOT NULL REFERENCES workspaces(id) ON DELETE CASCADE,

user_id UUID REFERENCES auth.users(id),

event_type TEXT NOT NULL, -- 'query', 'document_indexed', 'sync', 'member_invited'

metadata JSONB NOT NULL DEFAULT '{}',

tokens_used INT DEFAULT 0,

created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()

);

CREATE INDEX idx_usage_workspace ON usage_events(workspace_id);

CREATE INDEX idx_usage_type ON usage_events(event_type);

CREATE INDEX idx_usage_created ON usage_events(created_at DESC);


Table : profiles (existante — extended)

-- Étendre la table profiles existante

ALTER TABLE profiles ADD COLUMN IF NOT EXISTS full_name TEXT;

ALTER TABLE profiles ADD COLUMN IF NOT EXISTS avatar_url TEXT;

ALTER TABLE profiles ADD COLUMN IF NOT EXISTS current_workspace_id UUID REFERENCES workspaces(id);

ALTER TABLE profiles ADD COLUMN IF NOT EXISTS onboarding_completed BOOLEAN DEFAULT false;

ALTER TABLE profiles ADD COLUMN IF NOT EXISTS onboarding_step TEXT DEFAULT 'workspace';


RLS Policies

-- ============================================================

-- WORKSPACES

-- ============================================================

ALTER TABLE workspaces ENABLE ROW LEVEL SECURITY;

CREATE POLICY "workspace_select" ON workspaces FOR SELECT

USING (

owner_id = auth.uid() OR

id IN (SELECT workspace_id FROM workspace_members WHERE user_id = auth.uid())

);

CREATE POLICY "workspace_insert" ON workspaces FOR INSERT

WITH CHECK (owner_id = auth.uid());

CREATE POLICY "workspace_update" ON workspaces FOR UPDATE

USING (

owner_id = auth.uid() OR

id IN (SELECT workspace_id FROM workspace_members WHERE user_id = auth.uid() AND role IN ('owner', 'admin'))

);

CREATE POLICY "workspace_delete" ON workspaces FOR DELETE

USING (owner_id = auth.uid());

-- ============================================================

-- WORKSPACE_MEMBERS

-- ============================================================

ALTER TABLE workspace_members ENABLE ROW LEVEL SECURITY;

CREATE POLICY "wm_select" ON workspace_members FOR SELECT

USING (

workspace_id IN (SELECT workspace_id FROM workspace_members WHERE user_id = auth.uid())

);

CREATE POLICY "wm_insert" ON workspace_members FOR INSERT

WITH CHECK (

workspace_id IN (

SELECT workspace_id FROM workspace_members

WHERE user_id = auth.uid() AND role IN ('owner', 'admin')

)

);

CREATE POLICY "wm_delete" ON workspace_members FOR DELETE

USING (

user_id = auth.uid() OR -- quitter le workspace

workspace_id IN (

SELECT workspace_id FROM workspace_members

WHERE user_id = auth.uid() AND role IN ('owner', 'admin')

)

);

-- ============================================================

-- DOCUMENTS

-- ============================================================

ALTER TABLE documents ENABLE ROW LEVEL SECURITY;

CREATE POLICY "documents_workspace_member" ON documents FOR ALL

USING (

workspace_id IN (SELECT workspace_id FROM workspace_members WHERE user_id = auth.uid())

);

-- ============================================================

-- DOCUMENT_CHUNKS

-- ============================================================

ALTER TABLE document_chunks ENABLE ROW LEVEL SECURITY;

CREATE POLICY "chunks_workspace_member" ON document_chunks FOR ALL

USING (

workspace_id IN (SELECT workspace_id FROM workspace_members WHERE user_id = auth.uid())

);

-- ============================================================

-- CONVERSATIONS & MESSAGES

-- ============================================================

ALTER TABLE conversations ENABLE ROW LEVEL SECURITY;

CREATE POLICY "conversations_owner" ON conversations FOR ALL

USING (user_id = auth.uid());

-- Les admins du workspace peuvent voir toutes les conversations

CREATE POLICY "conversations_admin" ON conversations FOR SELECT

USING (

workspace_id IN (

SELECT workspace_id FROM workspace_members

WHERE user_id = auth.uid() AND role IN ('owner', 'admin')

)

);

ALTER TABLE messages ENABLE ROW LEVEL SECURITY;

CREATE POLICY "messages_via_conversation" ON messages FOR ALL

USING (

conversation_id IN (SELECT id FROM conversations WHERE user_id = auth.uid())

OR

workspace_id IN (

SELECT workspace_id FROM workspace_members

WHERE user_id = auth.uid() AND role IN ('owner', 'admin')

)

);

-- ============================================================

-- INTEGRATIONS

-- ============================================================

ALTER TABLE integrations ENABLE ROW LEVEL SECURITY;

CREATE POLICY "integrations_admin" ON integrations FOR ALL

USING (

workspace_id IN (

SELECT workspace_id FROM workspace_members

WHERE user_id = auth.uid() AND role IN ('owner', 'admin')

)

);

-- ============================================================

-- INVITATIONS

-- ============================================================

ALTER TABLE invitations ENABLE ROW LEVEL SECURITY;

CREATE POLICY "invitations_manage" ON invitations FOR ALL

USING (

workspace_id IN (

SELECT workspace_id FROM workspace_members

WHERE user_id = auth.uid() AND role IN ('owner', 'admin')

)

);

-- ============================================================

-- SYNC_JOBS

-- ============================================================

ALTER TABLE sync_jobs ENABLE ROW LEVEL SECURITY;

CREATE POLICY "sync_jobs_member" ON sync_jobs FOR SELECT

USING (

workspace_id IN (SELECT workspace_id FROM workspace_members WHERE user_id = auth.uid())

);

CREATE POLICY "sync_jobs_admin_write" ON sync_jobs FOR INSERT

WITH CHECK (

workspace_id IN (

SELECT workspace_id FROM workspace_members

WHERE user_id = auth.uid() AND role IN ('owner', 'admin')

)

);


Fonction utilitaire : Recherche vectorielle

CREATE OR REPLACE FUNCTION search_documents(

query_embedding VECTOR(1536),

workspace_uuid UUID,

match_threshold FLOAT DEFAULT 0.75,

match_count INT DEFAULT 10

)

RETURNS TABLE (

chunk_id UUID,

document_id UUID,

content TEXT,

similarity FLOAT,

doc_title TEXT,

doc_source TEXT,

source_url TEXT,

metadata JSONB

)

LANGUAGE sql STABLE AS $$

SELECT

dc.id AS chunk_id,

dc.document_id AS document_id,

dc.content AS content,

1 - (dc.embedding <=> query_embedding) AS similarity,

d.title AS doc_title,

d.source_type AS doc_source,

d.source_url AS source_url,

dc.metadata AS metadata

FROM document_chunks dc

JOIN documents d ON d.id = dc.document_id

WHERE

dc.workspace_id = workspace_uuid

AND d.status = 'indexed'

AND 1 - (dc.embedding <=> query_embedding) > match_threshold

ORDER BY dc.embedding <=> query_embedding

LIMIT match_count;

$$;


4. Routes API

Authentication & Onboarding

| Method | Route | Description | Auth |

|--------|-------|-------------|------|

| POST | /api/auth/callback | Callback OAuth Supabase (magic link, Google) | Non |

| POST | /api/onboarding/workspace | Créer workspace + slug + rôle owner | Oui |

| GET | /api/onboarding/status | Retourner l'étape onboarding courante | Oui |

Workspace & Membres

| Method | Route | Description | Auth |

|--------|-------|-------------|------|

| GET | /api/workspace | Infos workspace courant | Oui |

| PATCH| /api/workspace | Mettre à jour nom, logo, settings | Admin |

| GET | /api/workspace/members | Lister les membres | Oui |

| DELETE| /api/workspace/members/[userId] | Retirer un membre | Admin |

| PATCH| /api/workspace/members/[userId] | Changer rôle d'un membre | Admin |

| POST | /api/workspace/invite | Envoyer invitation email | Admin |

| GET | /api/workspace/invite/[token] | Vérifier token invitation | Non |

| POST | /api/workspace/invite/[token]/accept | Accepter invitation | Oui |

Intégrations

| Method | Route | Description | Auth |

|--------|-------|-------------|------|

| GET | /api/integrations | Lister toutes les intégrations du workspace | Admin |

| GET | /api/integrations/notion/auth | Initier OAuth Notion → redirect | Admin |

| GET | /api/integrations/notion/callback | Callback OAuth Notion, stocker token | Admin |

| DELETE| /api/integrations/[id] | Déconnecter une intégration | Admin |

| POST | `/api/integrations