Back to work
AI · Full-Stack

PalAsk

An AI assistant built on a creator's own content, so their audience gets real product advice instead of a static affiliate link list.

Visit site
Role
Developer: backend, integrations and the RAG pipeline
Stack
Next.js · TypeScript · PayloadCMS · MongoDB · Qdrant · DeepInfra · AWS S3 · Vercel
Status
Live

Client project, built with a small team. Live at palask.ai taking waitlist sign-ups. The codebase has been dormant since February 2026 following a handover.

Context

A creator with an audience has the same problem every day. Someone asks which camera to buy for travel vlogging under a thousand dollars, and the honest answer, the one that weighs what they actually said they needed, is a two-minute conversation the creator does not have time to have a hundred times.

So the audience gets a page of affiliate links instead, and has to work it out themselves. PalAsk was built to close that gap: an assistant that knows what a specific creator has actually covered, and answers as they would.

What I built

A multi-tenant platform where each creator gets their own assistant, grounded in their own material.

  • A creator uploads their existing content: video, audio or text. It is transcribed, chunked, embedded and indexed automatically.
  • Their followers then chat with an assistant that answers only about products that creator has genuinely covered, and cites the creator's own transcripts as the source.
  • Recommendations link to a generated product page and carry an FTC affiliate disclosure. The compliance is built in rather than bolted on.
  • Amazon affiliate credentials connect per creator, so attribution flows to the right person.
  • A metered commercial model: per-minute processing and per-question costs, wallets and top-ups, and a default revenue share back to the creator.

The behavioural rules live in one prompt file rather than being scattered through the code. It opens with an identity lock, classifies each incoming question by type, and caps the answer length per type, so a yes/no question gets a yes/no answer instead of four paragraphs. Off-topic questions are refused by name: general knowledge, news, health, legal, financial, programming.

Architecture

Framework
Next.js 15 on the App Router, in TypeScript, with PayloadCMS 3 as both the admin surface and the data layer.
Database
MongoDB Atlas, through Payload's Mongoose adapter.
Multi-tenancy
Payload's multi-tenant plugin, with the creator record itself as the tenant. Fourteen collections are tenant-scoped, and the chat endpoints deliberately take the tenant from the session cookie rather than from the request body. A client cannot ask for someone else's data by editing a payload.
Vector search
Qdrant Cloud. One shared collection, 4096-dimension vectors, cosine distance, with tenant isolation done by a keyword-indexed filter on the creator id rather than a collection per creator.
Models
DeepInfra throughout: DeepSeek V3.1 for the grounded chat, Qwen3-Embedding-8B for embeddings, Whisper large v3 turbo for transcription. A named fallback chain is configured per job.
Storage & auth
S3 with a private bucket, read through a proxy route so files are never publicly addressable. NextAuth over a hand-written Payload adapter, with Google, GitHub and password sign-in.
Hosting
Vercel, auto-deploying from main.

The trap in the documentation

Worth recording, because it is the failure mode I now assume is present in any project I inherit.

The project's own written documentation describes the vector layer as one Qdrant collection per creator, using OpenAI's ada-002 embeddings at 1536 dimensions. None of that is what runs. The live code uses a single shared collection, 4096-dimension vectors from a Qwen embedding model, and isolates tenants with a payload filter.

Both descriptions are internally coherent. The documented one was presumably true once, or was the plan. But an engineer who trusted it would size the infrastructure wrong, debug the wrong isolation boundary, and describe the system incorrectly to anyone who asked.

The same shape appears twice more in this codebase: a second, unreferenced Qdrant client still using the per-creator layout, and OpenAI transcription and detection routes that nothing calls. Dead paths that read exactly like live ones.

The habit that comes out of it is simple. Trace from the user-facing entry point to the call site before believing any architectural claim, including one written by the team that built it. Documentation describes intent; only the import graph describes behaviour.

How AI was used to build it

This was a team project run on an in-house AI development methodology: a versioned process with a task tool as the single source of truth and a defined per-task workflow. I did not design that methodology; I worked within it, and contributed the section governing where work belongs: tests and shipping docs in the code repository, plans and checkpoints in the documentation one.

AI-assisted authorship is explicit in the history rather than implied: a substantial share of commits across both repositories carry co-author trailers.

My own largest contribution to how the project was verified was a set of thirteen scripted manual testing guides and five checkpoint documents. They exist because the project had no automated test infrastructure, and scripted scenarios with explicit pass, fail and blocked states were a more honest substitute than claiming coverage that did not exist. I would rather have written tests; given the constraint, that was the version of rigour available.

On my role, precisely: I was one of three contributors, and the largest committer on the original repository. My area was features, backend and integrations; a colleague owned the interface work, which reached the main branch through merges I made. I was not the sole author of this project and it would be wrong to present it that way.

Outcome and current status

The site is live at palask.ai and taking waitlist sign-ups ahead of open registration.

The codebase has been dormant since February 2026. The last work was a database handover to the client's own infrastructure, which reads as the end of the engagement rather than a pause.

I have deliberately quoted no user numbers, traffic or performance figures. The latency and cost strings in the model configuration are hand-written constants, not measurements, and nothing in the codebase reports real usage.

Screenshots