IHUI AI实战案例:一个Monorepo同时输出Web/API/CLI/桌面/插件/移动/小程序的架构设计。
“我们要做一个 AI 应用,先上 Web,再做小程序,然后是桌面端和浏览器插件……”——产品经理的话还没说完,前端 Lead 已经开始头疼了。8 个端、8 套代码、8 倍工作量?
本文是 IHUI AI 项目(开源 8 端全栈 AI 操作系统)落地“8 端同源”架构时的真实工程总结。你会看到:一个 TypeScript Monorepo 如何同时输出 8 个端、哪些内容共享、哪些内容不共享、跨端契约如何对齐,以及实战数据(340 张表 / 144 次迁移 / 1300+ 个 API / 5346 个测试)。
Web 端先上:用 Next.js + React 写一个聊天页面。
老板要小程序:再开一个 Taro 工程,API client 重写一遍。
用户要桌面端:用 Electron 封装一下,但类型定义又复制了一份。
运营要浏览器插件:Chrome Extension MV3,又是一套。
CTO 要 CLI:用 npx ihui 调用 API,再写一遍 SDK。
投资人要 Mobile:React Native,从头再来。
API 后端:Fastify + Drizzle,与前端类型只能手工同步。
结果:同一个 User 类型在 8 个端有 8 份定义,字段一改,全端爆炸;同一个 API 调用在 8 个端写 8 遍,Bug 修 8 次;同一个 UI 组件(按钮、对话框、消息气泡)在 5 个端各实现一次,设计稿一变,全端都要跟着改。
这不是工程能力问题,而是架构问题:你把 8 个端当成了 8 个独立项目。
IHUI AI 的解法是“8 端同源 Monorepo”:所有端共享同一套 packages/,只在各自的 apps/<端名>/ 下编写端特有代码。
IHUI-AI/
├── apps/
│ ├── web/ # Next.js 15 + React 19 + Tailwind 4
│ ├── api/ # Fastify 5 + Drizzle ORM 0.38
│ ├── ai-service/ # FastAPI + LangGraph + LiteLLM
│ ├── cli/ # Commander 封装,发布到 npm
│ ├── desktop/ # Electron + 复用 web 构建
│ ├── extension/ # Chrome MV3
│ ├── mobile-rn/ # React Native
│ └── miniapp-taro/ # Taro 4 + React(微信/支付宝/抖音多端)
├── packages/
│ ├── types/ # 全端共享 TypeScript 类型(单一真相)
│ ├── database/ # Drizzle schema + 迁移(340 表 / 144 迁移)
│ ├── auth/ # authenticate 函数,前后端共用
│ ├── ui/ # shadcn/ui 封装,web/RN/miniapp 复用
│ ├── config/ # ESLint / TSConfig / Tailwind preset
│ ├── eslint-config/ # 统一 lint 规则
│ └── tsconfig/ # 统一 tsconfig 基线
├── pnpm-workspace.yaml
├── turbo.json
└── package.json
packages/types 是整个 Monorepo 的契约层。apps/api 的路由参数、apps/web 的 API client、apps/cli 的命令选项,全部从 @ihui/types 导入。修改一个字段,8 个端的 typecheck 会立刻报错,不存在“前端忘了同步”的情况。
packages/database 使用 Drizzle ORM 定义 340 张表的 schema,并生成迁移文件。apps/api 直接 import schema 操作数据库;apps/ai-service(Python)则通过 OpenAPI 桥接消费同一份契约。这就避免了“后端修改了字段,AI 服务还在使用旧 schema”的经典漂移问题。
shadcn/ui 基于 Radix + Tailwind,本身就是“逻辑 + 样式”分层。我们在 packages/ui 暴露 <Button>,apps/web 可以直接使用;apps/miniapp-taro 通过 Taro 适配器重写样式,但保留 props 接口;apps/mobile-rn 则使用 RN 适配器。API 保持一致,样式由各端自治。
packages/types/src/chat.ts:
export interface ChatMessage {
id: string;
role: 'system' | 'user' | 'assistant' | 'tool';
content: string;
model?: string;
createdAt: string; // ISO 8601,全端统一
tokens?: { input: number; output: number };
}
export interface ChatCompletionRequest {
messages: ChatMessage[];
model: string; // 176 模型任选
stream: boolean;
temperature?: number;
maxTokens?: number;
tools?: ToolDefinition[];
}
export interface ChatCompletionResponse {
message: ChatMessage;
usage: { inputTokens: number; outputTokens: number; cost: number };
finishReason: 'stop' | 'length' | 'tool_calls';
}
// apps/api/src/routes/chat.ts
import type { ChatCompletionRequest, ChatCompletionResponse } from '@ihui/types';
import { FastifyInstance } from 'fastify';
import { z } from 'zod';
const chatSchema = z.object({
messages: z.array(z.object({
role: z.enum(['system', 'user', 'assistant', 'tool']),
content: z.string(),
})),
model: z.string(),
stream: z.boolean().default(false),
}) satisfies z.ZodType<ChatCompletionRequest>;
export default async function (app: FastifyInstance) {
app.post('/chat/completions', {
preHandler: app.authenticate, // 共享 auth 包
}, async (req, reply): Promise<ChatCompletionResponse> => {
const body = chatSchema.parse(req.body);
// ... 调用 ai-service
});
}
apps/web 的 API client:
// apps/web/src/lib/api-client.ts
import type { ChatCompletionRequest, ChatCompletionResponse } from '@ihui/types';
export async function chatComplete(
req: ChatCompletionRequest,
): Promise<ChatCompletionResponse> {
const res = await fetch('/api/chat/completions', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(req),
});
if (!res.ok) throw new Error(`HTTP ${res.status}`);
return res.json();
}
// apps/cli/src/commands/chat.ts
import type { ChatCompletionRequest } from '@ihui/types';
import { program } from 'commander';
program
.command('chat')
.option('-m, --model <model>', '模型 ID', 'gpt-4o-mini')
.option('-s, --stream', '流式输出', true)
.action(async (opts) => {
const req: ChatCompletionRequest = {
messages: [{ role: 'user', content: '你好' }],
model: opts.model,
stream: opts.stream,
};
// 调用 API
});
关键收益:给 ChatMessage 增加一个字段后,8 个端的 typecheck 会同时报警,5 分钟内即可完成全端同步。
packages/ui/src/button.tsx:
import { ButtonHTMLAttributes, forwardRef } from 'react';
export interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
variant?: 'default' | 'outline' | 'ghost' | 'destructive';
size?: 'sm' | 'md' | 'lg';
}
export const Button = forwardRef<HTMLButtonElement, ButtonProps>(
({ variant = 'default', size = 'md', className = '', ...props }, ref) => (
<button
ref={ref}
className={`ihui-btn ihui-btn-${variant} ihui-btn-${size} ${className}`}
{...props}
/>
),
);
Button.displayName = 'Button';
apps/miniapp-taro 适配层只需覆盖样式:
// apps/miniapp-taro/src/adapters/Button.tsx
import { Button as TaroButton } from '@tarojs/components';
import type { ButtonProps } from '@ihui/ui';
export function Button({ variant = 'default', children, ...props }: ButtonProps) {
return (
<TaroButton
className={`ihui-btn ihui-btn-${variant}`}
{...props}
>
{children}
</TaroButton>
);
}
API 完全一致,业务代码无需改动。运营要把 Web 聊天页迁移到小程序,只需更换 import 路径。
packages/sdk/src/index.ts 暴露统一的 client,8 个端都通过它访问 API,并自动处理鉴权、重试和错误格式:
import type { ChatCompletionRequest } from '@ihui/types';
export class IHUIClient {
constructor(
private baseUrl: string,
private getToken: () => string | null,
) {}
async chat(req: ChatCompletionRequest) {
const token = this.getToken();
const res = await fetch(`${this.baseUrl}/api/chat/completions`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
...(token ? { Authorization: `Bearer ${token}` } : {}),
},
body: JSON.stringify(req),
});
if (res.status === 401) throw new AuthError('未登录');
if (!res.ok) throw new APIError(await res.text());
return res.json();
}
}
Web 端注入 cookie token,CLI 端注入配置文件中的 API key,Mobile 端注入 SecureStorage token——业务代码完全相同。
packages/database/src/schema/users.ts:
import { pgTable, uuid, varchar, timestamp, boolean } from 'drizzle-orm/pg-core';
export const users = pgTable('users', {
id: uuid('id').primaryKey().defaultRandom(),
email: varchar('email', { length: 255 }).notNull().unique(),
nickname: varchar('nickname', { length: 100 }),
roleId: uuid('role_id').notNull().default('00000000-...-user'),
isVerified: boolean('is_verified').default(false),
createdAt: timestamp('created_at', { withTimezone: true }).defaultNow().notNull(),
});
export type User = typeof users.$inferSelect; // 全端共享
export type NewUser = typeof users.$inferInsert;
API、CLI、桌面端、扩展、移动端全部 import 同一份 User 类型。数据库字段一改,全端立刻就能感知。
只有类型还不够。IHUI AI 使用 4 道防线保证跨端连通。
API 路由使用 zod 定义 schema,并在启动时自动生成 OpenAPI 文档。CLI、SDK 和 Mobile 使用 openapi-typescript 生成类型,确保客户端与服务端契约一致。
pre-commit 钩子中有一个脚本,会检测 staged 改动:如果只修改了一个端,却没有改动 packages/*,就会发出 warn 提醒——“你是不是漏了同步其他端?”
$ git commit -m "feat: 添加收藏功能"
⚠️ multi-end-sync: 触及 apps/web 但未触及 packages/*,请确认是否需要同步其他端
pnpm turbo build typecheck lint test 一条命令运行全部 8 个端,任何一个端出错都会 fail。这条命令在 CI 中是硬门禁。
关键链路(注册 → 登录 → 对话 → 付费)配有跨端 E2E:Web、API 和 AI-service 一起运行,确保整条链路畅通。
这套架构不是 PPT。以下是 IHUI AI 仓库中的真实数据:
真实案例:有一次,我们为 ChatMessage 增加 tokens 字段,用于统计 token 消耗。修改 packages/types 后,8 个端的 typecheck 立即报出 14 处错误,5 分钟内全部修复。如果使用 8 个独立仓库,这种改动至少需要 2 天来协调、上线和回滚。
团队少于 3 人,并且只做 1~2 个端:不需要 Monorepo,单仓库更快。
端之间的技术栈差异巨大:例如一个端使用 Rust,另一个端使用 Swift,共享收益很低。
没有强制执行 typecheck 的习惯:类型一旦无法在全端同步,反而会更加混乱。
我们之所以采用这套架构,是因为 IHUI AI 从一开始就定位为“8 端全栈 AI 操作系统”,从第一天起就知道要做 8 个端。越早投入,越早受益;如果等到第 5 个端再迁移到 Monorepo,成本会是现在的 10 倍。
8 端同源的核心,并不是“把 8 个端塞进同一个仓库”,而是:
类型同源:一份类型,8 个端共享,修改一处,全端报警。
schema 同源:一份 Drizzle schema,由后端和 AI 服务共用。
UI 同源:packages/ui 暴露 API,各端编写样式适配器。
SDK 同源:一份 client,各端注入自己的鉴权方式。
这套架构让 IHUI AI 在 6 个月内独立交付了 8 个端、340 张表、1300+ 个 API 和 5346 个测试——单人能够完成这些工作的核心原因,不是因为我特别能卷,而是因为架构让我的每一次改动都能自动同步到 8 个端。
如果你也在开发多端 AI 应用,强烈建议从第一天起就采用 Monorepo + 共享 packages。开始得越晚,成本就会呈指数级上升。
IHUI AI 是一站式 8 端全栈 AI 操作系统,基于 Apache 2.0 协议开源。
🌐 官网:https://aizhs.top
💻 GitHub:https://github.com/IHUI-INF-AI/IHUI-AI(欢迎点 Star 支持一下 ⭐)
📦 8 端同源:Web / API / CLI / Desktop / Extension / Mobile / Miniapp
🤖 176 个模型:OpenAI / Claude / Gemini / 通义 / DeepSeek / 智谱 / 文心 / 豆包 / Kimi / Ollama
💰 定价:Free / Pro ¥49/月 / Team ¥199/人/月 / Enterprise ¥2999/月起
5 分钟内从 Fork 到上线,替代 ChatGPT Team + Claude Code + Notion AI,每月节省 $60+。
本文最初发布于 IHUI AI Blog。欢迎在 GitHub 上关注我们,获取更多 AI 工程内容。
如需采取进一步措施,你可以考虑屏蔽此人和/或举报滥用行为。