1. 项目背景与核心挑战
去年团队接到一个看似简单的需求:让开发者能够随时随地使用AI编程助手。产品经理最初认为这不过是"做个Web版的Claude Code套个壳",但实际开发中遇到的挑战远超预期。这个项目最终演变成需要从零设计一套完整的分布式AI编程平台架构。
核心挑战包括:
- 浏览器环境下如何调用本地CLI工具
- 流式输出如何实时推送到前端
- 多用户工作区隔离问题
- 移动端代码编辑体验优化
- 不同AI工具输出格式的统一处理
2. 架构设计与关键技术实现
2.1 多CLI工具适配器模式
不同AI编程工具(Claude Code、Codex等)的输出格式和会话机制差异巨大。最初考虑用if-else分支处理,但这种方法难以维护且扩展性差。
最终采用适配器模式,定义统一接口:
csharp复制public interface ICliToolAdapter
{
string[] SupportedToolIds { get; }
bool SupportsStreamParsing { get; }
string BuildArguments(CliToolConfig tool, string prompt, CliSessionContext context);
CliOutputEvent? ParseOutputLine(string line);
string? ExtractSessionId(CliOutputEvent outputEvent);
}
每个工具实现自己的适配器,如Claude Code的参数构建:
csharp复制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}\"";
}
这种设计使新增工具只需添加适配器,核心逻辑无需修改。
2.2 流式输出优化方案
AI的流式输出在Web端实现面临异步读取、并发渲染和状态同步的挑战。最初直接更新UI的方案导致CPU飙升至100%:
csharp复制while ((line = await reader.ReadLineAsync()) != null)
{
var chunk = ParseJsonLine(line);
_currentMessage += chunk.Content;
StateHasChanged(); // 每读一行就刷新UI
}
优化后采用防抖+批量更新策略:
csharp复制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;
_updateTimer?.Dispose();
_updateTimer = new Timer(_ =>
{
_hasPendingUpdate = false;
InvokeAsync(StateHasChanged);
}, null, 50, Timeout.Infinite);
}
}
将UI刷新频率控制在50ms一次,平衡流畅度和性能。
2.3 多用户工作区安全隔离
为确保多用户环境下的安全性,实现三层防护机制:
- 会话隔离:每个会话使用随机UUID作为工作目录
csharp复制public string GetOrCreateSessionWorkspace(string sessionId)
{
lock (_workspaceLock)
{
var workspacePath = Path.Combine(
GetEffectiveWorkspaceRoot(),
sessionId // UUID,无法猜测
);
Directory.CreateDirectory(workspacePath);
return workspacePath;
}
}
- 路径验证:检查所有文件操作是否在工作区内
csharp复制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);
}
- 命令白名单:严格转义用户输入
csharp复制private static string EscapeShellArgument(string argument)
{
return argument
.Replace("\\", "\\\\")
.Replace("\"", "\\\"")
.Replace("$", "\\$")
.Replace("`", "\\`");
}
3. 智能上下文管理系统
3.1 上下文优先级管理
AI模型的上下文窗口有限(如Claude的100K tokens),需要智能管理。设计上下文项优先级系统:
csharp复制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;
}
默认优先级规则:
- 错误信息:9(最高)
- 用户消息:7
- 代码片段:6
- AI回复:5
- 文件引用:4
3.2 智能压缩策略
当上下文接近限制时触发智能压缩:
csharp复制private async Task CompressSmartSummaryAsync(...)
{
// 高优先级项永远保留
var highPriorityItems = items.Where(i => i.Priority >= 7);
// 最近的用户消息保留
var recentUserMessages = items
.Where(i => i.Type == ContextItemType.UserMessage)
.OrderByDescending(i => i.CreatedAt)
.Take(config.KeepRecentMessages);
// 其他内容生成摘要
foreach (var item in itemsToCompress)
{
if (item.Type == ContextItemType.CodeSnippet)
{
item.Content = GenerateCodeSnippetSummary(item.Content);
item.EstimatedTokens = TokenEstimator.EstimateTokens(item.Content);
}
}
}
3.3 Token估算优化
针对不同内容类型采用不同的估算策略:
csharp复制public static int EstimateTokens(string text)
{
var chineseChars = text.Count(c => c >= 0x4E00 && c <= 0x9FFF);
var otherChars = text.Length - chineseChars;
return (int)Math.Ceiling(chineseChars / 1.5 + otherChars / 4.0);
}
public static int EstimateCodeTokens(string code)
{
return (int)Math.Ceiling(code.Length / 3.5); // 代码Token密度更高
}
4. 移动端专项优化
4.1 视口高度适配
解决iOS Safari的100vh包含地址栏问题:
css复制.container {
height: 100vh;
height: 100dvh; /* 动态视口高度 */
height: -webkit-fill-available; /* 兼容旧版本 */
}
4.2 虚拟键盘处理
监听视口变化调整布局:
javascript复制if (window.visualViewport) {
window.visualViewport.addEventListener('resize', () => {
const keyboardHeight = window.innerHeight - window.visualViewport.height;
document.documentElement.style.setProperty('--keyboard-height', `${keyboardHeight}px`);
});
}
CSS中使用计算值:
css复制.input-area {
padding-bottom: calc(env(safe-area-inset-bottom) + var(--keyboard-height, 0px));
}
4.3 触摸目标优化
遵循人机界面指南,确保最小触摸目标:
css复制.touch-target {
min-width: 44px;
min-height: 44px;
padding: 12px;
}
5. 性能优化实践
5.1 文件树虚拟滚动
处理大规模文件列表:
csharp复制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渲染缓存
避免重复解析:
csharp复制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 输出状态防抖保存
优化持久化操作:
csharp复制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. 未来演进方向
6.1 多模型对比执行
同一问题并行获取多个AI的回答:
csharp复制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 实时协作支持
计划引入CRDT或OT算法实现多人协作编辑,架构预留扩展点:
csharp复制public interface IPluginService
{
void RegisterCliAdapter(ICliToolAdapter adapter);
List<PluginUIComponent> GetPluginUIComponents(string location);
}
7. 开发经验总结
这个项目带来的关键收获:
- 架构设计:适配器模式解决多工具兼容问题
- 性能优化:流式处理的防抖和批量更新策略
- 安全防护:多层隔离机制确保系统安全
- 移动适配:针对移动端的专项优化方案
- 上下文管理:智能压缩保持对话连贯性
实际开发中,每个"简单"需求背后都可能涉及复杂的技术挑战,需要全面考虑进程管理、流式处理、安全隔离、跨平台兼容和性能优化等多个维度。