踩坑三周,我终于把 Claude Code 和 Codex 塞进了浏览器— 一个让手机也能写代码的疯狂想法

「在地铁上用手机写代码」,这个念头最早是怎么蹦出来的,我已经记不清了。只记得那天加班到凌晨两点,拖着疲惫的身躯挤进末班地铁,手里还攥着一个没解决的 bug。要是这时候能掏出手机,让 AI 帮我把代码改了该多好?

于是,一个「远程驱动 AI 编程助手」的项目就这样诞生了。

听起来简单,做起来要命。

一、背景:当 AI 编程助手遇上「移动办公」

先说说痛点。

现在市面上的 AI 编程助手,无论是 Claude Code CLI、OpenAI Codex CLI,还是 GitHub Copilot CLI,都有一个共同的「硬伤」——它们都是命令行工具

这意味着什么?意味着你得有一台电脑,打开终端,敲命令。手机?平板?想都别想。

但问题是,我们这代程序员,已经被移动互联网惯坏了。微信能在手机上发消息,钉钉能在手机上审批,为什么写代码就必须坐在电脑前?

有没有一种可能,让浏览器成为 AI 编程助手的「遥控器」?

你在手机上输入需求,服务器上的 Claude Code 或 Codex 帮你执行,结果实时推送到你的屏幕上。不管你是在咖啡馆、地铁上,还是躺在沙发上——只要有浏览器,就能写代码。

这就是 WebCodeCli 要做的事情。

二、技术选型:为什么是 Blazor Server?

很多人第一反应可能是:「这不就是个 Web 终端吗?用 xterm.js 套个壳不就完了?」

我最初也是这么想的。但真正动手才发现,事情远没有那么简单。

2.1 流式输出的噩梦

AI 编程助手有一个显著特征——流式输出

它不是一次性返回结果,而是像打字机一样,一个字一个字地「敲」出来。这对用户体验至关重要:如果你发了一个需求,等 30 秒没任何反馈,你会以为程序挂了。但如果你能看到 AI 正在「思考」、正在「写代码」,就会安心很多。

问题在于,Claude Code 和 Codex 的流式输出格式完全不同。

Claude Code使用的是stream-json格式,输出类似这样:

{"type":"system","subtype":"init","session_id":"abc123","cwd":"/workspace"} {"type":"assistant","message":{"role":"assistant","content":[{"type":"text","text":"我来帮你..."}]}} {"type":"tool_use","name":"Read","input":{"path":"src/main.ts"}}

Codex使用的是JSONL格式,结构又是另一套:

{"type":"thread.started","thread_id":"xyz789"} {"type":"item.started","item":{"type":"agent_message"}} {"type":"item.updated","item":{"type":"agent_message","text":"让我分析一下..."}} {"type":"turn.completed","usage":{"input_tokens":1234,"output_tokens":567}}

如果用传统的 REST API + 轮询方案,这种流式体验根本做不出来。用 WebSocket?可以,但状态管理会变得异常复杂。

最终我选择了Blazor Server

为什么?因为 Blazor Server 有一个杀手级特性——SignalR 长连接

服务端和客户端之间天然保持着一条实时通道,DOM 更新通过这条通道即时推送。这意味着我可以在服务端读取 CLI 进程的输出流,直接把结果「推」到用户浏览器上,延迟低到几乎感知不到

更爽的是,我不用自己处理 WebSocket 的连接管理、心跳检测、断线重连这些脏活累活——Blazor 全给我包了。

2.2 为什么不用 WebAssembly?

可能有人会问:「Blazor 有两种模式,为什么不用 WebAssembly?WASM 可是纯前端运行,还不用服务器!」

问题在于,这个项目的核心逻辑必须在服务端运行

想想看:Claude Code CLI 和 Codex CLI 是要安装在服务器上的,它们需要访问文件系统、需要执行命令、需要网络权限。这些事情,浏览器沙箱里的 WASM 根本做不了。

Blazor Server 正好满足我的需求:UI 在浏览器渲染,逻辑在服务端执行,两者通过 SignalR 实时同步。

说白了,浏览器只是个「皮」,真正干活的还是服务器。

三、架构设计:适配器模式的优雅与挣扎

确定技术栈后,第一个要解决的问题就是:如何统一处理不同 CLI 工具的差异?

Claude Code 和 Codex 就像两个性格迥异的人——一个喜欢用type: assistant表示回复,另一个偏要用item.type: agent_message;一个把会话 ID 叫session_id,另一个非得叫thread_id

如果每来一个新工具就写一坨 if-else,代码很快就会变成一锅粥。

于是我祭出了老朋友——适配器模式

3.1 接口设计:一个接口统一天下

首先,我定义了一个ICliToolAdapter接口:

public interface ICliToolAdapter { string[] SupportedToolIds { get; } bool SupportsStreamParsing { get; } bool CanHandle(CliToolConfig tool); string BuildArguments(CliToolConfig tool, string prompt, CliSessionContext context); CliOutputEvent? ParseOutputLine(string line); string? ExtractSessionId(CliOutputEvent outputEvent); string? ExtractAssistantMessage(CliOutputEvent outputEvent); string GetEventTitle(CliOutputEvent outputEvent); string GetEventBadgeClass(CliOutputEvent outputEvent); string GetEventBadgeLabel(CliOutputEvent outputEvent); }

看起来有点长,但每个方法都有它存在的意义:

  • BuildArguments:不同 CLI 工具的命令行参数格式不同。Claude Code 需要-p --verbose --output-format=stream-json,Codex 需要exec --json。这个方法负责「翻译」用户输入到具体命令。

  • ParseOutputLine:这是最核心的方法。每读到一行输出,就调用它把 JSON 字符串解析成统一的CliOutputEvent对象。

  • ExtractSessionId:AI 编程助手通常支持「会话恢复」功能。比如你中途断开,下次可以接着聊。但前提是你得保存住会话 ID。这个方法负责从输出中「揪」出会话 ID。

  • GetEventBadgeClass/GetEventBadgeLabel:纯粹为了 UI 显示。不同类型的事件用不同颜色标注,比如「工具调用」是蓝色,「错误」是红色。

3.2 Claude Code 适配器:细节里的魔鬼

以 Claude Code 适配器为例,来看看实际处理有多复杂。

Claude Code 的输出格式看起来规整,但实际上有好几种「方言」:

  1. 旧版格式type直接就是initmessagetool_use这些。

  2. 新版格式typesystemassistant,具体类型要看内嵌的subtypemessage.role

  3. 非 JSON 行:有时候 CLI 会吐出一些日志或错误信息,根本不是 JSON。

处理逻辑大概是这样的:

public CliOutputEvent? ParseOutputLine(string line) { var trimmed = line.Trim(); // 第一关:过滤非 JSON 行 if (!trimmed.StartsWith("{") && !trimmed.StartsWith("[")) { var isError = trimmed.StartsWith("Error:", StringComparison.OrdinalIgnoreCase); return new CliOutputEvent { EventType = isError ? "error" : "raw", IsError = isError, Title = isError ? "错误" : "输出", Content = trimmed }; } // 第二关:尝试 JSON 解析 try { using var document = JsonDocument.Parse(trimmed); var root = document.RootElement; var eventType = GetStringProperty(root, "type") ?? "unknown"; var outputEvent = new CliOutputEvent { EventType = eventType, RawJson = line }; switch (eventType) { case "init": ParseInitEvent(root, outputEvent); break; case "system": // 新版格式:检查 subtype if (root.TryGetProperty("subtype", out var subtypeEl) && subtypeEl.GetString() == "init") { outputEvent.EventType = "init"; ParseInitEvent(root, outputEvent); } else { ParseSystemEvent(root, outputEvent); } break; case "assistant": ParseAssistantOrUserEvent(root, outputEvent, isAssistant: true); break; // ... 更多 case } return outputEvent; } catch (JsonException) { // JSON 解析失败也不要慌,当普通输出处理 return new CliOutputEvent { EventType = "raw", Title = "输出", Content = trimmed }; } }

这里有个设计决策值得一提:绝不让解析失败破坏用户体验

早期版本里,我遇到解析不了的行就直接抛异常,结果整个输出流都断了。后来改成「兜底策略」——解析失败就当普通文本显示,至少用户能看到原始输出,而不是一脸懵逼对着空白屏幕。

3.3 工具调用的「待办列表」坑

还有一个让我头疼了整整两天的问题:待办列表(TodoWrite)的渲染

Claude Code 有个叫TodoWrite的工具,AI 会用它来记录任务清单。输出格式是这样的:

{ "type": "assistant", "message": { "content": [{ "type": "tool_use", "name": "TodoWrite", "input": { "todos": [ {"content": "分析需求", "status": "completed"}, {"content": "实现功能", "status": "in_progress"}, {"content": "编写测试", "status": "pending"} ] } }] } }

一开始我把它当普通的「工具调用」处理,UI 上显示的是一坨难看的 JSON。

后来专门加了一段逻辑,检测到是TodoWrite工具时,把 JSON 转成用户友好的格式:

✓ 分析需求 ◐ 实现功能 ○ 编写测试

这个细节花了不少时间,但效果立竿见影——用户终于能看懂 AI 在干什么了。

四、会话管理:IndexedDB + 防抖,小小的优化大大的提升

AI 编程助手的一个核心体验是会话连续性。你跟 AI 聊了半小时,中途刷新一下页面,之前的对话不能丢。

最直接的方案是存服务端数据库,但这样有两个问题:

  1. 读写频繁:每发一条消息就往数据库里存,对服务器压力很大。

  2. 隐私顾虑:用户可能不希望对话内容被服务器留存。

所以我选择了IndexedDB——浏览器内置的本地数据库。

4.1 Blazor 调用 IndexedDB 的「桥接」

Blazor Server 的代码跑在服务端,要操作浏览器的 IndexedDB,必须通过IJSRuntime做 JavaScript 互操作。

我在前端写了一套 IndexedDB 的封装:

window.webCliIndexedDB = { saveSession: async function(session) { const db = await openDatabase(); const tx = db.transaction('sessions', 'readwrite'); const store = tx.objectStore('sessions'); await store.put(session); return true; }, loadSessions: async function() { const db = await openDatabase(); const tx = db.transaction('sessions', 'readonly'); const store = tx.objectStore('sessions'); return await store.getAll(); }, deleteSession: async function(sessionId) { const db = await openDatabase(); const tx = db.transaction('sessions', 'readwrite'); const store = tx.objectStore('sessions'); await store.delete(sessionId); return true; } };

然后在 C# 里这样调用:

var success = await _jsRuntime.InvokeAsync<bool>("webCliIndexedDB.saveSession", session);

简单粗暴,但有效。

4.2 防抖:别让保存操作把浏览器干崩

问题来了。

AI 的流式输出是一个字一个字往外蹦的,如果每收到一点内容就存一次 IndexedDB,一条消息可能触发几十上百次写入。浏览器扛不住不说,还会严重影响渲染性能。

解决方案是防抖(Debounce)。

核心思想:收到保存请求后,不立即执行,而是等一小段时间(比如 500ms)。如果这段时间内又来了新请求,就重置计时器。只有「安静」了 500ms 后,才真正执行保存。

public Task SaveSessionAsync(SessionHistory session) { lock (_saveLock) { _hasPendingSave = true; _pendingSession = session; // 重置定时器 _saveTimer?.Dispose(); _saveTimer = new System.Threading.Timer(async _ => { await ExecuteSaveAsync(); }, null, SaveDebounceMs, Timeout.Infinite); } return Task.CompletedTask; }

这招一出,IndexedDB 写入次数直接从每秒几十次降到每秒一两次,浏览器瞬间丝滑。

4.3 存储空间的「优雅降级」

还有个细节:IndexedDB 虽然容量比 localStorage 大得多,但也不是无限的。如果用户存了太多会话,可能会触发QuotaExceededError

我的处理策略是:

  1. 限制单个会话的消息数量(上限 1000 条,超出就删除最早的)

  2. 捕获配额异常并友好提示

catch (JSException ex) when (ex.Message.Contains("QuotaExceededError")) { _logger.LogWarning(ex, "IndexedDB 空间不足"); throw new QuotaExceededException("存储空间不足,请删除一些旧会话以释放空间", ex); }

五、进程管理:一次性 vs 持久化,两种模式的抉择

接下来聊聊进程管理。

调用 CLI 工具,本质上就是启动一个子进程,把用户输入传进去,再把输出读出来。但怎么管理这个进程,大有讲究。

5.1 一次性进程模式

最简单的方案:每次用户发消息,就启动一个新进程,执行完就杀掉。

var process = new Process { StartInfo = new ProcessStartInfo { FileName = "claude", Arguments = "-p \"用户的问题\"", RedirectStandardOutput = true, RedirectStandardError = true, UseShellExecute = false, CreateNoWindow = true } }; process.Start(); // 读取输出... process.WaitForExit(); process.Dispose();

优点是简单粗暴,每次都是干净的环境。

缺点也很明显:启动开销。每次启动 Claude Code CLI,它都要加载配置、初始化 MCP 服务器、连接 API……这套流程走下来,可能要好几秒。用户体验极差。

5.2 持久化进程模式

更聪明的做法是复用进程

进程启动后不杀掉,保持在后台运行。每次有新消息,就通过标准输入「喂」进去,然后读取标准输出。这样启动开销只有第一次,后续交互都是毫秒级。

但这带来了新的挑战:

  1. 进程生命周期管理:怎么知道进程还活着?挂了怎么办?

  2. 并发控制:多个用户同时使用,进程怎么隔离?

  3. 输出边界判断:一次性进程可以等WaitForExit(),持久化进程怎么知道「这轮回答结束了」?

我的方案是用一个PersistentProcessManager来统一管理:

public class PersistentProcessManager { private readonly ConcurrentDictionary<string, PersistentProcessInfo> _processes = new(); public PersistentProcessInfo GetOrCreateProcess( string sessionId, string toolId, CliToolConfig tool, string workingDirectory) { var key = $"{sessionId}_{toolId}"; return _processes.GetOrAdd(key, _ => { // 启动新进程 var process = StartProcess(tool, workingDirectory); return new PersistentProcessInfo { Process = process, SessionId = sessionId, ToolId = toolId }; }); } }

输出边界判断用的是「超时检测」:如果连续 2 秒没有新输出,就认为这轮回答结束了。

var noOutputTimeout = TimeSpan.FromSeconds(2); while (!cancellationToken.IsCancellationRequested) { bool hasNewOutput = false; if (outputReader.Peek() >= 0) { int bytesRead = await outputReader.ReadAsync(buffer); if (bytesRead > 0) { hasNewOutput = true; lastOutputTime = DateTime.UtcNow; yield return new StreamOutputChunk { Content = new string(buffer, 0, bytesRead) }; } } if (!hasNewOutput && (DateTime.UtcNow - lastOutputTime) > noOutputTimeout) { // 超时,认为输出结束 break; } await Task.Delay(50, cancellationToken); }

这个 2 秒的阈值是反复调优的结果——太短会误判(AI 思考中间可能停顿一下),太长用户等得难受。

六、会话恢复:让 AI 记住「上次聊到哪儿了」

AI 编程助手一个很爽的功能是「会话恢复」——你可以告诉它「继续上次的工作」,它就能接着之前的上下文继续执行。

但这需要保存「会话 ID」。Claude Code 叫session_id,Codex 叫thread_id,本质上是同一个东西。

难点在于:这个 ID 是 CLI 工具在运行时动态生成的,你得从输出流里「捞」出来。

我的做法是:

  1. 适配器在解析输出时,遇到包含会话 ID 的事件就提取出来

  2. 执行服务把提取到的 ID 存起来

  3. 下次执行时,把 ID 传给适配器,让它拼接到命令行参数里

// 适配器构建命令时,检查是否有会话 ID public string BuildArguments(CliToolConfig tool, string prompt, CliSessionContext context) { var argsBuilder = new StringBuilder(); argsBuilder.Append("-p --verbose --output-format=stream-json "); // 会话恢复参数 if (context.IsResume && !string.IsNullOrEmpty(context.CliThreadId)) { argsBuilder.Append($"--resume {context.CliThreadId} "); } argsBuilder.Append($"\"{escapedPrompt}\""); return argsBuilder.ToString(); }
// 执行服务保存会话 ID if (hasAdapter && string.IsNullOrEmpty(cliThreadId)) { var output = fullOutput.ToString(); var parsedThreadId = ParseCliThreadId(output, adapter); if (!string.IsNullOrEmpty(parsedThreadId)) { SetCliThreadId(sessionId, parsedThreadId); } }

这套机制跑通后,用户终于可以跨多次交互保持上下文了。比如让 AI 先写一个函数,然后再让它加个测试——AI 知道你说的是哪个函数。

七、移动端适配:44px 的触摸区域有多重要

说了这么多后端,来聊聊前端。

既然目标是「手机也能写代码」,移动端适配就是重中之重。

7.1 响应式布局

桌面端是左右分栏布局:左边是对话区,右边是预览区。

但手机屏幕那么窄,左右分栏根本不现实。我改成了上下布局,并加了一个「折叠预览区」的按钮:

<button @onclick="TogglePreviewPanel" class="lg:hidden fixed top-1/2 right-2 -translate-y-1/2 z-50 w-10 h-10 bg-gray-800 text-white rounded-full"> @if (_isPreviewCollapsed) { <span>▲</span> } else { <span>▼</span> } </button>

lg:hidden意味着这个按钮只在小屏幕上显示,大屏幕上自动隐藏。

7.2 触摸优化

移动端有个很容易被忽视的细节:手指比鼠标指针粗太多了

Apple 的人机界面指南建议,触摸目标至少要 44x44 像素。我最初没在意,结果测试时发现按钮根本点不准。

后来统一给交互元素加上了最小尺寸:

.min-h-[44px] .min-w-[44px]

还加了触摸反馈:

.active:scale-95 /* 按下时轻微缩小 */ .active:bg-gray-200 /* 按下时变色 */

7.3 虚拟键盘的坑

iOS Safari 有个臭名昭著的问题:虚拟键盘弹出时,视口高度会变化,但100vh还是按原来的高度算,导致页面布局乱掉。

解决方案是用 CSS 自定义属性动态更新视口高度:

function updateViewportHeight() { const vh = window.innerHeight * 0.01; document.documentElement.style.setProperty('--vh', `${vh}px`); } window.addEventListener('resize', updateViewportHeight);

然后在 CSS 里用calc(var(--vh, 1vh) * 100)代替100vh

八、工作区隔离:每个会话一个「沙盒」

AI 编程助手会生成文件、执行命令,必须做好隔离,不能让不同用户的文件混在一起。

我的方案是:每个会话一个独立的工作目录

private string GetOrCreateSessionWorkspace(string sessionId) { lock (_workspaceLock) { if (_sessionWorkspaces.TryGetValue(sessionId, out var existingWorkspace)) { return existingWorkspace; } var workspacePath = Path.Combine(workspaceRoot, sessionId); if (!Directory.Exists(workspacePath)) { Directory.CreateDirectory(workspacePath); } _sessionWorkspaces[sessionId] = workspacePath; // 创建标记文件,记录创建时间 var markerFile = Path.Combine(workspacePath, ".workspace_info"); File.WriteAllText(markerFile, $"Created: {DateTime.UtcNow:O}\nSessionId: {sessionId}"); return workspacePath; } }

启动 CLI 进程时,把工作目录设成这个隔离目录:

startInfo.WorkingDirectory = sessionWorkspace;

这样 AI 生成的文件都在各自的目录里,互不干扰。

8.1 过期清理

长期运行后,工作区目录会越积越多,磁盘迟早撑爆。

我加了一个定时清理的后台服务,默认 24 小时没访问的工作区自动删除:

public void CleanupExpiredWorkspaces() { var expirationTime = DateTime.UtcNow.AddHours(-_options.WorkspaceExpirationHours); var directories = Directory.GetDirectories(workspaceRoot); foreach (var dir in directories) { var markerFile = Path.Combine(dir, ".workspace_info"); var lastAccessTime = File.Exists(markerFile) ? File.GetLastWriteTimeUtc(markerFile) : Directory.GetLastWriteTimeUtc(dir); if (lastAccessTime < expirationTime) { Directory.Delete(dir, recursive: true); } } }

8.2 安全边界

另一个必须考虑的是路径穿越攻击

如果用户构造一个类似../../../etc/passwd的路径,可能会读到不该读的文件。

所有涉及文件操作的地方,我都加了路径校验:

var normalizedWorkspace = Path.GetFullPath(workspacePath); var normalizedFile = Path.GetFullPath(fullPath); if (!normalizedFile.StartsWith(normalizedWorkspace)) { _logger.LogWarning("尝试访问工作区外的文件: {File}", relativePath); return null; }

九、Markdown 渲染与代码高亮

AI 的回复里经常包含 Markdown 格式的内容,直接显示原始文本太丑了。

我用的是Markdig,一个高性能的 .NET Markdown 解析库:

private static readonly MarkdownPipeline _outputMarkdownPipeline = new MarkdownPipelineBuilder() .UseAdvancedExtensions() .DisableHtml() // 禁用原始 HTML,防止 XSS .Build(); private MarkupString RenderMarkdown(string? markdown) { if (string.IsNullOrWhiteSpace(markdown)) { return new MarkupString(string.Empty); } // 使用缓存避免重复渲染 if (_markdownCache.TryGetValue(markdown, out var cached)) { return cached; } var html = Markdown.ToHtml(markdown, _outputMarkdownPipeline); var result = new MarkupString(html); // 限制缓存大小 if (_markdownCache.Count > 100) { _markdownCache.Clear(); } _markdownCache[markdown] = result; return result; }

.DisableHtml()很重要——AI 生成的内容不可控,如果允许原始 HTML,可能被注入恶意脚本。

代码高亮用的是Monaco Editor(就是 VS Code 用的那个编辑器),配合前端的语法高亮渲染,效果相当不错。

十、国际化:从硬编码到动态切换

项目一开始,界面上的文字都是硬编码的中文。后来想着要支持海外用户,不得不补国际化。

我用的是 JSON 资源文件 + 动态加载:

// zh-CN.json { "codeAssistant.title": "AI 编程助手", "codeAssistant.newSession": "新建会话", "codeAssistant.sessionHistory": "会话历史" } // en-US.json { "codeAssistant.title": "AI Coding Assistant", "codeAssistant.newSession": "New Session", "codeAssistant.sessionHistory": "Session History" }

然后在 Blazor 组件里通过一个T()方法获取翻译:

<h2>@T("codeAssistant.sessionHistory")</h2>

语言切换时,重新加载对应的 JSON 文件,刷新缓存。

老实说,这套方案有点「土」,但胜在简单可控。等需求复杂了再考虑引入成熟的 i18n 库。

十一、踩过的坑,你可以绕过去

最后聊聊几个印象深刻的坑。

11.1 Codex 的 stderr 里有正常输出

大多数 CLI 工具,stderr 用来输出错误信息,stdout 用来输出正常内容。

但 Codex 不按套路出牌——它把 JSONL 日志全往 stderr 写。

一开始我只读 stdout,结果啥也读不到。查了半天才发现问题,改成同时读取两个流并合并输出。

11.2 Windows 上的只读属性

清理工作区目录时,偶尔会遇到删除失败。

排查后发现是某些文件被设成了只读属性(不知道是哪个 CLI 工具干的)。

解决方案是先递归清除只读属性,再删除:

private static void NormalizeDirectoryAttributes(string directoryPath) { foreach (var file in Directory.EnumerateFiles(directoryPath, "*", SearchOption.AllDirectories)) { try { File.SetAttributes(file, FileAttributes.Normal); } catch { } } }

11.3 JSON 解析的边界情况

你以为 CLI 的输出永远是规整的 JSON?太天真了。

有时候会混进一些非 JSON 的内容,比如:

  • 启动时的 banner 信息

  • 调试日志

  • ANSI 颜色码

如果直接扔给 JSON 解析器,必挂。

我的策略是先做一层过滤:

if (!trimmed.StartsWith("{") && !trimmed.StartsWith("[")) { // 不是 JSON,当普通文本处理 return new CliOutputEvent { EventType = "raw", Content = trimmed }; }

十二、未来的坑和机会

项目跑起来了,但还有很多可以优化的地方:

  1. 更多 CLI 工具支持:目前只适配了 Claude Code 和 Codex,后续可以加入 GitHub Copilot CLI、Qwen CLI、Gemini CLI 等。适配器模式的好处就是扩展方便,加个新类就行。

  2. 协作功能:多人同时编辑同一个项目?想想都兴奋,但实现起来是另一个量级的复杂度。

  3. AI 生成代码的即时预览:现在只能预览 HTML,如果能直接运行 React/Vue 组件就更爽了。可以考虑集成在线 IDE 的沙箱能力。

  4. 性能优化:Blazor Server 的 SignalR 连接是有状态的,服务器内存随用户数线性增长。如果要支持大规模并发,可能得考虑 Blazor WebAssembly + 独立 API 的架构。

写在最后

从一个「在地铁上写代码」的念头,到真正把 Claude Code 和 Codex 塞进浏览器,这一路踩了不少坑,也学到了很多东西。

如果你也在做类似的项目,希望这篇文章能给你一些启发。

如果你只是路过看个热闹,那就当听了一个程序员的深夜絮叨吧。

代码已开源,地址:https://github.com/xuzeyu91/WebCode

欢迎 Star、Fork、提 Issue。


更多AIGC文章

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

更多VibeCoding文章

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/1164694.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

一行隐藏文本可劫持AI系统——无需点击,无需恶意软件,仅凭文字

一行隐藏文本可劫持AI系统——无需点击&#xff0c;无需恶意软件&#xff0c;仅凭文字 英国NCSC警告该弱点可能永远无法完全修复——因为它与语言模型如何阅读文本紧密相连。 一位银行客户要求ChatGPT查询账户余额。这个人工智能返回了另外十七位客户的账户详情&#xff0c;并开…

django-flask基于python的关于流量业务的用户投诉管理系统

目录Django-Flask 流量业务用户投诉管理系统摘要关于博主开发技术路线相关技术介绍核心代码参考示例结论源码lw获取/同行可拿货,招校园代理 &#xff1a;文章底部获取博主联系方式&#xff01;Django-Flask 流量业务用户投诉管理系统摘要 该系统基于 Python 的 Django 和 Flas…

django-flask基于python的管网隐患安全巡检系统

目录 Django-Flask 基于 Python 的管网隐患安全巡检系统摘要 关于博主开发技术路线相关技术介绍核心代码参考示例结论源码lw获取/同行可拿货,招校园代理 &#xff1a;文章底部获取博主联系方式&#xff01; Django-Flask 基于 Python 的管网隐患安全巡检系统摘要 随着城市化进…

毕设实战:基于多尺度空洞注意力(MSDA)的YOLOv8改进与视觉识别优化

文章目录 毕设实战:基于多尺度空洞注意力(MSDA)的YOLOv8改进与视觉识别优化 一、技术背景与方案优势 二、环境搭建与依赖准备 2.1 虚拟环境配置 2.2 数据集准备 三、MSDA模块的代码实现 3.1 多尺度空洞注意力(MSDA)核心代码 3.2 嵌入MSDA到YOLOv8的Backbone 四、模型训练与…

BQB有几种认证方式?需要哪些资料?

BQB 认证&#xff1a;认证方式及所需资料&#xff08;纯文本版&#xff09;BQB 认证即蓝牙技术联盟&#xff08;Bluetooth SIG&#xff09;的蓝牙产品资格认证&#xff0c;是蓝牙产品合法使用蓝牙商标、接入蓝牙技术体系的全球必备认证&#xff0c;通过后产品将获得 QDID&#…

办理3C认证需要准备哪些资料?

办理 3C 认证&#xff08;中国强制性产品认证&#xff09;的资料分为通用基础资料&#xff08;所有产品必备&#xff09;、专项技术资料&#xff08;按产品类型补充&#xff09;、工厂质量体系文件&#xff08;第三方认证模式必备&#xff09;、特殊情况补充资料&#xff08;如…

办理3C认证需要准备哪些资料?

办理 3C 认证&#xff08;中国强制性产品认证&#xff09;的资料分为通用基础资料&#xff08;所有产品必备&#xff09;、专项技术资料&#xff08;按产品类型补充&#xff09;、工厂质量体系文件&#xff08;第三方认证模式必备&#xff09;、特殊情况补充资料&#xff08;如…

3c认证的相关内容介绍

3C 认证&#xff0c;全称中国强制性产品认证&#xff08;China Compulsory Certification&#xff09;&#xff0c;是中国政府依据《中华人民共和国认证认可条例》《强制性产品认证管理规定》实施的法定强制性产品合格评定制度&#xff0c;自 2002 年正式实施&#xff0c;旨在保…

提示工程已死?上下文工程才是大模型开发的“黄金标准“,小白秒变AI大神!

几年前&#xff0c;包括一些顶尖的 AI 研究人员在内的许多人声称&#xff0c;提示工程&#xff08;prompt engineering&#xff09;很快就会消亡。 显然&#xff0c;他们大错特错。事实上&#xff0c;提示工程现在比以往任何时候都更加重要&#xff0c;其重要性甚至让它被重新…

短信为何在亚洲更 “吃香”?中美通信习惯差异的底层逻辑

为什么大多数亚洲国家手机短信的使用远比美国更加普遍&#xff1f;短信为何在亚洲更 “吃香”&#xff1f;中美通信习惯差异的底层逻辑大多数亚洲国家手机短信的使用远比美国更普遍&#xff0c;核心是通信成本、基础设施、社交文化、功能替代四大因素的差异&#xff0c;让短信在…

手把手教你用7款AI写论文工具,精准控率无压力操作指南

还在为开题报告无从下笔而焦虑&#xff1f;或是被导师的修改意见搞得晕头转向&#xff1f;又或者&#xff0c;面对查重和AI检测率感到束手无策&#xff1f;别担心&#xff0c;你不是一个人在战斗。随着AI技术的飞速发展&#xff0c;一系列强大的AI论文写作工具应运而生&#xf…

哪些类型的产品需要做 CCC 认证?

CCC 认证&#xff08;中国强制性产品认证&#xff09;的产品范围由国家市场监督管理总局、国家认监委动态调整&#xff0c;截至 2025 年 12 月&#xff0c;最新《强制性产品认证目录》涵盖16 大类核心产品&#xff0c;细分类别超过 100 种&#xff0c;均为与消费者人身安全、公…

openEuler + MindSpore 全栈部署实战

openEuler MindSpore 全栈部署实战&#xff1a;从国产操作系统到大模型推理当国产生态相遇&#xff0c;一场软件与硬件的深度协同优化正在悄然发生。还记得第一次成功在 openEuler 上跑通一个完整的 MindSpore 模型训练任务时&#xff0c;系统监控面板上平稳的CPU和内存曲线—…

基于VUE的摄影分享平台系统[VUE]-计算机毕业设计源码+LW文档

摘要&#xff1a;随着摄影文化的广泛传播和互联网技术的发展&#xff0c;摄影分享平台成为摄影爱好者交流和展示的重要场所。本文介绍基于VUE框架的摄影分享平台系统的设计与实现过程。通过需求分析明确系统功能&#xff0c;利用VUE及相关技术进行开发&#xff0c;实现用户管理…

【程序员必看】11种RAG技术让AI不再“胡说八道“,大模型开发从此告别幻觉,代码效率提升200%!

一、引言 随着人工智能的快速发展&#xff0c;检索增强生成&#xff08;Retrieval-Augmented Generation&#xff0c;RAG&#xff09;技术正在经历前所未有的演变。RAG技术通过将外部知识融入大型语言模型&#xff08;LLM&#xff09;的生成过程&#xff0c;极大地提高了AI系统…

从单ECU到整车网络,TC10在工程中的应用

在智能汽车电子系统中&#xff0c;车载以太网已经成为重要的网络通信技术。随着网络规模扩大&#xff0c;一个在早期并不明显的问题开始频繁出现&#xff1a;在车辆静态或低负载状态下&#xff0c;网络是否仍在持续运行&#xff0c;是否存在不可控的功耗消耗。TC10的出现&#…

学长亲荐!继续教育必备TOP8 AI论文写作软件测评

学长亲荐&#xff01;继续教育必备TOP8 AI论文写作软件测评 2026年继续教育AI论文写作工具测评维度解析 在当前学术研究日益数字化的背景下&#xff0c;AI论文写作工具已成为提升效率、优化内容质量的重要辅助。对于继续教育领域的学习者和研究者来说&#xff0c;选择一款合适的…

安达发|电器行业生产突围战:APS高级排程打造“智慧工厂”新内核

走进一家现代化的电器制造工厂&#xff0c;你会看到注塑机规律地开合、自动化装配线有序流转、AGV小车精准配送物料。然而&#xff0c;在这井然有序的表象下&#xff0c;生产计划部门往往正面临着一场无声的战争&#xff1a;新品上市导致的生产线频繁切换、促销季订单的爆炸式增…

【计算机毕业设计案例】基于Java的某音乐乐库歌手歌曲管理系统(程序+文档+讲解+定制)

博主介绍&#xff1a;✌️码农一枚 &#xff0c;专注于大学生项目实战开发、讲解和毕业&#x1f6a2;文撰写修改等。全栈领域优质创作者&#xff0c;博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java、小程序技术领域和毕业项目实战 ✌️技术范围&#xff1a;&am…

伦敦地铁网络扩展4G/5G连接覆盖

伦敦地铁网络正在扩展移动连接覆盖范围&#xff0c;让地下30米深处成为英国首都移动信号最强的地方之一。这是伦敦交通局&#xff08;TfL&#xff09;与Boldyn Networks正在进行的开发项目的一部分&#xff0c;更多地铁站和隧道段现已覆盖4G和5G移动网络。网络覆盖的重要性伦敦…