从“手机上写代码“的疯狂想法,到一套完整的AI编程平台架构——WebCode深度技术剖析

从“手机上写代码“的疯狂想法,到一套完整的AI编程平台架构——WebCode深度技术剖析
说实话,当我第一次在地铁上用手机修复了一个线上Bug的时候,我整个人都是懵的。不是因为Bug有多难,而是因为——我TM居然真的在手机上写代码了?

一、那个让我失眠的需求

故事要从去年说起。

当时我们团队接到一个"看起来很简单"的需求:让开发者能够随时随地使用AI编程助手。产品经理信誓旦旦地说:"不就是做个Web版的Claude Code吗?套个壳就行了。"

我当时就笑了。

你知道Claude Code CLI是怎么工作的吗?它是一个本地进程,需要读写文件系统,需要执行Shell命令,需要维护会话状态,需要解析流式JSON输出...把这玩意儿搬到Web上?还要支持手机?

那几天我躺在床上翻来覆去睡不着,脑子里全是问题:

  • 浏览器怎么调用本地CLI工具?
  • 流式输出怎么实时推送到前端?
  • 多个用户同时使用,工作区怎么隔离?
  • 手机上那个破键盘,怎么让人愉快地写代码?
  • Claude Code和Codex的输出格式完全不一样,怎么统一处理?

后来我想明白了一件事:这不是一个"套壳"的活儿,这是要从零设计一套分布式AI编程平台的架构

二、架构设计:我踩过的那些坑

2.1 第一个大坑:CLI工具的"性格"都不一样

你以为所有AI CLI工具的输出格式都一样?太天真了。

Claude Code输出的是stream-json格式,长这样:

{"type":"assistant","message":{"role":"assistant","content":[{"type":"text","text":"让我来帮你..."}]}} 

而Codex输出的是JSONL格式,长这样:

{"type":"item.updated","item":{"type":"agent_message","text":"正在分析代码..."}} 

更要命的是,它们的会话恢复机制也不一样。Claude Code用--resume session_id,Codex用resume session_id(注意,没有双横线)。

一开始我想的是:写一堆if-else判断不就完了?

// 千万别这么写,我已经替你踩过坑了 if (toolId == "claude-code") {     // 处理Claude Code的逻辑 } else if (toolId == "codex") {     // 处理Codex的逻辑 } else if (toolId == "copilot") {     // 又来一个... } 

这种代码写出来,三个月后你自己都不想维护。

最后我选择了适配器模式。每个CLI工具都有自己的适配器,实现统一的接口:

public interface ICliToolAdapter {     // 这个工具支持哪些ID     string[] SupportedToolIds { get; }          // 是否支持流式解析     bool SupportsStreamParsing { get; }          // 构建命令行参数(每个工具的参数格式都不一样)     string BuildArguments(CliToolConfig tool, string prompt, CliSessionContext context);          // 解析输出(每个工具的输出格式都不一样)     CliOutputEvent? ParseOutputLine(string line);          // 提取会话ID(用于会话恢复)     string? ExtractSessionId(CliOutputEvent outputEvent); } 

这样做的好处是什么?新增一个CLI工具,只需要写一个新的适配器,核心代码一行不用改

举个例子,Claude Code的适配器是这样处理参数构建的:

public string BuildArguments(CliToolConfig tool, string prompt, CliSessionContext context) {     var escapedPrompt = EscapeShellArgument(prompt);          // 会话恢复参数     var sessionArg = string.Empty;     if (context.IsResume && !string.IsNullOrEmpty(context.CliThreadId))     {         sessionArg = $"--resume {context.CliThreadId}";  // Claude Code用双横线     }          return $"-p --output-format=stream-json {sessionArg} \"{escapedPrompt}\""; } 

而Codex的适配器是这样的:

public string BuildArguments(CliToolConfig tool, string prompt, CliSessionContext context) {     var escapedPrompt = EscapeJsonString(prompt);          var sessionArg = string.Empty;     if (context.IsResume && !string.IsNullOrEmpty(context.CliThreadId))     {         sessionArg = $"resume {context.CliThreadId}";  // Codex不用双横线     }          return $"exec --json {sessionArg} \"{escapedPrompt}\""; } 

看到没?差异被封装在适配器里,上层代码完全不用关心

2.2 第二个大坑:流式输出的"时序地狱"

AI的回复是流式的,一个字一个字往外蹦。这在命令行里看着很酷,但在Web上实现起来简直是噩梦。

问题出在哪儿?异步读取 + 并发渲染 + 状态同步

想象一下这个场景:

  1. 后端从CLI进程读取输出
  2. 通过SSE推送到前端
  3. 前端解析JSON
  4. 更新UI状态
  5. 触发Blazor重新渲染

这五步里,任何一步出问题,用户看到的就是乱码或者卡顿。

我最初的实现是这样的:

// 错误示范:直接在读取循环里更新UI while ((line = await reader.ReadLineAsync()) != null) {     var chunk = ParseJsonLine(line);     _currentMessage += chunk.Content;     StateHasChanged();  // 每读一行就刷新UI } 

结果呢?**CPU直接飙到100%**,页面卡得像PPT。

为什么?因为StateHasChanged()会触发整个组件树的重新渲染,而AI输出的速度是每秒几十上百个字符,你让Blazor每秒渲染几十次,它不崩谁崩?

最后的解决方案是防抖 + 批量更新

private System.Threading.Timer? _updateTimer; private readonly object _updateLock = new object(); private bool _hasPendingUpdate = false; private void QueueUIUpdate() {     lock (_updateLock)     {         if (_hasPendingUpdate) return;         _hasPendingUpdate = true;                  // 50ms内的更新合并成一次         _updateTimer?.Dispose();         _updateTimer = new Timer(_ =>         {             _hasPendingUpdate = false;             InvokeAsync(StateHasChanged);         }, null, 50, Timeout.Infinite);     } } 

这样一来,不管AI输出多快,UI最多每50ms刷新一次,既保证了流畅度,又不会把浏览器搞崩。

2.3 第三个大坑:工作区隔离的"安全噩梦"

多用户场景下,每个人都有自己的工作区。听起来简单,做起来要命。

最直接的问题:用户A能不能访问用户B的文件?

如果你的回答是"不能",那恭喜你,你需要考虑以下场景:

  • 路径穿越攻击:../../etc/passwd
  • 符号链接攻击:在工作区里创建一个指向系统目录的软链接
  • 命令注入:用户输入里带;rm -rf /

我的解决方案是多层防护

第一层:会话隔离

每个会话有独立的工作目录,目录名是随机生成的UUID:

public string GetOrCreateSessionWorkspace(string sessionId) {     lock (_workspaceLock)     {         if (_sessionWorkspaces.TryGetValue(sessionId, out var existing))             return existing;                  var workspacePath = Path.Combine(             GetEffectiveWorkspaceRoot(),             sessionId  // UUID,无法猜测         );                  Directory.CreateDirectory(workspacePath);         _sessionWorkspaces[sessionId] = workspacePath;         return workspacePath;     } } 

第二层:路径验证

所有文件操作都要验证路径是否在工作区内:

private bool IsPathSafe(string workspacePath, string requestedPath) {     var fullPath = Path.GetFullPath(Path.Combine(workspacePath, requestedPath));     var normalizedWorkspace = Path.GetFullPath(workspacePath);          // 确保解析后的路径仍在工作区内     return fullPath.StartsWith(normalizedWorkspace, StringComparison.OrdinalIgnoreCase); } 

第三层:命令白名单

CLI工具只能执行预定义的命令,用户输入会被转义:

private static string EscapeShellArgument(string argument) {     // 转义所有可能导致命令注入的字符     return argument         .Replace("\\", "\\\\")         .Replace("\"", "\\\"")         .Replace("$", "\\$")         .Replace("`", "\\`"); } 

三、上下文管理:AI的"记忆"问题

这是整个项目里我最得意的一块设计。

你有没有遇到过这种情况:跟AI聊了半天,它突然"失忆"了,之前说的话全忘了?

这是因为AI模型有上下文窗口限制。Claude的上下文窗口是100K tokens,听起来很大,但如果你在讨论一个大型项目,贴几个文件进去,分分钟就爆了。

传统的解决方案是"截断"——超过限制就把最早的消息删掉。但这样做的问题是:可能把关键信息删掉了

我设计了一套智能上下文管理系统

3.1 上下文项的优先级

不是所有信息都同等重要。用户的问题比AI的回复重要,错误信息比普通输出重要,最近的消息比很久以前的消息重要。

public class ContextItem {     public ContextItemType Type { get; set; }     public string Content { get; set; }     public int EstimatedTokens { get; set; }     public int Priority { get; set; }  // 0-10,越高越重要     public bool IsIncluded { get; set; } = true; } 

优先级的默认规则:

  • 用户消息:7
  • 错误信息:9(最高,因为通常是当前要解决的问题)
  • AI回复:5
  • 代码片段:6
  • 文件引用:4

3.2 智能压缩策略

当上下文快满的时候,系统会自动触发压缩。但不是简单地删除,而是智能摘要

private async Task CompressSmartSummaryAsync(...) {     // 1. 高优先级项永远保留     var highPriorityItems = items.Where(i => i.Priority >= 7);          // 2. 最近的用户消息保留     var recentUserMessages = items         .Where(i => i.Type == ContextItemType.UserMessage)         .OrderByDescending(i => i.CreatedAt)         .Take(config.KeepRecentMessages);          // 3. 其他内容生成摘要     foreach (var item in itemsToCompress)     {         if (item.Type == ContextItemType.CodeSnippet)         {             // 代码片段:保留函数签名,删除实现细节             item.Content = GenerateCodeSnippetSummary(item.Content);             item.EstimatedTokens = TokenEstimator.EstimateTokens(item.Content);         }     } } 

这样做的效果是:即使上下文被压缩了,AI仍然知道之前讨论过什么,只是细节没那么清楚了

3.3 Token估算

准确估算Token数量是个技术活。不同语言的Token密度不一样:

public static int EstimateTokens(string text) {     // 中文:约1.5字符/Token     // 英文:约4字符/Token     var chineseChars = text.Count(c => c >= 0x4E00 && c <= 0x9FFF);     var otherChars = text.Length - chineseChars;          return (int)Math.Ceiling(chineseChars / 1.5 + otherChars / 4.0); } 

代码的Token密度更高(因为有很多符号和关键字),所以单独处理:

public static int EstimateCodeTokens(string code) {     return (int)Math.Ceiling(code.Length / 3.5); } 

四、移动端适配:那些让我抓狂的细节

"支持手机"这四个字,背后是无数个深夜的调试。

4.1 iOS Safari的100vh问题

你知道iOS Safari的100vh不是真正的视口高度吗?它包含了地址栏的高度,导致页面底部会被遮挡。

解决方案:

.container {     height: 100vh;     height: 100dvh;  /* 动态视口高度,iOS 15+支持 */     height: -webkit-fill-available;  /* 兼容旧版本 */ } 

4.2 虚拟键盘弹出时的布局问题

手机上弹出键盘时,视口高度会变化,如果处理不好,输入框会被键盘遮挡。

我的解决方案是监听visualViewport的变化:

if (window.visualViewport) {     window.visualViewport.addEventListener('resize', () => {         const keyboardHeight = window.innerHeight - window.visualViewport.height;         document.documentElement.style.setProperty('--keyboard-height', `${keyboardHeight}px`);     }); } 

然后在CSS里使用这个变量:

.input-area {     padding-bottom: calc(env(safe-area-inset-bottom) + var(--keyboard-height, 0px)); } 

4.3 触摸目标大小

苹果的人机界面指南建议触摸目标至少44x44像素。但很多开发者(包括以前的我)都忽略了这一点。

.touch-target {     min-width: 44px;     min-height: 44px;     padding: 12px;  /* 即使内容小,点击区域也要够大 */ } 

五、性能优化:从"能用"到"好用"

5.1 文件树的虚拟滚动

工作区可能有成百上千个文件,如果全部渲染到DOM里,页面会卡死。

解决方案是懒加载 + 虚拟滚动

private const int MaxVisibleNodes = 100; private int _currentVisibleNodes = MaxVisibleNodes; // 只渲染可见区域的节点 private List<WorkspaceFileNode> GetVisibleNodes() {     return _workspaceFiles         .Take(_currentVisibleNodes)         .ToList(); } // 滚动到底部时加载更多 private void LoadMoreNodes() {     _currentVisibleNodes += 50;     StateHasChanged(); } 

5.2 Markdown渲染缓存

AI的回复通常包含大量Markdown,每次渲染都要解析一遍很浪费。

private readonly Dictionary<string, MarkupString> _markdownCache = new(); private MarkupString RenderMarkdown(string? markdown) {     if (_markdownCache.TryGetValue(markdown, out var cached))         return cached;          var html = Markdown.ToHtml(markdown, _markdownPipeline);     var result = new MarkupString(html);          // 限制缓存大小     if (_markdownCache.Count > 100)         _markdownCache.Clear();          _markdownCache[markdown] = result;     return result; } 

5.3 输出状态的防抖保存

用户的输出结果需要持久化,但不能每次更新都写数据库。

private void QueueSaveOutputState() {     lock (_outputStateSaveLock)     {         if (_hasPendingOutputStateSave) return;         _hasPendingOutputStateSave = true;                  _outputStateSaveTimer?.Dispose();         _outputStateSaveTimer = new Timer(async _ =>         {             _hasPendingOutputStateSave = false;             await SaveOutputStateAsync();         }, null, OutputStateSaveDebounceMs, Timeout.Infinite);     } } 

六、未来的坑(和机会)

这个项目还在持续迭代,有几个方向我特别想做:

6.1 多模型对比

同一个问题,让Claude和GPT同时回答,对比结果。这对于选择最佳方案很有帮助。

技术上的挑战是并行执行 + 结果同步

public async Task<List<ModelResponse>> ExecuteParallelAsync(     string prompt,      List<string> toolIds) {     var tasks = toolIds.Select(toolId =>          ExecuteSingleAsync(prompt, toolId));          return await Task.WhenAll(tasks); } 

6.2 实时协作

多人同时编辑同一个工作区,像Google Docs那样。

这需要引入CRDT(无冲突复制数据类型)或者OT(操作转换)算法,复杂度直接上一个台阶。

6.3 插件系统

让用户可以自己添加新的CLI工具适配器,不需要改核心代码。

架构上已经预留了扩展点:

public interface IPluginService {     void RegisterCliAdapter(ICliToolAdapter adapter);     List<PluginUIComponent> GetPluginUIComponents(string location); } 

七、写在最后

回头看这个项目,最大的收获不是代码本身,而是对复杂系统的理解

一个"简单"的需求背后,可能涉及:

  • 进程管理和IPC
  • 流式数据处理
  • 安全和隔离
  • 跨平台兼容
  • 性能优化
  • 用户体验

每一个点展开都是一个深坑。

但这也是软件开发的魅力所在,不是吗?

你永远不知道下一个Bug会把你带到哪个知识领域。


如果你对这个项目感兴趣,欢迎来GitHub上看看:WebCodehttps://github.com/xuzeyu91/WebCode

有问题可以在评论区讨论,我会尽量回复。

最后,如果这篇文章对你有帮助,点个赞呗?写了一整天,手都酸了 😂

更多AIGC文章

RAG技术全解:从原理到实战的简明指南

更多VibeCoding文章

Read more

财务RPA机器人上岗:90%重复工作被取代,会计人该恐慌还是进化?

“每月花3天核对500张发票,现在借助八爪鱼RPA机器人1小时就能搞定;手工录入200条银行流水,出错率高达5%,机器人却能实现零误差;月末结账熬夜到凌晨的日子彻底结束,机器人可自动生成精准报表……”这不是科幻剧情,而是国内多家大中型企业财务部的真实工作场景。 如今,以RPA(机器人流程自动化)技术为核心的财务机器人,正凭借“秒级处理速度”和“零失误率”席卷财务领域,90%的重复性财务工作逐渐被替代。对于会计人而言,这并非职业危机,而是升级转型的关键契机。 本文将深度拆解财务RPA机器人的3大核心能力、5大落地应用场景,并为会计人梳理清晰的“进化路径”,助力大家在这场财务自动化革命中掌握主动权。 一、财务RPA机器人“有多强”?3大核心能力颠覆传统财务模式 能力1:7×24小时不间断作业,效率提升超10倍 典型场景:某连锁零售企业每月需处理10万张销售小票,传统人工模式下,需5名财务人员连续工作3天(合计15人天)才能完成核对、录入与异常标记。引入八爪鱼RPA机器人后,通过OCR图文识别+预设规则校验,1小时即可完成全部10万张小票的处理工作,错误率从人工的3%直接降至

汇川机器人软件RobotLab常规操作

汇川机器人软件RobotLab常规操作

一.权限管理注意事项 1.1 软件登录权限管理 连接上软件后,修改轴参数、点位数据需要权限。点击人物图标,登录对应的权限,管理员权限登录密码6个0。 1.2机器人控制权限管理 点击“锁”,打开机器人控制权配置页面。 选择“InoRoboLabt”,机器人受编程软件控制,使用软件可手动移动点位、示教位置信息。 选择“远程IO单元”,机器人受外部设备控制如PLC、上位机,机器人进入自动模式,收到交互信号就按照程序执行。 选择“远程以太网客户端”,机器人受远程客户短控制,用于查找问题、远程调试。 二、 使用过渡点注意事项 程序中点到点直线运动会有机构干涉或有安全风险时,使用过渡点在运动规避风险。 使用过渡点时,注意指令的工具坐标系,选择正确的Wobj工具好,否则运动出错有撞机风险。 如下图所示为例,wobj0为A工位,wobj1为B工位,注意在“轴控制面板”中选择对应工具坐标号 三、使用全局点位移动注意事项 双击左侧“P.

.NET/JAVA集成GoView低代码可视化大屏完整案例详解【.NET篇】

.NET/JAVA集成GoView低代码可视化大屏完整案例详解【.NET篇】

文章目录 * 一、GoView简介 * 二、.NET集成GoView方案 * 三、集成步骤详解 * 1. 环境准备 * 2. 获取并构建GoView * 3. 创建.NET项目 * 5. 修改.NET路由配置 * 6. 配置API接口 * 7. 修改GoView配置 * 四、进阶集成方案 * 1. 身份验证集成 * 2. 动态主题切换 * 3. 数据缓存优化 * 五、常见问题解决 * 1.跨域问题: * 2. 静态文件404错误: * 3. API请求路径问题: * 4. 性能优化: * 六、总结 一、GoView简介 GoView 是一款基于 Vue3.x 构建的低代码数据可视化开发平台,它允许开发者通过简单的配置快速构建各种数据可视化大屏。 * 官网文档:

(10-1)大模型时代的人形机器人感知:视觉-语言模型在机器人中的应用

(10-1)大模型时代的人形机器人感知:视觉-语言模型在机器人中的应用

本章内容聚焦大模型时代人形机器人的感知体系升级,系统介绍了视觉—语言模型、多模态Transformer与3D大模型在机器人中的核心作用,详细讲解了文本、视觉、点云与语音等信息的语义对齐与融合机制,介绍了从语言指令到视觉目标的Grounding、任务分解与意图理解方法,并通过闭环感知与决策联动,展示了大模型支撑机器人在复杂真实场景中的理解、规划与实时行动的用法。 10.1  视觉-语言模型在机器人中的应用 视觉—语言模型(Vision-Language Model,VLM)通过统一建模视觉与自然语言,使机器人具备“看懂并理解语言”的能力,是大模型时代机器人感知与认知融合的核心技术。VLM不仅能够完成图像识别、目标检测等传统感知任务,还可以直接理解语言指令、进行语义推理,并将高层语义映射为可执行的感知与行动目标,在人形机器人中广泛应用于交互理解、场景认知和任务执行等环节。 10.1.1  CLIP/BLIP/Flamingo等模型简介 随着大规模多模态数据与Transformer架构的发展,视觉—语言模型逐渐从“跨模态对齐”演进为“多模态理解与推理”。CLIP、BLIP与Flam