基于SvelteKit与Supabase构建智能日记应用:全栈开发实战
1. 项目概述一个能与日记对话的智能应用最近在折腾一个挺有意思的副业项目灵感来源于一个很朴素的想法我们每天都在手机或电脑上记录零碎的想法、工作日志或者个人日记但这些记录写完就“沉睡”了很少会回头系统性地分析。如果能有一个工具不仅能安全地存储这些私密文字还能像和一个贴心的朋友聊天一样随时向它提问、让它帮你总结、甚至让它基于你的记录给出建议那该多有用。这就是Chat Journal项目的核心——一个结合了个人日记与ChatGPT智能对话能力的开源 Web 应用。这个项目本质上是一个全栈 Web 应用前端使用Svelte和SvelteKit构建界面基于Skeleton UI和TailwindCSS后端数据存储和用户认证完全交给Supabase而最关键的与ChatGPT的交互逻辑则通过Supabase Edge Functions边缘函数来实现。这意味着你的日记数据完全由你自己掌控部署在自己的 Supabase 项目里与 AI 的对话也在你控制的服务器端环境中进行避免了前端直接暴露 API 密钥的安全风险。对于开发者而言这是一个学习现代全栈技术栈SvelteKit Supabase Serverless Functions的绝佳范例对于最终用户它则提供了一个高度定制化和私密的智能日记体验。接下来我将从技术选型、环境搭建、核心功能实现到部署上线的全流程为你详细拆解这个项目。2. 技术栈深度解析与选型考量为什么是这套技术组合在启动一个项目时技术选型直接决定了开发效率、维护成本和最终的用户体验。Chat Journal 的选择体现了对开发体验、性能和安全性的综合权衡。2.1 前端为什么是 SvelteKit 和 Skeleton UISvelte的核心优势在于其编译时compile-time理念。与 React 或 Vue 在运行时runtime进行虚拟 DOM 比对不同Svelte 在构建阶段就将组件编译成高效的原生 JavaScript 代码直接操作 DOM。这带来的最直观好处是更小的打包体积和更快的运行时性能。对于一个日记应用页面交互复杂如实时聊天、日记列表的增删改查但又不至于像大型社交网络那样需要极其复杂的客户端状态管理Svelte 的简洁心智模型和高效输出非常适合。SvelteKit是 Svelte 的官方全栈框架它解决了单页应用SPA在 SEO、首屏加载、路由、服务端渲染SSR等方面的痛点。对于 Chat Journal使用 SvelteKit 可以轻松实现服务端渲染SSR日记列表页可以优先在服务端渲染好 HTML提升首屏加载速度和 SEO 友好度。API 路由在src/routes/api/目录下创建端点处理一些简单的服务端逻辑。文件式路由像 Next.js 一样基于文件系统自动生成路由开发体验直观。Skeleton UI是一个为 Svelte 量身打造的高质量组件库。选择它而不是从头编写样式或使用其他通用 UI 库如 Bootstrap主要基于两点一是原生集成其组件是纯粹的 Svelte 组件能充分利用 Svelte 的响应式语法没有额外的适配层或性能损耗二是设计系统成熟项目说明中提到其 UI 布局很大程度上参考了 Skeleton 的文档站这意味着开发者能快速搭建出一个美观、一致且响应式的界面将精力集中在业务逻辑而非样式调试上。配合TailwindCSS进行原子化 CSS 工具类开发进一步保证了样式编写的效率和灵活性。2.2 后端与数据层Supabase 的一站式解决方案Supabase是这个项目的基石它扮演了多个关键角色数据库PostgreSQL存储用户信息、日记条目、聊天会话等所有结构化数据。PostgreSQL 的可靠性、JSONB 字段对灵活数据的支持非常适合此类应用。身份认证Auth提供了完整的用户注册、登录、第三方 OAuth如 GitHub, Google集成。supabase-auth和supabase-js库让前端集成变得异常简单。实时订阅Realtime虽然当前项目可能未深度使用但 Supabase 内置的实时功能为未来实现多端日记同步或协作功能预留了可能。存储Storage可以扩展用于存储用户上传的日记附件如图片、语音。边缘函数Edge Functions这是项目与ChatGPT交互的关键。边缘函数是部署在全球边缘网络的 Serverless 函数用TypeScript编写。将调用 OpenAI API 的逻辑放在这里而非浏览器或传统服务器有三大好处安全性敏感的环境变量如 OpenAI API Key完全不会暴露给前端。低延迟边缘网络部署使函数执行更靠近用户减少请求延迟。无服务器无需管理服务器按需执行自动扩缩容。选择 Supabase 而非单独搭建 PostgreSQL 自行编写 Auth 寻找 Serverless 平台极大地降低了后端复杂度和运维成本让独立开发者或小团队能快速构建生产级应用。2.3 通信关键SSEServer-Sent Events与ChatGPT的聊天功能尤其是流式响应打字机效果是通过SSE实现的。为什么不是更常见的 WebSocket对于这种主要由服务器向客户端推送数据AI 回复的流式文本的场景SSE 是更轻量、更简单的选择。它是基于 HTTP 的单向通信协议浏览器原生支持使用简单。在 Supabase Edge Function 中可以方便地创建一个返回text/event-stream类型的响应将ChatGPT API返回的流式数据块chunks实时推送到前端。前端使用EventSourceAPI 或相应的库进行监听和渲染即可实现流畅的聊天体验。注意SSE 是单向的服务端到客户端因此发送消息用户提问仍需通过普通的 HTTP POST 请求到边缘函数。这是一个典型的“请求-流式响应”模式。3. 本地开发环境搭建全指南让我们从零开始把这个项目跑起来。假设你已经有基本的 Node.js、Git 和代码编辑器如 VS Code环境。3.1 获取项目代码与初始化首先将项目代码克隆到本地git clone https://github.com/alexpunct/chatgpt-journal.git cd chatgpt-journal npm install运行npm install会安装所有依赖包括 SvelteKit、Skeleton UI、Supabase 客户端库等。3.2 配置 Supabase云端与本地之选你有两种选择使用 Supabase 的云服务或者在本地使用 Docker 运行。对于开发和测试强烈建议从云服务开始它更简单且功能完整。选项A使用 Supabase 云服务推荐访问 supabase.com 并注册/登录。点击 “New project”创建一个新项目。选择离你近的区域设置数据库密码。项目创建完成后进入Project Settings - API。这里你能看到关键的配置信息Project URL你的 Supabase 实例地址格式如https://xxxxxx.supabase.co。anon/public key这个密钥用于在前端以匿名用户身份与 Supabase 交互受 Row Level Security 策略限制。service_role key务必保密这个密钥拥有绕过 RLS 的权限仅在服务端如 Edge Functions或可信环境中使用。接下来需要将项目中的数据库结构迁移到你的云项目。在本地项目根目录下执行npx supabase link --project-ref your-project-ref npx supabase db push第一条命令将本地项目与云端项目关联your-project-ref在 Project Settings - API 页面找到。第二条命令会将supabase/migrations文件夹下的 SQL 迁移文件推送到云端创建所有必需的数据表如profiles,journals,messages等和 Row Level Security (RLS) 策略。选项B本地 Docker 运行如果你需要完全离线的开发环境或深度定制数据库可以选择本地运行。确保已安装 Docker 和 Docker Compose。在项目根目录Supabase 已经提供了配置。通常你可以运行npx supabase start这个命令会启动一套本地的 Supabase 服务PostgreSQL, Auth, Storage 等。启动后控制台会输出本地服务的 URL 和密钥其使用方式与云服务相同。3.3 配置环境变量与 OpenAI API项目根目录下有一个.env.local.example文件复制它并重命名为.env.localcp .env.local.example .env.local打开.env.local文件你需要填写以下关键变量# 来自 Supabase 项目设置 - API PUBLIC_SUPABASE_URLhttps://xxxxxx.supabase.co PUBLIC_SUPABASE_ANON_KEYyour-anon-key # 用于服务端操作Edge Functions来自 Supabase 项目设置 - API SUPABASE_SERVICE_ROLE_KEYyour-service-role-key # 你的 OpenAI API 密钥从 platform.openai.com 获取 OPENAI_API_KEYsk-...PUBLIC_SUPABASE_URL和PUBLIC_SUPABASE_ANON_KEY是前端环境变量必须以PUBLIC_开头这样 SvelteKit 才能在客户端代码中访问到。SUPABASE_SERVICE_ROLE_KEY和OPENAI_API_KEY是服务器端环境变量不会暴露给浏览器。它们将在部署 Edge Functions 时被使用。3.4 部署 Supabase Edge Functions与ChatGPT交互的核心逻辑位于supabase/functions目录下。你需要将这些函数部署到你的 Supabase 项目中。首先登录 Supabase CLI如果尚未登录npx supabase login部署函数。假设函数目录下有一个名为chat-with-journal的函数npx supabase functions deploy chat-with-journal --project-ref your-project-ref部署时CLI 会自动将.env.local文件中除PUBLIC_开头的变量之外的环境变量即SUPABASE_SERVICE_ROLE_KEY和OPENAI_API_KEY注入到函数运行环境中。这是保证 API 密钥安全的关键。3.5 启动开发服务器完成以上所有配置后运行开发服务器npm run dev访问http://localhost:5173或其他端口根据终端提示你应该能看到应用界面。尝试注册一个账号创建第一篇日记然后就可以开始与你的“AI 日记伙伴”聊天了。实操心得在开发过程中务必关注浏览器的开发者控制台和终端日志。SvelteKit 的热重载非常快但有时环境变量配置错误或 Supabase RLS 策略问题会导致前端静默失败。通过console.log和 Supabase 仪表板的日志查询功能可以高效定位问题。4. 核心功能实现细节剖析了解环境搭建后我们深入代码层看看几个核心功能是如何实现的。4.1 用户认证与数据关系设计用户通过 Supabase Auth 注册登录后会在auth.users系统表中创建一条记录。通常我们需要一个扩展的profiles表来存储用户的显示名、头像等自定义信息。项目中的数据库迁移文件应该已经创建了类似下表-- 示例 profiles 表结构 create table public.profiles ( id uuid references auth.users on delete cascade primary key, username text unique, avatar_url text, updated_at timestamp with time zone );前端通过supabase/supabase-js库进行认证操作// 登录示例 import { supabase } from $lib/supabaseClient; const { data, error } await supabase.auth.signInWithPassword({ email: userexample.com, password: password });登录成功后后续的所有 API 请求都会自动在请求头中携带 JWT 令牌。Supabase 的 PostgreSQL 数据库通过 Row Level Security (RLS) 策略来确保用户只能访问自己的数据。例如为journals表添加的策略可能如下-- 启用 RLS ALTER TABLE journals ENABLE ROW LEVEL SECURITY; -- 策略用户只能插入自己的日记 CREATE POLICY Users can insert their own journal ON journals FOR INSERT WITH CHECK (auth.uid() user_id); -- 策略用户只能查询、更新、删除自己的日记 CREATE POLICY Users can manage own journals ON journals FOR ALL USING (auth.uid() user_id);这样即使用户在前端尝试修改 API 请求去访问其他用户的日记 ID数据库层面也会直接拒绝确保了数据隔离的安全性。4.2 日记的 CRUD 与状态管理日记的创建、读取、更新、删除操作主要通过 Supabase 客户端库完成。在 Svelte 组件中可以利用 Svelte 独特的响应式语法$:来轻松实现状态管理。script langts import { supabase } from $lib/supabaseClient; let journals: Journal[] []; let loading false; // 使用 $: 自动响应式获取日记列表 $: { if (supabase.auth.getUser()) { fetchJournals(); } } async function fetchJournals() { loading true; const { data, error } await supabase .from(journals) .select(*) .order(created_at, { ascending: false }); if (!error) journals data; loading false; } async function createJournal(title: string, content: string) { const { error } await supabase .from(journals) .insert([{ title, content, user_id: (await supabase.auth.getUser()).data.user?.id }]); if (!error) fetchJournals(); // 重新获取列表 } /script这种模式非常直观当用户认证状态或依赖项变化时自动执行数据获取并更新 UI。4.3 AI 聊天功能的实现Edge Function 与 SSE这是项目的精髓。流程如下前端发起请求用户在聊天界面输入问题点击发送。前端不是直接调用 OpenAI API而是向部署好的 Supabase Edge Function 发送一个 POST 请求请求体中包含用户问题、当前日记的上下文或 ID以及会话历史。const response await fetch(https://your-project-ref.supabase.co/functions/v1/chat-with-journal, { method: POST, headers: { Authorization: Bearer ${userAccessToken}, Content-Type: application/json }, body: JSON.stringify({ message: userInput, journalId: activeJournalId }) });Edge Function 处理在supabase/functions/chat-with-journal/index.ts中函数首先验证 JWT 令牌确保是合法用户然后根据journalId从数据库中查询出相关的日记内容作为上下文。构造 Prompt 并调用 OpenAI函数会构造一个精心设计的系统提示词System Prompt例如“你是一个善于分析和总结的日记助手。请基于用户以下的日记内容以友好、支持性的口吻回答用户的问题。日记内容[此处插入日记文本]”。然后将用户的问题作为用户消息User Message调用 OpenAI 的 Chat Completions API并指定stream: true以开启流式传输。import { createClient } from npm:supabase/supabase-js2.38.4; import OpenAI from npm:openai4.20.0; const openai new OpenAI({ apiKey: Deno.env.get(OPENAI_API_KEY)! }); const completionStream await openai.chat.completions.create({ model: gpt-4o-mini, messages: [{ role: system, content: systemPrompt }, { role: user, content: userMessage }], stream: true, });SSE 流式返回函数设置响应头Content-Type: text/event-stream然后遍历 OpenAI 返回的流将每个数据块chunk以 SSE 规定的格式data: chunk\n\n发送回前端。const encoder new TextEncoder(); const stream new ReadableStream({ async start(controller) { for await (const chunk of completionStream) { const content chunk.choices[0]?.delta?.content || ; controller.enqueue(encoder.encode(data: ${JSON.stringify({ content })}\n\n)); } controller.close(); }, }); return new Response(stream, { headers: { Content-Type: text/event-stream, Cache-Control: no-cache }, });前端接收并渲染前端使用EventSource或更现代的fetch配合ReadableStream来读取这个流并实时将返回的文本追加到聊天界面上形成“打字机”效果。注意事项在 Edge Function 中处理流式响应时错误处理至关重要。必须用try...catch包裹核心逻辑并在发生错误时向流中发送一个包含错误信息的特定事件如event: error\ndata: ...前端需要监听这个事件来优雅地提示用户。4.4 不同的 AI 代理Prompts设计项目提到“using a few different agents/prompts”这意味着不仅仅是简单的问答。可以在 Edge Function 中根据用户选择的“模式”或“代理”动态切换系统提示词。例如总结模式提示词侧重于“请用三点总结用户过去一周日记中的主要情绪和事件”。提问模式提示词设计为“请根据日记内容向用户提出三个能引发深度思考的问题”。建议模式提示词可能是“假设你是一位人生教练请基于用户的日记提供一些温和的行动建议”。这些不同的“代理”本质上就是预定义好的、具有不同角色和目标的系统提示词模板。实现上可以在前端提供一个模式选择器将模式标识符传递给 Edge Function函数内部根据标识符选择对应的提示词模板。5. 生产环境部署指南开发完成后你需要将应用部署到公网让用户能够访问。项目作者提到 chatjournal.ai 部署在Vercel上这确实是与 SvelteKit 搭配的黄金组合。5.1 部署前端到 Vercel代码推送将你的代码推送到 GitHub、GitLab 或 Bitbucket 仓库。Vercel 关联在 vercel.com 导入你的仓库。环境变量配置在 Vercel 项目的 Settings - Environment Variables 中添加你在.env.local中配置的PUBLIC_SUPABASE_URL和PUBLIC_SUPABASE_ANON_KEY。注意SUPABASE_SERVICE_ROLE_KEY和OPENAI_API_KEY不应该在这里设置它们只属于 Edge Functions 环境。构建配置Vercel 对 SvelteKit 有自动检测和优化配置。通常无需额外设置它会自动运行npm run build并使用正确的适配器如sveltejs/adapter-vercel。确保你的svelte.config.js中使用了合适的适配器。import adapter from sveltejs/adapter-vercel; export default { kit: { adapter: adapter() } };部署点击部署。Vercel 会自动分配一个生产域名如your-project.vercel.app。5.2 部署 Supabase Edge Functions 到生产环境Edge Functions 的部署我们在开发环节已经用 CLI 做过了supabase functions deploy。在生产环境中你需要确保函数代码已经是最新版本。生产环境 Supabase 项目中的环境变量OPENAI_API_KEY等已正确设置。这可以在 Supabase 项目仪表板的Settings - API - Edge Functions部分进行配置。考虑设置函数的地理区域--region标志以尽可能靠近你的主要用户群体降低延迟。5.3 配置自定义域名与 HTTPSVercel 和 Supabase 都提供了免费的 SSL 证书。在 Vercel 上你可以为自己的域名如chat.yourdomain.com添加 DNS 记录并在 Vercel 控制台配置自定义域名。Supabase Edge Functions 的域名是固定的与你的项目引用关联但你可以通过 Supabase 的自定义域名功能或在前端通过反向代理来统一域名。5.4 性能与监控前端性能SvelteKit 配合 Vercel 的边缘网络能提供优秀的全球访问速度。利用 SvelteKit 的 SSR 和预加载特性可以进一步优化感知性能。数据库与函数监控Supabase 仪表板提供了数据库查询性能、函数调用次数和延迟的监控。定期查看这些指标对于优化查询如为user_id和created_at字段添加索引和函数逻辑至关重要。错误追踪集成像 Sentry 这样的错误监控服务捕获前端和 Edge Functions 中的运行时错误。6. 常见问题与排查技巧实录在实际开发和部署过程中你几乎一定会遇到下面这些问题。这里记录了我的排查思路和解决方案。6.1 认证与 RLS 策略失败问题现象前端操作如查询日记返回 403 错误或提示“行级安全策略拒绝”。检查1用户登录状态确保supabase.auth.getSession()或supabase.auth.getUser()返回了有效的用户会话。在 SvelteKit 的load函数或页面初始化时应等待认证状态确定后再进行数据获取。检查2RLS 策略进入 Supabase 仪表板的Table Editor - 选中表 - Policies确认已为相关表profiles,journals,messages启用了 RLS并且存在正确的策略。一个常见的错误是策略的USING或WITH CHECK子句写错字段名。可以使用 Supabase 提供的SQL Editor运行select * from auth.uid();来测试当前请求的 UID。检查3客户端初始化确保 Supabase 客户端初始化时正确传递了auth的persistSession等选项并且在前端任何需要认证的请求中supabase客户端实例是同一个。6.2 Edge Function 部署或调用失败问题现象部署函数时报错或前端调用函数时返回 5xx 错误。部署失败依赖问题Edge Functions 运行在 Deno 环境中。检查supabase/functions/your-function/deno.json或导入语句确保依赖的第三方模块有明确的版本号且兼容 Deno。使用npm:前缀来导入 npm 包如import OpenAI from npm:openai4.20.0;。权限问题确保 CLI 已登录 (supabase login) 且有项目部署权限。调用失败500错误查看日志在 Supabase 项目仪表板的Functions - 点击对应函数 - Logs中查看详细错误信息。这是最重要的调试手段。环境变量确认函数的环境变量已正确设置。在日志中可以通过Deno.env.get(KEY)来测试是否成功读取但注意不要将敏感信息打印到日志中。函数超时默认超时时间可能较短。对于复杂的 AI 交互可以在部署时增加超时时间supabase functions deploy your-function --timeout 120单位秒。调用失败404错误确认函数名称拼写正确且部署成功。函数的调用 URL 格式为https://[project-ref].supabase.co/functions/v1/[function-name]。6.3 SSE 流式响应在前端中断或不显示问题现象聊天界面卡住只显示部分回复或者完全不显示。检查网络打开浏览器开发者工具的Network标签页找到对 Edge Function 的请求查看其类型是否为text/event-stream状态码是否为 200。查看Response标签是否能看到持续流入的data: {...}事件。前端事件监听确保前端正确使用了EventSource或fetchReadableStream来读取流。使用EventSource时注意监听message事件并解析event.data。使用fetch时确保正确处理分块读取。// 使用 fetch 读取流示例 const response await fetch(functionUrl, options); const reader response.body?.getReader(); const decoder new TextDecoder(); while (true) { const { done, value } await reader.read(); if (done) break; const chunk decoder.decode(value); // 处理每个 chunk通常需要按 \n\n 分割并解析 JSON const lines chunk.split(\n\n).filter(line line.startsWith(data: )); for (const line of lines) { const data JSON.parse(line.replace(data: , )); // 更新 UI } }错误处理Edge Function 中发生错误时可能没有正确关闭流或发送错误事件导致前端流挂起。确保函数内有完整的try...catch并在catch块中向流发送一个明确的错误结束信号。6.4 样式与 UI 组件问题问题现象Skeleton UI 组件不显示或样式错乱。检查导入确保在app.html或布局组件中正确引入了 Skeleton 的主题 CSS 文件。检查 PostCSS 配置Skeleton 和 Tailwind 都依赖 PostCSS。确保postcss.config.js文件存在并正确配置了tailwindcss和autoprefixer。检查类名冲突Tailwind 是原子化的有时自定义的 CSS 可能会意外覆盖组件样式。使用浏览器开发者工具检查元素看预期的 CSS 类是否被应用。6.5 数据库连接与查询性能问题现象应用在数据量大时变慢。建立索引对于经常用于WHERE、ORDER BY或JOIN的字段如journals表的user_id和created_at务必创建索引。CREATE INDEX idx_journals_user_id ON journals(user_id); CREATE INDEX idx_journals_created_at ON journals(created_at DESC);避免 N1 查询在获取日记列表及其关联数据时使用 Supabase 的select进行关联查询而不是在前端循环中发起多个查询。使用分页对于日记列表实现分页查询range而不是一次性拉取所有数据。7. 项目扩展与进阶思考这个开源项目提供了一个强大的基础框架你可以在此基础上进行许多有趣的扩展使其更贴合个人或特定用户群体的需求。1. 多模态日记目前的日记主要是文本。可以集成 Supabase Storage允许用户上传图片、音频甚至短视频作为日记的一部分。在 AI 聊天时可以尝试使用GPT-4V等视觉模型来分析图片日记或者使用 Whisper API 将语音日记转成文本后再进行分析。2. 情感分析与趋势可视化在 Edge Function 中除了进行对话还可以在后台异步调用 OpenAI API 对每篇新日记进行情感分析正面/负面/中性以及具体情绪如喜悦、悲伤、愤怒等并将结果存入数据库的一个新字段如sentiment_score。前端可以据此绘制用户情绪随时间变化的折线图或日历热力图让用户直观了解自己的情绪波动。3. 模板与提示词市场构建一个提示词模板系统。允许用户创建、保存和分享针对不同场景的聊天“代理”模板如“周报生成器”、“旅行回忆提问官”、“健身目标监督员”。甚至可以形成一个社区驱动的提示词市场。4. 离线支持与 PWA利用浏览器的本地存储如 IndexedDB和 Service Worker实现日记的离线撰写和保存。当网络恢复后再同步到云端。将应用安装为 Progressive Web App (PWA)提供接近原生应用的体验。5. 数据导出与隐私增强用户的数据主权感。提供一键导出所有日记和聊天记录为标准格式如 JSON、PDF 或 Markdown的功能。同时在 UI 上明确展示数据存储位置用户的 Supabase 项目并说明 OpenAI API 调用时的数据隐私政策例如日记内容作为 API 请求的一部分会发送到 OpenAI但根据其政策这些数据在一定期限内后不会被用于训练模型。开发这类个人数据与 AI 结合的应用始终要在功能创新与用户隐私、数据安全之间找到平衡。Chat Journal 的开源架构将数据控制权交给开发者或最终用户自己是一个非常好的起点。通过深入理解和定制这个项目你不仅能打造一个完全属于自己的智能日记伴侣还能深刻掌握一套现代、高效的全栈开发范式。
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2579679.html
如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!