尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

基于Next.js与OpenAI构建全栈AI语音日记应用:从语音识别到语义搜索

基于Next.js与OpenAI构建全栈AI语音日记应用:从语音识别到语义搜索 1. 这篇文章真正要解决的问题你有没有想过每天花几分钟对着手机说话就能自动生成一篇结构清晰、充满洞察的日记这听起来像是科幻电影里的场景但今天一个名为Echologue的开源项目让它变成了现实。这不仅仅是一个简单的录音转文字工具而是一个集成了大语言模型LLM的私人AI语音日记本它能理解你话语中的情绪、提炼核心事件甚至帮你发现连自己都未曾察觉的思考模式。对于开发者而言Echologue 的价值远不止于“又一个AI应用”。它提供了一个绝佳的全栈AI应用实战样本。它清晰地展示了如何将语音识别ASR、大语言模型LLM、向量数据库和现代Web框架Next.js无缝集成构建一个注重隐私、功能闭环的个人生产力工具。如果你正在学习如何将前沿AI能力落地为一个可用的产品或者想为自己的下一个Side Project寻找技术灵感那么深入剖析Echologue的架构与实现将比阅读十篇泛泛而谈的“AI趋势”文章更有收获。本文将带你从零开始深度解析Echologue。我们不仅会复现其核心功能更会探讨其技术选型背后的思考拆解其数据处理流程并基于工程实践给出扩展建议和避坑指南。读完本文你将能理解一个完整AI应用的技术栈构成。掌握使用Next.js、OpenAI API、Supabase等工具构建类似应用的核心流程。获得可直接复用的代码模块用于处理语音、生成文本和存储数据。了解在开发此类应用时关于隐私、成本和性能的关键考量。2. Echologue 是什么核心价值与架构总览Echologue 的核心定位是“私人的AI语音日记”。用户通过语音记录日常应用在后台自动完成转录、内容分析、总结归档并生成可搜索的日记条目。其核心价值在于将零散的、即时的语音想法转化为结构化、可检索的深度文字记录并在此过程中通过AI提供额外的认知辅助。从技术架构上看Echologue 是一个典型的前后端分离的现代Web应用但其数据流中嵌入了多个AI服务。我们可以将其核心流程拆解为以下几个关键环节前端交互层Next.js提供友好的Web界面实现语音录制、播放、日记列表展示和搜索功能。关键点是利用浏览器MediaRecorderAPI进行录音。语音转文本层OpenAI Whisper将用户上传的音频文件如MP3、WAV转换为准确的文字稿。这是将非结构化语音数据转化为可处理文本的第一步。内容理解与增强层OpenAI GPT将转录得到的原始文本送入大语言模型进行“消化”。模型的任务包括生成一个简明的标题、提炼关键要点、分析记录时的情绪基调并为内容打上相关标签。这一步是应用“智能”的核心。数据存储与检索层Supabase使用PostgreSQL数据库存储用户、日记条目等结构化数据。同时利用Supabase的扩展或集成向量数据库如pgvector将日记内容的嵌入向量存储起来以实现基于语义的相似性搜索而不仅仅是关键词匹配。后端服务层Next.js API Routes作为中枢协调前端请求调用外部AI服务OpenAI API并与数据库进行交互。它负责处理敏感的逻辑如API密钥管理和数据处理流水线。整个架构的亮点在于其简洁性和聚焦性。它没有引入过多复杂的微服务而是利用Serverless友好的Next.js API和成熟的外部SaaS服务OpenAI, Supabase快速构建了一个功能完整、可扩展的原型。这对于独立开发者或小团队启动项目极具参考价值。3. 环境准备与核心技术栈选型要开始构建或复现一个类似Echologue的应用你需要准备以下开发环境和技术栈。我们的选型将尽量贴近原项目同时提供可行的替代方案。3.1 开发环境准备Node.js: 版本 18.x 或更高。这是运行Next.js的基础。npm 或 yarn 或 pnpm: 包管理工具推荐使用pnpm以获得更快的安装速度。代码编辑器: VS Code 及其相关扩展如 ES7 React/Redux, Prettier是绝佳选择。Git: 用于版本控制。3.2 核心技术栈详解Next.js 14 (App Router): 作为全栈React框架它同时承担了前端UI渲染和后端API逻辑。App Router提供了更直观的路由和布局定义方式并原生支持React Server Components有利于构建高性能应用。OpenAI API: 提供两大核心能力。Whisper API: 用于语音转文本。相比自行部署Whisper模型使用API省去了处理音频模型、GPU资源的麻烦精度高且稳定。GPT-4/GPT-3.5-Turbo API: 用于文本理解与生成。我们将通过设计特定的system prompt来引导模型完成日记的总结、标题生成和情感分析。Supabase: 开源Firebase替代品提供以下开箱即用的服务PostgreSQL数据库: 存储用户、日记等数据。身份验证Auth: 管理用户注册、登录。存储Storage: 可选用于存储用户上传的原始音频文件。向量扩展pgvector: 用于实现日记内容的语义搜索。Tailwind CSS: 实用优先的CSS框架可以快速构建美观的UI界面。TypeScript: 为整个项目提供类型安全减少运行时错误提升开发体验。3.3 关键账户与密钥申请在开始编码前你需要注册并获取以下服务的密钥OpenAI平台: 访问 platform.openai.com 创建账户并生成API Key。请注意保管并设置使用额度限制。Supabase: 访问 supabase.com 创建一个新项目。项目创建后在设置页面可以找到项目URL和anon/service_role密钥。4. 项目初始化与基础结构搭建让我们从零开始搭建项目的骨架。# 使用 Next.js 官方脚手架创建项目选择 TypeScript 和 Tailwind CSS npx create-next-applatest echologue-demo --typescript --tailwind --app cd echologue-demo # 安装核心依赖 pnpm add openai supabase supabase/supabase-js pnpm add -D types/node # 安装UI组件库可选这里以 shadcn/ui 为例它基于 Tailwind # 初始化 shadcn/ui按提示选择配置 npx shadcn-uilatest init接下来创建项目的基础环境变量文件。在项目根目录创建.env.local并填入你的密钥。# .env.local # OpenAI API 配置 OPENAI_API_KEYsk-your-openai-api-key-here # Supabase 配置 NEXT_PUBLIC_SUPABASE_URLhttps://your-project-ref.supabase.co NEXT_PUBLIC_SUPABASE_ANON_KEYyour-anon-key-here SUPABASE_SERVICE_ROLE_KEYyour-service-role-key-here # 仅用于服务端安全操作 # 应用基础配置 NEXT_PUBLIC_APP_URLhttp://localhost:3000重要安全提示.env.local文件必须被添加到.gitignore中切勿提交到版本库。SUPABASE_SERVICE_ROLE_KEY具有较高权限应在服务器端环境使用绝不要暴露给前端。5. 数据库设计与Supabase配置我们首先在Supabase中设计数据表。登录Supabase控制台进入SQL编辑器执行以下SQL语句来创建核心的journals表。-- 在 Supabase SQL 编辑器中执行 -- 启用 pgvector 扩展用于后续的语义搜索 CREATE EXTENSION IF NOT EXISTS vector; -- 创建 journals 表 CREATE TABLE IF NOT EXISTS journals ( id UUID DEFAULT gen_random_uuid() PRIMARY KEY, user_id UUID REFERENCES auth.users(id) ON DELETE CASCADE NOT NULL, title TEXT, content TEXT NOT NULL, -- 语音转录的原始文本 summary TEXT, -- AI生成的摘要 mood TEXT, -- AI分析的情绪/基调 tags TEXT[], -- AI生成或用户添加的标签数组 audio_url TEXT, -- 存储在Supabase Storage中的音频文件地址可选 transcription_model TEXT, -- 使用的转录模型如whisper-1 embedding vector(1536), -- OpenAI text-embedding-ada-002 生成的向量维度1536 created_at TIMESTAMP WITH TIME ZONE DEFAULT TIMEZONE(utc::text, NOW()) NOT NULL, updated_at TIMESTAMP WITH TIME ZONE DEFAULT TIMEZONE(utc::text, NOW()) NOT NULL ); -- 为快速查询用户日记列表创建索引 CREATE INDEX idx_journals_user_id_created_at ON journals(user_id, created_at DESC); -- 为向量相似性搜索创建索引使用 ivfflat 或 hnsw这里以 ivfflat 为例 -- 注意在有一定数据量后创建索引效果更好 CREATE INDEX ON journals USING ivfflat (embedding vector_cosine_ops) WITH (lists 100);这张表的设计涵盖了Echologue的核心数据content: 原始转录文本是AI分析的原料。summary,mood,tags: AI生成的衍生数据提升了日记的可用性。embedding: 向量字段是实现“搜索你模糊记得的想法”这一功能的关键。user_id和外键约束确保了数据隔离每个用户只能访问自己的日记。接下来在Next.js项目中初始化Supabase客户端。创建lib/supabase.ts。// lib/supabase.ts import { createClient } from supabase/supabase-js; const supabaseUrl process.env.NEXT_PUBLIC_SUPABASE_URL!; const supabaseAnonKey process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!; export const supabase createClient(supabaseUrl, supabaseAnonKey); // 用于服务端操作的客户端使用具有更高权限的service_role key // 注意此客户端仅应在API路由等服务器端环境中使用 const supabaseServiceUrl process.env.NEXT_PUBLIC_SUPABASE_URL!; const supabaseServiceKey process.env.SUPABASE_SERVICE_ROLE_KEY!; export const supabaseAdmin createClient(supabaseServiceUrl, supabaseServiceKey);6. 核心功能实现语音处理与AI分析流水线这是整个应用最核心的部分。我们将实现一个API路由它接收音频文件然后顺序执行上传、转录、AI分析、向量化、存储。6.1 创建语音处理API路由首先创建文件app/api/journal/process/route.ts。// app/api/journal/process/route.ts import { NextRequest, NextResponse } from next/server; import OpenAI from openai; import { supabaseAdmin } from /lib/supabase; import { getServerSession } from next-auth; // 假设使用 next-auth需先安装配置 // 注意实际项目中需配置 next-auth 或其他认证方案获取用户身份 const openai new OpenAI({ apiKey: process.env.OPENAI_API_KEY!, }); export async function POST(request: NextRequest) { // 1. 身份验证 (此处为示例需替换为你的认证逻辑) // const session await getServerSession(); // if (!session?.user?.id) { return NextResponse.json({ error: Unauthorized }, { status: 401 }); } // const userId session.user.id; // 为简化演示我们假设从请求头或body中获取一个模拟userId const formData await request.formData(); const audioFile formData.get(audio) as File; const userId formData.get(userId) as string; // 实际应从认证token获取 if (!audioFile || !userId) { return NextResponse.json({ error: Missing audio file or user ID }, { status: 400 }); } try { // 2. 可选将音频文件上传到 Supabase Storage const audioFileName ${Date.now()}-${audioFile.name}; const { data: uploadData, error: uploadError } await supabaseAdmin .storage .from(journal-audios) // 需先在Supabase控制台创建此存储桶 .upload(user-${userId}/${audioFileName}, audioFile); if (uploadError) throw uploadError; const audioUrl uploadData?.path; // 3. 使用 OpenAI Whisper API 进行语音转文本 const transcriptionResponse await openai.audio.transcriptions.create({ file: audioFile, model: whisper-1, response_format: text, }); const rawTranscript transcriptionResponse.text; // 4. 使用 GPT 分析转录文本 const analysisPrompt 你是一个专业的日记分析助手。请根据用户提供的日记文本生成以下内容 1. 一个简洁、吸引人的标题不超过10个词。 2. 一段概括日记核心内容的摘要约100字。 3. 用一个词或短语描述日记的整体情绪基调如平静、兴奋、反思、焦虑、感恩、充满希望等。 4. 生成3-5个描述日记内容的关键词标签。 日记文本 ${rawTranscript} 请以严格的JSON格式回复包含以下键title, summary, mood, tags。tags是一个字符串数组。 ; const analysisResponse await openai.chat.completions.create({ model: gpt-3.5-turbo, // 或 gpt-4-turbo-preview 以获得更好效果 messages: [{ role: user, content: analysisPrompt }], temperature: 0.7, response_format: { type: json_object }, // 要求返回JSON }); const analysisResult JSON.parse(analysisResponse.choices[0].message.content!); const { title, summary, mood, tags } analysisResult; // 5. 为原始文本生成嵌入向量用于语义搜索 const embeddingResponse await openai.embeddings.create({ model: text-embedding-ada-002, input: rawTranscript, }); const embedding embeddingResponse.data[0].embedding; // 6. 将所有数据存入数据库 const { data: journalEntry, error: dbError } await supabaseAdmin .from(journals) .insert({ user_id: userId, title, content: rawTranscript, summary, mood, tags, audio_url: audioUrl, transcription_model: whisper-1, embedding, }) .select() .single(); if (dbError) throw dbError; return NextResponse.json({ success: true, data: journalEntry, }); } catch (error) { console.error(Journal processing error:, error); return NextResponse.json( { error: Failed to process journal entry, details: (error as Error).message }, { status: 500 } ); } }这个API路由是一个完整的服务端数据处理流水线。它清晰地展示了从原始音频到结构化、可搜索的日记条目的每一步。关键点在于使用system prompt精确引导GPT的输出格式以及将生成的向量存入数据库。6.2 前端语音录制与上传组件现在我们创建一个前端组件来录制语音并调用上面的API。创建components/VoiceRecorder.tsx。// components/VoiceRecorder.tsx use client; import { useState, useRef } from react; import { Button } from /components/ui/button; // 假设使用了 shadcn/ui 的 Button import { Mic, Square, Loader2 } from lucide-react; export default function VoiceRecorder({ userId }: { userId: string }) { const [isRecording, setIsRecording] useState(false); const [isProcessing, setIsProcessing] useState(false); const [recordingTime, setRecordingTime] useState(0); const mediaRecorderRef useRefMediaRecorder | null(null); const audioChunksRef useRefBlob[]([]); const timerRef useRefNodeJS.Timeout | null(null); const startRecording async () { try { const stream await navigator.mediaDevices.getUserMedia({ audio: true }); const mediaRecorder new MediaRecorder(stream); mediaRecorderRef.current mediaRecorder; audioChunksRef.current []; mediaRecorder.ondataavailable (event) { if (event.data.size 0) { audioChunksRef.current.push(event.data); } }; mediaRecorder.onstop async () { const audioBlob new Blob(audioChunksRef.current, { type: audio/webm }); // 或 audio/mp3 await uploadAudio(audioBlob); // 停止所有轨道 stream.getTracks().forEach(track track.stop()); }; mediaRecorder.start(); setIsRecording(true); setRecordingTime(0); timerRef.current setInterval(() { setRecordingTime(prev prev 1); }, 1000); } catch (err) { console.error(Error accessing microphone:, err); alert(无法访问麦克风请检查权限设置。); } }; const stopRecording () { if (mediaRecorderRef.current isRecording) { mediaRecorderRef.current.stop(); setIsRecording(false); if (timerRef.current) { clearInterval(timerRef.current); timerRef.current null; } } }; const uploadAudio async (audioBlob: Blob) { setIsProcessing(true); const formData new FormData(); // 将Blob转换为File对象便于后端处理 const audioFile new File([audioBlob], recording-${Date.now()}.webm, { type: audio/webm }); formData.append(audio, audioFile); formData.append(userId, userId); // 实际应从全局状态如Context/Redux获取 try { const response await fetch(/api/journal/process, { method: POST, body: formData, // 注意不要手动设置 Content-Type浏览器会为 FormData 自动设置正确的 boundary }); if (!response.ok) { const error await response.json(); throw new Error(error.details || 上传失败); } const result await response.json(); alert(日记已成功保存并分析); // 可以触发父组件刷新日记列表 // onSuccess?.(); } catch (error) { console.error(Upload failed:, error); alert(处理失败: ${(error as Error).message}); } finally { setIsProcessing(false); } }; const formatTime (seconds: number) { const mins Math.floor(seconds / 60); const secs seconds % 60; return ${mins.toString().padStart(2, 0)}:${secs.toString().padStart(2, 0)}; }; return ( div classNameflex flex-col items-center gap-4 p-6 border rounded-lg shadow-sm div classNametext-2xl font-semibold{formatTime(recordingTime)}/div Button onClick{isRecording ? stopRecording : startRecording} disabled{isProcessing} variant{isRecording ? destructive : default} sizelg classNamegap-2 {isProcessing ? ( Loader2 classNameh-5 w-5 animate-spin / ) : isRecording ? ( Square classNameh-5 w-5 / ) : ( Mic classNameh-5 w-5 / )} {isProcessing ? 处理中... : isRecording ? 停止录制 : 开始录音日记} /Button p classNametext-sm text-gray-500 text-center {isRecording ? 正在聆听... 点击方块按钮结束录制并自动分析。 : 点击麦克风按钮开始记录你的想法。} /p /div ); }这个组件实现了完整的浏览器端录音逻辑。它使用MediaRecorderAPI将录制的音频数据打包并通过FormData发送到我们之前创建的API端点。7. 实现语义搜索功能Echologue的一个高级功能是基于内容的语义搜索。我们利用存储在journals表中的embedding向量字段来实现。首先创建一个搜索API。// app/api/journal/search/route.ts import { NextRequest, NextResponse } from next/server; import OpenAI from openai; import { supabaseAdmin } from /lib/supabase; const openai new OpenAI({ apiKey: process.env.OPENAI_API_KEY!, }); export async function POST(request: NextRequest) { // 省略身份验证步骤... const { query, userId } await request.json(); if (!query || !userId) { return NextResponse.json({ error: Missing query or user ID }, { status: 400 }); } try { // 1. 将搜索查询文本转换为向量 const embeddingResponse await openai.embeddings.create({ model: text-embedding-ada-002, input: query, }); const queryEmbedding embeddingResponse.data[0].embedding; // 2. 在数据库中执行向量相似性搜索 // Supabase 的 rpc 可以调用数据库函数。这里我们直接使用 match_documents 函数需先在DB中创建 const { data: results, error } await supabaseAdmin.rpc(match_journals, { query_embedding: queryEmbedding, match_threshold: 0.78, // 相似度阈值可调整 match_count: 10, // 返回数量 p_user_id: userId, // 过滤用户 }); if (error) throw error; return NextResponse.json({ success: true, results }); } catch (error) { console.error(Search error:, error); return NextResponse.json({ error: Search failed }, { status: 500 }); } }为了使上述rpc调用生效我们需要在Supabase数据库中创建一个用于向量匹配的PostgreSQL函数。-- 在 Supabase SQL 编辑器中执行 -- 创建用于向量相似性搜索的函数 CREATE OR REPLACE FUNCTION match_journals( query_embedding vector(1536), match_threshold float, match_count int, p_user_id uuid ) RETURNS TABLE ( id uuid, title text, content text, summary text, mood text, tags text[], similarity float ) LANGUAGE plpgsql AS $$ BEGIN RETURN QUERY SELECT journals.id, journals.title, journals.content, journals.summary, journals.mood, journals.tags, 1 - (journals.embedding query_embedding) as similarity -- 是 pgvector 的余弦距离运算符 FROM journals WHERE journals.user_id p_user_id AND 1 - (journals.embedding query_embedding) match_threshold ORDER BY journals.embedding query_embedding LIMIT match_count; END; $$;这个函数计算查询向量与日记向量之间的余弦相似度1 - 距离并返回相似度高于阈值的结果。前端可以调用/api/journal/search传入搜索关键词即可得到语义上相关的日记条目而不仅仅是关键词匹配。8. 运行、验证与效果展示8.1 启动应用确保所有环境变量已正确配置然后启动开发服务器。pnpm dev访问http://localhost:3000。8.2 功能验证流程前端界面你应该能看到一个包含“开始录音日记”按钮的界面。录音测试点击按钮授予麦克风权限说一段话例如“今天项目进展很顺利解决了那个困扰已久的异步状态管理问题感觉如释重负。”然后点击停止。观察网络请求打开浏览器开发者工具的“网络(Network)”选项卡。你会看到一个向/api/journal/process的POST请求。观察其响应应该返回一个包含title、summary、mood、tags等字段的JSON对象。数据库验证登录Supabase控制台进入“表编辑器”查看journals表。应该能看到一条新记录其中content字段是你的原话转录summary等字段是AI生成的分析。搜索测试在应用中构建一个简单的搜索框调用/api/journal/search搜索“开心”或“技术问题”。即使你的原话中没有这些词AI也能通过语义理解找到相关的日记如“如释重负”对应“开心”“异步状态管理”对应“技术问题”。8.3 预期效果转录准确Whisper API对清晰语音的转录准确率很高。分析到位GPT生成的标题如“攻克技术难题的喜悦”摘要能概括核心情绪标签为“轻松/满足”标签可能包含[工作, 成就感, 技术]。搜索相关语义搜索能突破字面匹配找到概念相关的记录。9. 常见问题与排查思路在开发和部署过程中你可能会遇到以下问题问题现象可能原因排查方式解决方案API路由返回 401/4031. 用户未认证。2. Supabase RLS行级安全策略阻止了操作。1. 检查API路由中的认证逻辑。2. 在Supabase控制台的“认证-策略”中检查journals表的RLS策略。1. 实现完整的用户认证如NextAuth.js。2. 为journals表创建允许用户操作自己数据的RLS策略。例如CREATE POLICY 用户可管理自己的日记 ON journals FOR ALL USING (auth.uid() user_id);Whisper转录失败或乱码1. 音频格式或编码不支持。2. 音频文件过大或质量太差。3. OpenAI API配额用尽或密钥错误。1. 检查前端录制的音频格式audio/webm。2. 查看OpenAI API返回的错误信息。3. 检查OpenAI平台用量和余额。1. 在前端将音频转换为mp3或wav格式再上传。2. 限制前端录音时长和文件大小。3. 确保API密钥正确且有效。GPT分析返回非JSON格式system prompt指令不够严格或模型“幻觉”。检查API响应内容看是否是有效的JSON字符串。强化system prompt明确要求返回JSON并指定键名。可以增加“必须”、“严格”等词汇。也可以在代码中添加JSON解析的异常捕获和重试逻辑。向量搜索速度慢或无结果1. 数据量少向量索引未生效。2.match_threshold阈值设置过高。3.embedding字段为空或维度不对。1. 在Supabase中使用EXPLAIN ANALYZE分析查询计划。2. 检查journals表中embedding字段是否有数据。1. 待数据量积累如1000条后索引优势才明显。2. 适当降低match_threshold如0.7。3. 确保调用嵌入API成功且向量维度为1536。前端录音在Safari或iOS上失败不同浏览器对MediaRecorder和音频编码的支持有差异。在目标浏览器中测试查看控制台错误。使用如recordrtc、wavesurfer.js等第三方库处理跨浏览器兼容性问题或提示用户使用Chrome/Firefox。应用部署后API调用超时Serverless函数如Vercel的Serverless Function有执行时长限制默认10秒处理长音频可能超时。查看部署平台的函数日志。1. 优化流程先快速响应前端“已接收”使用队列如Redis Bull异步处理。2. 对于Vercel可考虑使用更长的超时配置或升级计划。10. 最佳实践与工程化建议将原型转化为一个健壮、可维护的项目需要考虑以下几点异步任务队列语音处理和AI分析是耗时操作不应阻塞HTTP响应。最佳实践是引入任务队列如Bull with Redis。API路由只负责接收文件、创建任务ID并立即返回。Worker进程在后台消费任务处理完成后更新数据库状态或通过WebSocket通知前端。错误处理与重试OpenAI API可能因速率限制或临时故障失败。必须实现带有指数退避的重试机制并对关键步骤如转录、分析进行状态记录便于问题追踪和手动修复。成本控制与监控OpenAI API调用是主要成本来源。务必在OpenAI平台设置用量限制和预算警报。记录每次调用的模型、Tokens数量进行成本核算。对于非关键功能如标签生成考虑使用更便宜的模型如gpt-3.5-turbo。隐私与数据安全端到端加密可选但推荐如果对隐私要求极高可以在前端对音频进行加密后再上传密钥由用户自己保管。但这会牺牲语音转录等云端服务能力或需自行部署开源模型。数据清理提供用户数据导出和彻底删除功能满足GDPR等法规要求。审计日志记录关键操作登录、删除、导出的日志。前端状态管理使用状态管理库如Zustand、Redux Toolkit或React Context来管理用户会话、日记列表和加载状态避免Props层层传递。部署与扩展数据库连接池在生产环境中确保Supabase客户端连接被正确复用避免连接数耗尽。CDN for Assets如果存储音频通过Supabase Storage的CDN分发提升播放速度。监控与告警集成Sentry等错误监控并设置关键业务接口如/api/journal/process的健康检查告警。Echologue项目为我们展示了一个AI原生应用的优雅实现路径。它没有追求大而全而是精准地解决了“语音记录 - 智能文本 - 深度回顾”这一核心用户旅程。通过拆解其实现我们不仅学会了如何组合OpenAI的Whisper和GPT API如何利用Supabase的PostgreSQL和向量能力更重要的是理解了如何以开发者视角将一个创意快速转化为可运行、可迭代的产品原型。下一步你可以尝试在此基础上扩展增加多语言支持、引入自定义分析维度如识别对话中的项目名、人名、实现每周/每月自动生成回顾报告或者将向量搜索升级为更复杂的RAG检索增强生成系统让AI基于你的全部日记进行深度问答。技术的乐趣正在于从这样一个清晰的起点构建出无限可能。
返回列表