├── .gitignore ├── public ├── social_share.png ├── favicon.svg └── logo.svg ├── .env.example ├── tsconfig.json ├── .markdownlint-cli2.cjs ├── src ├── styles │ └── global.css ├── content │ ├── config.ts │ ├── blog-zh │ │ ├── 2025-09-16-instant-model-mentions_zh.md │ │ ├── 2025-09-18-smart-quick-chat-dialog-positioning_zh.md │ │ ├── 2025-08-19-chatollama-deepagents-integration_zh.md │ │ ├── 2025-08-25-langchain-upgrade-chat-fix_zh.md │ │ ├── 2025-08-19-feature-flags-in-docker-and-nuxt_zh.md │ │ ├── 2025-08-18-ui-improvements-and-chat-fixes_zh.md │ │ ├── 2025-08-25-docker-langchain-module-resolution-fix_zh.md │ │ ├── 2025-09-11-building-contextual-quick-chat-inspired-by-ai-ides_zh.md │ │ ├── 2025-08-26-model-api-refactoring-parallel-execution_zh.md │ │ ├── 2025-08-28-openai-langchain-image-parsing-fix_zh.md │ │ └── 2025-09-09-improving-ai-chat-experience-with-smart-title-generation_zh.md │ └── blog │ │ ├── 2025-09-16-instant-model-mentions.md │ │ ├── 2025-09-18-smart-quick-chat-dialog-positioning.md │ │ ├── 2025-08-19-feature-flags-in-docker-and-nuxt.md │ │ ├── 2025-08-18-ui-improvements-and-chat-fixes.md │ │ ├── 2025-08-25-langchain-upgrade-chat-fix.md │ │ ├── 2025-08-19-chatollama-deepagents-integration.md │ │ ├── 2025-09-09-improving-ai-chat-experience-with-smart-title-generation.md │ │ ├── 2025-08-25-docker-langchain-module-resolution-fix.md │ │ └── 2025-08-26-model-api-refactoring-parallel-execution.md ├── pages │ ├── blog │ │ ├── [...slug].astro │ │ └── index.astro │ ├── zh │ │ ├── blog │ │ │ ├── [...slug].astro │ │ │ └── index.astro │ │ └── index.astro │ └── index.astro ├── utils │ ├── blog.ts │ └── i18n.ts ├── components │ └── BlogCard.astro └── layouts │ ├── BlogLayout.astro │ └── BaseLayout.astro ├── astro.config.mjs ├── package.json ├── content ├── zh │ ├── 20250916-instant-model-mentions_zh.md │ ├── 20250819-chatollama-deepagents-integration_zh.md │ ├── 20250825-langchain-upgrade-chat-fix_zh.md │ ├── 20250819-feature-flags-in-docker-and-nuxt_zh.md │ ├── 20250818-ui-improvements-and-chat-fixes_zh.md │ ├── 20250825-docker-langchain-module-resolution-fix_zh.md │ ├── 20250826-model-api-refactoring-parallel-execution_zh.md │ └── 20250828-openai-langchain-image-parsing-fix_zh.md ├── 20250916-instant-model-mentions.md ├── 20250819-feature-flags-in-docker-and-nuxt.md ├── 20250818-ui-improvements-and-chat-fixes.md ├── 20250825-langchain-upgrade-chat-fix.md ├── 20250825-docker-langchain-module-resolution-fix.md ├── 20250826-model-api-refactoring-parallel-execution.md ├── 20250909-improving-ai-chat-experience-with-smart-title-generation.md └── 20250828-openai-langchain-image-parsing-fix.md └── tailwind.config.mjs /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .astro 3 | .DS_Store 4 | dist 5 | .env 6 | -------------------------------------------------------------------------------- /public/social_share.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sugarforever/chat-ollama-blog/main/public/social_share.png -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | # Base site settings 2 | SITE_URL=https://blog.chatollama.cloud 3 | # Set to true to enable draft previews in dev 4 | ENABLE_DRAFTS=false 5 | -------------------------------------------------------------------------------- /public/favicon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "astro/tsconfigs/strict", 3 | "compilerOptions": { 4 | "baseUrl": ".", 5 | "paths": { 6 | "@/*": ["src/*"], 7 | "@/components/*": ["src/components/*"], 8 | "@/layouts/*": ["src/layouts/*"], 9 | "@/utils/*": ["src/utils/*"] 10 | } 11 | } 12 | } -------------------------------------------------------------------------------- /.markdownlint-cli2.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | config: { 3 | default: true, 4 | MD013: false, 5 | MD009: false, 6 | MD022: false, 7 | MD031: false, 8 | MD032: false, 9 | MD025: false, 10 | MD036: false, 11 | MD040: false, 12 | MD041: false, 13 | MD047: false, 14 | MD026: false, 15 | }, 16 | ignores: ['src/content/**/*.mdx'], 17 | }; 18 | -------------------------------------------------------------------------------- /src/styles/global.css: -------------------------------------------------------------------------------- 1 | @import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap'); 2 | 3 | @tailwind base; 4 | @tailwind components; 5 | @tailwind utilities; 6 | 7 | html { 8 | font-family: 'Inter', system-ui, sans-serif; 9 | } 10 | 11 | /* Custom scrollbar */ 12 | ::-webkit-scrollbar { 13 | width: 8px; 14 | } 15 | 16 | ::-webkit-scrollbar-track { 17 | background: #f1f1f1; 18 | } 19 | 20 | ::-webkit-scrollbar-thumb { 21 | background: #c1c1c1; 22 | border-radius: 4px; 23 | } 24 | 25 | ::-webkit-scrollbar-thumb:hover { 26 | background: #a8a8a8; 27 | } -------------------------------------------------------------------------------- /src/content/config.ts: -------------------------------------------------------------------------------- 1 | import { defineCollection, z } from 'astro:content'; 2 | 3 | const blogSchema = z.object({ 4 | title: z.string(), 5 | date: z.string().or(z.date()).optional(), 6 | feature: z.string().optional(), 7 | timeToShip: z.string().optional(), 8 | description: z.string().optional(), 9 | tags: z.array(z.string()).optional(), 10 | }); 11 | 12 | export const collections = { 13 | 'blog': defineCollection({ 14 | type: 'content', 15 | schema: blogSchema, 16 | }), 17 | 'blog-zh': defineCollection({ 18 | type: 'content', 19 | schema: blogSchema, 20 | }), 21 | }; -------------------------------------------------------------------------------- /astro.config.mjs: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'astro/config'; 2 | import mdx from '@astrojs/mdx'; 3 | import tailwind from '@astrojs/tailwind'; 4 | 5 | export default defineConfig({ 6 | site: 'https://blog.chatollama.cloud', 7 | integrations: [ 8 | mdx(), 9 | tailwind({ 10 | applyBaseStyles: false, 11 | }), 12 | ], 13 | markdown: { 14 | shikiConfig: { 15 | theme: 'github-light', 16 | wrap: true 17 | } 18 | }, 19 | i18n: { 20 | defaultLocale: "en", 21 | locales: ["en", "zh"], 22 | routing: { 23 | prefixDefaultLocale: false 24 | } 25 | } 26 | }); -------------------------------------------------------------------------------- /src/pages/blog/[...slug].astro: -------------------------------------------------------------------------------- 1 | --- 2 | import { getCollection } from 'astro:content'; 3 | import BlogLayout from '@/layouts/BlogLayout.astro'; 4 | 5 | export async function getStaticPaths() { 6 | const posts = await getCollection('blog'); 7 | return posts.map((post) => ({ 8 | params: { slug: post.slug }, 9 | props: post, 10 | })); 11 | } 12 | 13 | const post = Astro.props; 14 | const { Content } = await post.render(); 15 | --- 16 | 17 | 24 | 25 | -------------------------------------------------------------------------------- /src/pages/zh/blog/[...slug].astro: -------------------------------------------------------------------------------- 1 | --- 2 | import { getCollection } from 'astro:content'; 3 | import BlogLayout from '@/layouts/BlogLayout.astro'; 4 | 5 | export async function getStaticPaths() { 6 | const posts = await getCollection('blog-zh'); 7 | return posts.map((post) => ({ 8 | params: { slug: post.slug }, 9 | props: post, 10 | })); 11 | } 12 | 13 | const post = Astro.props; 14 | const { Content } = await post.render(); 15 | --- 16 | 17 | 24 | 25 | -------------------------------------------------------------------------------- /src/pages/zh/blog/index.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import BaseLayout from '@/layouts/BaseLayout.astro'; 3 | import BlogCard from '@/components/BlogCard.astro'; 4 | import { getBlogPosts } from '@/utils/blog'; 5 | import { useTranslations } from '@/utils/i18n'; 6 | 7 | const t = useTranslations('zh'); 8 | const posts = await getBlogPosts('zh'); 9 | --- 10 | 11 | 12 |
13 |
14 |

{t('blog.allPosts')}

15 |

16 | {posts.length} 篇关于人工智能、软件开发和创新的文章。 17 |

18 |
19 | 20 |
21 | {posts.map((post) => ( 22 | 23 | ))} 24 |
25 |
26 |
-------------------------------------------------------------------------------- /src/pages/blog/index.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import BaseLayout from '@/layouts/BaseLayout.astro'; 3 | import BlogCard from '@/components/BlogCard.astro'; 4 | import { getBlogPosts } from '@/utils/blog'; 5 | import { useTranslations } from '@/utils/i18n'; 6 | 7 | const t = useTranslations('en'); 8 | const posts = await getBlogPosts('en'); 9 | --- 10 | 11 | 12 |
13 |
14 |

{t('blog.allPosts')}

15 |

16 | {posts.length} posts about AI, software development, and innovation. 17 |

18 |
19 | 20 |
21 | {posts.map((post) => ( 22 | 23 | ))} 24 |
25 |
26 |
-------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "chat-ollama-blog", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "dev": "astro dev", 8 | "start": "astro dev", 9 | "build": "astro build", 10 | "preview": "astro preview", 11 | "astro": "astro" 12 | }, 13 | "repository": { 14 | "type": "git", 15 | "url": "git+https://github.com/sugarforever/chat-ollama-blog.git" 16 | }, 17 | "keywords": [], 18 | "author": "", 19 | "license": "ISC", 20 | "type": "commonjs", 21 | "bugs": { 22 | "url": "https://github.com/sugarforever/chat-ollama-blog/issues" 23 | }, 24 | "homepage": "https://github.com/sugarforever/chat-ollama-blog#readme", 25 | "dependencies": { 26 | "@astrojs/mdx": "^4.3.5", 27 | "@astrojs/tailwind": "^6.0.2", 28 | "@tailwindcss/typography": "^0.5.16", 29 | "@vercel/analytics": "^1.5.0", 30 | "astro": "^5.13.7", 31 | "tailwindcss": "^3.4.17" 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /public/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /src/utils/blog.ts: -------------------------------------------------------------------------------- 1 | import { getCollection } from 'astro:content'; 2 | 3 | export interface BlogPost { 4 | id: string; 5 | slug: string; 6 | body: string; 7 | collection: string; 8 | data: { 9 | title: string; 10 | date: Date; 11 | feature?: string; 12 | timeToShip?: string; 13 | [key: string]: any; 14 | }; 15 | } 16 | 17 | export async function getBlogPosts(locale: 'en' | 'zh' = 'en'): Promise { 18 | try { 19 | const collection = locale === 'zh' ? 'blog-zh' : 'blog'; 20 | const posts = await getCollection(collection); 21 | 22 | return posts 23 | .map(post => ({ 24 | ...post, 25 | data: { 26 | ...post.data, 27 | date: new Date(post.data.date || extractDateFromFilename(post.id)) 28 | } 29 | })) 30 | .sort((a, b) => b.data.date.getTime() - a.data.date.getTime()); 31 | } catch (error) { 32 | console.warn(`No posts found for locale: ${locale}`); 33 | return []; 34 | } 35 | } 36 | 37 | export function extractDateFromFilename(filename: string): string { 38 | const match = filename.match(/(\d{4}-\d{2}-\d{2})/); 39 | return match ? match[1] : new Date().toISOString().split('T')[0]; 40 | } 41 | 42 | -------------------------------------------------------------------------------- /content/zh/20250916-instant-model-mentions_zh.md: -------------------------------------------------------------------------------- 1 | # 使用 @ 提及即时切换模型对话 2 | 3 | **日期:** 2025年9月16日 4 | **功能:** 在消息里直接点名模型,让下一条回复换成对应模型 5 | **上线耗时:** 约 1 天 6 | 7 | ## 🎯 为什么要做这个功能 8 | 9 | 长对话默认绑定同一个模型可以保持语境一致,但如果临时想问问别的模型,就必须反复打开设置面板切换: 10 | 11 | 1. 打开会话设置 12 | 2. 改掉默认模型 13 | 3. 发送问题等待回答 14 | 4. 再切回原来的默认模型 15 | 16 | 当你需要对比不同供应商、或者只想用某个模型补一句答案时,这套流程既慢又容易忘记切回来。我们希望它像标记同事一样轻松。 17 | 18 | ## 💡 功能概述 19 | 20 | 现在在消息中输入 `@模型名` 就能临时“劫持”本次请求,回复会来自被点名的模型,而会话默认模型完全不动。 21 | 22 | - 提及单个模型(例如 `@gpt-4o-mini`)即可只改动本条信息。 23 | - 连续提及多个模型(例如 `@openrouter/claude-3.5-sonnet @llama3`)可以一次性并行提问。 24 | - 不写 `@` 时仍使用会话默认模型,体验与之前一致。 25 | 26 | 输入框在你键入 `@` 后会弹出模型家族与实时搜索,并支持键盘导航,不必死记硬背模型 ID。 27 | 28 | ## 🔍 核心实现细节 29 | 30 | ### 1. 解析提及同时保持提示干净 31 | `ChatInputBox.vue` 在用户输入时同步解析提及,将结果记录在 `hijackedModels`,并把原始文本里的 `@模型` 去掉,生成 `sanitizedContent`。服务器收到的就是这份干净内容,模型提示里不会留下 `@claude` 一类的符号。 32 | 33 | ### 2. 单条消息生效的覆盖策略 34 | `Chat.vue` 在发送前先看 `hijackedModels` 是否存在。一旦检测到,就只对本次请求临时替换模型;会话侧栏的默认配置完全不会被修改。消息历史也会使用 `sanitizedContent`,确保回放时内容干净。 35 | 36 | ### 3. 提及在界面上是“一等公民” 37 | 我们更新了 `ModelMentionText.vue`,无论是简单模型名还是 `openrouter/claude-3.5-sonnet` 这样的命名空间,都能渲染成统一的徽标。自动生成标题的逻辑同样读取去除提及后的文本,不会把对话命名成“@gpt-4o mini 讨论”。 38 | 39 | ## ✅ 最终效果 40 | 41 | - 一键临时切换模型,默认配置安全无副作用。 42 | - 键盘即可操作,适合高频对比不同模型。 43 | - 消息记录与模型提示保持整洁,即使用户频繁提及模型。 44 | 45 | 现在就试试:在输入框里输入 `@`,选择一个模型,享受无阻力的模型对比体验。 46 | -------------------------------------------------------------------------------- /src/content/blog-zh/2025-09-16-instant-model-mentions_zh.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "使用 @ 提及即时切换模型对话" 3 | date: "2025-09-16" 4 | description: "Blog post about 使用 @ 提及即时切换模型对话" 5 | --- 6 | 7 | 8 | **日期:** 2025年9月16日 9 | **功能:** 在消息里直接点名模型,让下一条回复换成对应模型 10 | **上线耗时:** 约 1 天 11 | 12 | ## 🎯 为什么要做这个功能 13 | 14 | 长对话默认绑定同一个模型可以保持语境一致,但如果临时想问问别的模型,就必须反复打开设置面板切换: 15 | 16 | 1. 打开会话设置 17 | 2. 改掉默认模型 18 | 3. 发送问题等待回答 19 | 4. 再切回原来的默认模型 20 | 21 | 当你需要对比不同供应商、或者只想用某个模型补一句答案时,这套流程既慢又容易忘记切回来。我们希望它像标记同事一样轻松。 22 | 23 | ## 💡 功能概述 24 | 25 | 现在在消息中输入 `@模型名` 就能临时“劫持”本次请求,回复会来自被点名的模型,而会话默认模型完全不动。 26 | 27 | - 提及单个模型(例如 `@gpt-4o-mini`)即可只改动本条信息。 28 | - 连续提及多个模型(例如 `@openrouter/claude-3.5-sonnet @llama3`)可以一次性并行提问。 29 | - 不写 `@` 时仍使用会话默认模型,体验与之前一致。 30 | 31 | 输入框在你键入 `@` 后会弹出模型家族与实时搜索,并支持键盘导航,不必死记硬背模型 ID。 32 | 33 | ## 🔍 核心实现细节 34 | 35 | ### 1. 解析提及同时保持提示干净 36 | `ChatInputBox.vue` 在用户输入时同步解析提及,将结果记录在 `hijackedModels`,并把原始文本里的 `@模型` 去掉,生成 `sanitizedContent`。服务器收到的就是这份干净内容,模型提示里不会留下 `@claude` 一类的符号。 37 | 38 | ### 2. 单条消息生效的覆盖策略 39 | `Chat.vue` 在发送前先看 `hijackedModels` 是否存在。一旦检测到,就只对本次请求临时替换模型;会话侧栏的默认配置完全不会被修改。消息历史也会使用 `sanitizedContent`,确保回放时内容干净。 40 | 41 | ### 3. 提及在界面上是“一等公民” 42 | 我们更新了 `ModelMentionText.vue`,无论是简单模型名还是 `openrouter/claude-3.5-sonnet` 这样的命名空间,都能渲染成统一的徽标。自动生成标题的逻辑同样读取去除提及后的文本,不会把对话命名成“@gpt-4o mini 讨论”。 43 | 44 | ## ✅ 最终效果 45 | 46 | - 一键临时切换模型,默认配置安全无副作用。 47 | - 键盘即可操作,适合高频对比不同模型。 48 | - 消息记录与模型提示保持整洁,即使用户频繁提及模型。 49 | 50 | 现在就试试:在输入框里输入 `@`,选择一个模型,享受无阻力的模型对比体验。 51 | -------------------------------------------------------------------------------- /src/content/blog-zh/2025-09-18-smart-quick-chat-dialog-positioning_zh.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "智能快速聊天对话框定位:更好的用户体验" 3 | date: "2025-09-18" 4 | description: "ChatOllama 快速聊天功能的重大改进,具备智能对话框定位和增强的视口感知能力" 5 | --- 6 | 7 | 我们刚刚推出了 ChatOllama 快速聊天功能的重大改进,解决了用户在屏幕边缘选择文本时遇到的常见问题。快速聊天对话框现在可以智能定位,确保始终保持在视口内,同时为 AI 回复提供更多空间。 8 | 9 | ## 问题所在 10 | 11 | 之前,当用户在屏幕右下角或视口边缘附近选择文本时,快速聊天对话框会部分出现在可见区域之外或完全被截断。这使得阅读 AI 回复和与对话框交互变得困难。此外,对话框相当狭窄(320px),限制了可以舒适显示的文本量。 12 | 13 | ## 解决方案 14 | 15 | 我们的新智能定位算法通过几个关键改进解决了这些问题: 16 | 17 | ### 1. 智能定位逻辑 18 | 19 | 对话框现在遵循复杂的定位策略: 20 | 21 | - **水平定位**:首先尝试定位在选定文本的右侧,如果空间不足则定位在左侧,如果两侧都不行则水平居中 22 | - **垂直定位**:首先尝试定位在选择区域下方,如果需要则定位在上方,最后选择垂直居中 23 | - **视口感知**:始终确保对话框保持在屏幕边界内,具有适当的边距 24 | 25 | ### 2. 更大的对话框尺寸 26 | 27 | - **宽度增加**:从 320px 增加到 480px,提高可读性 28 | - **动态高度**:根据回复内容长度自动调整 29 | - **回复区域**:最大高度从 160px 增加到 320px 30 | - **更好的排版**:回复文本大小从超小号增加到小号,提高可读性 31 | 32 | ### 3. 动态内容适应 33 | 34 | 对话框现在根据 AI 回复长度计算最佳尺寸,确保较长的回复有足够的空间,同时保持较短回复的紧凑性。 35 | 36 | ## 技术实现 37 | 38 | 定位算法使用几个关键常量: 39 | 40 | ```typescript 41 | const DIALOG_WIDTH = 480 // 从 320px 增加 42 | const DIALOG_MIN_HEIGHT = 280 43 | const DIALOG_MAX_HEIGHT = 600 // 回复较长时的最大高度 44 | const VIEWPORT_PADDING = 20 45 | const OFFSET_FROM_SELECTION = 10 46 | ``` 47 | 48 | 智能定位逻辑确保对话框: 49 | - 与视口边缘保持 20px 边距 50 | - 与选定文本保持 10px 距离 51 | - 根据回复内容动态调整高度 52 | - 永远不会被截断或出现在可见区域之外 53 | 54 | ## 对用户体验的影响 55 | 56 | 这些改进带来了几个实质性的好处: 57 | 58 | 1. **更好的可访问性**:用户现在可以在屏幕的任何地方选择文本,无需担心对话框定位问题 59 | 2. **改善的可读性**:更大的对话框和文本大小使 AI 回复更容易阅读 60 | 3. **更智能的行为**:对话框自动适应不同的屏幕尺寸和选择位置 -------------------------------------------------------------------------------- /tailwind.config.mjs: -------------------------------------------------------------------------------- 1 | /** @type {import('tailwindcss').Config} */ 2 | export default { 3 | content: ['./src/**/*.{astro,html,js,jsx,md,mdx,svelte,ts,tsx,vue}'], 4 | theme: { 5 | extend: { 6 | fontFamily: { 7 | mono: ['SF Mono', 'Monaco', 'Inconsolata', 'Fira Code', 'Consolas', 'monospace'], 8 | sans: ['Inter', '-apple-system', 'BlinkMacSystemFont', 'sans-serif'], 9 | }, 10 | typography: { 11 | DEFAULT: { 12 | css: { 13 | maxWidth: '65ch', 14 | color: '#000', 15 | a: { 16 | color: '#000', 17 | textDecorationLine: 'underline', 18 | textDecorationColor: '#666', 19 | '&:hover': { 20 | textDecorationColor: '#000', 21 | }, 22 | }, 23 | 'h1,h2,h3,h4': { 24 | color: '#000', 25 | }, 26 | code: { 27 | color: '#000', 28 | backgroundColor: '#f5f5f5', 29 | padding: '0.2em 0.4em', 30 | borderRadius: '0.25rem', 31 | fontWeight: '400', 32 | }, 33 | 'code::before': { 34 | content: '""' 35 | }, 36 | 'code::after': { 37 | content: '""' 38 | }, 39 | pre: { 40 | backgroundColor: '#f8f8f8', 41 | border: '1px solid #e5e5e5', 42 | }, 43 | blockquote: { 44 | borderLeftColor: '#000', 45 | color: '#666', 46 | }, 47 | }, 48 | }, 49 | }, 50 | }, 51 | }, 52 | plugins: [ 53 | require('@tailwindcss/typography'), 54 | ], 55 | } -------------------------------------------------------------------------------- /src/pages/zh/index.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import BaseLayout from '@/layouts/BaseLayout.astro'; 3 | import BlogCard from '@/components/BlogCard.astro'; 4 | import { getBlogPosts } from '@/utils/blog'; 5 | import { useTranslations } from '@/utils/i18n'; 6 | 7 | const t = useTranslations('zh'); 8 | const posts = await getBlogPosts('zh'); 9 | const latestPosts = posts.slice(0, 6); 10 | --- 11 | 12 | 13 |
14 |
15 |

16 | {t('site.subtitle')} 17 |

18 |

19 | {t('meta.description')} 20 |

21 |
22 | 23 | 体验 ChatOllama 24 | 25 | 26 | 27 | 28 |
29 |
30 | 31 |
32 |
33 |

{t('blog.latestPosts')}

34 | 35 | {t('blog.allPosts')} → 36 | 37 |
38 | 39 |
40 | {latestPosts.map((post) => ( 41 | 42 | ))} 43 |
44 |
45 |
46 |
-------------------------------------------------------------------------------- /src/pages/index.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import BaseLayout from '@/layouts/BaseLayout.astro'; 3 | import BlogCard from '@/components/BlogCard.astro'; 4 | import { getBlogPosts } from '@/utils/blog'; 5 | import { useTranslations } from '@/utils/i18n'; 6 | 7 | const t = useTranslations('en'); 8 | const posts = await getBlogPosts('en'); 9 | const latestPosts = posts.slice(0, 6); 10 | --- 11 | 12 | 13 |
14 |
15 |

16 | {t('site.subtitle')} 17 |

18 |

19 | {t('meta.description')} 20 |

21 |
22 | 23 | Try ChatOllama 24 | 25 | 26 | 27 | 28 |
29 |
30 | 31 |
32 |
33 |

{t('blog.latestPosts')}

34 | 35 | {t('blog.allPosts')} → 36 | 37 |
38 | 39 |
40 | {latestPosts.map((post) => ( 41 | 42 | ))} 43 |
44 |
45 |
46 |
-------------------------------------------------------------------------------- /src/components/BlogCard.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import { formatDate, getLangFromUrl, useTranslations, type Locale } from '@/utils/i18n'; 3 | import type { BlogPost } from '@/utils/blog'; 4 | 5 | export interface Props { 6 | post: BlogPost; 7 | } 8 | 9 | const { post } = Astro.props; 10 | const lang = getLangFromUrl(Astro.url) as Locale; 11 | const t = useTranslations(lang); 12 | 13 | const href = lang === 'en' ? `/blog/${post.slug}` : `/zh/blog/${post.slug}`; 14 | --- 15 | 16 |
17 |
18 |

19 | 20 | {post.data.title} 21 | 22 |

23 | 24 |
25 | 28 | {post.data.feature && ( 29 | 30 | {post.data.feature} 31 | 32 | )} 33 | {post.data.timeToShip && ( 34 | 35 | {post.data.timeToShip} 36 | 37 | )} 38 |
39 |
40 | 41 | {post.data.description && ( 42 |

43 | {post.data.description} 44 |

45 | )} 46 | 47 | 58 |
-------------------------------------------------------------------------------- /src/utils/i18n.ts: -------------------------------------------------------------------------------- 1 | export type Locale = 'en' | 'zh'; 2 | 3 | export const languages = { 4 | en: 'English', 5 | zh: '中文' 6 | }; 7 | 8 | export const defaultLang: Locale = 'en'; 9 | 10 | export const ui = { 11 | en: { 12 | 'nav.home': 'Home', 13 | 'nav.blog': 'Blog', 14 | 'nav.about': 'About', 15 | 'nav.project': 'ChatOllama', 16 | 'blog.readMore': 'Read more', 17 | 'blog.allPosts': 'All Posts', 18 | 'blog.latestPosts': 'Latest Posts', 19 | 'blog.publishedOn': 'Published on', 20 | 'meta.description': 'Official blog of ChatOllama - Open source chatbot platform with AI agents, knowledge bases, and multi-modal chat', 21 | 'lang.switch': 'Switch to Chinese', 22 | 'site.title': 'ChatOllama Blog', 23 | 'site.subtitle': 'Updates, features, and insights from the ChatOllama team', 24 | }, 25 | zh: { 26 | 'nav.home': '首页', 27 | 'nav.blog': '博客', 28 | 'nav.about': '关于', 29 | 'nav.project': 'ChatOllama', 30 | 'blog.readMore': '阅读更多', 31 | 'blog.allPosts': '所有文章', 32 | 'blog.latestPosts': '最新文章', 33 | 'blog.publishedOn': '发布于', 34 | 'meta.description': 'ChatOllama 官方博客 - 开源聊天机器人平台,支持AI代理、知识库和多模态聊天', 35 | 'lang.switch': 'Switch to English', 36 | 'site.title': 'ChatOllama 博客', 37 | 'site.subtitle': 'ChatOllama 团队的更新、功能和见解', 38 | } 39 | } as const; 40 | 41 | export function getLangFromUrl(url: URL): Locale { 42 | const [, lang] = url.pathname.split('/'); 43 | if (lang in ui) return lang as Locale; 44 | return defaultLang; 45 | } 46 | 47 | export function useTranslations(lang: Locale) { 48 | return function t(key: keyof typeof ui[typeof defaultLang]) { 49 | return ui[lang][key] || ui[defaultLang][key]; 50 | } 51 | } 52 | 53 | export function formatDate(date: Date, locale: Locale = 'en'): string { 54 | return new Intl.DateTimeFormat(locale === 'zh' ? 'zh-CN' : 'en-US', { 55 | year: 'numeric', 56 | month: 'long', 57 | day: 'numeric' 58 | }).format(date); 59 | } -------------------------------------------------------------------------------- /content/zh/20250819-chatollama-deepagents-integration_zh.md: -------------------------------------------------------------------------------- 1 | # ChatOllama 集成 DeepAgents:为开源 AI 聊天带来深度研究能力 2 | 3 | 大家好!今天想和大家分享一个令人兴奋的更新 —— 我为 ChatOllama 集成了 DeepAgents,这让我们的开源 AI 聊天应用具备了强大的深度研究能力。 4 | 5 | ## 什么是 DeepAgents? 6 | 7 | 在开始之前,让我先介绍一下 DeepAgents。传统的 AI 智能体通常采用简单的"LLM + 工具调用"模式,虽然能完成基本任务,但在面对复杂、多步骤的研究工作时往往力不从心。这些"浅层"智能体缺乏规划能力,无法有效地分解和执行复杂任务。 8 | 9 | DeepAgents 的出现改变了这一现状。它借鉴了 Claude Code、Deep Research 等成功应用的设计理念,通过四个核心组件构建真正"深度"的智能体: 10 | 11 | - **🎯 规划工具**:帮助智能体制定和跟踪结构化计划 12 | - **🤖 子智能体**:专门处理特定任务,实现上下文隔离 13 | - **📁 文件系统**:提供持久化状态管理 14 | - **📝 精细提示**:基于成功案例优化的系统提示 15 | 16 | 这种架构让智能体能够像人类研究员一样工作:分解复杂问题、制定研究计划、调用专门工具、整理和分析信息,最终产出高质量的研究报告。 17 | 18 | ## 为什么选择集成到 ChatOllama? 19 | 20 | 作为一个专注于本地化 AI 体验的开源项目,ChatOllama 一直致力于为用户提供强大而易用的 AI 工具。DeepAgents 的加入让我们能够: 21 | 22 | ### 1. **提供专业级研究能力** 23 | 现在用户可以直接在 ChatOllama 中进行深度研究,智能体会自动: 24 | - 制定研究计划 25 | - 搜索相关信息 26 | - 分析和整合数据 27 | - 生成结构化报告 28 | 29 | ### 2. **无缝的 MCP 集成** 30 | DeepAgents 原生支持 MCP(模型上下文协议),这意味着集成过程异常顺畅。我们只需要: 31 | ```javascript 32 | // 简单的集成代码 33 | const agent = createDeepAgent({ 34 | tools: mcpTools, 35 | instructions: researchInstructions 36 | }) 37 | ``` 38 | 39 | ### 3. **保持开源精神** 40 | DeepAgents 本身就是开源的,这与 ChatOllama 的理念完美契合。用户可以完全控制自己的数据和研究过程。 41 | 42 | ## 技术实现亮点 43 | 44 | ### 智能的流式处理 45 | 我们实现了服务器端的智能内容处理,确保: 46 | - AI 响应内容在服务器端累积,避免客户端的复杂逻辑 47 | - 每个对话轮次使用唯一 UUID 分组,保持上下文清晰 48 | - 工具调用结果以可折叠的 UI 组件展示,用户体验更佳 49 | 50 | ### 工具调用可视化 51 | 当智能体使用工具时,用户可以清楚地看到: 52 | - 调用了哪个工具(搜索、浏览器、文件操作等) 53 | - 工具的执行结果 54 | - 可以展开查看详细信息 55 | 56 | ### 多语言支持 57 | 我们为新功能添加了完整的中英文支持,确保不同语言用户都能获得良好体验。 58 | 59 | ## 实际使用场景 60 | 61 | 想象一下这些使用场景: 62 | 63 | **学术研究**:询问"帮我研究一下量子计算在密码学中的应用",智能体会自动搜索最新论文、分析技术趋势、整理关键观点。 64 | 65 | **市场分析**:请求"分析一下 2024 年 AI 芯片市场的竞争格局",智能体会收集市场数据、分析竞争对手、生成详细报告。 66 | 67 | **技术调研**:提问"比较不同的容器编排方案",智能体会研究各种方案的优缺点、使用场景、最佳实践。 68 | 69 | ## 开发体验 70 | 71 | 得益于 DeepAgents 优秀的架构设计和 MCP 的标准化,整个集成过程非常顺畅: 72 | 73 | 1. **快速集成**:几行代码就能启用深度研究功能 74 | 2. **灵活配置**:可以根据需要调整智能体的指令和工具 75 | 3. **易于扩展**:通过 MCP 可以轻松添加新的工具和能力 76 | 77 | ## 未来展望 78 | 79 | 这只是开始。接下来我们计划: 80 | - 添加更多专业领域的研究模板 81 | - 支持自定义研究工作流 82 | - 集成更多专业工具和数据源 83 | - 优化长时间研究任务的性能 84 | 85 | ## 总结 86 | 87 | DeepAgents 的集成为 ChatOllama 带来了质的飞跃。我们不再只是一个简单的聊天工具,而是成为了一个强大的研究助手。这种能力的提升,加上开源的特性和本地化的优势,让 ChatOllama 在 AI 应用领域更具竞争力。 88 | 89 | 如果你对这个功能感兴趣,欢迎试用最新版本的 ChatOllama,体验 AI 深度研究的魅力。也欢迎在 GitHub 上给我们反馈,让我们一起把这个功能做得更好! 90 | 91 | --- 92 | 93 | *ChatOllama 是一个开源的本地 AI 聊天应用,致力于为用户提供私密、强大、易用的 AI 体验。* 94 | -------------------------------------------------------------------------------- /src/content/blog-zh/2025-08-19-chatollama-deepagents-integration_zh.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "ChatOllama 集成 DeepAgents:为开源 AI 聊天带来深度研究能力" 3 | date: "2025-08-19" 4 | description: "Blog post about ChatOllama 集成 DeepAgents:为开源 AI 聊天带来深度研究能力" 5 | --- 6 | 7 | 8 | 大家好!今天想和大家分享一个令人兴奋的更新 —— 我为 ChatOllama 集成了 DeepAgents,这让我们的开源 AI 聊天应用具备了强大的深度研究能力。 9 | 10 | ## 什么是 DeepAgents? 11 | 12 | 在开始之前,让我先介绍一下 DeepAgents。传统的 AI 智能体通常采用简单的"LLM + 工具调用"模式,虽然能完成基本任务,但在面对复杂、多步骤的研究工作时往往力不从心。这些"浅层"智能体缺乏规划能力,无法有效地分解和执行复杂任务。 13 | 14 | DeepAgents 的出现改变了这一现状。它借鉴了 Claude Code、Deep Research 等成功应用的设计理念,通过四个核心组件构建真正"深度"的智能体: 15 | 16 | - **🎯 规划工具**:帮助智能体制定和跟踪结构化计划 17 | - **🤖 子智能体**:专门处理特定任务,实现上下文隔离 18 | - **📁 文件系统**:提供持久化状态管理 19 | - **📝 精细提示**:基于成功案例优化的系统提示 20 | 21 | 这种架构让智能体能够像人类研究员一样工作:分解复杂问题、制定研究计划、调用专门工具、整理和分析信息,最终产出高质量的研究报告。 22 | 23 | ## 为什么选择集成到 ChatOllama? 24 | 25 | 作为一个专注于本地化 AI 体验的开源项目,ChatOllama 一直致力于为用户提供强大而易用的 AI 工具。DeepAgents 的加入让我们能够: 26 | 27 | ### 1. **提供专业级研究能力** 28 | 现在用户可以直接在 ChatOllama 中进行深度研究,智能体会自动: 29 | - 制定研究计划 30 | - 搜索相关信息 31 | - 分析和整合数据 32 | - 生成结构化报告 33 | 34 | ### 2. **无缝的 MCP 集成** 35 | DeepAgents 原生支持 MCP(模型上下文协议),这意味着集成过程异常顺畅。我们只需要: 36 | ```javascript 37 | // 简单的集成代码 38 | const agent = createDeepAgent({ 39 | tools: mcpTools, 40 | instructions: researchInstructions 41 | }) 42 | ``` 43 | 44 | ### 3. **保持开源精神** 45 | DeepAgents 本身就是开源的,这与 ChatOllama 的理念完美契合。用户可以完全控制自己的数据和研究过程。 46 | 47 | ## 技术实现亮点 48 | 49 | ### 智能的流式处理 50 | 我们实现了服务器端的智能内容处理,确保: 51 | - AI 响应内容在服务器端累积,避免客户端的复杂逻辑 52 | - 每个对话轮次使用唯一 UUID 分组,保持上下文清晰 53 | - 工具调用结果以可折叠的 UI 组件展示,用户体验更佳 54 | 55 | ### 工具调用可视化 56 | 当智能体使用工具时,用户可以清楚地看到: 57 | - 调用了哪个工具(搜索、浏览器、文件操作等) 58 | - 工具的执行结果 59 | - 可以展开查看详细信息 60 | 61 | ### 多语言支持 62 | 我们为新功能添加了完整的中英文支持,确保不同语言用户都能获得良好体验。 63 | 64 | ## 实际使用场景 65 | 66 | 想象一下这些使用场景: 67 | 68 | **学术研究**:询问"帮我研究一下量子计算在密码学中的应用",智能体会自动搜索最新论文、分析技术趋势、整理关键观点。 69 | 70 | **市场分析**:请求"分析一下 2024 年 AI 芯片市场的竞争格局",智能体会收集市场数据、分析竞争对手、生成详细报告。 71 | 72 | **技术调研**:提问"比较不同的容器编排方案",智能体会研究各种方案的优缺点、使用场景、最佳实践。 73 | 74 | ## 开发体验 75 | 76 | 得益于 DeepAgents 优秀的架构设计和 MCP 的标准化,整个集成过程非常顺畅: 77 | 78 | 1. **快速集成**:几行代码就能启用深度研究功能 79 | 2. **灵活配置**:可以根据需要调整智能体的指令和工具 80 | 3. **易于扩展**:通过 MCP 可以轻松添加新的工具和能力 81 | 82 | ## 未来展望 83 | 84 | 这只是开始。接下来我们计划: 85 | - 添加更多专业领域的研究模板 86 | - 支持自定义研究工作流 87 | - 集成更多专业工具和数据源 88 | - 优化长时间研究任务的性能 89 | 90 | ## 总结 91 | 92 | DeepAgents 的集成为 ChatOllama 带来了质的飞跃。我们不再只是一个简单的聊天工具,而是成为了一个强大的研究助手。这种能力的提升,加上开源的特性和本地化的优势,让 ChatOllama 在 AI 应用领域更具竞争力。 93 | 94 | 如果你对这个功能感兴趣,欢迎试用最新版本的 ChatOllama,体验 AI 深度研究的魅力。也欢迎在 GitHub 上给我们反馈,让我们一起把这个功能做得更好! 95 | 96 | --- 97 | 98 | *ChatOllama 是一个开源的本地 AI 聊天应用,致力于为用户提供私密、强大、易用的 AI 体验。* 99 | -------------------------------------------------------------------------------- /content/zh/20250825-langchain-upgrade-chat-fix_zh.md: -------------------------------------------------------------------------------- 1 | # LangChain 核心包版本升级导致聊天功能中断:快速修复记录 2 | 3 | **日期:** 2025年8月25日 4 | **问题:** LangChain 依赖升级后聊天功能中断 5 | **解决时间:** 约4小时 6 | 7 | ## 🐛 问题描述 8 | 9 | 原本是一次常规的 `LangChain` 依赖升级(`0.3.49` -> `0.3.72`),目的是解决 Docker 模块解析问题,但很快就演变成了一个严重的事故。在升级 LangChain 包之后,整个平台的聊天功能完全停止工作。用户无法发送消息或从任何 AI 模型获得响应,这实际上使得 ChatOllama 的核心功能完全不可用。 10 | 11 | 这个问题特别令人沮丧,因为在升级过程中没有明显的错误消息或警告。应用程序正常启动,但每次聊天尝试都会静默失败。 12 | 13 | ## 🔍 根本原因调查 14 | 15 | 通过深入研究日志和跟踪代码,我们发现 LangChain 升级在聊天模型构造函数中引入了破坏性的 API 更改。使这个问题特别棘手的是,这些不是编译时错误——旧的参数名称被简单地忽略了,导致模型使用未定义的配置进行初始化。 16 | 17 | 在 LangChain 升级过程中,ChatOpenAI 模型构造函数中的参数名称发生了一些变化。虽然仅仅被标记为 `deprecated`,但参数在下游的使用中已经发生了变化。deprecated 参数包括: 18 | 19 | - `modelName` 20 | - `openAIApiKey` 21 | 22 | 这些破坏性更改影响了多个模型提供商,每个都需要特定的参数名称更新: 23 | 24 | ### 修复前(有效的): 25 | ```typescript 26 | new ChatOpenAI({ 27 | configuration: { baseURL }, 28 | openAIApiKey: params.key, // ❌ 已弃用 29 | modelName: modelName, // ❌ 已弃用 30 | }) 31 | 32 | new ChatAnthropic({ 33 | anthropicApiUrl: endpoint, 34 | anthropicApiKey: params.key, // ❌ 已弃用 35 | modelName: modelName, // ❌ 已弃用 36 | }) 37 | ``` 38 | 39 | ### 修复后(已修复): 40 | ```typescript 41 | new ChatOpenAI({ 42 | configuration: { baseURL }, 43 | apiKey: params.key, // ✅ 新 API 44 | model: modelName, // ✅ 新 API 45 | }) 46 | 47 | new ChatAnthropic({ 48 | anthropicApiUrl: endpoint, 49 | apiKey: params.key, // ✅ 新 API 50 | model: modelName, // ✅ 新 API 51 | }) 52 | ``` 53 | 54 | ## 🔧 修复实施 55 | 56 | 一旦我们确定了根本原因,修复就相对简单,但需要仔细注意细节。我们需要在所有受影响的模型提供商中更新参数名称,同时确保向后兼容性并添加更好的错误处理。 57 | 58 | 以下模型需要更新: 59 | - **OpenAI (ChatOpenAI)** - 最常用的提供商 60 | - **Anthropic (ChatAnthropic)** - AI 代理功能的关键组件 61 | - **Gemini (ChatGoogleGenerativeAI)** - 用于多模态功能 62 | - **Groq (ChatGroq)** - 高性能推理选项 63 | 64 | 实施的关键更改包括: 65 | 1. 将 `openAIApiKey` 和 `anthropicApiKey` 标准化为统一的 `apiKey` 参数 66 | 2. 在所有提供商中将 `modelName` 更新为更简洁的 `model` 参数 67 | 3. 增强错误处理,在配置缺失时提供清晰的反馈 68 | 69 | 除了修复参数名称,我们还借此机会添加了强大的回退逻辑。现在,当外部 API 提供商由于缺少密钥或配置问题而失败时,系统会优雅地回退到 Ollama,确保用户即使在首选提供商配置错误的情况下也能继续聊天。 70 | 71 | ## 📚 经验教训 72 | 73 | 这次事件强化了在生产应用程序中管理依赖项的几个重要原则: 74 | 75 | **主要升级后彻底测试:** 即使看似微小的版本更新也可能引入不明显的破坏性更改。对所有功能进行全面测试是必要的,不仅仅是您期望受到影响的区域。 76 | 77 | **拥抱 API 标准化:** 虽然最初会造成干扰,但 LangChain 在提供商之间标准化参数名称的举措是一个积极的长期变化,将减少混乱并使代码库更易于维护。 78 | 79 | **始终实施优雅降级:** 拥有强大的回退机制不仅仅是良好的实践——当外部依赖项失败或意外更改时,这对于维护用户信任至关重要。 80 | 81 | ## 🚀 影响和解决方案 82 | 83 | 修复在识别后立即部署,为用户实现了零停机时间。更新的实现在利用新的标准化 API 的同时保持完全的向后兼容性。作为额外的好处,增强的错误处理和回退机制实际上提高了聊天系统的整体可靠性。 84 | 85 | 这次事件提醒我们,在 AI 和机器学习库快速发展的世界中,保持依赖项的最新状态需要持续的警惕和彻底的测试实践。 86 | 87 | --- 88 | 89 | *这是主要升级中"静默"破坏性更改的典型案例——这种情况使经验丰富的开发人员总是会仔细阅读变更日志两遍。一旦确定,修复就很简单,但这次经历突出了为什么我们永远不会把看似常规的更新视为理所当然。* 90 | -------------------------------------------------------------------------------- /src/layouts/BlogLayout.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import BaseLayout from './BaseLayout.astro'; 3 | import { formatDate, getLangFromUrl, useTranslations, type Locale } from '@/utils/i18n'; 4 | 5 | export interface Props { 6 | title: string; 7 | date: Date; 8 | feature?: string; 9 | timeToShip?: string; 10 | description?: string; 11 | } 12 | 13 | const { title, date, feature, timeToShip, description } = Astro.props; 14 | const lang = getLangFromUrl(Astro.url) as Locale; 15 | const t = useTranslations(lang); 16 | 17 | // Enhanced title for blog posts 18 | const fullTitle = `${title} | ChatOllama Blog`; 19 | const blogDescription = description || `${title} - Updates and insights from the ChatOllama team`; 20 | --- 21 | 22 | 23 |
24 |
25 |

{title}

26 | 27 |
28 | 31 | {feature && ( 32 | 33 | {feature} 34 | 35 | )} 36 | {timeToShip && ( 37 | 38 | {timeToShip} 39 | 40 | )} 41 |
42 |
43 | 44 |
45 | 46 |
47 |
48 |
49 | 50 | -------------------------------------------------------------------------------- /src/content/blog-zh/2025-08-25-langchain-upgrade-chat-fix_zh.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "LangChain 核心包版本升级导致聊天功能中断:快速修复记录" 3 | date: "2025-08-25" 4 | description: "Blog post about LangChain 核心包版本升级导致聊天功能中断:快速修复记录" 5 | --- 6 | 7 | 8 | **日期:** 2025年8月25日 9 | **问题:** LangChain 依赖升级后聊天功能中断 10 | **解决时间:** 约4小时 11 | 12 | ## 🐛 问题描述 13 | 14 | 原本是一次常规的 `LangChain` 依赖升级(`0.3.49` -> `0.3.72`),目的是解决 Docker 模块解析问题,但很快就演变成了一个严重的事故。在升级 LangChain 包之后,整个平台的聊天功能完全停止工作。用户无法发送消息或从任何 AI 模型获得响应,这实际上使得 ChatOllama 的核心功能完全不可用。 15 | 16 | 这个问题特别令人沮丧,因为在升级过程中没有明显的错误消息或警告。应用程序正常启动,但每次聊天尝试都会静默失败。 17 | 18 | ## 🔍 根本原因调查 19 | 20 | 通过深入研究日志和跟踪代码,我们发现 LangChain 升级在聊天模型构造函数中引入了破坏性的 API 更改。使这个问题特别棘手的是,这些不是编译时错误——旧的参数名称被简单地忽略了,导致模型使用未定义的配置进行初始化。 21 | 22 | 在 LangChain 升级过程中,ChatOpenAI 模型构造函数中的参数名称发生了一些变化。虽然仅仅被标记为 `deprecated`,但参数在下游的使用中已经发生了变化。deprecated 参数包括: 23 | 24 | - `modelName` 25 | - `openAIApiKey` 26 | 27 | 这些破坏性更改影响了多个模型提供商,每个都需要特定的参数名称更新: 28 | 29 | ### 修复前(有效的): 30 | ```typescript 31 | new ChatOpenAI({ 32 | configuration: { baseURL }, 33 | openAIApiKey: params.key, // ❌ 已弃用 34 | modelName: modelName, // ❌ 已弃用 35 | }) 36 | 37 | new ChatAnthropic({ 38 | anthropicApiUrl: endpoint, 39 | anthropicApiKey: params.key, // ❌ 已弃用 40 | modelName: modelName, // ❌ 已弃用 41 | }) 42 | ``` 43 | 44 | ### 修复后(已修复): 45 | ```typescript 46 | new ChatOpenAI({ 47 | configuration: { baseURL }, 48 | apiKey: params.key, // ✅ 新 API 49 | model: modelName, // ✅ 新 API 50 | }) 51 | 52 | new ChatAnthropic({ 53 | anthropicApiUrl: endpoint, 54 | apiKey: params.key, // ✅ 新 API 55 | model: modelName, // ✅ 新 API 56 | }) 57 | ``` 58 | 59 | ## 🔧 修复实施 60 | 61 | 一旦我们确定了根本原因,修复就相对简单,但需要仔细注意细节。我们需要在所有受影响的模型提供商中更新参数名称,同时确保向后兼容性并添加更好的错误处理。 62 | 63 | 以下模型需要更新: 64 | - **OpenAI (ChatOpenAI)** - 最常用的提供商 65 | - **Anthropic (ChatAnthropic)** - AI 代理功能的关键组件 66 | - **Gemini (ChatGoogleGenerativeAI)** - 用于多模态功能 67 | - **Groq (ChatGroq)** - 高性能推理选项 68 | 69 | 实施的关键更改包括: 70 | 1. 将 `openAIApiKey` 和 `anthropicApiKey` 标准化为统一的 `apiKey` 参数 71 | 2. 在所有提供商中将 `modelName` 更新为更简洁的 `model` 参数 72 | 3. 增强错误处理,在配置缺失时提供清晰的反馈 73 | 74 | 除了修复参数名称,我们还借此机会添加了强大的回退逻辑。现在,当外部 API 提供商由于缺少密钥或配置问题而失败时,系统会优雅地回退到 Ollama,确保用户即使在首选提供商配置错误的情况下也能继续聊天。 75 | 76 | ## 📚 经验教训 77 | 78 | 这次事件强化了在生产应用程序中管理依赖项的几个重要原则: 79 | 80 | **主要升级后彻底测试:** 即使看似微小的版本更新也可能引入不明显的破坏性更改。对所有功能进行全面测试是必要的,不仅仅是您期望受到影响的区域。 81 | 82 | **拥抱 API 标准化:** 虽然最初会造成干扰,但 LangChain 在提供商之间标准化参数名称的举措是一个积极的长期变化,将减少混乱并使代码库更易于维护。 83 | 84 | **始终实施优雅降级:** 拥有强大的回退机制不仅仅是良好的实践——当外部依赖项失败或意外更改时,这对于维护用户信任至关重要。 85 | 86 | ## 🚀 影响和解决方案 87 | 88 | 修复在识别后立即部署,为用户实现了零停机时间。更新的实现在利用新的标准化 API 的同时保持完全的向后兼容性。作为额外的好处,增强的错误处理和回退机制实际上提高了聊天系统的整体可靠性。 89 | 90 | 这次事件提醒我们,在 AI 和机器学习库快速发展的世界中,保持依赖项的最新状态需要持续的警惕和彻底的测试实践。 91 | 92 | --- 93 | 94 | *这是主要升级中"静默"破坏性更改的典型案例——这种情况使经验丰富的开发人员总是会仔细阅读变更日志两遍。一旦确定,修复就很简单,但这次经历突出了为什么我们永远不会把看似常规的更新视为理所当然。* 95 | -------------------------------------------------------------------------------- /content/zh/20250819-feature-flags-in-docker-and-nuxt_zh.md: -------------------------------------------------------------------------------- 1 | # 在 Docker 中启用功能开关:为什么 MCP_ENABLED 没生效?如何修复? 2 | 3 | *2025年8月19日* 4 | 5 | 大家好!👋 6 | 7 | 延续昨天的界面与聊天可靠性优化,今天聊一个在部署中踩到的坑:在本地开发环境里设置 `MCP_ENABLED=true` 一切正常,但在 Docker 容器中却不生效。下面是原因与解决方案。 8 | 9 | ## 🐛 现象 10 | 11 | - **本地开发**:`.env` 中设置 `MCP_ENABLED=true`,设置页能看到「MCP」模块。 12 | - **Docker**:`docker-compose.yaml` 中设置 `MCP_ENABLED=true`,设置页没有出现「MCP」模块。 13 | 14 | ## 🔎 根因:Nuxt runtimeConfig 的构建时 vs 运行时 15 | 16 | Nuxt 3 的 `runtimeConfig` 会在「构建阶段」读取 `process.env`。想在「运行时」覆盖配置,需要使用带有 `NUXT_` 前缀、且能映射到配置键名的环境变量。 17 | 18 | 我们在 `nuxt.config.ts` 中是这样写的: 19 | 20 | ```ts 21 | runtimeConfig: { 22 | knowledgeBaseEnabled: process.env.KNOWLEDGE_BASE_ENABLED === 'true', 23 | realtimeChatEnabled: process.env.REALTIME_CHAT_ENABLED === 'true', 24 | modelsManagementEnabled: process.env.MODELS_MANAGEMENT_ENABLED === 'true', 25 | mcpEnabled: process.env.MCP_ENABLED === 'true', 26 | public: { /* ... */ } 27 | } 28 | ``` 29 | 30 | - 在本地开发中,`.env` 会在构建前加载,因此 `process.env.MCP_ENABLED` 在构建时就为 true → `mcpEnabled` 被“烘焙”为 true。 31 | - 在 Docker 中,我们运行的是预构建镜像。仅在运行时设置 `MCP_ENABLED=true` 无法改变 `runtimeConfig.mcpEnabled`。必须使用 `NUXT_MCP_ENABLED=true` 才能在运行时覆盖。 32 | 33 | 这也解释了为什么 `/api/features` 的日志里 `process.env.MCP_ENABLED` 显示为 true,但 `useRuntimeConfig().mcpEnabled` 仍然是 false。 34 | 35 | ## ✅ 解决方案 36 | 37 | ### 方案 A(推荐):在 Docker 中使用 `NUXT_` 前缀变量 38 | 39 | 修改 `docker-compose.yaml`: 40 | 41 | ```yaml 42 | services: 43 | chatollama: 44 | environment: 45 | - NUXT_MCP_ENABLED=true 46 | - NUXT_KNOWLEDGE_BASE_ENABLED=true 47 | - NUXT_REALTIME_CHAT_ENABLED=true 48 | - NUXT_MODELS_MANAGEMENT_ENABLED=true 49 | ``` 50 | 51 | 这样即可直接在运行时映射到 `runtimeConfig`,无需改代码。 52 | 53 | ### 方案 B:同时兼容旧变量与 `NUXT_` 54 | 55 | 如果希望继续兼容 `MCP_ENABLED`,可以在 `nuxt.config.ts` 中优先读取运行时的 `NUXT_` 变量,并回退到旧变量: 56 | 57 | ```ts 58 | runtimeConfig: { 59 | knowledgeBaseEnabled: process.env.NUXT_KNOWLEDGE_BASE_ENABLED === 'true' || process.env.KNOWLEDGE_BASE_ENABLED === 'true', 60 | realtimeChatEnabled: process.env.NUXT_REALTIME_CHAT_ENABLED === 'true' || process.env.REALTIME_CHAT_ENABLED === 'true', 61 | modelsManagementEnabled: process.env.NUXT_MODELS_MANAGEMENT_ENABLED === 'true' || process.env.MODELS_MANAGEMENT_ENABLED === 'true', 62 | mcpEnabled: process.env.NUXT_MCP_ENABLED === 'true' || process.env.MCP_ENABLED === 'true', 63 | public: { /* ... */ } 64 | } 65 | ``` 66 | 67 | ## 🔧 验证步骤 68 | 69 | 1. 使用更新后的 Compose 环境变量重新部署。 70 | 2. 请求 `/api/features` 并查看容器日志(会打印环境变量与 `runtimeConfig` 值)。 71 | 3. 打开设置页:当 `mcpEnabled` 为 true 时,应显示「MCP」模块。 72 | 73 | ## 🤔 为什么本地可用、Docker 不行? 74 | 75 | - **本地**:`.env` 在构建前加载 → `runtimeConfig` 在构建时就被设置为 true。 76 | - **Docker**:使用预构建镜像 → 运行时覆盖必须使用 `NUXT_` 前缀变量。 77 | 78 | ## 📝 小的开发体验改进(可选) 79 | 80 | - 在 `composables/useFeatures.ts` 的 `FeatureFlags` 接口中补充 `modelsManagementEnabled`,以保持类型完整。 81 | 82 | ## 🎯 总结 83 | 84 | 使用 Nuxt 3 做容器化部署时,牢记:构建时环境变量决定默认值;运行时覆盖需要使用 `NUXT_` 前缀。配置正确后,设置页的功能模块就会在所有环境中一致显示。 85 | -------------------------------------------------------------------------------- /content/zh/20250818-ui-improvements-and-chat-fixes_zh.md: -------------------------------------------------------------------------------- 1 | # 界面优化与聊天可靠性修复 2 | 3 | *2025年8月18日* 4 | 5 | 大家好!👋 6 | 7 | 过去几天我一直在改进聊天界面的一些重要功能。下面是新功能和修复的问题。 8 | 9 | ## 🐛 重要Bug修复 10 | 11 | ### 创建新聊天按钮问题 12 | 最令人沮丧的bug之一是"创建新聊天"按钮无响应。用户点击后没有反应,然后多次点击,突然会创建多个新聊天。 13 | 14 | **问题原因:** 15 | - `scrollToBottom` 函数在DOM元素准备好之前就尝试访问 `messageListEl.value.scrollHeight` 16 | - 没有加载状态保护,快速点击会触发多个API调用 17 | - 聊天创建流程中的竞态条件 18 | 19 | **修复方案:** 20 | ```javascript 21 | // 在 scrollToBottom 中添加空值检查 22 | const scrollToBottom = (_behavior: ScrollBehavior) => { 23 | behavior.value = _behavior 24 | if (messageListEl.value) { 25 | y.value = messageListEl.value.scrollHeight 26 | } 27 | } 28 | 29 | // 在 ChatSessionList 中添加加载状态 30 | const isCreatingChat = ref(false) 31 | 32 | async function onNewChat() { 33 | if (isCreatingChat.value) return 34 | 35 | isCreatingChat.value = true 36 | try { 37 | const data = await createChatSession() 38 | sessionList.value.unshift(data) 39 | await router.push(`/chat/${data.id}`) 40 | } finally { 41 | isCreatingChat.value = false 42 | } 43 | } 44 | ``` 45 | 46 | 这是一个典型的例子,说明小的时序问题如何创造出非常恼人的用户体验问题! 47 | 48 | ## ✨ 新功能:增强预览面板 49 | 50 | 代码工件预览系统得到了重大升级!以前用户只能在基本的侧边面板中查看代码工件。现在我们有了: 51 | 52 | ### 分屏视图模式 53 | - 聊天占用剩余空间 54 | - 预览面板固定500px宽度 55 | - 两者同时可见,便于对照查看 56 | 57 | ### 全屏模式 58 | - 预览覆盖整个视窗 59 | - 完全隐藏标题栏以获得最大查看区域 60 | - 带半透明背景的浮动关闭按钮 61 | - 非常适合查看复杂的HTML演示或详细图表 62 | 63 | ### 智能状态管理 64 | 这比听起来要复杂。关键洞察是将"显示/隐藏预览"状态与"正常/全屏"状态分离: 65 | 66 | ```javascript 67 | // 两个独立状态而不是一个混乱的状态 68 | const showArtifacts = ref(false) 69 | const isFullscreen = ref(false) 70 | 71 | // 智能关闭行为 72 | const closeArtifacts = () => { 73 | showArtifacts.value = false 74 | isFullscreen.value = false // 关闭时重置全屏状态 75 | } 76 | 77 | // 全屏关闭只退出全屏,不关闭预览 78 | const toggleFullscreen = () => { 79 | isFullscreen.value = !isFullscreen.value 80 | } 81 | ``` 82 | 83 | 现在的用户体验流程是: 84 | 1. 点击预览 → 在分屏视图中打开 85 | 2. 点击全屏 → 扩展到全屏 86 | 3. 在全屏中点击X → 返回分屏视图 87 | 4. 在分屏视图中点击X → 完全关闭预览 88 | 89 | ## 🎨 动画优化 90 | 91 | 将预览图标动画从滑入效果改为淡入效果。有时最小的改变会对界面的精致感产生最大的影响。 92 | 93 | ```scss 94 | // 之前:从右侧滑入 95 | .artifact-btn { 96 | opacity: 0; 97 | transform: translateX(8px); 98 | } 99 | 100 | // 之后:简单淡入 101 | .artifact-btn { 102 | opacity: 0; 103 | transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); 104 | } 105 | ``` 106 | 107 | ## 📚 学到的经验 108 | 109 | ### 1. DOM时序问题无处不在 110 | `scrollToBottom` bug提醒我们Vue的响应式很快,但DOM仍然需要时间更新。在访问元素属性之前,始终检查元素是否存在。 111 | 112 | ### 2. 状态管理的复杂性 113 | 最初,我试图让预览系统变得"智能",有可调整大小的分割和复杂状态。但简单往往更好——两种清晰的模式(分屏/全屏)和明显的过渡对用户来说效果更好。 114 | 115 | ### 3. 用户测试揭示边缘情况 116 | 聊天创建bug只在特定时序条件下发生。真实用户行为(当某些东西看起来坏了时快速点击)经常揭示在正常开发测试中不会出现的问题。 117 | 118 | ## 💭 给开发者同行的思考 119 | 120 | 这些UI可靠性修复可能不够华丽,但对用户体验有巨大影响。一个95%时间有效的按钮对用户来说就是坏的。花时间处理边缘情况和竞态条件是区分好界面和优秀界面的关键。 121 | 122 | 另外,在构建预览/模态系统时,始终像考虑进入流程一样考虑退出流程。用户需要理解如何回到他们来自的地方! 123 | 124 | --- 125 | 126 | *你希望下次看到哪些功能得到改进?在issues中留下你的想法!* 127 | 128 | *- 你的开发团队* -------------------------------------------------------------------------------- /content/20250916-instant-model-mentions.md: -------------------------------------------------------------------------------- 1 | # Instantly Talk to Any Model with @ Mentions 2 | 3 | **Date:** September 16, 2025 4 | **Feature:** Tag a model inline to override the next reply without touching session settings 5 | **Time to Ship:** ~1 day 6 | 7 | ## 🎯 Why We Built It 8 | 9 | Our long-form chat sessions have always been anchored to a single "default" model. That kept conversations coherent, but it also meant lots of friction when you wanted to toss a quick question to a different model: 10 | 11 | 1. Open session settings 12 | 2. Swap the default model 13 | 3. Ask the question 14 | 4. Switch the model back so the rest of the thread stays consistent 15 | 16 | That dance got even worse when comparing outputs from multiple providers or when you only needed one specialty answer (e.g. “let me sanity-check this with Claude real quick”). We needed something as fast as tagging a teammate in Slack. 17 | 18 | ## 💡 The Solution in a Nutshell 19 | 20 | You can now add `@model-name` to any outgoing message. Each mentioned model temporarily "hijacks" the request, so the response comes from that model while the session’s default choice remains unchanged. 21 | 22 | - Mention a single model (`@gpt-4o-mini`) to reroute just this message. 23 | - Mention multiple models (`@openrouter/claude-3.5-sonnet @llama3`) to fan out the question. 24 | - Don’t mention anything and the chat continues using the default model like before. 25 | 26 | The UI keeps the override discoverable: as soon as you type `@`, the input shows model families, live search, and keyboard navigation so you never have to remember exact IDs. 27 | 28 | ## 🔍 What Changed Under the Hood 29 | 30 | ### 1. Parsing Mentions Without Polluting Prompts 31 | `ChatInputBox.vue` now parses model mentions every time the user types. We persist the selected models in `hijackedModels` and simultaneously strip the `@model` tokens into a `sanitizedContent` payload. That sanitized payload is what the backend actually sees, so no model receives literal `@claude` strings in its prompt. 32 | 33 | ### 2. Respecting Overrides in Delivery 34 | `Chat.vue` checks for `hijackedModels` before sending. If we find any, we bypass the session defaults for that single send, while the session metadata (and sidebar defaults) stay untouched. The outgoing transcript reuses `sanitizedContent` so historical messages read cleanly without the mentions. 35 | 36 | ### 3. Making Mentions First-Class Citizens in the UI 37 | We refreshed `ModelMentionText.vue` to show consistent badges for mentions, even when the provider uses namespaced values like `openrouter/claude-3.5-sonnet`. The existing auto-title generator also consumes the sanitized message to avoid naming chats "@gpt-4o mini thoughts". 38 | 39 | ## ✅ Results 40 | 41 | - Instant context switches without breaking the session model. 42 | - Keyboard-only flow for power users comparing providers. 43 | - Clean transcripts and prompts even when users pepper messages with mentions. 44 | 45 | Give it a try: type `@` in any chat input, pick a model, and enjoy frictionless comparisons. 46 | -------------------------------------------------------------------------------- /src/content/blog-zh/2025-08-19-feature-flags-in-docker-and-nuxt_zh.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "在 Docker 中启用功能开关:为什么 MCP_ENABLED 没生效?如何修复?" 3 | date: "2025-08-19" 4 | description: "Blog post about 在 Docker 中启用功能开关:为什么 MCP_ENABLED 没生效?如何修复?" 5 | --- 6 | 7 | 8 | *2025年8月19日* 9 | 10 | 大家好!👋 11 | 12 | 延续昨天的界面与聊天可靠性优化,今天聊一个在部署中踩到的坑:在本地开发环境里设置 `MCP_ENABLED=true` 一切正常,但在 Docker 容器中却不生效。下面是原因与解决方案。 13 | 14 | ## 🐛 现象 15 | 16 | - **本地开发**:`.env` 中设置 `MCP_ENABLED=true`,设置页能看到「MCP」模块。 17 | - **Docker**:`docker-compose.yaml` 中设置 `MCP_ENABLED=true`,设置页没有出现「MCP」模块。 18 | 19 | ## 🔎 根因:Nuxt runtimeConfig 的构建时 vs 运行时 20 | 21 | Nuxt 3 的 `runtimeConfig` 会在「构建阶段」读取 `process.env`。想在「运行时」覆盖配置,需要使用带有 `NUXT_` 前缀、且能映射到配置键名的环境变量。 22 | 23 | 我们在 `nuxt.config.ts` 中是这样写的: 24 | 25 | ```ts 26 | runtimeConfig: { 27 | knowledgeBaseEnabled: process.env.KNOWLEDGE_BASE_ENABLED === 'true', 28 | realtimeChatEnabled: process.env.REALTIME_CHAT_ENABLED === 'true', 29 | modelsManagementEnabled: process.env.MODELS_MANAGEMENT_ENABLED === 'true', 30 | mcpEnabled: process.env.MCP_ENABLED === 'true', 31 | public: { /* ... */ } 32 | } 33 | ``` 34 | 35 | - 在本地开发中,`.env` 会在构建前加载,因此 `process.env.MCP_ENABLED` 在构建时就为 true → `mcpEnabled` 被“烘焙”为 true。 36 | - 在 Docker 中,我们运行的是预构建镜像。仅在运行时设置 `MCP_ENABLED=true` 无法改变 `runtimeConfig.mcpEnabled`。必须使用 `NUXT_MCP_ENABLED=true` 才能在运行时覆盖。 37 | 38 | 这也解释了为什么 `/api/features` 的日志里 `process.env.MCP_ENABLED` 显示为 true,但 `useRuntimeConfig().mcpEnabled` 仍然是 false。 39 | 40 | ## ✅ 解决方案 41 | 42 | ### 方案 A(推荐):在 Docker 中使用 `NUXT_` 前缀变量 43 | 44 | 修改 `docker-compose.yaml`: 45 | 46 | ```yaml 47 | services: 48 | chatollama: 49 | environment: 50 | - NUXT_MCP_ENABLED=true 51 | - NUXT_KNOWLEDGE_BASE_ENABLED=true 52 | - NUXT_REALTIME_CHAT_ENABLED=true 53 | - NUXT_MODELS_MANAGEMENT_ENABLED=true 54 | ``` 55 | 56 | 这样即可直接在运行时映射到 `runtimeConfig`,无需改代码。 57 | 58 | ### 方案 B:同时兼容旧变量与 `NUXT_` 59 | 60 | 如果希望继续兼容 `MCP_ENABLED`,可以在 `nuxt.config.ts` 中优先读取运行时的 `NUXT_` 变量,并回退到旧变量: 61 | 62 | ```ts 63 | runtimeConfig: { 64 | knowledgeBaseEnabled: process.env.NUXT_KNOWLEDGE_BASE_ENABLED === 'true' || process.env.KNOWLEDGE_BASE_ENABLED === 'true', 65 | realtimeChatEnabled: process.env.NUXT_REALTIME_CHAT_ENABLED === 'true' || process.env.REALTIME_CHAT_ENABLED === 'true', 66 | modelsManagementEnabled: process.env.NUXT_MODELS_MANAGEMENT_ENABLED === 'true' || process.env.MODELS_MANAGEMENT_ENABLED === 'true', 67 | mcpEnabled: process.env.NUXT_MCP_ENABLED === 'true' || process.env.MCP_ENABLED === 'true', 68 | public: { /* ... */ } 69 | } 70 | ``` 71 | 72 | ## 🔧 验证步骤 73 | 74 | 1. 使用更新后的 Compose 环境变量重新部署。 75 | 2. 请求 `/api/features` 并查看容器日志(会打印环境变量与 `runtimeConfig` 值)。 76 | 3. 打开设置页:当 `mcpEnabled` 为 true 时,应显示「MCP」模块。 77 | 78 | ## 🤔 为什么本地可用、Docker 不行? 79 | 80 | - **本地**:`.env` 在构建前加载 → `runtimeConfig` 在构建时就被设置为 true。 81 | - **Docker**:使用预构建镜像 → 运行时覆盖必须使用 `NUXT_` 前缀变量。 82 | 83 | ## 📝 小的开发体验改进(可选) 84 | 85 | - 在 `composables/useFeatures.ts` 的 `FeatureFlags` 接口中补充 `modelsManagementEnabled`,以保持类型完整。 86 | 87 | ## 🎯 总结 88 | 89 | 使用 Nuxt 3 做容器化部署时,牢记:构建时环境变量决定默认值;运行时覆盖需要使用 `NUXT_` 前缀。配置正确后,设置页的功能模块就会在所有环境中一致显示。 90 | -------------------------------------------------------------------------------- /src/content/blog-zh/2025-08-18-ui-improvements-and-chat-fixes_zh.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "界面优化与聊天可靠性修复" 3 | date: "2025-08-18" 4 | description: "ChatOllama界面优化和聊天功能可靠性修复" 5 | --- 6 | 7 | 8 | *2025年8月18日* 9 | 10 | 大家好!👋 11 | 12 | 过去几天我一直在改进聊天界面的一些重要功能。下面是新功能和修复的问题。 13 | 14 | ## 🐛 重要Bug修复 15 | 16 | ### 创建新聊天按钮问题 17 | 最令人沮丧的bug之一是"创建新聊天"按钮无响应。用户点击后没有反应,然后多次点击,突然会创建多个新聊天。 18 | 19 | **问题原因:** 20 | - `scrollToBottom` 函数在DOM元素准备好之前就尝试访问 `messageListEl.value.scrollHeight` 21 | - 没有加载状态保护,快速点击会触发多个API调用 22 | - 聊天创建流程中的竞态条件 23 | 24 | **修复方案:** 25 | ```javascript 26 | // 在 scrollToBottom 中添加空值检查 27 | const scrollToBottom = (_behavior: ScrollBehavior) => { 28 | behavior.value = _behavior 29 | if (messageListEl.value) { 30 | y.value = messageListEl.value.scrollHeight 31 | } 32 | } 33 | 34 | // 在 ChatSessionList 中添加加载状态 35 | const isCreatingChat = ref(false) 36 | 37 | async function onNewChat() { 38 | if (isCreatingChat.value) return 39 | 40 | isCreatingChat.value = true 41 | try { 42 | const data = await createChatSession() 43 | sessionList.value.unshift(data) 44 | await router.push(`/chat/${data.id}`) 45 | } finally { 46 | isCreatingChat.value = false 47 | } 48 | } 49 | ``` 50 | 51 | 这是一个典型的例子,说明小的时序问题如何创造出非常恼人的用户体验问题! 52 | 53 | ## ✨ 新功能:增强预览面板 54 | 55 | 代码工件预览系统得到了重大升级!以前用户只能在基本的侧边面板中查看代码工件。现在我们有了: 56 | 57 | ### 分屏视图模式 58 | - 聊天占用剩余空间 59 | - 预览面板固定500px宽度 60 | - 两者同时可见,便于对照查看 61 | 62 | ### 全屏模式 63 | - 预览覆盖整个视窗 64 | - 完全隐藏标题栏以获得最大查看区域 65 | - 带半透明背景的浮动关闭按钮 66 | - 非常适合查看复杂的HTML演示或详细图表 67 | 68 | ### 智能状态管理 69 | 这比听起来要复杂。关键洞察是将"显示/隐藏预览"状态与"正常/全屏"状态分离: 70 | 71 | ```javascript 72 | // 两个独立状态而不是一个混乱的状态 73 | const showArtifacts = ref(false) 74 | const isFullscreen = ref(false) 75 | 76 | // 智能关闭行为 77 | const closeArtifacts = () => { 78 | showArtifacts.value = false 79 | isFullscreen.value = false // 关闭时重置全屏状态 80 | } 81 | 82 | // 全屏关闭只退出全屏,不关闭预览 83 | const toggleFullscreen = () => { 84 | isFullscreen.value = !isFullscreen.value 85 | } 86 | ``` 87 | 88 | 现在的用户体验流程是: 89 | 1. 点击预览 → 在分屏视图中打开 90 | 2. 点击全屏 → 扩展到全屏 91 | 3. 在全屏中点击X → 返回分屏视图 92 | 4. 在分屏视图中点击X → 完全关闭预览 93 | 94 | ## 🎨 动画优化 95 | 96 | 将预览图标动画从滑入效果改为淡入效果。有时最小的改变会对界面的精致感产生最大的影响。 97 | 98 | ```scss 99 | // 之前:从右侧滑入 100 | .artifact-btn { 101 | opacity: 0; 102 | transform: translateX(8px); 103 | } 104 | 105 | // 之后:简单淡入 106 | .artifact-btn { 107 | opacity: 0; 108 | transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); 109 | } 110 | ``` 111 | 112 | ## 📚 学到的经验 113 | 114 | ### 1. DOM时序问题无处不在 115 | `scrollToBottom` bug提醒我们Vue的响应式很快,但DOM仍然需要时间更新。在访问元素属性之前,始终检查元素是否存在。 116 | 117 | ### 2. 状态管理的复杂性 118 | 最初,我试图让预览系统变得"智能",有可调整大小的分割和复杂状态。但简单往往更好——两种清晰的模式(分屏/全屏)和明显的过渡对用户来说效果更好。 119 | 120 | ### 3. 用户测试揭示边缘情况 121 | 聊天创建bug只在特定时序条件下发生。真实用户行为(当某些东西看起来坏了时快速点击)经常揭示在正常开发测试中不会出现的问题。 122 | 123 | ## 💭 给开发者同行的思考 124 | 125 | 这些UI可靠性修复可能不够华丽,但对用户体验有巨大影响。一个95%时间有效的按钮对用户来说就是坏的。花时间处理边缘情况和竞态条件是区分好界面和优秀界面的关键。 126 | 127 | 另外,在构建预览/模态系统时,始终像考虑进入流程一样考虑退出流程。用户需要理解如何回到他们来自的地方! 128 | 129 | --- 130 | 131 | *你希望下次看到哪些功能得到改进?在issues中留下你的想法!* 132 | 133 | *- 你的开发团队* -------------------------------------------------------------------------------- /src/content/blog/2025-09-16-instant-model-mentions.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Instantly Talk to Any Model with @ Mentions" 3 | date: "2025-09-16" 4 | feature: "Tag a model inline to override the next reply without touching session settings" 5 | timeToShip: "~1 day" 6 | description: "Instantly switch between AI models in conversations using @ mentions without changing session settings" 7 | --- 8 | 9 | ## 🎯 Why We Built It 10 | 11 | Our long-form chat sessions have always been anchored to a single "default" model. That kept conversations coherent, but it also meant lots of friction when you wanted to toss a quick question to a different model: 12 | 13 | 1. Open session settings 14 | 2. Swap the default model 15 | 3. Ask the question 16 | 4. Switch the model back so the rest of the thread stays consistent 17 | 18 | That dance got even worse when comparing outputs from multiple providers or when you only needed one specialty answer (e.g. “let me sanity-check this with Claude real quick”). We needed something as fast as tagging a teammate in Slack. 19 | 20 | ## 💡 The Solution in a Nutshell 21 | 22 | You can now add `@model-name` to any outgoing message. Each mentioned model temporarily "hijacks" the request, so the response comes from that model while the session’s default choice remains unchanged. 23 | 24 | - Mention a single model (`@gpt-4o-mini`) to reroute just this message. 25 | - Mention multiple models (`@openrouter/claude-3.5-sonnet @llama3`) to fan out the question. 26 | - Don’t mention anything and the chat continues using the default model like before. 27 | 28 | The UI keeps the override discoverable: as soon as you type `@`, the input shows model families, live search, and keyboard navigation so you never have to remember exact IDs. 29 | 30 | ## 🔍 What Changed Under the Hood 31 | 32 | ### 1. Parsing Mentions Without Polluting Prompts 33 | `ChatInputBox.vue` now parses model mentions every time the user types. We persist the selected models in `hijackedModels` and simultaneously strip the `@model` tokens into a `sanitizedContent` payload. That sanitized payload is what the backend actually sees, so no model receives literal `@claude` strings in its prompt. 34 | 35 | ### 2. Respecting Overrides in Delivery 36 | `Chat.vue` checks for `hijackedModels` before sending. If we find any, we bypass the session defaults for that single send, while the session metadata (and sidebar defaults) stay untouched. The outgoing transcript reuses `sanitizedContent` so historical messages read cleanly without the mentions. 37 | 38 | ### 3. Making Mentions First-Class Citizens in the UI 39 | We refreshed `ModelMentionText.vue` to show consistent badges for mentions, even when the provider uses namespaced values like `openrouter/claude-3.5-sonnet`. The existing auto-title generator also consumes the sanitized message to avoid naming chats "@gpt-4o mini thoughts". 40 | 41 | ## ✅ Results 42 | 43 | - Instant context switches without breaking the session model. 44 | - Keyboard-only flow for power users comparing providers. 45 | - Clean transcripts and prompts even when users pepper messages with mentions. 46 | 47 | Give it a try: type `@` in any chat input, pick a model, and enjoy frictionless comparisons. 48 | -------------------------------------------------------------------------------- /src/content/blog/2025-09-18-smart-quick-chat-dialog-positioning.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Smart Quick Chat Dialog Positioning: A Better User Experience" 3 | date: "2025-09-18" 4 | description: "Significant improvement to ChatOllama's Quick Chat feature with intelligent dialog positioning and enhanced viewport awareness" 5 | --- 6 | 7 | We've just rolled out a significant improvement to ChatOllama's Quick Chat feature that addresses a common frustration users experience when selecting text near screen edges. The Quick Chat dialog now intelligently positions itself to stay within the viewport while providing more space for AI responses. 8 | 9 | ## The Problem 10 | 11 | Previously, when users selected text at the bottom right corner of their screen or near viewport edges, the Quick Chat dialog would appear partially outside the visible area or get cut off entirely. This made it difficult to read AI responses and interact with the dialog effectively. Additionally, the dialog was quite narrow (320px), limiting the amount of text that could be displayed comfortably. 12 | 13 | ## The Solution 14 | 15 | Our new smart positioning algorithm addresses these issues with several key improvements: 16 | 17 | ### 1. Intelligent Positioning Logic 18 | 19 | The dialog now follows a sophisticated positioning strategy: 20 | 21 | - **Horizontal positioning**: First tries to position to the right of the selected text, then to the left if there's insufficient space, and finally centers horizontally if neither side works 22 | - **Vertical positioning**: Attempts to position below the selection first, then above if needed, and centers vertically as a last resort 23 | - **Viewport awareness**: Always ensures the dialog stays within screen bounds with proper padding 24 | 25 | ### 2. Larger Dialog Size 26 | 27 | - **Width increased**: From 320px to 480px for better readability 28 | - **Dynamic height**: Automatically adjusts based on response content length 29 | - **Response area**: Doubled from 160px to 320px maximum height 30 | - **Better typography**: Response text size increased from extra-small to small for improved readability 31 | 32 | ### 3. Dynamic Content Adaptation 33 | 34 | The dialog now calculates its optimal size based on the AI response length, ensuring longer responses have adequate space while keeping shorter ones compact. 35 | 36 | ## Technical Implementation 37 | 38 | The positioning algorithm uses several key constants: 39 | 40 | ```typescript 41 | const DIALOG_WIDTH = 480 // Increased from 320px 42 | const DIALOG_MIN_HEIGHT = 280 43 | const DIALOG_MAX_HEIGHT = 600 // Maximum height when response is long 44 | const VIEWPORT_PADDING = 20 45 | const OFFSET_FROM_SELECTION = 10 46 | ``` 47 | 48 | The smart positioning logic ensures the dialog: 49 | - Maintains a 20px padding from viewport edges 50 | - Positions 10px away from the selected text 51 | - Dynamically adjusts height based on response content 52 | - Never gets cut off or appears outside the visible area 53 | 54 | ## Impact on User Experience 55 | 56 | These improvements deliver several tangible benefits: 57 | 58 | 1. **Better accessibility**: Users can now select text anywhere on the screen without worrying about dialog positioning 59 | 2. **Improved readability**: Larger dialog and text size make AI responses easier to read 60 | 3. **Smarter behavior**: The dialog adapts to different screen sizes and selection positions automatically -------------------------------------------------------------------------------- /content/20250819-feature-flags-in-docker-and-nuxt.md: -------------------------------------------------------------------------------- 1 | # Feature Flags in Docker: Why MCP_ENABLED Didn’t Work and How We Fixed It 2 | 3 | *August 19, 2025* 4 | 5 | Hey everyone! 👋 6 | 7 | Following yesterday’s UI improvements post, I dug into a deployment gotcha that bit us when running in Docker: feature flags like MCP worked locally but not inside containers. Here’s what happened and how to fix it. 8 | 9 | ## 🐛 The Symptom 10 | 11 | - **Local dev**: Setting `MCP_ENABLED=true` in `.env` made the Settings → MCP module appear. 12 | - **Docker**: Setting `MCP_ENABLED=true` in `docker-compose.yaml` did nothing — the MCP section didn’t show up. 13 | 14 | ## 🔎 Root Cause: Nuxt runtimeConfig (build-time vs runtime) 15 | 16 | Nuxt 3 reads `runtimeConfig` values at build time via `process.env`. At runtime, overriding them requires environment variables that map to config keys with the `NUXT_` prefix. 17 | 18 | Our `nuxt.config.ts` had: 19 | 20 | ```ts 21 | runtimeConfig: { 22 | knowledgeBaseEnabled: process.env.KNOWLEDGE_BASE_ENABLED === 'true', 23 | realtimeChatEnabled: process.env.REALTIME_CHAT_ENABLED === 'true', 24 | modelsManagementEnabled: process.env.MODELS_MANAGEMENT_ENABLED === 'true', 25 | mcpEnabled: process.env.MCP_ENABLED === 'true', 26 | public: { /* ... */ } 27 | } 28 | ``` 29 | 30 | - In dev, `.env` is loaded before build, so `process.env.MCP_ENABLED` was true when we built → `mcpEnabled` baked as true. 31 | - In Docker, we used a prebuilt image. Setting `MCP_ENABLED=true` at runtime does not change `runtimeConfig.mcpEnabled`. You must use `NUXT_MCP_ENABLED=true` to override at runtime. 32 | 33 | This explains why `/api/features` logs showed `process.env.MCP_ENABLED` as true, but `useRuntimeConfig().mcpEnabled` stayed false. 34 | 35 | ## ✅ The Fix 36 | 37 | ### Option A (Recommended): Use `NUXT_`-prefixed env vars in Docker 38 | 39 | Update `docker-compose.yaml`: 40 | 41 | ```yaml 42 | services: 43 | chatollama: 44 | environment: 45 | - NUXT_MCP_ENABLED=true 46 | - NUXT_KNOWLEDGE_BASE_ENABLED=true 47 | - NUXT_REALTIME_CHAT_ENABLED=true 48 | - NUXT_MODELS_MANAGEMENT_ENABLED=true 49 | ``` 50 | 51 | This maps directly to `runtimeConfig` at runtime — no code changes needed. 52 | 53 | ### Option B: Support both legacy and `NUXT_` in code 54 | 55 | If you want `MCP_ENABLED` to keep working, make `nuxt.config.ts` prefer the runtime `NUXT_` variables and fall back to the legacy ones: 56 | 57 | ```ts 58 | runtimeConfig: { 59 | knowledgeBaseEnabled: process.env.NUXT_KNOWLEDGE_BASE_ENABLED === 'true' || process.env.KNOWLEDGE_BASE_ENABLED === 'true', 60 | realtimeChatEnabled: process.env.NUXT_REALTIME_CHAT_ENABLED === 'true' || process.env.REALTIME_CHAT_ENABLED === 'true', 61 | modelsManagementEnabled: process.env.NUXT_MODELS_MANAGEMENT_ENABLED === 'true' || process.env.MODELS_MANAGEMENT_ENABLED === 'true', 62 | mcpEnabled: process.env.NUXT_MCP_ENABLED === 'true' || process.env.MCP_ENABLED === 'true', 63 | public: { /* ... */ } 64 | } 65 | ``` 66 | 67 | ## 🔧 How to Verify 68 | 69 | 1. Redeploy with the updated Compose env vars. 70 | 2. Hit `/api/features` and check logs — they print both environment vars and `runtimeConfig` values. 71 | 3. Open Settings: the MCP section should appear when `mcpEnabled` is true. 72 | 73 | ## 🤔 Why it worked locally but not in Docker 74 | 75 | - **Local**: `.env` loaded before build → `runtimeConfig` baked with your values. 76 | - **Docker**: prebuilt image → runtime overrides require `NUXT_`-prefixed variables. 77 | 78 | ## 📝 Small DX touch-up (optional) 79 | 80 | - Add `modelsManagementEnabled` to the `FeatureFlags` interface in `composables/useFeatures.ts` for type completeness. 81 | 82 | ## 🎯 Takeaway 83 | 84 | Remember this rule of thumb with Nuxt 3: build-time envs bake defaults; runtime overrides need `NUXT_`. With that in place, the Settings page correctly reflects features across environments. 85 | -------------------------------------------------------------------------------- /src/content/blog/2025-08-19-feature-flags-in-docker-and-nuxt.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Feature Flags in Docker: Why MCP_ENABLED Didn't Work and How We Fixed It" 3 | date: "2025-08-19" 4 | description: "Troubleshooting and fixing MCP feature flags that work in development but fail in Docker containers" 5 | --- 6 | 7 | 8 | *August 19, 2025* 9 | 10 | Hey everyone! 👋 11 | 12 | Following yesterday’s UI improvements post, I dug into a deployment gotcha that bit us when running in Docker: feature flags like MCP worked locally but not inside containers. Here’s what happened and how to fix it. 13 | 14 | ## 🐛 The Symptom 15 | 16 | - **Local dev**: Setting `MCP_ENABLED=true` in `.env` made the Settings → MCP module appear. 17 | - **Docker**: Setting `MCP_ENABLED=true` in `docker-compose.yaml` did nothing — the MCP section didn’t show up. 18 | 19 | ## 🔎 Root Cause: Nuxt runtimeConfig (build-time vs runtime) 20 | 21 | Nuxt 3 reads `runtimeConfig` values at build time via `process.env`. At runtime, overriding them requires environment variables that map to config keys with the `NUXT_` prefix. 22 | 23 | Our `nuxt.config.ts` had: 24 | 25 | ```ts 26 | runtimeConfig: { 27 | knowledgeBaseEnabled: process.env.KNOWLEDGE_BASE_ENABLED === 'true', 28 | realtimeChatEnabled: process.env.REALTIME_CHAT_ENABLED === 'true', 29 | modelsManagementEnabled: process.env.MODELS_MANAGEMENT_ENABLED === 'true', 30 | mcpEnabled: process.env.MCP_ENABLED === 'true', 31 | public: { /* ... */ } 32 | } 33 | ``` 34 | 35 | - In dev, `.env` is loaded before build, so `process.env.MCP_ENABLED` was true when we built → `mcpEnabled` baked as true. 36 | - In Docker, we used a prebuilt image. Setting `MCP_ENABLED=true` at runtime does not change `runtimeConfig.mcpEnabled`. You must use `NUXT_MCP_ENABLED=true` to override at runtime. 37 | 38 | This explains why `/api/features` logs showed `process.env.MCP_ENABLED` as true, but `useRuntimeConfig().mcpEnabled` stayed false. 39 | 40 | ## ✅ The Fix 41 | 42 | ### Option A (Recommended): Use `NUXT_`-prefixed env vars in Docker 43 | 44 | Update `docker-compose.yaml`: 45 | 46 | ```yaml 47 | services: 48 | chatollama: 49 | environment: 50 | - NUXT_MCP_ENABLED=true 51 | - NUXT_KNOWLEDGE_BASE_ENABLED=true 52 | - NUXT_REALTIME_CHAT_ENABLED=true 53 | - NUXT_MODELS_MANAGEMENT_ENABLED=true 54 | ``` 55 | 56 | This maps directly to `runtimeConfig` at runtime — no code changes needed. 57 | 58 | ### Option B: Support both legacy and `NUXT_` in code 59 | 60 | If you want `MCP_ENABLED` to keep working, make `nuxt.config.ts` prefer the runtime `NUXT_` variables and fall back to the legacy ones: 61 | 62 | ```ts 63 | runtimeConfig: { 64 | knowledgeBaseEnabled: process.env.NUXT_KNOWLEDGE_BASE_ENABLED === 'true' || process.env.KNOWLEDGE_BASE_ENABLED === 'true', 65 | realtimeChatEnabled: process.env.NUXT_REALTIME_CHAT_ENABLED === 'true' || process.env.REALTIME_CHAT_ENABLED === 'true', 66 | modelsManagementEnabled: process.env.NUXT_MODELS_MANAGEMENT_ENABLED === 'true' || process.env.MODELS_MANAGEMENT_ENABLED === 'true', 67 | mcpEnabled: process.env.NUXT_MCP_ENABLED === 'true' || process.env.MCP_ENABLED === 'true', 68 | public: { /* ... */ } 69 | } 70 | ``` 71 | 72 | ## 🔧 How to Verify 73 | 74 | 1. Redeploy with the updated Compose env vars. 75 | 2. Hit `/api/features` and check logs — they print both environment vars and `runtimeConfig` values. 76 | 3. Open Settings: the MCP section should appear when `mcpEnabled` is true. 77 | 78 | ## 🤔 Why it worked locally but not in Docker 79 | 80 | - **Local**: `.env` loaded before build → `runtimeConfig` baked with your values. 81 | - **Docker**: prebuilt image → runtime overrides require `NUXT_`-prefixed variables. 82 | 83 | ## 📝 Small DX touch-up (optional) 84 | 85 | - Add `modelsManagementEnabled` to the `FeatureFlags` interface in `composables/useFeatures.ts` for type completeness. 86 | 87 | ## 🎯 Takeaway 88 | 89 | Remember this rule of thumb with Nuxt 3: build-time envs bake defaults; runtime overrides need `NUXT_`. With that in place, the Settings page correctly reflects features across environments. 90 | -------------------------------------------------------------------------------- /src/content/blog-zh/2025-08-25-docker-langchain-module-resolution-fix_zh.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "修复 Docker 模块解析错误:LangChain 依赖项调查" 3 | date: "2025-08-25" 4 | description: "Blog post about 修复 Docker 模块解析错误:LangChain 依赖项调查" 5 | --- 6 | 7 | 8 | **日期**:2025年8月25日 9 | **问题**:Docker 容器因 `Cannot find module '@langchain/core/prompts.js'` 错误而失败 10 | **解决方案**:LangChain 包间的依赖版本对齐 11 | 12 | ## 问题描述 13 | 14 | Docker 化的 ChatOllama 应用程序在聊天操作期间遇到关键的模块解析错误: 15 | 16 | ``` 17 | [nuxt] [request error] [unhandled] [500] Cannot find module '/app/.output/server/node_modules/@langchain/core/prompts.js' 18 | imported from /app/.output/server/chunks/routes/api/models/index.post.mjs 19 | ``` 20 | 21 | 此错误在多个 API 端点(`/api/models/chat`、`/api/instruction`、`/api/agents`)中持续出现,并阻止应用程序在 Docker 容器中正常运行。 22 | 23 | ## 调查过程 24 | 25 | ### 1. 初步分析 26 | - **错误模式**:`@langchain/core/prompts.js` 的 ESM 模块解析失败 27 | - **环境**:Docker 容器构建过程,而非本地开发 28 | - **受影响文件**:从 `@langchain/core/prompts` 导入的服务器 API 路由 29 | 30 | ### 2. 容器检查 31 | 调查发现 Docker 容器中缺少导出文件: 32 | 33 | ```bash 34 | /app/.output/server/node_modules/@langchain/core/prompts.js 35 | 36 | /app/.output/server/node_modules/@langchain/core/dist/prompts/index.js 37 | ``` 38 | 39 | ### 3. 版本冲突发现 40 | 在依赖树中发现了 **三个不同版本** 的 `@langchain/core`: 41 | 42 | - **项目规范**:`@langchain/core@^0.3.49` 43 | - **实际 Docker 解析**:`@langchain/core@0.3.72`(由 `deepagents@0.0.1` 引入) 44 | - **遗留版本**:`@langchain/core@0.1.54`(由较旧的包使用) 45 | 46 | 关键问题:`deepagents@0.0.1` 依赖项强制使用 `@langchain/core@0.3.72`,而项目指定了 `^0.3.49`,在 Nuxt 的构建打包过程中创建了版本冲突。 47 | 48 | ## 根因分析 49 | 50 | ### 核心问题 51 | **版本不匹配**:较新的 `@langchain/core@0.3.72` 具有不同的导出结构,与 Nuxt 为 Docker 部署打包模块的方式不兼容。 52 | 53 | ### 为什么 Docker 与本地不同? 54 | - **本地开发**:pnpm 的工作区解析优雅地处理了冲突 55 | - **Docker 构建**:Nuxt 的生产打包暴露了版本不一致性 56 | - **模块解析**:不同版本之间的 ESM 导出映射不同 57 | 58 | ### 技术细节 59 | ```json 60 | // package.json 指定的版本 61 | "@langchain/core": "^0.3.49" 62 | 63 | // 但依赖解析拉取了 64 | "deepagents@0.0.1" → "@langchain/core@0.3.72" 65 | 66 | // 导致打包期间缺少导出 67 | ``` 68 | 69 | ## 解决方案:依赖对齐 70 | 71 | ### 方法 72 | 我们没有选择手动文件补丁,而是通过将所有 LangChain 包更新为兼容版本来选择 **正确的依赖管理**。 73 | 74 | ### 应用的包更新 75 | 76 | ```json 77 | { 78 | // 版本对齐的核心更新 79 | "@langchain/core": "^0.3.49" → "^0.3.72", 80 | 81 | // 兼容包更新 82 | "@langchain/anthropic": "^0.3.19" → "^0.3.26", 83 | "@langchain/community": "^0.3.41" → "^0.3.53", 84 | "@langchain/google-genai": "^0.1.5" → "^0.2.16", 85 | "@langchain/groq": "^0.0.5" → "^0.2.3", 86 | "@langchain/ollama": "^0.2.0" → "^0.2.3", 87 | "@langchain/openai": "^0.5.7" → "^0.6.9", 88 | 89 | // 提供商特定更新 90 | "@langchain/azure-openai": "^0.0.4" → "^0.0.11", 91 | "@langchain/cohere": "^0.0.6" → "^0.3.4", 92 | 93 | // 对等依赖修复 94 | "ws": "^8.16.0" → "^8.18.0", 95 | "zod": "^3.23.8" → "^3.24.1" 96 | } 97 | ``` 98 | 99 | ### 实施步骤 100 | 101 | ```bash 102 | # 2. 重新安装依赖项 103 | pnpm install 104 | 105 | pnpm run build 106 | 107 | # (server/api/agents/[id].post.ts 中缺少括号) 108 | 109 | ✓ Built in 17.34s 110 | ``` 111 | 112 | ## 验证结果 113 | 114 | ### 修复前 115 | - **Docker 错误**:模块解析失败 116 | - **版本冲突**:3个不同的 @langchain/core 版本 117 | - **对等依赖**:多个警告 118 | - **构建状态**:在 Docker 中失败 119 | 120 | ### 修复后 121 | - **依赖解析**:所有 LangChain 包使用 `@langchain/core@0.3.72` 122 | - **本地构建**:✅ 成功(`pnpm run build`) 123 | - **模块导出**:所有包之间一致 124 | - **对等警告**:减少到最小的非关键问题 125 | 126 | ## 学到的最佳实践 127 | 128 | ### 1. 依赖管理 129 | - **始终对齐相关包家族的主要依赖版本** 130 | - **对 LangChain 核心等关键依赖使用精确或兼容范围** 131 | - **定期依赖审计** 以捕获版本偏移 132 | 133 | ### 2. Docker 特定考虑事项 134 | - **在开发期间在 Docker 中测试构建**,而不仅仅是本地 135 | - **版本冲突在容器化构建中的表现不同** 与本地开发 136 | - **ESM 模块解析** 对版本不匹配很敏感 137 | 138 | ### 3. 调查方法 139 | - **首先检查容器** 以了解实际文件结构 140 | - **依赖树分析** 以识别版本冲突 141 | - **标准工具而非手动修复** 用于可持续解决方案 142 | 143 | ## 开发者技术细节 144 | 145 | ### 修改的文件 146 | - `package.json`:更新了 LangChain 包版本 147 | - `pnpm-lock.yaml`:使用一致的解析重新生成 148 | - `server/api/agents/[id].post.ts`:修复了语法错误(缺少括号) 149 | 150 | ### 重现命令 151 | ```bash 152 | docker exec ls -la /app/.output/server/node_modules/@langchain/core/ 153 | 154 | docker exec find /app/.output/server/node_modules/@langchain/core -name "*prompt*" 155 | 156 | npm list @langchain/core 157 | ``` 158 | 159 | ### 预防策略 160 | ```json 161 | // package.json - 对关键依赖项使用更严格的版本范围 162 | { 163 | "@langchain/core": "~0.3.72", // 仅限补丁级别的波浪号 164 | "deepagents": "^0.0.1" // 确保兼容性 165 | } 166 | ``` 167 | 168 | ## 结论 169 | 170 | 这个问题突出了现代 JavaScript 应用程序中 **一致依赖管理** 的重要性,特别是在通过 Docker 部署时。正确的解决方案涉及将整个 LangChain 生态系统更新为兼容版本,而不是应用手动补丁。 171 | 172 | ### 关键要点 173 | 1. **版本冲突** 在本地和 Docker 环境之间可能表现不同 174 | 2. **依赖对齐** 对 ESM 模块解析至关重要 175 | 3. **标准包管理** 始终优于手动文件修复 176 | 4. **容器特定测试** 应该是开发工作流程的一部分 177 | 178 | 此修复确保 ChatOllama 的 Docker 部署可靠工作,同时保持标准构建过程并使依赖项与最新的 LangChain 生态系统改进保持同步。 -------------------------------------------------------------------------------- /content/zh/20250825-docker-langchain-module-resolution-fix_zh.md: -------------------------------------------------------------------------------- 1 | # 修复 Docker 模块解析错误:LangChain 依赖项调查 2 | 3 | **日期**:2025年8月25日 4 | **问题**:Docker 容器因 `Cannot find module '@langchain/core/prompts.js'` 错误而失败 5 | **解决方案**:LangChain 包间的依赖版本对齐 6 | 7 | ## 问题描述 8 | 9 | Docker 化的 ChatOllama 应用程序在聊天操作期间遇到关键的模块解析错误: 10 | 11 | ``` 12 | [nuxt] [request error] [unhandled] [500] Cannot find module '/app/.output/server/node_modules/@langchain/core/prompts.js' 13 | imported from /app/.output/server/chunks/routes/api/models/index.post.mjs 14 | ``` 15 | 16 | 此错误在多个 API 端点(`/api/models/chat`、`/api/instruction`、`/api/agents`)中持续出现,并阻止应用程序在 Docker 容器中正常运行。 17 | 18 | ## 调查过程 19 | 20 | ### 1. 初步分析 21 | - **错误模式**:`@langchain/core/prompts.js` 的 ESM 模块解析失败 22 | - **环境**:Docker 容器构建过程,而非本地开发 23 | - **受影响文件**:从 `@langchain/core/prompts` 导入的服务器 API 路由 24 | 25 | ### 2. 容器检查 26 | 调查发现 Docker 容器中缺少导出文件: 27 | 28 | ```bash 29 | # 预期但缺失的文件 30 | /app/.output/server/node_modules/@langchain/core/prompts.js 31 | 32 | # 可用的目录结构 33 | /app/.output/server/node_modules/@langchain/core/dist/prompts/index.js 34 | ``` 35 | 36 | ### 3. 版本冲突发现 37 | 在依赖树中发现了 **三个不同版本** 的 `@langchain/core`: 38 | 39 | - **项目规范**:`@langchain/core@^0.3.49` 40 | - **实际 Docker 解析**:`@langchain/core@0.3.72`(由 `deepagents@0.0.1` 引入) 41 | - **遗留版本**:`@langchain/core@0.1.54`(由较旧的包使用) 42 | 43 | 关键问题:`deepagents@0.0.1` 依赖项强制使用 `@langchain/core@0.3.72`,而项目指定了 `^0.3.49`,在 Nuxt 的构建打包过程中创建了版本冲突。 44 | 45 | ## 根因分析 46 | 47 | ### 核心问题 48 | **版本不匹配**:较新的 `@langchain/core@0.3.72` 具有不同的导出结构,与 Nuxt 为 Docker 部署打包模块的方式不兼容。 49 | 50 | ### 为什么 Docker 与本地不同? 51 | - **本地开发**:pnpm 的工作区解析优雅地处理了冲突 52 | - **Docker 构建**:Nuxt 的生产打包暴露了版本不一致性 53 | - **模块解析**:不同版本之间的 ESM 导出映射不同 54 | 55 | ### 技术细节 56 | ```json 57 | // package.json 指定的版本 58 | "@langchain/core": "^0.3.49" 59 | 60 | // 但依赖解析拉取了 61 | "deepagents@0.0.1" → "@langchain/core@0.3.72" 62 | 63 | // 导致打包期间缺少导出 64 | ``` 65 | 66 | ## 解决方案:依赖对齐 67 | 68 | ### 方法 69 | 我们没有选择手动文件补丁,而是通过将所有 LangChain 包更新为兼容版本来选择 **正确的依赖管理**。 70 | 71 | ### 应用的包更新 72 | 73 | ```json 74 | { 75 | // 版本对齐的核心更新 76 | "@langchain/core": "^0.3.49" → "^0.3.72", 77 | 78 | // 兼容包更新 79 | "@langchain/anthropic": "^0.3.19" → "^0.3.26", 80 | "@langchain/community": "^0.3.41" → "^0.3.53", 81 | "@langchain/google-genai": "^0.1.5" → "^0.2.16", 82 | "@langchain/groq": "^0.0.5" → "^0.2.3", 83 | "@langchain/ollama": "^0.2.0" → "^0.2.3", 84 | "@langchain/openai": "^0.5.7" → "^0.6.9", 85 | 86 | // 提供商特定更新 87 | "@langchain/azure-openai": "^0.0.4" → "^0.0.11", 88 | "@langchain/cohere": "^0.0.6" → "^0.3.4", 89 | 90 | // 对等依赖修复 91 | "ws": "^8.16.0" → "^8.18.0", 92 | "zod": "^3.23.8" → "^3.24.1" 93 | } 94 | ``` 95 | 96 | ### 实施步骤 97 | 98 | ```bash 99 | # 1. 使用兼容版本更新 package.json 100 | # 2. 重新安装依赖项 101 | pnpm install 102 | 103 | # 3. 验证构建成功 104 | pnpm run build 105 | 106 | # 4. 修复发现的语法错误 107 | # (server/api/agents/[id].post.ts 中缺少括号) 108 | 109 | # 5. 成功完成构建 110 | ✓ Built in 17.34s 111 | ``` 112 | 113 | ## 验证结果 114 | 115 | ### 修复前 116 | - **Docker 错误**:模块解析失败 117 | - **版本冲突**:3个不同的 @langchain/core 版本 118 | - **对等依赖**:多个警告 119 | - **构建状态**:在 Docker 中失败 120 | 121 | ### 修复后 122 | - **依赖解析**:所有 LangChain 包使用 `@langchain/core@0.3.72` 123 | - **本地构建**:✅ 成功(`pnpm run build`) 124 | - **模块导出**:所有包之间一致 125 | - **对等警告**:减少到最小的非关键问题 126 | 127 | ## 学到的最佳实践 128 | 129 | ### 1. 依赖管理 130 | - **始终对齐相关包家族的主要依赖版本** 131 | - **对 LangChain 核心等关键依赖使用精确或兼容范围** 132 | - **定期依赖审计** 以捕获版本偏移 133 | 134 | ### 2. Docker 特定考虑事项 135 | - **在开发期间在 Docker 中测试构建**,而不仅仅是本地 136 | - **版本冲突在容器化构建中的表现不同** 与本地开发 137 | - **ESM 模块解析** 对版本不匹配很敏感 138 | 139 | ### 3. 调查方法 140 | - **首先检查容器** 以了解实际文件结构 141 | - **依赖树分析** 以识别版本冲突 142 | - **标准工具而非手动修复** 用于可持续解决方案 143 | 144 | ## 开发者技术细节 145 | 146 | ### 修改的文件 147 | - `package.json`:更新了 LangChain 包版本 148 | - `pnpm-lock.yaml`:使用一致的解析重新生成 149 | - `server/api/agents/[id].post.ts`:修复了语法错误(缺少括号) 150 | 151 | ### 重现命令 152 | ```bash 153 | # 检查容器依赖项 154 | docker exec ls -la /app/.output/server/node_modules/@langchain/core/ 155 | 156 | # 检查缺少的导出 157 | docker exec find /app/.output/server/node_modules/@langchain/core -name "*prompt*" 158 | 159 | # 验证本地与容器的差异 160 | npm list @langchain/core 161 | ``` 162 | 163 | ### 预防策略 164 | ```json 165 | // package.json - 对关键依赖项使用更严格的版本范围 166 | { 167 | "@langchain/core": "~0.3.72", // 仅限补丁级别的波浪号 168 | "deepagents": "^0.0.1" // 确保兼容性 169 | } 170 | ``` 171 | 172 | ## 结论 173 | 174 | 这个问题突出了现代 JavaScript 应用程序中 **一致依赖管理** 的重要性,特别是在通过 Docker 部署时。正确的解决方案涉及将整个 LangChain 生态系统更新为兼容版本,而不是应用手动补丁。 175 | 176 | ### 关键要点 177 | 1. **版本冲突** 在本地和 Docker 环境之间可能表现不同 178 | 2. **依赖对齐** 对 ESM 模块解析至关重要 179 | 3. **标准包管理** 始终优于手动文件修复 180 | 4. **容器特定测试** 应该是开发工作流程的一部分 181 | 182 | 此修复确保 ChatOllama 的 Docker 部署可靠工作,同时保持标准构建过程并使依赖项与最新的 LangChain 生态系统改进保持同步。 -------------------------------------------------------------------------------- /src/layouts/BaseLayout.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import { getLangFromUrl, useTranslations } from '@/utils/i18n'; 3 | import '@/styles/global.css'; 4 | 5 | export interface Props { 6 | title: string; 7 | description?: string; 8 | } 9 | 10 | const { title, description } = Astro.props; 11 | const lang = getLangFromUrl(Astro.url); 12 | const t = useTranslations(lang); 13 | const metaDescription = description || t('meta.description'); 14 | 15 | // Use the configured site URL for production, fallback to Astro.url for development 16 | const siteUrl = import.meta.env.SITE || Astro.site || Astro.url.origin; 17 | const currentUrl = new URL(Astro.url.pathname, siteUrl); 18 | const imageUrl = new URL('/social_share.png', siteUrl); 19 | --- 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | {title} 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 |
61 |
62 | 91 |
92 | 93 |
94 | 95 |
96 | 97 |
98 |
99 |
100 | © {new Date().getFullYear()} ChatOllama. Open source chatbot platform. 101 |
102 |
103 |
104 |
105 | 106 | 110 | 111 | 112 | -------------------------------------------------------------------------------- /content/20250818-ui-improvements-and-chat-fixes.md: -------------------------------------------------------------------------------- 1 | # UI Improvements and Chat Reliability Fixes 2 | 3 | *August 18, 2025* 4 | 5 | Hey everyone! 👋 6 | 7 | I've been working on some important improvements to the chat interface over the past few days. Here's what's new and what got fixed. 8 | 9 | ## 🐛 Major Bug Fixes 10 | 11 | ### Chat Creation Button Issues 12 | One of the most frustrating bugs was the unresponsive "create new chat" button. Users would click it, nothing would happen, then they'd click multiple times and suddenly get several new chats created at once. 13 | 14 | **What was happening:** 15 | - The `scrollToBottom` function was trying to access `messageListEl.value.scrollHeight` before the DOM element was ready 16 | - No loading state protection meant rapid clicks could trigger multiple API calls 17 | - Race conditions in the chat creation flow 18 | 19 | **The fix:** 20 | ```javascript 21 | // Added null check in scrollToBottom 22 | const scrollToBottom = (_behavior: ScrollBehavior) => { 23 | behavior.value = _behavior 24 | if (messageListEl.value) { 25 | y.value = messageListEl.value.scrollHeight 26 | } 27 | } 28 | 29 | // Added loading state in ChatSessionList 30 | const isCreatingChat = ref(false) 31 | 32 | async function onNewChat() { 33 | if (isCreatingChat.value) return 34 | 35 | isCreatingChat.value = true 36 | try { 37 | const data = await createChatSession() 38 | sessionList.value.unshift(data) 39 | await router.push(`/chat/${data.id}`) 40 | } finally { 41 | isCreatingChat.value = false 42 | } 43 | } 44 | ``` 45 | 46 | This was a classic example of how small timing issues can create really annoying UX problems! 47 | 48 | ## ✨ New Feature: Enhanced Preview Panel 49 | 50 | The artifact preview system got a major upgrade! Previously, users could only view code artifacts in a basic side panel. Now we have: 51 | 52 | ### Split View Mode 53 | - Chat takes up the remaining space 54 | - Preview panel has a fixed 500px width 55 | - Both are visible simultaneously for context 56 | 57 | ### Fullscreen Mode 58 | - Preview covers the entire viewport 59 | - Header is completely hidden for maximum viewing area 60 | - Floating close button with semi-transparent background 61 | - Perfect for viewing complex HTML demos or detailed diagrams 62 | 63 | ### Smart State Management 64 | This was trickier than it sounds. The key insight was separating the "show/hide preview" state from the "normal/fullscreen" state: 65 | 66 | ```javascript 67 | // Two separate states instead of one confusing state 68 | const showArtifacts = ref(false) 69 | const isFullscreen = ref(false) 70 | 71 | // Smart close behavior 72 | const closeArtifacts = () => { 73 | showArtifacts.value = false 74 | isFullscreen.value = false // Reset fullscreen when closing 75 | } 76 | 77 | // Fullscreen close just exits fullscreen, doesn't close preview 78 | const toggleFullscreen = () => { 79 | isFullscreen.value = !isFullscreen.value 80 | } 81 | ``` 82 | 83 | The UX flow is now: 84 | 1. Click preview → Opens in split view 85 | 2. Click fullscreen → Expands to fullscreen 86 | 3. Click X in fullscreen → Returns to split view 87 | 4. Click X in split view → Closes preview completely 88 | 89 | ## 🎨 Animation Polish 90 | 91 | Changed the preview icon animation from a slide-in effect to a fade-in effect. Sometimes the smallest changes make the biggest difference in how polished an interface feels. 92 | 93 | ```scss 94 | // Before: Slide in from right 95 | .artifact-btn { 96 | opacity: 0; 97 | transform: translateX(8px); 98 | } 99 | 100 | // After: Simple fade 101 | .artifact-btn { 102 | opacity: 0; 103 | transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); 104 | } 105 | ``` 106 | 107 | ## 📚 What I Learned 108 | 109 | ### 1. DOM Timing Issues Are Everywhere 110 | The `scrollToBottom` bug was a reminder that Vue's reactivity is fast, but the DOM still needs time to update. Always check if elements exist before accessing their properties. 111 | 112 | ### 2. State Management Complexity 113 | Initially, I tried to make the preview system "smart" with resizable splits and complex state. But simpler is often better - two clear modes (split/fullscreen) with obvious transitions work much better for users. 114 | 115 | ### 3. User Testing Reveals Edge Cases 116 | The chat creation bug only happened under specific timing conditions. Real user behavior (rapid clicking when something seems broken) often reveals issues that don't show up in normal development testing. 117 | 118 | ## 💭 Thoughts for Fellow Developers 119 | 120 | These kinds of UI reliability fixes might not be glamorous, but they have huge impact on user experience. A button that works 95% of the time feels broken to users. Taking the time to handle edge cases and race conditions is what separates good interfaces from great ones. 121 | 122 | Also, when building preview/modal systems, always think about the exit flow as much as the entry flow. Users need to understand how to get back to where they came from! 123 | 124 | --- 125 | 126 | *What features would you like to see improved next? Drop your thoughts in the issues!* 127 | 128 | *- Your dev team* -------------------------------------------------------------------------------- /src/content/blog/2025-08-18-ui-improvements-and-chat-fixes.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "UI Improvements and Chat Reliability Fixes" 3 | date: "2025-08-18" 4 | description: "Important improvements to the chat interface and bug fixes for better reliability" 5 | --- 6 | 7 | 8 | *August 18, 2025* 9 | 10 | Hey everyone! 👋 11 | 12 | I've been working on some important improvements to the chat interface over the past few days. Here's what's new and what got fixed. 13 | 14 | ## 🐛 Major Bug Fixes 15 | 16 | ### Chat Creation Button Issues 17 | One of the most frustrating bugs was the unresponsive "create new chat" button. Users would click it, nothing would happen, then they'd click multiple times and suddenly get several new chats created at once. 18 | 19 | **What was happening:** 20 | - The `scrollToBottom` function was trying to access `messageListEl.value.scrollHeight` before the DOM element was ready 21 | - No loading state protection meant rapid clicks could trigger multiple API calls 22 | - Race conditions in the chat creation flow 23 | 24 | **The fix:** 25 | ```javascript 26 | // Added null check in scrollToBottom 27 | const scrollToBottom = (_behavior: ScrollBehavior) => { 28 | behavior.value = _behavior 29 | if (messageListEl.value) { 30 | y.value = messageListEl.value.scrollHeight 31 | } 32 | } 33 | 34 | // Added loading state in ChatSessionList 35 | const isCreatingChat = ref(false) 36 | 37 | async function onNewChat() { 38 | if (isCreatingChat.value) return 39 | 40 | isCreatingChat.value = true 41 | try { 42 | const data = await createChatSession() 43 | sessionList.value.unshift(data) 44 | await router.push(`/chat/${data.id}`) 45 | } finally { 46 | isCreatingChat.value = false 47 | } 48 | } 49 | ``` 50 | 51 | This was a classic example of how small timing issues can create really annoying UX problems! 52 | 53 | ## ✨ New Feature: Enhanced Preview Panel 54 | 55 | The artifact preview system got a major upgrade! Previously, users could only view code artifacts in a basic side panel. Now we have: 56 | 57 | ### Split View Mode 58 | - Chat takes up the remaining space 59 | - Preview panel has a fixed 500px width 60 | - Both are visible simultaneously for context 61 | 62 | ### Fullscreen Mode 63 | - Preview covers the entire viewport 64 | - Header is completely hidden for maximum viewing area 65 | - Floating close button with semi-transparent background 66 | - Perfect for viewing complex HTML demos or detailed diagrams 67 | 68 | ### Smart State Management 69 | This was trickier than it sounds. The key insight was separating the "show/hide preview" state from the "normal/fullscreen" state: 70 | 71 | ```javascript 72 | // Two separate states instead of one confusing state 73 | const showArtifacts = ref(false) 74 | const isFullscreen = ref(false) 75 | 76 | // Smart close behavior 77 | const closeArtifacts = () => { 78 | showArtifacts.value = false 79 | isFullscreen.value = false // Reset fullscreen when closing 80 | } 81 | 82 | // Fullscreen close just exits fullscreen, doesn't close preview 83 | const toggleFullscreen = () => { 84 | isFullscreen.value = !isFullscreen.value 85 | } 86 | ``` 87 | 88 | The UX flow is now: 89 | 1. Click preview → Opens in split view 90 | 2. Click fullscreen → Expands to fullscreen 91 | 3. Click X in fullscreen → Returns to split view 92 | 4. Click X in split view → Closes preview completely 93 | 94 | ## 🎨 Animation Polish 95 | 96 | Changed the preview icon animation from a slide-in effect to a fade-in effect. Sometimes the smallest changes make the biggest difference in how polished an interface feels. 97 | 98 | ```scss 99 | // Before: Slide in from right 100 | .artifact-btn { 101 | opacity: 0; 102 | transform: translateX(8px); 103 | } 104 | 105 | // After: Simple fade 106 | .artifact-btn { 107 | opacity: 0; 108 | transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); 109 | } 110 | ``` 111 | 112 | ## 📚 What I Learned 113 | 114 | ### 1. DOM Timing Issues Are Everywhere 115 | The `scrollToBottom` bug was a reminder that Vue's reactivity is fast, but the DOM still needs time to update. Always check if elements exist before accessing their properties. 116 | 117 | ### 2. State Management Complexity 118 | Initially, I tried to make the preview system "smart" with resizable splits and complex state. But simpler is often better - two clear modes (split/fullscreen) with obvious transitions work much better for users. 119 | 120 | ### 3. User Testing Reveals Edge Cases 121 | The chat creation bug only happened under specific timing conditions. Real user behavior (rapid clicking when something seems broken) often reveals issues that don't show up in normal development testing. 122 | 123 | ## 💭 Thoughts for Fellow Developers 124 | 125 | These kinds of UI reliability fixes might not be glamorous, but they have huge impact on user experience. A button that works 95% of the time feels broken to users. Taking the time to handle edge cases and race conditions is what separates good interfaces from great ones. 126 | 127 | Also, when building preview/modal systems, always think about the exit flow as much as the entry flow. Users need to understand how to get back to where they came from! 128 | 129 | --- 130 | 131 | *What features would you like to see improved next? Drop your thoughts in the issues!* 132 | 133 | *- Your dev team* -------------------------------------------------------------------------------- /content/20250825-langchain-upgrade-chat-fix.md: -------------------------------------------------------------------------------- 1 | # LangChain Core Package Upgrade Breaks Chat: A Quick Fix Story 2 | 3 | **Date:** August 25, 2025 4 | **Issue:** Chat functionality broken after LangChain dependency upgrade 5 | **Resolution Time:** ~4 hours 6 | 7 | ## 🐛 The Problem 8 | 9 | What started as a routine `LangChain` dependency upgrade (`0.3.49` -> `0.3.72`) to fix Docker module resolution issues quickly turned into a critical incident. After upgrading the LangChain packages, the chat functionality completely stopped working across the entire platform. Users could no longer send messages or receive responses from any AI models, effectively rendering the core feature of ChatOllama unusable. 10 | 11 | The issue was particularly frustrating because there were no obvious error messages or warnings during the upgrade process. The application started normally, but every chat attempt simply failed silently. 12 | 13 | ## 🔍 Root Cause Investigation 14 | 15 | After diving into the logs and tracing through the code, we discovered that the LangChain upgrade had introduced breaking API changes in the chat model constructors. What made this especially tricky was that these weren't compile-time errors - the old parameter names were simply ignored, causing the models to initialize with undefined configurations. 16 | 17 | During the LangChain upgrade process, several parameter names in the ChatOpenAI model constructor underwent changes. While these parameters were merely marked as `deprecated`, their usage in downstream implementations had already changed. The deprecated parameters include: 18 | 19 | - `modelName` 20 | - `openAIApiKey` 21 | 22 | The breaking changes affected multiple model providers, with each requiring specific parameter name updates: 23 | 24 | ### Before (Working): 25 | ```typescript 26 | new ChatOpenAI({ 27 | configuration: { baseURL }, 28 | openAIApiKey: params.key, // ❌ Deprecated 29 | modelName: modelName, // ❌ Deprecated 30 | }) 31 | 32 | new ChatAnthropic({ 33 | anthropicApiUrl: endpoint, 34 | anthropicApiKey: params.key, // ❌ Deprecated 35 | modelName: modelName, // ❌ Deprecated 36 | }) 37 | ``` 38 | 39 | ### After (Fixed): 40 | ```typescript 41 | new ChatOpenAI({ 42 | configuration: { baseURL }, 43 | apiKey: params.key, // ✅ New API 44 | model: modelName, // ✅ New API 45 | }) 46 | 47 | new ChatAnthropic({ 48 | anthropicApiUrl: endpoint, 49 | apiKey: params.key, // ✅ New API 50 | model: modelName, // ✅ New API 51 | }) 52 | ``` 53 | 54 | ## 🔧 The Fix Implementation 55 | 56 | Once we identified the root cause, the fix was relatively straightforward but required careful attention to detail. We needed to update parameter names across all affected model providers while ensuring backward compatibility and adding better error handling. 57 | 58 | The following models required updates: 59 | - **OpenAI (ChatOpenAI)** - Most commonly used provider 60 | - **Anthropic (ChatAnthropic)** - Critical for AI agents functionality 61 | - **Gemini (ChatGoogleGenerativeAI)** - Used for multimodal features 62 | - **Groq (ChatGroq)** - High-performance inference option 63 | 64 | The key changes implemented were: 65 | 1. Standardized `openAIApiKey` and `anthropicApiKey` to the unified `apiKey` parameter 66 | 2. Updated `modelName` to the more concise `model` parameter across all providers 67 | 3. Enhanced error handling to provide clear feedback when configurations are missing 68 | 69 | Beyond just fixing the parameter names, we took the opportunity to add robust fallback logic. Now, when external API providers fail due to missing keys or configuration issues, the system gracefully falls back to Ollama, ensuring users can continue chatting even if their preferred provider is misconfigured. 70 | 71 | ## 📚 Lessons Learned 72 | 73 | This incident reinforced several important principles for managing dependencies in production applications: 74 | 75 | **Test Thoroughly After Major Upgrades:** Even seemingly minor version bumps can introduce breaking changes that aren't immediately obvious. Comprehensive testing across all features is essential, not just the areas you expect to be affected. 76 | 77 | **Embrace API Standardization:** While initially disruptive, LangChain's move to standardize parameter names across providers is a positive long-term change that will reduce confusion and make the codebase more maintainable. 78 | 79 | **Always Implement Graceful Degradation:** Having robust fallback mechanisms isn't just good practice - it's essential for maintaining user trust when external dependencies fail or change unexpectedly. 80 | 81 | ## 🚀 Impact and Resolution 82 | 83 | The fix was deployed immediately after identification, resulting in zero downtime for users. The updated implementation maintains full backward compatibility while leveraging the new standardized APIs. As an added benefit, the enhanced error handling and fallback mechanisms have actually improved the overall reliability of the chat system. 84 | 85 | This incident serves as a reminder that in the fast-moving world of AI and machine learning libraries, staying current with dependencies requires constant vigilance and thorough testing practices. 86 | 87 | --- 88 | 89 | *This was a classic case of "silent" breaking changes in a major upgrade - the kind that make experienced developers always read changelogs twice. The fix was simple once identified, but the experience highlights why we never take seemingly routine updates for granted.* 90 | -------------------------------------------------------------------------------- /src/content/blog-zh/2025-09-11-building-contextual-quick-chat-inspired-by-ai-ides_zh.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "构建上下文快速聊天:当AI IDE启发Web应用" 3 | date: "2025-09-11" 4 | description: "如何将AI IDE中熟悉的快速编辑体验带到Web聊天应用中,解决文本选择持久化问题,创建真正的上下文AI助手" 5 | --- 6 | 7 | > 如何将AI IDE中熟悉的"快速编辑"体验带到基于Web的聊天应用程序中,解决文本选择持久化问题并创建真正的上下文AI助手。 8 | 9 | ## 灵感来源:AI IDE的正确做法 10 | 11 | 如果你使用过现代AI驱动的IDE,如Cursor、GitHub Copilot或Claude Code,你一定体验过一种令人愉悦的交互模式:选择一些代码,右键单击或使用键盘快捷键,立即在一个紧凑的对话框中获得上下文AI帮助。无需上下文切换,无需复制粘贴,不会在代码中迷失位置。 12 | 13 | 这种交互如此直观,以至于当用户遇到它时,他们立即明白该怎么做。选中的文本提供了完美的上下文,对话框准确地出现在需要的地方,AI帮助感觉真正集成到工作流程中。 14 | 15 | ## 挑战:Web环境的现实 16 | 17 | 将这种体验带到Web应用程序中面临独特的挑战: 18 | 19 | ### 1. **文本选择持久化** 20 | 在原生应用中,文本选择在模态窗口中保持稳定。在Web中,DOM操作、焦点变化和页面重新渲染都会清除选择。 21 | 22 | ### 2. **跨组件状态管理** 23 | 用户可能在一个组件中选择文本,但需要在另一个组件(聊天界面)中引用它。这需要可靠的状态管理。 24 | 25 | ### 3. **用户体验期望** 26 | 现代用户期望即时性。任何延迟或"重复劳动"的感觉都会破坏体验的魔力。 27 | 28 | ## 我们的解决方案:上下文快速聊天 29 | 30 | ### 核心架构 31 | 32 | 我们构建了一个系统,可以: 33 | 1. **捕获用户选择**:在页面的任何地方 34 | 2. **保留上下文**:即使通过导航和重新渲染 35 | 3. **提供即时访问**:通过键盘快捷键或右键菜单 36 | 4. **智能预填充**:带有选中文本和合理默认提示的聊天 37 | 38 | ### 技术实现 39 | 40 | #### 1. 选择捕获和存储 41 | ```typescript 42 | class SelectionManager { 43 | private selectedText: string = ''; 44 | private selectionContext: SelectionContext | null = null; 45 | 46 | captureSelection(): void { 47 | const selection = window.getSelection(); 48 | if (selection && selection.toString().trim()) { 49 | this.selectedText = selection.toString().trim(); 50 | this.selectionContext = { 51 | pageUrl: window.location.href, 52 | timestamp: Date.now(), 53 | surroundingContext: this.getSurroundingContext(selection) 54 | }; 55 | } 56 | } 57 | 58 | private getSurroundingContext(selection: Selection): string { 59 | // 获取选择前后的文本以提供更多上下文 60 | const range = selection.getRangeAt(0); 61 | const container = range.commonAncestorContainer; 62 | return container.textContent?.substring( 63 | Math.max(0, range.startOffset - 100), 64 | Math.min(container.textContent.length, range.endOffset + 100) 65 | ) || ''; 66 | } 67 | } 68 | ``` 69 | 70 | #### 2. 键盘快捷键集成 71 | ```typescript 72 | class QuickChatTrigger { 73 | constructor(private selectionManager: SelectionManager) { 74 | this.setupKeyboardShortcuts(); 75 | this.setupContextMenu(); 76 | } 77 | 78 | private setupKeyboardShortcuts(): void { 79 | document.addEventListener('keydown', (e) => { 80 | // Cmd/Ctrl + Shift + Enter 触发快速聊天 81 | if ((e.metaKey || e.ctrlKey) && e.shiftKey && e.key === 'Enter') { 82 | e.preventDefault(); 83 | this.triggerQuickChat(); 84 | } 85 | }); 86 | } 87 | 88 | private triggerQuickChat(): void { 89 | const context = this.selectionManager.getContext(); 90 | if (context.selectedText) { 91 | this.openQuickChatModal(context); 92 | } 93 | } 94 | } 95 | ``` 96 | 97 | #### 3. 上下文感知的聊天预填充 98 | ```vue 99 |