{"id":13645025,"url":"https://github.com/supabase-community/chatgpt-your-files","last_synced_at":"2025-05-15T17:02:14.981Z","repository":{"id":199318875,"uuid":"701867516","full_name":"supabase-community/chatgpt-your-files","owner":"supabase-community","description":"Production-ready MVP for securely chatting with your documents using pgvector","archived":false,"fork":false,"pushed_at":"2025-02-21T17:50:30.000Z","size":1285,"stargazers_count":448,"open_issues_count":13,"forks_count":163,"subscribers_count":12,"default_branch":"main","last_synced_at":"2025-05-13T22:34:51.107Z","etag":null,"topics":["ai","db","embeddings","ml","rag","supabase","vector"],"latest_commit_sha":null,"homepage":"https://youtu.be/ibzlEQmgPPY","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/supabase-community.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2023-10-07T19:48:52.000Z","updated_at":"2025-05-13T01:40:33.000Z","dependencies_parsed_at":"2024-01-10T18:16:37.661Z","dependency_job_id":"4b199a83-54cf-44ef-96c5-9b80fb760fbe","html_url":"https://github.com/supabase-community/chatgpt-your-files","commit_stats":{"total_commits":39,"total_committers":5,"mean_commits":7.8,"dds":0.3076923076923077,"last_synced_commit":"2e340561744cf73ad24e0621955dbb500e625b9e"},"previous_names":["supabase-community/chatgpt-your-files"],"tags_count":6,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/supabase-community%2Fchatgpt-your-files","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/supabase-community%2Fchatgpt-your-files/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/supabase-community%2Fchatgpt-your-files/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/supabase-community%2Fchatgpt-your-files/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/supabase-community","download_url":"https://codeload.github.com/supabase-community/chatgpt-your-files/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254384937,"owners_count":22062421,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["ai","db","embeddings","ml","rag","supabase","vector"],"created_at":"2024-08-02T01:02:23.885Z","updated_at":"2025-05-15T17:02:14.940Z","avatar_url":"https://github.com/supabase-community.png","language":"TypeScript","funding_links":[],"categories":["NLP","ai","TypeScript"],"sub_categories":[],"readme":"\u003cimg alt=\"pgvector to Prod in 2 hours\" src=\"./assets/hero.png\"\u003e\n\u003ch1 align=\"center\"\u003eWorkshop: pgvector to Prod in 2 hours\u003c/h1\u003e\n\n\u003cp align=\"center\"\u003e\nCreate a production-ready MVP for securely chatting with your documents.\n\u003c/p\u003e\n\n## ☑️ Features\n\n- **Interactive Chat Interface:** Interact with your documentation, leveraging the capabilities of OpenAI’s GPT models and retrieval augmented generation (RAG).\n- **Login With \u003c3rd Party\u003e:** Integrate one-click 3rd party login with any of our 18 auth providers and user/password.\n- **Document Storage:** Securely upload, store, and retrieve user uploaded documents.\n- **REST API:** Expose a flexible REST API that we’ll consume to build the interactive front-end.\n- **Row-level Security:** Secure all of your user data user data with production-ready row-level security.\n\n## 🎥 YouTube video\n\nThis entire workshop was recorded as a YouTube video. Feel free to watch it here:\n\nhttps://www.youtube.com/watch?v=ibzlEQmgPPY\n\n## 📄 Workshop Instructions\n\nThanks for joining! Let's dive in.\n\n![Workshop instructions](./assets/instructions.png)\n\n1. **Clone repo:** Clone this repo at tag `step-1`:\n\n   ```shell\n   git clone -b step-1 https://github.com/supabase-community/chatgpt-your-files.git\n   ```\n\n   This will automatically clone at [step 1](#step-1---storage), our starting point.\n\n1. **Git checkpoints:** The workshop is broken down into steps (git tags). There's a step for every major feature we are building.\n\n   Feel free to follow along live with the presenter. When it's time to jump to the next step, run:\n\n   ```shell\n   git stash push -u # stash your working directory\n   git checkout step-X # jump to a checkpoint (replace X wit step #)\n   ```\n\n1. **Step-by-step guide:** These steps are written out line-by-line. Feel free to follow along using the [steps below](#step-by-step).\n\n## 🧱 Pre-req’s\n\n- Unix-based OS (if Windows, WSL2)\n- Docker\n- Node.js 18+\n\n## 💿 Sample Data\n\nThis repository includes 3 sample markdown files that we'll use to test the app:\n\n[`./sample-files/roman-empire-1.md`](./sample-files/roman-empire-1.md)\n\n[`./sample-files/roman-empire-2.md`](./sample-files/roman-empire-2.md)\n\n[`./sample-files/roman-empire-3.md`](./sample-files/roman-empire-3.md)\n\n## 🪜 Step-by-step\n\nJump to a step:\n\n1. [Storage](#step-1---storage)\n2. [Documents](#step-2---documents)\n3. [Embeddings](#step-3---embeddings)\n4. [Chat](#step-4---chat)\n5. [Database Types](#step-5---database-types-bonus) (Bonus)\n6. [You're done!](#youre-done)\n\n---\n\n\u003cdetails\u003e\n\n\u003csummary\u003e\u003cstrong\u003eStep 0 - Setup\u003c/strong\u003e \u003cem\u003e(Optional)\u003c/em\u003e\u003c/summary\u003e\n\n### `Step 0` - Setup\n\n_Use this command to jump to the `step-0` checkpoint._\n\n```shell\ngit checkout step-0\n```\n\nThe beginning of step 0 is aka to:\n\n```shell\nnpx create-next-app -e with-supabase\n```\n\nRefer to this step if you want to learn about the additions added on top of `create-next-app` to get us up and running quicker for this workshop _(VS Code settings, UI components/styles/layouts)_. Otherwise, skip straight to [`step-1`](#step-1---storage).\n\n1. Install Supabase as dev dependency.\n\n   ```bash\n   npm i -D supabase@1.102.0\n   ```\n\n1. Initialize Supabase project.\n\n   ```bash\n   npx supabase init\n   ```\n\n1. (Optional) Setup VSCode environment.\n\n   ```bash\n   mkdir -p .vscode \u0026\u0026 cat \u003e .vscode/settings.json \u003c\u003c- EOF\n   {\n     \"deno.enable\": true,\n     \"deno.lint\": true,\n     \"deno.unstable\": false,\n     \"deno.enablePaths\": [\n       \"supabase\"\n     ],\n     \"deno.importMap\": \"./supabase/functions/import_map.json\"\n   }\n   EOF\n   ```\n\n1. (Optional) Setup VSCode recommended extensions.\n\n   ```bash\n   cat \u003e .vscode/extensions.json \u003c\u003c- EOF\n   {\n    \"recommendations\": [\n      \"denoland.vscode-deno\",\n      \"esbenp.prettier-vscode\",\n      \"dbaeumer.vscode-eslint\",\n      \"bradlc.vscode-tailwindcss\",\n    ],\n   }\n   EOF\n   ```\n\n   Then `cmd`+`shift`+`p` → `\u003eshow recommended extensions` → install all _(or whichever you like)_\n\n1. Create `import_map.json` with dependencies for our Supabase Edge Functions. We'll talk more about this in [step 2](#step-2---documents).\n\n   ```bash\n   cat \u003e supabase/functions/import_map.json \u003c\u003c- EOF\n    {\n      \"imports\": {\n        \"@std/\": \"https://deno.land/std@0.168.0/\",\n\n        \"@supabase/supabase-js\": \"https://esm.sh/@supabase/supabase-js@2.21.0\",\n        \"openai\": \"https://esm.sh/openai@4.10.0\",\n        \"common-tags\": \"https://esm.sh/common-tags@1.8.2\",\n        \"ai\": \"https://esm.sh/ai@2.2.13\",\n\n        \"mdast-util-from-markdown\": \"https://esm.sh/v132/mdast-util-from-markdown@2.0.0\",\n        \"mdast-util-to-markdown\": \"https://esm.sh/v132/mdast-util-to-markdown@2.1.0\",\n        \"mdast-util-to-string\": \"https://esm.sh/v132/mdast-util-to-string@4.0.0\",\n        \"unist-builder\": \"https://esm.sh/v132/unist-builder@4.0.0\",\n        \"mdast\": \"https://esm.sh/v132/@types/mdast@4.0.0/index.d.ts\",\n\n        \"https://esm.sh/v132/decode-named-character-reference@1.0.2/esnext/decode-named-character-reference.mjs\": \"https://esm.sh/decode-named-character-reference@1.0.2?target=deno\"\n      }\n    }\n    EOF\n   ```\n\n#### Scaffold Frontend\n\nWe use [`shadcn/ui`](https://ui.shadcn.com/docs) for our UI components.\n\n1. Initialize `shadcn-ui`.\n\n   ```bash\n   npx shadcn-ui@latest init\n   ```\n\n   ```bash\n   Would you like to use TypeScript (recommended)? yes\n   Which style would you like to use? › Default\n   Which color would you like to use as base color? › Slate\n   Where is your global CSS file? › › app/globals.css\n   Do you want to use CSS variables for colors? › yes\n   Where is your tailwind.config.js located? › tailwind.config.js\n   Configure the import alias for components: › @/components\n   Configure the import alias for utils: › @/lib/utils\n   Are you using React Server Components? › yes\n   ```\n\n1. Add components.\n\n   ```bash\n   npx shadcn-ui@latest add button input toast\n   ```\n\n1. Install dependencies.\n\n   ```bash\n   npm i @tanstack/react-query three-dots\n   ```\n\n1. Wrap the app in a `\u003cQueryClientProvider\u003e`.\n1. Build layouts.\n\n\u003c/details\u003e\n\n---\n\n### `Step 1` - Storage\n\n_Use this command to jump to the `step-1` checkpoint._\n\n```shell\ngit checkout step-1\n```\n\nWe'll start by handling file uploads. Supabase has a built-in object storage (backed by S3 under the hood) that integrates directly with your Postgres database.\n\n#### Install dependencies\n\nFirst install NPM dependencies.\n\n```bash\nnpm i\n```\n\n#### Setup Supabase stack\n\nWhen developing a project in Supabase, you can choose to develop locally or directly on the cloud.\n\n##### Local\n\n1. Start a local version of Supabase _(runs in Docker)_.\n\n   ```shell\n   npx supabase start\n   ```\n\n1. Store the Supabase URL \u0026 public anon key in `.env.local` for Next.js.\n\n   ```bash\n   npx supabase status -o env \\\n     --override-name api.url=NEXT_PUBLIC_SUPABASE_URL \\\n     --override-name auth.anon_key=NEXT_PUBLIC_SUPABASE_ANON_KEY |\n       grep NEXT_PUBLIC \u003e .env.local\n   ```\n\n##### Cloud\n\n1. Create a Supabase project at https://database.new, or via the CLI:\n\n   ```shell\n   npx supabase projects create -i \"ChatGPT Your Files\"\n   ```\n\n   Your Org ID can be found in the URL after [selecting an org](https://supabase.com/dashboard/org/_/general).\n\n1. Link your CLI to the project.\n\n   ```shell\n   npx supabase link --project-ref=\u003cproject-id\u003e\n   ```\n\n   You can get the project ID from the [general settings page](https://supabase.com/dashboard/project/_/settings/general).\n\n1. Store Supabase URL \u0026 public anon key in `.env.local` for Next.js.\n\n   ```shell\n   NEXT_PUBLIC_SUPABASE_URL=\u003capi-url\u003e\n   NEXT_PUBLIC_SUPABASE_ANON_KEY=\u003canon-key\u003e\n   ```\n\n   You can get the project API URL and anonymous key from the [API settings page](https://supabase.com/dashboard/project/_/settings/api).\n\n#### Build a SQL migration\n\n1.  Create migration file.\n\n    ```bash\n    npx supabase migration new files\n    ```\n\n    A new file will be created under `./supabase/migrations`.\n\n1.  Within that file, create a private schema.\n\n    ```sql\n    create schema private;\n    ```\n\n1.  Add bucket called 'files' via the `buckets` table in the `storage` schema.\n\n    ```sql\n    insert into storage.buckets (id, name)\n    values ('files', 'files')\n    on conflict do nothing;\n    ```\n\n1.  Add RLS policies to restrict access to files.\n\n    ```sql\n    create policy \"Authenticated users can upload files\"\n    on storage.objects for insert to authenticated with check (\n      bucket_id = 'files' and owner = auth.uid()\n    );\n\n    create policy \"Users can view their own files\"\n    on storage.objects for select to authenticated using (\n      bucket_id = 'files' and owner = auth.uid()\n    );\n\n    create policy \"Users can update their own files\"\n    on storage.objects for update to authenticated with check (\n      bucket_id = 'files' and owner = auth.uid()\n    );\n\n    create policy \"Users can delete their own files\"\n    on storage.objects for delete to authenticated using (\n      bucket_id = 'files' and owner = auth.uid()\n    );\n    ```\n\n#### Modify frontend\n\nNext let's update `./app/files/page.tsx` to support file upload.\n\n1.  Setup Supabase client at the top of the component.\n\n    ```tsx\n    const supabase = createClientComponentClient();\n    ```\n\n1.  Handle file upload in the `\u003cInput\u003e`'s `onChange` prop.\n\n    ```tsx\n    await supabase.storage\n      .from('files')\n      .upload(`${crypto.randomUUID()}/${selectedFile.name}`, selectedFile);\n    ```\n\n#### Improve upload RLS policy\n\nWe can improve our previous RLS policy to require a UUID in the uploaded file path.\n\n1.  Create `uuid_or_null()` function.\n\n    ```sql\n    create or replace function private.uuid_or_null(str text)\n    returns uuid\n    language plpgsql\n    as $$\n    begin\n      return str::uuid;\n      exception when invalid_text_representation then\n        return null;\n      end;\n    $$;\n    ```\n\n1.  Modify insert policy to check for UUID in the first path segment _(Postgres arrays are 1-based)_.\n\n    ```sql\n    create policy \"Authenticated users can upload files\"\n    on storage.objects for insert to authenticated with check (\n      bucket_id = 'files' and\n        owner = auth.uid() and\n        private.uuid_or_null(path_tokens[1]) is not null\n    );\n    ```\n\n1.  Apply the migration to our local database.\n\n    ```bash\n    npx supabase migration up\n    ```\n\n    or if you are developing directly on the cloud, push your migrations up:\n\n    ```\n    npx supabase db push\n    ```\n\n---\n\n### `Step 2` - Documents\n\nJump to a step:\n\n1. [Storage](#step-1---storage)\n2. [Documents](#step-2---documents)\n3. [Embeddings](#step-3---embeddings)\n4. [Chat](#step-4---chat)\n5. [Database Types](#step-5---database-types-bonus) (Bonus)\n6. [You're done!](#youre-done)\n\n---\n\n_Use these commands to jump to the `step-2` checkpoint._\n\n```shell\ngit stash push -u -m \"my work on step-1\"\ngit checkout step-2\n```\n\nNext we'll need to process our files for retrieval augmented generation (RAG). Specifically we'll split the contents of our markdown documents by heading, which will allow us to query smaller and more meaningful sections.\n\nLet's create a `documents` and `document_sections` table to store our processed files.\n\n![Documents ER diagram](./assets/step-2-er-diagram.png)\n\n#### Add a new SQL migration\n\n1.  Create migration file.\n\n    ```bash\n    npx supabase migration new documents\n    ```\n\n1.  Enable `pgvector` and `pg_net` extensions.\n\n    We'll use `pg_net` later to send HTTP requests to our edge functions.\n\n    ```sql\n    create extension if not exists pg_net with schema extensions;\n    create extension if not exists vector with schema extensions;\n    ```\n\n1.  Create `documents` table.\n\n    ```sql\n    create table documents (\n      id bigint primary key generated always as identity,\n      name text not null,\n      storage_object_id uuid not null references storage.objects (id),\n      created_by uuid not null references auth.users (id) default auth.uid(),\n      created_at timestamp with time zone not null default now()\n    );\n    ```\n\n1.  We'll also create a view `documents_with_storage_path` that provides easy access to the storage object path.\n\n    ```sql\n    create view documents_with_storage_path\n    with (security_invoker=true)\n    as\n      select documents.*, storage.objects.name as storage_object_path\n      from documents\n      join storage.objects\n        on storage.objects.id = documents.storage_object_id;\n    ```\n\n1.  Create `document_sections` table.\n\n    ```sql\n    create table document_sections (\n      id bigint primary key generated always as identity,\n      document_id bigint not null references documents (id),\n      content text not null,\n      embedding vector (384)\n    );\n    ```\n\n    _Note: Since the video was published, `on delete cascade` was\n    added as a new migration so that the lifecycle of `document_sections`\n    is tied to their respective document._\n\n    ```sql\n    alter table document_sections\n    drop constraint document_sections_document_id_fkey,\n    add constraint document_sections_document_id_fkey\n      foreign key (document_id)\n      references documents(id)\n      on delete cascade;\n    ```\n\n1.  Add HNSW index.\n\n    Unlike IVFFlat indexes, HNSW indexes can be create immediately on an empty table.\n\n    ```sql\n    create index on document_sections using hnsw (embedding vector_ip_ops);\n    ```\n\n1.  Setup RLS to control who has access to which documents.\n\n    ```sql\n    alter table documents enable row level security;\n    alter table document_sections enable row level security;\n\n    create policy \"Users can insert documents\"\n    on documents for insert to authenticated with check (\n      auth.uid() = created_by\n    );\n\n    create policy \"Users can query their own documents\"\n    on documents for select to authenticated using (\n      auth.uid() = created_by\n    );\n\n    create policy \"Users can insert document sections\"\n    on document_sections for insert to authenticated with check (\n      document_id in (\n        select id\n        from documents\n        where created_by = auth.uid()\n      )\n    );\n\n    create policy \"Users can update their own document sections\"\n    on document_sections for update to authenticated using (\n      document_id in (\n        select id\n        from documents\n        where created_by = auth.uid()\n      )\n    ) with check (\n      document_id in (\n        select id\n        from documents\n        where created_by = auth.uid()\n      )\n    );\n\n    create policy \"Users can query their own document sections\"\n    on document_sections for select to authenticated using (\n      document_id in (\n        select id\n        from documents\n        where created_by = auth.uid()\n      )\n    );\n    ```\n\n1.  If developing locally, add `supabase_url` secret to `./supabase/seed.sql`. We will use this to query our Edge Functions within our local environment.\n\n    ```sql\n    select vault.create_secret(\n      'http://api.supabase.internal:8000',\n      'supabase_url'\n    );\n    ```\n\n    If you are developing directly on the cloud, open up the [SQL Editor](https://supabase.com/dashboard/project/_/sql/new) and set this to your Supabase project's API URL:\n\n    ```sql\n    select vault.create_secret(\n      '\u003capi-url\u003e',\n      'supabase_url'\n    );\n    ```\n\n    You can get the project API URL from the [API settings page](https://supabase.com/dashboard/project/_/settings/api).\n\n1.  Create a function to retrieve the URL.\n\n    ```sql\n    create function supabase_url()\n    returns text\n    language plpgsql\n    security definer\n    as $$\n    declare\n      secret_value text;\n    begin\n      select decrypted_secret into secret_value from vault.decrypted_secrets where name = 'supabase_url';\n      return secret_value;\n    end;\n    $$;\n    ```\n\n1.  Create a trigger to process new documents when they're inserted. This uses `pg_net` to send an HTTP request to our Edge Function (coming up next).\n\n    ```sql\n    create function private.handle_storage_update()\n    returns trigger\n    language plpgsql\n    as $$\n    declare\n      document_id bigint;\n      result int;\n    begin\n      insert into documents (name, storage_object_id, created_by)\n        values (new.path_tokens[2], new.id, new.owner)\n        returning id into document_id;\n\n      select\n        net.http_post(\n          url := supabase_url() || '/functions/v1/process',\n          headers := jsonb_build_object(\n            'Content-Type', 'application/json',\n            'Authorization', current_setting('request.headers')::json-\u003e\u003e'authorization'\n          ),\n          body := jsonb_build_object(\n            'document_id', document_id\n          )\n        )\n      into result;\n\n      return null;\n    end;\n    $$;\n\n    create trigger on_file_upload\n      after insert on storage.objects\n      for each row\n      execute procedure private.handle_storage_update();\n    ```\n\n1.  Apply the migration to our local database.\n\n    ```bash\n    npx supabase migration up\n    ```\n\n    or if you are developing directly on the cloud, push your migrations up:\n\n    ```\n    npx supabase db push\n    ```\n\n#### Edge function for `process`\n\n1.  Create the Edge Function file.\n\n    ```bash\n    npx supabase functions new process\n    ```\n\n    This will create the file `./supabase/functions/process/index.ts`.\n\n    Make sure you have the latest version of `deno` installed\n\n    ```bash\n    brew install deno\n    ```\n\n1.  First let's note how dependencies are resolved using an import map - `./supabase/functions/import_map.json`.\n\n    Import maps aren't required in Deno, but they can simplify imports and keep dependency versions consistent. All of our dependencies come from NPM, but since we're using Deno we fetch them from a CDN like https://esm.sh or https://cdn.jsdelivr.net.\n\n    ```json\n    {\n      \"imports\": {\n        \"@std/\": \"https://deno.land/std@0.168.0/\",\n\n        \"@supabase/supabase-js\": \"https://esm.sh/@supabase/supabase-js@2.21.0\",\n        \"openai\": \"https://esm.sh/openai@4.10.0\",\n        \"common-tags\": \"https://esm.sh/common-tags@1.8.2\",\n        \"ai\": \"https://esm.sh/ai@2.2.13\",\n\n        \"mdast-util-from-markdown\": \"https://esm.sh/mdast-util-from-markdown@2.0.0\",\n        \"mdast-util-to-markdown\": \"https://esm.sh/mdast-util-to-markdown@2.1.0\",\n        \"mdast-util-to-string\": \"https://esm.sh/mdast-util-to-string@4.0.0\",\n        \"unist-builder\": \"https://esm.sh/unist-builder@4.0.0\",\n        \"mdast\": \"https://esm.sh/v132/@types/mdast@4.0.0/index.d.ts\",\n\n        \"https://esm.sh/v132/decode-named-character-reference@1.0.2/esnext/decode-named-character-reference.mjs\": \"https://esm.sh/decode-named-character-reference@1.0.2?target=deno\"\n      }\n    }\n    ```\n\n    _Note: URL based imports and import maps aren't a Deno invention. These are a [web standard](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script/type/importmap) that Deno follows as closely as possible._\n\n1.  In `process/index.ts`, first grab the Supabase environment variables.\n\n    ```tsx\n    import { createClient } from '@supabase/supabase-js';\n    import { processMarkdown } from '../_lib/markdown-parser.ts';\n\n    // These are automatically injected\n    const supabaseUrl = Deno.env.get('SUPABASE_URL');\n    const supabaseAnonKey = Deno.env.get('SUPABASE_ANON_KEY');\n\n    Deno.serve(async (req) =\u003e {\n      if (!supabaseUrl || !supabaseAnonKey) {\n        return new Response(\n          JSON.stringify({\n            error: 'Missing environment variables.',\n          }),\n          {\n            status: 500,\n            headers: { 'Content-Type': 'application/json' },\n          }\n        );\n      }\n    });\n    ```\n\n    _Note: These environment variables are automatically injected into the edge runtime for you. Even so, we check for their existence as a TypeScript best practice (type narrowing)._\n\n1.  _(Optional)_ If you are using VS Code, you may get prompted to cache your imported dependencies. You can do this by hitting `cmd`+`shift`+`p` and type `\u003eDeno: Cache Dependencies`.\n\n1.  Create Supabase client and configure it to inherit the original user’s permissions via the authorization header. This way we can continue to take advantage of our RLS policies.\n\n    ```tsx\n    const authorization = req.headers.get('Authorization');\n\n    if (!authorization) {\n      return new Response(\n        JSON.stringify({ error: `No authorization header passed` }),\n        {\n          status: 500,\n          headers: { 'Content-Type': 'application/json' },\n        }\n      );\n    }\n\n    const supabase = createClient(supabaseUrl, supabaseAnonKey, {\n      global: {\n        headers: {\n          authorization,\n        },\n      },\n      auth: {\n        persistSession: false,\n      },\n    });\n    ```\n\n1.  Grab the `document_id` from the request body and query it.\n\n    ```tsx\n    const { document_id } = await req.json();\n\n    const { data: document } = await supabase\n      .from('documents_with_storage_path')\n      .select()\n      .eq('id', document_id)\n      .single();\n\n    if (!document?.storage_object_path) {\n      return new Response(\n        JSON.stringify({ error: 'Failed to find uploaded document' }),\n        {\n          status: 500,\n          headers: { 'Content-Type': 'application/json' },\n        }\n      );\n    }\n    ```\n\n1.  Use the Supabase client to download the file by storage path.\n\n    ```tsx\n    const { data: file } = await supabase.storage\n      .from('files')\n      .download(document.storage_object_path);\n\n    if (!file) {\n      return new Response(\n        JSON.stringify({ error: 'Failed to download storage object' }),\n        {\n          status: 500,\n          headers: { 'Content-Type': 'application/json' },\n        }\n      );\n    }\n\n    const fileContents = await file.text();\n    ```\n\n1.  Process the markdown file and store the resulting subsections into the `document_sections` table.\n\n    _Note: `processMarkdown()` is pre-built into this repository for convenience. Feel free to read through its code to learn how it splits the markdown content._\n\n    ```tsx\n    const processedMd = processMarkdown(fileContents);\n\n    const { error } = await supabase.from('document_sections').insert(\n      processedMd.sections.map(({ content }) =\u003e ({\n        document_id,\n        content,\n      }))\n    );\n\n    if (error) {\n      console.error(error);\n      return new Response(\n        JSON.stringify({ error: 'Failed to save document sections' }),\n        {\n          status: 500,\n          headers: { 'Content-Type': 'application/json' },\n        }\n      );\n    }\n\n    console.log(\n      `Saved ${processedMd.sections.length} sections for file '${document.name}'`\n    );\n    ```\n\n1.  Return a success response.\n\n    ```tsx\n    return new Response(null, {\n      status: 204,\n      headers: { 'Content-Type': 'application/json' },\n    });\n    ```\n\n1.  If developing locally, open a new terminal and serve the edge functions.\n\n    ```bash\n    npx supabase functions serve\n    ```\n\n    _Note: Local Edge Functions are automatically served as part of `npx supabase start`, but this command allows us to also monitor their logs._\n\n    If you're developing directly on the cloud, deploy your edge function:\n\n    ```shell\n    npx supabase functions deploy\n    ```\n\n#### Display documents on the frontend\n\nLet's update `./app/files/page.tsx` to list out the uploaded documents.\n\n1.  At the top of the component, fetch documents using the `useQuery` hook:\n\n    ```tsx\n    const { data: documents } = useQuery(['files'], async () =\u003e {\n      const { data, error } = await supabase\n        .from('documents_with_storage_path')\n        .select();\n\n      if (error) {\n        toast({\n          variant: 'destructive',\n          description: 'Failed to fetch documents',\n        });\n        throw error;\n      }\n\n      return data;\n    });\n    ```\n\n1.  In each document's `onClick` handler, download the respective file.\n\n    ```tsx\n    const { data, error } = await supabase.storage\n      .from('files')\n      .createSignedUrl(document.storage_object_path, 60);\n\n    if (error) {\n      toast({\n        variant: 'destructive',\n        description: 'Failed to download file. Please try again.',\n      });\n      return;\n    }\n\n    window.location.href = data.signedUrl;\n    ```\n\n---\n\n### `Step 3` - Embeddings\n\nJump to a step:\n\n1. [Storage](#step-1---storage)\n2. [Documents](#step-2---documents)\n3. [Embeddings](#step-3---embeddings)\n4. [Chat](#step-4---chat)\n5. [Database Types](#step-5---database-types-bonus) (Bonus)\n6. [You're done!](#youre-done)\n\n---\n\n_Use these commands to jump to the `step-3` checkpoint._\n\n```shell\ngit stash push -u -m \"my work on step-2\"\ngit checkout step-3\n```\n\nNow let's add logic to generate embeddings automatically anytime new rows are added to the `document_sections` table.\n\n#### Create SQL migration\n\n1.  Create migration file\n\n    ```bash\n    npx supabase migration new embed\n    ```\n\n1.  Create `embed()` trigger function. We'll use this general purpose trigger function to asynchronously generate embeddings on arbitrary tables using a new `embed` Edge Function (coming up).\n\n    ```sql\n    create function private.embed()\n    returns trigger\n    language plpgsql\n    as $$\n    declare\n      content_column text = TG_ARGV[0];\n      embedding_column text = TG_ARGV[1];\n      batch_size int = case when array_length(TG_ARGV, 1) \u003e= 3 then TG_ARGV[2]::int else 5 end;\n      timeout_milliseconds int = case when array_length(TG_ARGV, 1) \u003e= 4 then TG_ARGV[3]::int else 5 * 60 * 1000 end;\n      batch_count int = ceiling((select count(*) from inserted) / batch_size::float);\n    begin\n      -- Loop through each batch and invoke an edge function to handle the embedding generation\n      for i in 0 .. (batch_count-1) loop\n      perform\n        net.http_post(\n          url := supabase_url() || '/functions/v1/embed',\n          headers := jsonb_build_object(\n            'Content-Type', 'application/json',\n            'Authorization', current_setting('request.headers')::json-\u003e\u003e'authorization'\n          ),\n          body := jsonb_build_object(\n            'ids', (select json_agg(ds.id) from (select id from inserted limit batch_size offset i*batch_size) ds),\n            'table', TG_TABLE_NAME,\n            'contentColumn', content_column,\n            'embeddingColumn', embedding_column\n          ),\n          timeout_milliseconds := timeout_milliseconds\n        );\n      end loop;\n\n      return null;\n    end;\n    $$;\n    ```\n\n1.  Add embed trigger to `document_sections` table\n\n    ```sql\n    create trigger embed_document_sections\n      after insert on document_sections\n      referencing new table as inserted\n      for each statement\n      execute procedure private.embed(content, embedding);\n    ```\n\n    Note we pass 2 trigger arguments to `embed()`:\n\n    - The first specifies which column contains the text content to embed.\n    - The second specifies the destination column to save the embedding into.\n\n    There are also 2 more optional trigger arguments available:\n\n    ```sql\n    create trigger embed_document_sections\n      after insert on document_sections\n      referencing new table as inserted\n      for each statement\n      execute procedure private.embed(content, embedding, 5, 300000);\n    ```\n\n    - The third argument specifies the batch size (number of records to include in each edge function call). Default is 5.\n    - The fourth argument specifies the HTTP connection timeout for each edge function call. Default is 300000 ms (5 minutes).\n\n    Feel free to adjust these according to your needs. A larger batch size will require a longer timeout per request, since each invocation will have more embeddings to generate. A smaller batch size can use a lower timeout.\n\n    \u003cdetails\u003e\n    \u003csummary\u003e\u003ci\u003eNote: Lifecycle of triggered edge functions\u003c/i\u003e\u003c/summary\u003e\n    If the triggered edge function fails, you will end up with\n    document sections missing embeddings. During development,\n    we can run `supabase db reset` to reset the database. In production,\n    some potential options are:\n\n    - Add another function that can be triggered manually which checks for `document_sections` with missing embeddings and invokes the `/embed` edge function for them.\n    - Create a [scheduled function](https://supabase.com/docs/guides/functions/schedule-functions) that periodically checks for `document_sections` with missing embeddings and re-generates them. We would likely need to add a locking mechanism (ie. via another column) to prevent the scheduled function from conflicting with the normal `embed` trigger.\n    \u003c/details\u003e\n\n1.  Apply the migration to our local database.\n\n    ```bash\n    npx supabase migration up\n    ```\n\n    or if you are developing directly on the cloud, push your migrations up:\n\n    ```\n    npx supabase db push\n    ```\n\n#### Create Edge Function for `embed`\n\n1.  Create edge function file.\n\n    ```bash\n    npx supabase functions new embed\n    ```\n\n1.  In `embed/index.ts`, create an inference session using Supabase's AI inference engine.\n\n    ```tsx\n    // Setup type definitions for built-in Supabase Runtime APIs\n    /// \u003creference types=\"https://esm.sh/@supabase/functions-js/src/edge-runtime.d.ts\" /\u003e\n\n    import { createClient } from '@supabase/supabase-js';\n\n    const model = new Supabase.ai.Session('gte-small');\n    ```\n\n    _Note: The original code from the video tutorial used Transformers.js to perform inference in the Edge Function. We've since released [Supabase.ai APIs](https://supabase.com/docs/guides/functions/ai-models) that can perform inference natively within the runtime itself (vs. WASM) which is faster and uses less CPU time._\n\n1.  Just like before, grab the Supabase variables and check for their existence _(type narrowing)_.\n\n    ```tsx\n    // These are automatically injected\n    const supabaseUrl = Deno.env.get('SUPABASE_URL');\n    const supabaseAnonKey = Deno.env.get('SUPABASE_ANON_KEY');\n\n    Deno.serve(async (req) =\u003e {\n      if (!supabaseUrl || !supabaseAnonKey) {\n        return new Response(\n          JSON.stringify({\n            error: 'Missing environment variables.',\n          }),\n          {\n            status: 500,\n            headers: { 'Content-Type': 'application/json' },\n          }\n        );\n      }\n    });\n    ```\n\n1.  Create a Supabase client and configure to inherit the user’s permissions.\n\n    ```tsx\n    const authorization = req.headers.get('Authorization');\n\n    if (!authorization) {\n      return new Response(\n        JSON.stringify({ error: `No authorization header passed` }),\n        {\n          status: 500,\n          headers: { 'Content-Type': 'application/json' },\n        }\n      );\n    }\n\n    const supabase = createClient(supabaseUrl, supabaseAnonKey, {\n      global: {\n        headers: {\n          authorization,\n        },\n      },\n      auth: {\n        persistSession: false,\n      },\n    });\n    ```\n\n1.  Fetch the text content from the specified table/column.\n\n    ```tsx\n    const { ids, table, contentColumn, embeddingColumn } = await req.json();\n\n    const { data: rows, error: selectError } = await supabase\n      .from(table)\n      .select(`id, ${contentColumn}` as '*')\n      .in('id', ids)\n      .is(embeddingColumn, null);\n\n    if (selectError) {\n      return new Response(JSON.stringify({ error: selectError }), {\n        status: 500,\n        headers: { 'Content-Type': 'application/json' },\n      });\n    }\n    ```\n\n1.  Generate an embedding for each piece of text and update the respective rows.\n\n    ```ts\n    for (const row of rows) {\n      const { id, [contentColumn]: content } = row;\n\n      if (!content) {\n        console.error(`No content available in column '${contentColumn}'`);\n        continue;\n      }\n\n      const output = (await model.run(content, {\n        mean_pool: true,\n        normalize: true,\n      })) as number[];\n\n      const embedding = JSON.stringify(output);\n\n      const { error } = await supabase\n        .from(table)\n        .update({\n          [embeddingColumn]: embedding,\n        })\n        .eq('id', id);\n\n      if (error) {\n        console.error(\n          `Failed to save embedding on '${table}' table with id ${id}`\n        );\n      }\n\n      console.log(\n        `Generated embedding ${JSON.stringify({\n          table,\n          id,\n          contentColumn,\n          embeddingColumn,\n        })}`\n      );\n    }\n    ```\n\n1.  Return a success response.\n\n    ```tsx\n    return new Response(null, {\n      status: 204,\n      headers: { 'Content-Type': 'application/json' },\n    });\n    ```\n\n1.  If you're developing directly on the cloud, deploy your edge function:\n\n    ```shell\n    npx supabase functions deploy\n    ```\n\n---\n\n### `Step 4` - Chat\n\nJump to a step:\n\n1. [Storage](#step-1---storage)\n2. [Documents](#step-2---documents)\n3. [Embeddings](#step-3---embeddings)\n4. [Chat](#step-4---chat)\n5. [Database Types](#step-5---database-types-bonus) (Bonus)\n6. [You're done!](#youre-done)\n\n---\n\n_Use these commands to jump to the `step-4` checkpoint._\n\n```shell\ngit stash push -u -m \"my work on step-3\"\ngit checkout step-4\n```\n\nFinally, let's implement the chat functionality. For this workshop, we're going to generate our query embedding client side using a new custom hook called `usePipeline()`.\n\n#### Update Frontend\n\n1.  Install dependencies\n\n    ```bash\n    npm i @xenova/transformers ai\n    ```\n\n    We'll use [Transformers.js](https://github.com/xenova/transformers.js) to perform inference directly in the browser.\n\n1.  Configure `next.config.js` to support Transformers.js\n\n    ```jsx\n      webpack: (config) =\u003e {\n        config.resolve.alias = {\n          ...config.resolve.alias,\n          sharp$: false,\n          'onnxruntime-node$': false,\n        };\n        return config;\n      },\n    ```\n\n1.  Import dependencies\n\n    ```tsx\n    import { usePipeline } from '@/lib/hooks/use-pipeline';\n    import { createClientComponentClient } from '@supabase/auth-helpers-nextjs';\n    import { useChat } from 'ai/react';\n    ```\n\n    _Note: `usePipeline()` was pre-built into this repository for convenience. It uses Web Workers to asynchronously generate embeddings in another thread using Transformers.js._\n\n1.  Create a Supabase client in `chat/page.tsx`.\n\n    ```tsx\n    const supabase = createClientComponentClient();\n    ```\n\n1.  Create embedding pipeline.\n\n    ```tsx\n    const generateEmbedding = usePipeline(\n      'feature-extraction',\n      'Supabase/gte-small'\n    );\n    ```\n\n    _Note: it's important that the embedding model you set here matches the model used in the Edge Function, otherwise your future matching logic will be meaningless._\n\n    _Transformers.js requires models to exist in the ONNX format. Specifically\n    the Hugging Face model you specify in the pipeline must have an `.onnx` file\n    under the `./onnx` folder, otherwise you will see the error\n    `Could not locate file [...] xxx.onnx`. Check out\n    [this explanation](https://www.youtube.com/watch?v=QdDoFfkVkcw\u0026t=3825s) for more details.\n    To convert an existing model (eg. PyTorch, Tensorflow, etc) to ONNX, see\n    the [custom usage documentation](https://huggingface.co/docs/transformers.js/en/custom_usage#convert-your-models-to-onnx)._\n\n1.  Manage chat messages and state with `useChat()`.\n\n    ```tsx\n    const { messages, input, handleInputChange, handleSubmit, isLoading } =\n      useChat({\n        api: `${process.env.NEXT_PUBLIC_SUPABASE_URL}/functions/v1/chat`,\n      });\n    ```\n\n    _Note: `useChat()` is a convenience hook provided by Vercel's `ai` package to handle chat message state and streaming. We'll point it to an Edge Function called `chat` (coming up)._\n\n1.  Set the ready status to `true` when pipeline has loaded.\n\n    ```tsx\n    const isReady = !!generateEmbedding;\n    ```\n\n1.  Connect `input` and `handleInputChange` to our `\u003cInput\u003e` props.\n\n    ```tsx\n    \u003cInput\n      type=\"text\"\n      autoFocus\n      placeholder=\"Send a message\"\n      value={input}\n      onChange={handleInputChange}\n    /\u003e\n    ```\n\n1.  Generate an embedding and submit messages on form submit.\n\n    ```tsx\n    if (!generateEmbedding) {\n      throw new Error('Unable to generate embeddings');\n    }\n\n    const output = await generateEmbedding(input, {\n      pooling: 'mean',\n      normalize: true,\n    });\n\n    const embedding = JSON.stringify(Array.from(output.data));\n\n    const {\n      data: { session },\n    } = await supabase.auth.getSession();\n\n    if (!session) {\n      return;\n    }\n\n    handleSubmit(e, {\n      options: {\n        headers: {\n          authorization: `Bearer ${session.access_token}`,\n        },\n        body: {\n          embedding,\n        },\n      },\n    });\n    ```\n\n1.  Disable send button until the component is ready.\n\n    ```tsx\n    \u003cButton type=\"submit\" disabled={!isReady}\u003e\n      Send\n    \u003c/Button\u003e\n    ```\n\n#### SQL Migration\n\n1.  Create migration file for a new match function\n\n    ```bash\n    npx supabase migration new match\n    ```\n\n1.  Create a `match_document_sections` Postgres function.\n\n    ```sql\n    create or replace function match_document_sections(\n      embedding vector(384),\n      match_threshold float\n    )\n    returns setof document_sections\n    language plpgsql\n    as $$\n    #variable_conflict use_variable\n    begin\n      return query\n      select *\n      from document_sections\n      where document_sections.embedding \u003c#\u003e embedding \u003c -match_threshold\n    \torder by document_sections.embedding \u003c#\u003e embedding;\n    end;\n    $$;\n    ```\n\n    This function uses pgvector's negative inner product operator `\u003c#\u003e` to perform similarity search. Inner product requires less computations than other distance functions like cosine distance `\u003c=\u003e`, and therefore provides better query performance.\n\n    _Note: Our embeddings are normalized, so inner product and cosine similarity are equivalent in terms of output. Note though that pgvector's `\u003c=\u003e` operator is cosine distance, not cosine similarity, so `inner product == 1 - cosine distance`._\n\n    We also filter by a `match_threshold` in order to return only the most relevant results (1 = most similar, -1 = most dissimilar).\n\n    _Note: `match_threshold` is negated because `\u003c#\u003e` is a negative inner product. See the pgvector docs for more details on why `\u003c#\u003e` is negative._\n\n1.  Apply the migration to our local database.\n\n    ```bash\n    npx supabase migration up\n    ```\n\n    or if you are developing directly on the cloud, push your migrations up:\n\n    ```\n    npx supabase db push\n    ```\n\n#### Create `chat` Edge Function\n\n**Note:** In this tutorial we use models provided by OpenAI to implement the chat logic.\nHowever since making this tutorial, many new LLM providers exist, such as:\n\n- [together.ai](https://docs.together.ai/docs/openai-api-compatibility#nodejs)\n- [fireworks.ai](https://readme.fireworks.ai/docs/openai-compatibility)\n- [endpoints.anyscale.com](https://docs.endpoints.anyscale.com/examples/work-with-openai/)\n- [local models served with Ollama](https://github.com/ollama/ollama/blob/main/docs/openai.md#openai-javascript-library)\n\nWhichever provider you choose, you can reuse the code below (that uses the OpenAI lib) as long as they offer an OpenAI-compatible API _(all of providers listed above do)_. We'll discuss how to do this in each step using Ollama, but the same logic applies to the other providers.\n\n1.  First generate an API key from [OpenAI](https://platform.openai.com/account/api-keys) and save it in `supabase/functions/.env`.\n\n    ```bash\n    cat \u003e supabase/functions/.env \u003c\u003c- EOF\n    OPENAI_API_KEY=\u003cyour-api-key\u003e\n    EOF\n    ```\n\n1.  Create the edge function file.\n\n    ```bash\n    npx supabase functions new chat\n    ```\n\n1.  Load the OpenAI and Supabase keys.\n\n    ```tsx\n    import { createClient } from '@supabase/supabase-js';\n    import { OpenAIStream, StreamingTextResponse } from 'ai';\n    import { codeBlock } from 'common-tags';\n    import OpenAI from 'openai';\n\n    const openai = new OpenAI({\n      apiKey: Deno.env.get('OPENAI_API_KEY'),\n    });\n\n    // These are automatically injected\n    const supabaseUrl = Deno.env.get('SUPABASE_URL');\n    const supabaseAnonKey = Deno.env.get('SUPABASE_ANON_KEY');\n    ```\n\n    \u003cdetails\u003e\n    \u003csummary\u003e\u003ci\u003eNote: Ollama support\u003c/i\u003e\u003c/summary\u003e\n\n    For Ollama (and other OpenAI-compatible providers), adjust the `baseURL` and `apiKey` when instantiating `openai`:\n\n    ```tsx\n    const openai = new OpenAI({\n      baseURL: 'http://host.docker.internal:11434/v1/',\n      apiKey: 'ollama',\n    });\n    ```\n\n    We assume here that you're running `ollama serve` locally\n    with the default port `:11434`.\n    Since local edge functions run inside a Docker container,\n    we specify `host.docker.internal` instead of `localhost`\n    in order to reach Ollama running on your host.\n\n    \u003c/details\u003e\n\n1.  Since our frontend is served at a different domain origin than our Edge Function, we must handle cross origin resource sharing (CORS).\n\n    ```tsx\n    export const corsHeaders = {\n      'Access-Control-Allow-Origin': '*',\n      'Access-Control-Allow-Headers':\n        'authorization, x-client-info, apikey, content-type',\n    };\n\n    Deno.serve(async (req) =\u003e {\n      // Handle CORS\n      if (req.method === 'OPTIONS') {\n        return new Response('ok', { headers: corsHeaders });\n      }\n    });\n    ```\n\n    Handle CORS simply by checking for an `OPTIONS` HTTP request and returning the CORS headers (`*` = allow any domain). In production, consider limiting the origins to specific domains that serve your frontend.\n\n1.  Check for environment variables and create Supabase client.\n\n    ```tsx\n    if (!supabaseUrl || !supabaseAnonKey) {\n      return new Response(\n        JSON.stringify({\n          error: 'Missing environment variables.',\n        }),\n        {\n          status: 500,\n          headers: { 'Content-Type': 'application/json' },\n        }\n      );\n    }\n\n    const authorization = req.headers.get('Authorization');\n\n    if (!authorization) {\n      return new Response(\n        JSON.stringify({ error: `No authorization header passed` }),\n        {\n          status: 500,\n          headers: { 'Content-Type': 'application/json' },\n        }\n      );\n    }\n\n    const supabase = createClient(supabaseUrl, supabaseAnonKey, {\n      global: {\n        headers: {\n          authorization,\n        },\n      },\n      auth: {\n        persistSession: false,\n      },\n    });\n    ```\n\n1.  The first step of RAG is to perform similarity search using our `match_document_sections()` function. Postgres functions are executed using the `.rpc()` method.\n\n    ```tsx\n    const { chatId, message, messages, embedding } = await req.json();\n\n    const { data: documents, error: matchError } = await supabase\n      .rpc('match_document_sections', {\n        embedding,\n        match_threshold: 0.8,\n      })\n      .select('content')\n      .limit(5);\n\n    if (matchError) {\n      console.error(matchError);\n\n      return new Response(\n        JSON.stringify({\n          error: 'There was an error reading your documents, please try again.',\n        }),\n        {\n          status: 500,\n          headers: { 'Content-Type': 'application/json' },\n        }\n      );\n    }\n    ```\n\n1.  The second step of RAG is to build our prompt, injecting in the relevant documents retrieved from our previous similarity search.\n\n    ```tsx\n    const injectedDocs =\n      documents \u0026\u0026 documents.length \u003e 0\n        ? documents.map(({ content }) =\u003e content).join('\\n\\n')\n        : 'No documents found';\n\n    const completionMessages: OpenAI.Chat.Completions.ChatCompletionMessageParam[] =\n      [\n        {\n          role: 'user',\n          content: codeBlock`\n              You're an AI assistant who answers questions about documents.\n    \n              You're a chat bot, so keep your replies succinct.\n    \n              You're only allowed to use the documents below to answer the question.\n    \n              If the question isn't related to these documents, say:\n              \"Sorry, I couldn't find any information on that.\"\n    \n              If the information isn't available in the below documents, say:\n              \"Sorry, I couldn't find any information on that.\"\n    \n              Do not go off topic.\n    \n              Documents:\n              ${injectedDocs}\n            `,\n        },\n        ...messages,\n      ];\n    ```\n\n    _Note: the `codeBlock` template tag is a convenience function that will strip away indentations in our multiline string. This allows us to format our code nicely while preserving the intended indentation._\n\n1.  Finally, create a completion stream and return it.\n\n    ```tsx\n    const completionStream = await openai.chat.completions.create({\n      model: 'gpt-3.5-turbo-0125',\n      messages: completionMessages,\n      max_tokens: 1024,\n      temperature: 0,\n      stream: true,\n    });\n\n    const stream = OpenAIStream(completionStream);\n    return new StreamingTextResponse(stream, { headers: corsHeaders });\n    ```\n\n    `OpenAIStream` and `StreamingTextResponse` are convenience helpers from Vercel's `ai` package that translate OpenAI's response stream into a format that `useChat()` understands on the frontend.\n\n    _Note: we must also return CORS headers here (or anywhere else we send a response)._\n\n    \u003cdetails\u003e\n    \u003csummary\u003e\u003ci\u003eNote: Ollama support\u003c/i\u003e\u003c/summary\u003e\n    Change the model to a model you're serving locally, for example:\n\n    ```diff\n    -     model: 'gpt-3.5-turbo-0125',\n    +     model: 'dolphin-mistral',\n    ```\n\n    \u003c/details\u003e\n\n1.  If you're developing directly on the cloud, set your `OPENAI_API_KEY` secret in the cloud:\n\n    ```shell\n    npx supabase secrets set OPENAI_API_KEY=\u003copenai-key\u003e\n    ```\n\n    Then deploy your edge function:\n\n    ```shell\n    npx supabase functions deploy\n    ```\n\n#### Try it!\n\nLet's try it out! Here are some questions you could try asking:\n\n- What kind of buildings did they live in?\n- What was the most common food?\n- What did people do for fun?\n\n---\n\n### `Step 5` - Database Types (Bonus)\n\nJump to a step:\n\n1. [Storage](#step-1---storage)\n2. [Documents](#step-2---documents)\n3. [Embeddings](#step-3---embeddings)\n4. [Chat](#step-4---chat)\n5. [Database Types](#step-5---database-types-bonus) (Bonus)\n6. [You're done!](#youre-done)\n\n---\n\n_Use these commands to jump to the `step-5` checkpoint._\n\n```shell\ngit stash push -u -m \"my work on step-4\"\ngit checkout step-5\n```\n\nYou may have noticed that all of our DB data coming back from the `supabase` client has had an `any` type (such as `documents` or `document_sections`). This isn't great, since we're missing relevant type information and lose type safety _(making our app more error-prone)_.\n\nThe Supabase CLI comes with a built-in command to generate TypeScript types based on your database's schema.\n\n1. Generate TypeScript types based on local DB schema.\n\n   ```bash\n   supabase gen types typescript --local \u003e supabase/functions/_lib/database.ts\n   ```\n\n1. Add the `\u003cDatabase\u003e` generic to all Supabase clients across our project.\n\n   1. In React\n\n      ```tsx\n      import { Database } from '@/supabase/functions/_lib/database';\n\n      const supabase = createClientComponentClient\u003cDatabase\u003e();\n      ```\n\n      ```tsx\n      import { Database } from '@/supabase/functions/_lib/database';\n\n      const supabase = createServerComponentClient\u003cDatabase\u003e();\n      ```\n\n   1. In Edge Functions\n\n      ```tsx\n      import { Database } from '../_lib/database.ts';\n\n      const supabase = createClient\u003cDatabase\u003e(...);\n      ```\n\n1. Fix type errors 😃\n\n   1. Looks like we found a type error in `./app/files/page.tsx`! Let's add this check to top of the document's click handler _(type narrowing)_.\n\n      ```tsx\n      if (!document.storage_object_path) {\n        toast({\n          variant: 'destructive',\n          description: 'Failed to download file, please try again.',\n        });\n        return;\n      }\n      ```\n\n### You're done!\n\n🎉 Congrats! You've built your own full stack pgvector app in 2 hours.\n\nIf you would like to jump directly to the completed app, simply checkout the `main` branch:\n\n```shell\ngit checkout main\n```\n\nJump to a previous step:\n\n1. [Storage](#step-1---storage)\n2. [Documents](#step-2---documents)\n3. [Embeddings](#step-3---embeddings)\n4. [Chat](#step-4---chat)\n5. [Database Types](#step-5---database-types-bonus) (Bonus)\n6. [You're done!](#youre-done)\n\n## 🚀 Going to prod\n\nIf you've been developing the app locally, follow these instructions to deploy your app to a production Supabase project.\n\n1. Create a Supabase project at https://database.new, or via the CLI:\n\n   ```shell\n   npx supabase projects create -i \"ChatGPT Your Files\"\n   ```\n\n1. Link the CLI with your Supabase project.\n\n   ```bash\n   npx supabase link --project-ref=\u003cproject-ref\u003e\n   ```\n\n   You can grab your project's reference ID in your [project’s settings](https://supabase.com/dashboard/project/_/settings/general).\n\n1. Push migrations to remote database.\n\n   ```bash\n   npx supabase db push\n   ```\n\n1. Set Edge Function secrets (OpenAI key).\n\n   ```bash\n   npx supabase secrets set OPENAI_API_KEY=\u003copenai-key\u003e\n   ```\n\n1. Deploy Edge Functions.\n\n   ```bash\n   npx supabase functions deploy\n   ```\n\n1. Deploy to Vercel _(or CDN of your choice - must support Next.js API routes for authentication)_.\n\n   - Follow Vercel’s [deploy instructions](https://nextjs.org/learn/basics/deploying-nextjs-app/deploy).\n   - Be sure to set `NEXT_PUBLIC_SUPABASE_URL` and `NEXT_PUBLIC_SUPABASE_ANON_KEY` for your Supabase project.\n\n     You can find these in your [project’s API settings](https://supabase.com/dashboard/project/_/settings/api).\n\n## 📈 Next steps\n\nFeel free to extend this app in any way you like. Here are some ideas for next steps:\n\n- Record message history in the database (and generate embeddings on them for RAG memory)\n- Support more file formats than just markdown\n- Pull in documents from the Notion API\n- Restrict chat to user-selected documents\n- Perform RAG on images using CLIP embeddings\n\n## 💬 Feedback and issues\n\nPlease file feedback and issues on the [on this repo's issue board](https://github.com/supabase-community/chatgpt-your-files/issues/new/choose).\n\n## 🔗 Supabase Vector resources\n\n- [Supabase AI \u0026 Vector](https://supabase.com/docs/guides/ai)\n- [pgvector Columns](https://supabase.com/docs/guides/ai/vector-columns)\n- [pgvector Indexes](https://supabase.com/docs/guides/ai/vector-indexes)\n- [Generate Embeddings using Edge Functions](https://supabase.com/docs/guides/ai/quickstarts/generate-text-embeddings)\n- [Going to Production](https://supabase.com/docs/guides/ai/going-to-prod)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsupabase-community%2Fchatgpt-your-files","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsupabase-community%2Fchatgpt-your-files","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsupabase-community%2Fchatgpt-your-files/lists"}