1. 项目缘起:从终端苦海中解脱

作为一名长期与代码打交道的开发者,我最近几个月深度体验了Claude Code这类AI编程助手。它们的能力确实令人惊叹,能理解复杂的上下文、生成高质量的代码片段,甚至能协助调试和重构。但有一个问题始终如鲠在喉: 交互体验太“原始”了

我指的“原始”,是那种必须蜷缩在终端(Terminal)里,通过一行行命令和纯文本输出来与一个强大的AI智能体对话的割裂感。想象一下,你正在IDE里优雅地编写一个React组件,突然需要Claude帮你分析一段后端逻辑,于是你不得不:

  1. 切到另一个终端窗口。
  2. 输入启动命令,等待模型加载。
  3. 在单调的命令行提示符 > 后面,小心翼翼地粘贴你的代码片段和问题。
  4. 面对瀑布般滚动的、没有任何高亮和格式的纯文本输出,费力地从中寻找关键信息。
  5. 如果需要来回讨论、修改代码,这个过程会不断重复,窗口切换和上下文丢失让人心力交瘁。

这种体验,我称之为“终端苦海”。它打断了流畅的开发心流,将本应高效的协作变成了笨拙的、低带宽的信息交换。更别提那些需要可视化展示的环节,比如让AI帮你生成一个图表的数据处理代码,或者解释一段涉及UI布局的逻辑——在终端里,这一切都失去了直观性。

所以,我决定自己动手, 为Claude Code这类代码智能体构建一个干净、现代的Web用户界面(Web UI) 。这个项目的核心目标非常明确: 将AI编程助手的强大能力,封装进一个与开发者日常工具(浏览器、IDE)无缝集成、视觉友好、交互高效的界面中 ,彻底告别令人疲惫的终端交互。

2. 核心设计思路与架构选型

2.1 需求拆解:一个好用的AI编程UI应该是什么样?

在动手写第一行代码之前,我花了大量时间思考:除了“不用终端”,这个Web UI到底要解决哪些具体痛点?我总结出了以下几个核心需求:

  1. 代码友好型交互 :核心场景是代码讨论。界面必须原生支持代码块,具备语法高亮、代码折叠、行号显示等基础功能,并且能方便地复制、插入到编辑器中。
  2. 对话上下文管理 :与AI的编程对话往往是多轮、有状态的。UI需要清晰展示对话历史,允许用户轻松回溯、引用之前的任何一轮问答,甚至能保存和加载不同的“会话”(Session)。
  3. 多模态输入支持 :开发者不仅会输入代码,还可能附带错误信息、配置文件、甚至草图或截图。UI需要支持便捷的文件上传、拖拽粘贴图片,并能将非文本内容智能地转化为对话上下文的一部分。
  4. 实时性与流式响应 :等待AI生成大段代码或解释时,看着光标闪烁的空白行是一种煎熬。界面必须支持流式输出(Streaming),让用户能实时看到AI“思考”和“书写”的过程,这能极大提升交互的确定性和流畅感。
  5. 项目上下文感知 :高级的编程助手能理解整个项目的结构。UI最好能集成简单的文件树浏览,允许用户将整个项目或特定文件作为背景知识提供给AI,而不是每次手动粘贴片段。
  6. 极简与高效 :界面不能臃肿。它应该像一个专注的“编程伙伴”窗口,随时可以呼出或隐藏,不占用过多屏幕空间,快捷键操作要流畅。

2.2 技术栈选型:为什么是Next.js + Tailwind CSS + Vercel AI SDK?

基于以上需求,我选择了以下技术组合,这是一个经过深思熟虑的、面向现代Web开发的高效方案:

  • 前端框架:Next.js 14 (App Router) 。这是最关键的选择。Next.js不仅提供了开箱即用的React服务端渲染(SSR)和静态生成(SSG),其最新的App Router模式对构建需要复杂路由和数据获取的应用(如多会话管理)非常友好。更重要的是,它对 服务端组件(Server Components)和流式渲染(Streaming) 的支持是原生且一流的,这正是实现AI响应流式输出的关键技术基础。我不需要自己搭建复杂的WebSocket或SSE(Server-Sent Events)连接管理,Next.js的架构让这变得异常简单。

  • UI与样式:Tailwind CSS + shadcn/ui 。Tailwind的实用优先(Utility-first)理念让我能快速构建出既美观又响应式的界面,而无需在CSS文件和组件间来回切换。 shadcn/ui 是一个基于Radix UI构建的、可复制粘贴的高质量组件库,它提供了对话框、下拉菜单、代码编辑器等现成的、可无障碍访问的组件,极大地加速了开发,同时保证了UI的一致性和专业性。

  • AI交互核心:Vercel AI SDK 。这是一个为构建AI应用而生的工具包。它抽象了与不同AI提供商(如OpenAI、Anthropic等)API交互的复杂性,提供了统一的 useChat useCompletion 等React Hooks。我只需要配置好API密钥和模型端点,就能轻松实现带有流式响应、历史记录管理、停止生成等功能的完整聊天界面。它完美契合了我的需求,避免了重复造轮子。

  • 代码编辑器:CodeMirror 6 。虽然有一些更知名的编辑器如Monaco(VS Code的核心),但CodeMirror 6更轻量、模块化程度更高,并且对自定义和集成到React中非常友好。它提供了强大的语法高亮、代码折叠、自动补全等能力,足以满足项目中代码展示和轻量编辑的需求。

  • 部署与后端:Vercel Platform 。Next.js应用部署在Vercel上是天作之合。它提供了全球CDN、自动HTTPS、以及极其简单的Git集成部署。对于后端,我计划使用Next.js的API Routes(现在叫Route Handlers)来创建安全的代理端点,用于转发前端请求到Claude API,这样可以避免在前端暴露敏感的API密钥。

注意:API密钥安全是重中之重。 绝对不要将 ANTHROPIC_API_KEY 这样的密钥硬编码在客户端代码或环境变量文件中。正确的做法是:在Vercel的项目设置中配置环境变量,然后在Next.js的API Route(服务端)中通过 process.env 读取,由服务端代为发起请求。这样密钥永远不会暴露给浏览器。

这个技术栈的组合,确保了项目在开发效率、用户体验、性能和维护性上都能达到一个很高的起点。

3. 核心功能模块实现详解

3.1 聊天会话界面的构建

这是应用的心脏。我利用Vercel AI SDK的 useChat hook 快速搭建了核心的聊天逻辑。

// app/api/chat/route.ts - 服务端API端点
import { Anthropic } from '@anthropic-ai/sdk';
import { Message, StreamingTextResponse } from 'ai';

export async function POST(req: Request) {
  const { messages } = await req.json();
  const anthropic = new Anthropic({
    apiKey: process.env.ANTHROPIC_API_KEY!,
  });

  const stream = await anthropic.messages.create({
    model: 'claude-3-sonnet-20240229', // 或你使用的模型
    max_tokens: 4096,
    messages: messages.map(msg => ({
      role: msg.role === 'user' ? 'user' : 'assistant',
      content: msg.content,
    })),
    stream: true, // 关键:启用流式输出
  });

  // 将Anthropic的流转换为标准的AI SDK流
  const encoder = new TextEncoder();
  const transformStream = new ReadableStream({
    async start(controller) {
      for await (const chunk of stream) {
        if (chunk.type === 'content_block_delta' && chunk.delta.type === 'text_delta') {
          controller.enqueue(encoder.encode(chunk.delta.text));
        }
      }
      controller.close();
    },
  });

  return new StreamingTextResponse(transformStream);
}

在客户端组件中,集成变得非常简单:

// app/components/chat-window.tsx
'use client';
import { useChat } from 'ai/react';

export function ChatWindow() {
  const { messages, input, handleInputChange, handleSubmit, isLoading } = useChat({
    api: '/api/chat', // 指向我们刚创建的服务端端点
    initialMessages: [{ id: '1', role: 'system', content: '你是一个专业的编程助手Claude。请用清晰、准确的语言回答用户的编程问题,代码部分请提供语法高亮。' }],
  });

  return (
    <div className="flex flex-col h-full">
      <div className="flex-1 overflow-y-auto">
        {messages.map(m => (
          <div key={m.id} className={`p-4 ${m.role === 'user' ? 'bg-gray-50' : ''}`}>
            <strong>{m.role === 'user' ? '我' : 'Claude'}:</strong>
            {/* 这里会渲染消息内容,如果是代码,会用CodeMirror组件特殊处理 */}
            <MessageContent content={m.content} />
          </div>
        ))}
      </div>
      <form onSubmit={handleSubmit} className="border-t p-4">
        <textarea
          value={input}
          onChange={handleInputChange}
          placeholder="输入你的问题或代码..."
          className="w-full p-2 border rounded"
          rows={3}
        />
        <button type="submit" disabled={isLoading} className="mt-2 px-4 py-2 bg-blue-500 text-white rounded">
          {isLoading ? '思考中...' : '发送'}
        </button>
      </form>
    </div>
  );
}

实操心得 useChat hook 自动管理了消息列表、加载状态和错误处理。最关键的是,当服务端返回流式响应时, messages 数组会自动更新,新的token会实时追加到最后一条助手消息中,实现了“打字机”效果。这比手动管理 fetch EventSource 要省心太多。

3.2 代码高亮与交互增强

纯文本的代码是难以阅读的。我集成CodeMirror 6来渲染消息中的代码块。

  1. 代码块识别与提取 :AI的回复通常是Markdown格式,代码块被包裹在 ``` 中。我写了一个简单的解析函数,将消息内容拆分为文本段落和代码块。
  2. 动态创建编辑器实例 :对于每个识别出的代码块,我使用 @uiw/react-codemirror 这个React封装库,动态创建一个只读的CodeMirror编辑器实例,并配置对应的语言模式(如 javascript python html 等)。
  3. 添加实用操作 :在每个代码块的上方,我添加了一个工具栏,包含:
    • 复制按钮 :一键复制全部代码。
    • 语言标签 :显示当前代码的语言。
    • 插入到输入框按钮 (可选):对于AI生成的代码片段,有时你想基于它继续提问。这个按钮可以将代码快速粘贴到下方的输入框中,方便你添加修改意见或追问。
// app/components/code-block.tsx
'use client';
import CodeMirror from '@uiw/react-codemirror';
import { javascript } from '@codemirror/lang-javascript';
import { python } from '@codemirror/lang-python';
// ... 导入其他语言支持
import { copyToClipboard } from '@/lib/utils'; // 一个复制到剪贴板的工具函数

export function CodeBlock({ code, language }: { code: string; language: string }) {
  const langExtension = getLanguageExtension(language); // 根据语言字符串返回对应的CodeMirror语言包

  return (
    <div className="relative my-4 rounded-md border border-gray-200 bg-gray-900">
      <div className="flex justify-between items-center px-3 py-2 border-b border-gray-700 bg-gray-800 text-sm text-gray-300">
        <span className="font-mono">{language}</span>
        <button
          onClick={() => copyToClipboard(code)}
          className="px-2 py-1 text-xs hover:bg-gray-700 rounded"
        >
          复制
        </button>
      </div>
      <CodeMirror
        value={code}
        extensions={[langExtension]}
        editable={false}
        basicSetup={{
          lineNumbers: true,
          foldGutter: true,
          syntaxHighlighting: true,
        }}
        theme="dark"
        height="auto"
        className="text-sm"
      />
    </div>
  );
}

注意事项 :CodeMirror 6是“headless”的,主题需要单独配置。我选择了暗色主题来匹配代码编辑器的常见风格,并确保其在高亮、字体等方面的可读性。同时,将编辑器设置为 editable={false} ,因为它主要用于展示,而非编辑。

3.3 会话管理与文件上下文

一个健壮的编程助手UI必须能管理多个独立的对话。

  1. 会话列表侧边栏 :我在主界面左侧设计了一个可折叠的侧边栏。它显示所有已保存的会话,每个会话项显示标题(自动从第一条消息生成或用户重命名)、创建时间和预览片段。点击即可切换当前会话。
  2. 本地存储与状态同步 :我使用 zustand 作为状态管理库,因为它轻量且对TypeScript支持友好。会话列表和当前会话ID存储在zustand的store中。同时,为了持久化,我利用 localStorage 在用户浏览器端保存会话数据。zustand store初始化时会从 localStorage 读取数据,任何变更也会同步写回。
  3. 文件上传与项目集成 :在输入框区域,我添加了一个文件上传按钮。用户可以上传 .txt .js .py .json 等文本文件。上传后,文件内容会被读取并自动以“以下是文件 [文件名] 的内容:”的形式插入到输入框中,作为用户消息的一部分发送给AI。对于更复杂的项目上下文,我实现了一个简单的“项目文件树”面板(使用类似 react-arborist 的库),允许用户从本地目录选择多个文件,系统会将这些文件的路径和内容摘要作为系统提示的一部分,让AI在回答时能参考项目结构。

踩坑记录 :最初我将完整的文件内容直接拼接到用户消息中,这导致在对话历史很长时,令牌数(Token)迅速超标,API调用失败且成本激增。 优化方案是 :对于作为上下文的文件,不在每轮对话中都全量发送。而是创建一个“会话上下文”的概念,在首次上传时,将文件元信息(路径、大小)和关键摘要(如函数名、类名)存储起来。当用户提问涉及特定文件时,通过一个轻量级的索引,只提取相关部分的内容附加到问题中。这大大减少了令牌消耗。

4. 部署、优化与安全考量

4.1 部署到Vercel

部署过程出乎意料的简单,这正是选择这个技术栈的优势之一。

  1. 将代码推送到GitHub仓库。
  2. 在Vercel官网导入该GitHub仓库。
  3. 在Vercel的项目设置(Settings -> Environment Variables)中,添加 ANTHROPIC_API_KEY 环境变量,填入你的真实密钥。
  4. Vercel会自动检测到这是Next.js项目,并运行构建命令。构建成功后,应用就拥有了一个全球可访问的URL。

重要提示 :确保你的 next.config.js 或其他配置中,没有意外地将环境变量暴露给客户端。在Next.js中,只有以 NEXT_PUBLIC_ 为前缀的环境变量才会被发送到浏览器。

4.2 性能与用户体验优化

  1. 流式响应优化 :除了使用AI SDK的流式输出,我还对UI做了细微调整。在AI回复时,不仅内容区域流式显示,我还将发送按钮的状态改为“思考中...”并禁用,同时输入框也暂时禁用,防止用户在中途发送新消息导致上下文混乱。当流式传输完成或用户点击“停止生成”按钮时,界面状态才恢复。
  2. 消息虚拟列表 :当对话历史非常长时,渲染所有DOM节点会严重影响滚动性能。我引入了 react-virtuoso tanstack-virtual 这类虚拟滚动库。它们只渲染可视区域内的消息,极大提升了长列表的渲染效率。
  3. 离线支持与自动保存 :利用浏览器的 beforeunload 事件,在用户意外关闭页面时提示保存。同时,实现一个防抖(debounce)自动保存机制,每当消息列表或会话数据发生变化,延迟几秒后自动保存到 localStorage ,减少数据丢失风险。
  4. 快捷键支持 :为常用操作添加快捷键,提升效率。例如:
    • Ctrl + Enter / Cmd + Enter :发送消息。
    • Ctrl + K / Cmd + K :聚焦到输入框。
    • Ctrl + L / Cmd + L :清空输入框。

4.3 安全加固

对于任何涉及API密钥和用户数据的应用,安全都是底线。

  1. API密钥代理 :如前所述,所有对Claude API的调用都通过Next.js服务端路由( /api/chat )进行中转。前端只与自己的后端通信,密钥安全地存储在Vercel的服务端环境变量中。
  2. 输入验证与清理 :在服务端API路由中,对接收到的 messages 进行严格的验证(例如,检查是否为数组,内容是否为字符串,长度是否在合理范围内)。虽然Claude API自身有内容安全策略,但服务端进行基础清洗可以防止一些基础的注入攻击或意外错误。
  3. 速率限制(Rate Limiting) :为了防止滥用,我在服务端API路由上增加了简单的速率限制。可以使用像 rate-limiter-flexible 这样的库,基于用户IP或会话ID,限制每分钟或每小时的请求次数。这对于防止恶意刷API消耗额度至关重要。
  4. 错误处理与用户反馈 :网络错误、API额度不足、模型超时等情况都可能发生。UI上需要有友好的错误提示,而不是控制台一片红。我使用 react-hot-toast 库来显示操作成功或失败的非阻塞式通知。

5. 从终端到Web:体验的飞跃与未来可能

构建并实际使用这个Web UI几周后,我可以肯定地说,开发体验得到了质的提升。我不再需要与终端搏斗,而是拥有了一个 专注、美观、功能集中的编程协作者工作台

  • 心流保持 :Web UI可以作为一个浏览器标签或PWA应用固定在屏幕一侧,与我的IDE并排。提问、查看代码、复制结果,都在鼠标和键盘的短距离移动内完成,几乎感觉不到上下文切换。
  • 信息密度与可读性 :语法高亮的代码、结构清晰的对话历史、可折叠的冗长回答,让信息的获取效率倍增。
  • 能力扩展的想象空间 :Web界面打开了更多可能性。例如,我可以很容易地集成一个简单的“运行沙箱”,让AI生成的Python或JavaScript代码能在一个安全的隔离环境中直接运行并查看结果。或者,增加一个“思维链”可视化面板,让AI展示其推理步骤(如果模型支持)。

当然,这个项目并非要完全替代终端。对于需要复杂命令行操作、进程管理或深度系统集成的任务,终端依然无可替代。但这个Web UI精准地填补了**“与AI代码助手进行高效、舒适对话”** 这一空白场景。

如果你也受困于在终端中使用AI编程助手,不妨尝试自己动手构建一个类似的界面。从技术上看,利用现代Web框架和AI SDK,实现核心功能的门槛并不高。关键在于深刻理解你自己的 workflow,然后打造一个真正贴合你习惯的工具。毕竟,最好的工具,往往是自己为自己打造的。

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐