From 6f3fe50ee05b3925e3c527171d672fffb3bd6f40 Mon Sep 17 00:00:00 2001 From: yuzhiran-dev Date: Fri, 29 May 2026 10:26:50 +0800 Subject: [PATCH] docs & cleanup: update stack info, remove dead code, fix backend bugs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Fix AGENTS.md and 技术架构设计.md to reflect actual stack (Prisma+MySQL, static export, PM2) - Remove 4 unused frontend components (page-transition, page-layout, image-upload, section-card) - Fix card.tsx hardcoded colors → CSS variables - Remove provider name from model selector, remove sensenova-u1-fast from models - Fix analytics.controller.ts raw SQL table names (runtime bug) - Add JWT auth guard to tools POST endpoint - Fix AI gateway test: update env vars and model names - Fix admin test: mock role structure for Prisma relation --- AGENTS.md | 6 +- .../src/modules/admin/analytics.controller.ts | 6 +- .../modules/admin/tests/admin.service.spec.ts | 2 +- backend/src/modules/ai/ai-gateway.service.ts | 8 +- .../ai/tests/ai-gateway.service.spec.ts | 12 +- backend/src/modules/tools/tools.controller.ts | 7 +- docs/技术架构设计.md | 213 ++++++++---------- frontend/src/components/ui/card.tsx | 4 +- frontend/src/components/ui/image-upload.tsx | 82 ------- frontend/src/components/ui/model-selector.tsx | 2 +- frontend/src/components/ui/page-layout.tsx | 25 -- .../src/components/ui/page-transition.tsx | 15 -- frontend/src/components/ui/section-card.tsx | 30 --- frontend/src/lib/assistant-actions.ts | 3 +- frontend/src/lib/models.ts | 7 +- 15 files changed, 118 insertions(+), 304 deletions(-) delete mode 100644 frontend/src/components/ui/image-upload.tsx delete mode 100644 frontend/src/components/ui/page-layout.tsx delete mode 100644 frontend/src/components/ui/page-transition.tsx delete mode 100644 frontend/src/components/ui/section-card.tsx diff --git a/AGENTS.md b/AGENTS.md index 8f1404d..f6770fe 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -4,7 +4,7 @@ ## 项目概览 -宇之然 AI 学习与实践平台。前端 Next.js + shadcn/ui,后端 NestJS + PostgreSQL。 +宇之然 AI 学习与实践平台。前端 Next.js + shadcn/ui,后端 NestJS + MySQL。 | 项目 | 值 | |------|-----| @@ -25,7 +25,7 @@ ### 后端 - **框架**: NestJS 模块化架构 -- **ORM**: TypeORM + PostgreSQL(schema 在 `database/migrations/`) +- **ORM**: Prisma + MySQL(schema 在 `prisma/schema.prisma`) - **认证**: JWT + Passport - **API 前缀**: `/api/v1` - **CORS**: `origin: true`, `maxAge: 0`(避免浏览器预检缓存) @@ -82,7 +82,7 @@ - `users.service.ts` 的 `if (status)` 曾误写为 `if (params.status)`(已修复) - 静态构建 (`next build`) 已修复 —— 所有动态路由都通过 server wrapper 模式导出 `generateStaticParams()`(60 页面全部生成) - `public/favicon.ico` 和 `app/favicon.ico` 不能共存,会触发 500 -- 数据库 schema 通过 TypeORM migration 管理, 位于 `backend/src/database/migrations/` +- 数据库 schema 通过 Prisma migration 管理, 位于 `backend/prisma/migrations/` ## 项目管理流程 diff --git a/backend/src/modules/admin/analytics.controller.ts b/backend/src/modules/admin/analytics.controller.ts index 2bbe095..aa65f36 100644 --- a/backend/src/modules/admin/analytics.controller.ts +++ b/backend/src/modules/admin/analytics.controller.ts @@ -138,7 +138,7 @@ export class AnalyticsController { if (type === 'users') { const data = await this.prisma.$queryRaw<{ date: string; count: bigint }[]>` SELECT DATE(created_at) as date, COUNT(*) as count - FROM User + FROM users WHERE deleted_at IS NULL AND created_at >= ${startDate} GROUP BY DATE(created_at) ORDER BY date @@ -149,7 +149,7 @@ export class AnalyticsController { if (type === 'orders') { const data = await this.prisma.$queryRaw<{ date: string; count: bigint; revenue: bigint }[]>` SELECT DATE(created_at) as date, COUNT(*) as count, COALESCE(SUM(amount), 0) as revenue - FROM \`Order\` + FROM orders WHERE status = 'PAID' AND created_at >= ${startDate} GROUP BY DATE(created_at) ORDER BY date @@ -160,7 +160,7 @@ export class AnalyticsController { if (type === 'sessions') { const data = await this.prisma.$queryRaw<{ date: string; count: bigint }[]>` SELECT DATE(created_at) as date, COUNT(*) as count - FROM sandbox_session + FROM sandbox_sessions WHERE created_at >= ${startDate} GROUP BY DATE(created_at) ORDER BY date diff --git a/backend/src/modules/admin/tests/admin.service.spec.ts b/backend/src/modules/admin/tests/admin.service.spec.ts index 898a00b..26861a4 100644 --- a/backend/src/modules/admin/tests/admin.service.spec.ts +++ b/backend/src/modules/admin/tests/admin.service.spec.ts @@ -51,7 +51,7 @@ describe('AdminService', () => { id: 1, username: 'admin', passwordHash: '$2a$10$hashed', - role: 'superadmin', + role: { name: 'superadmin' }, status: 'ACTIVE', }; diff --git a/backend/src/modules/ai/ai-gateway.service.ts b/backend/src/modules/ai/ai-gateway.service.ts index a7d7652..9ebb1ca 100644 --- a/backend/src/modules/ai/ai-gateway.service.ts +++ b/backend/src/modules/ai/ai-gateway.service.ts @@ -31,7 +31,7 @@ const MODEL_CATALOG: Record = { 'gpt-4': { id: 'gpt-4', provider: 'OpenAI', capabilities: ['chat', 'code', 'vision'], contextWindow: 32768 }, 'deepseek-v4-flash': { id: 'deepseek-v4-flash', provider: '商汤科技', capabilities: ['chat', 'code'], contextWindow: 32768 }, 'sensenova-6.7-flash-lite': { id: 'sensenova-6.7-flash-lite', provider: '商汤科技', capabilities: ['chat', 'code'], contextWindow: 32768 }, - 'sensenova-u1-fast': { id: 'sensenova-u1-fast', provider: '商汤科技', capabilities: ['chat', 'code', 'vision'], contextWindow: 65536 }, + } @Injectable() @@ -65,7 +65,7 @@ export class AIGatewayService { if (!apiUrl.endsWith('/chat/completions')) { apiUrl = apiUrl.replace(/\/+$/, '') + '/chat/completions'; } - const models = ['deepseek-v4-flash', 'sensenova-6.7-flash-lite', 'sensenova-u1-fast']; + const models = ['deepseek-v4-flash', 'sensenova-6.7-flash-lite']; for (const modelName of models) { this.providers.set(modelName, new OpenAICompatibleProvider( process.env.SENSENOVA_API_KEY!, @@ -88,7 +88,7 @@ export class AIGatewayService { 'meituan/longcat-flash-lite': 'openai', 'deepseek-v4-flash': 'deepseek-v4-flash', 'sensenova-6.7-flash-lite': 'sensenova-6.7-flash-lite', - 'sensenova-u1-fast': 'sensenova-u1-fast', + }; const providerKey = modelMap[model.toLowerCase()] || (model.includes('/') ? 'openai' : model); @@ -125,7 +125,7 @@ export class AIGatewayService { 'meituan/longcat-flash-lite': 'openai', 'deepseek-v4-flash': 'deepseek-v4-flash', 'sensenova-6.7-flash-lite': 'sensenova-6.7-flash-lite', - 'sensenova-u1-fast': 'sensenova-u1-fast', + }; const providerKey = modelMap[model.toLowerCase()] || (model.includes('/') ? 'openai' : model); diff --git a/backend/src/modules/ai/tests/ai-gateway.service.spec.ts b/backend/src/modules/ai/tests/ai-gateway.service.spec.ts index b3b8e3f..f75e87b 100644 --- a/backend/src/modules/ai/tests/ai-gateway.service.spec.ts +++ b/backend/src/modules/ai/tests/ai-gateway.service.spec.ts @@ -6,8 +6,8 @@ describe('AIGatewayService', () => { beforeEach(async () => { // Clear env before each test - delete process.env.DEEPSEEK_API_KEY; - delete process.env.DASHSCOPE_API_KEY; + delete process.env.OPENAI_API_KEY; + delete process.env.SENSENOVA_API_KEY; const module: TestingModule = await Test.createTestingModule({ providers: [AIGatewayService], @@ -70,8 +70,8 @@ describe('AIGatewayService', () => { }); describe('model routing', () => { - it('should route deepseek models to deepseek provider (but fallback to mock)', async () => { - const result = await service.chat('deepseek-chat', [ + it('should route deepseek-v4-flash to its provider (but fallback to mock)', async () => { + const result = await service.chat('deepseek-v4-flash', [ { role: 'user', content: '你好' }, ]); @@ -79,8 +79,8 @@ describe('AIGatewayService', () => { expect(result).toContain('宇之然 AI 助手'); }); - it('should route qwen models to dashscope provider (but fallback to mock)', async () => { - const result = await service.chat('qwen-max', [ + it('should route general model to openai provider (but fallback to mock)', async () => { + const result = await service.chat('general', [ { role: 'user', content: '你好' }, ]); diff --git a/backend/src/modules/tools/tools.controller.ts b/backend/src/modules/tools/tools.controller.ts index 471e880..436c41d 100644 --- a/backend/src/modules/tools/tools.controller.ts +++ b/backend/src/modules/tools/tools.controller.ts @@ -1,5 +1,6 @@ -import { Controller, Get, Post, Body, Query } from '@nestjs/common'; -import { ApiTags } from '@nestjs/swagger'; +import { Controller, Get, Post, Body, Query, UseGuards } from '@nestjs/common'; +import { ApiTags, ApiBearerAuth } from '@nestjs/swagger'; +import { AuthGuard } from '@nestjs/passport'; import { ToolsService } from './tools.service'; @ApiTags('AI工具') @@ -13,6 +14,8 @@ export class ToolsController { } @Post() + @UseGuards(AuthGuard('jwt')) + @ApiBearerAuth() async create(@Body() body: { name: string; description?: string; url: string; icon?: string; categoryId?: number; tags?: string }) { return this.toolsService.create(body); } diff --git a/docs/技术架构设计.md b/docs/技术架构设计.md index d6a8426..2b6f302 100644 --- a/docs/技术架构设计.md +++ b/docs/技术架构设计.md @@ -1,7 +1,7 @@ # 宇之然 AI - 技术架构设计文档 -> 版本:v1.0 -> 日期:2026-05-08 +> 版本:v1.1 +> 日期:2026-05-29 --- @@ -12,16 +12,16 @@ ``` ┌─────────────────────────────────────────────────────────────────────┐ │ 客户端层 │ -│ ┌────────────┐ ┌──────────────┐ ┌────────────┐ │ -│ │ Next.js │ │ Uni-app │ │ 微信小程序 │ │ -│ │ 官网 (SSR) │ │ App (跨平台) │ │ │ │ -│ └────────────┘ └──────────────┘ └────────────┘ │ +│ ┌────────────────────┐ ┌──────────────────┐ │ +│ │ Next.js │ │ 微信小程序 │ │ +│ │ 官网 (静态导出) │ │ (规划中) │ │ +│ └────────────────────┘ └──────────────────┘ │ └──────────────────────────┬──────────────────────────────────────────┘ │ ┌──────────────────────────▼──────────────────────────────────────────┐ -│ API 网关层 │ -│ Nginx / 阿里云 SLB → API Gateway │ -│ 限流 / 鉴权 / 日志 / 路由转发 │ +│ API 网关层 (NestJS) │ +│ Nginx → PM2 → NestJS │ +│ 限流 / JWT 鉴权 / 日志 / 路由转发 │ └──────────────────────────┬──────────────────────────────────────────┘ │ ┌──────────────────────────▼──────────────────────────────────────────┐ @@ -36,14 +36,14 @@ │ ┌──────────────────────────▼──────────────────────────────────────────┐ │ AI 网关层 │ -│ 统一 API → 多模型路由 → 负载均衡 → 结果缓存 │ -│ 通义千问 │ 文心一言 │ GLM │ DeepSeek │ Kimi ... │ +│ 统一 API → 模型路由 → 流式响应 │ +│ qnaigc 兼容接口 → DeepSeek / SenseNova / 其他 OpenAI 兼容模型 │ └──────────────────────────┬──────────────────────────────────────────┘ │ ┌──────────────────────────▼──────────────────────────────────────────┐ │ 数据层 │ -│ MySQL 8.0 │ Redis 7 │ ES 8.x │ OSS │ RocketMQ │ -│ (主从/读写分离) (缓存/会话) (搜索) (存储) (消息队列) │ +│ MySQL 8.0 │ Redis 7 │ +│ (主库) │ (缓存/会话) │ └─────────────────────────────────────────────────────────────────────┘ ``` @@ -55,23 +55,18 @@ | 技术 | 版本 | 说明 | |------|------|------| -| Next.js | 14+ | React 框架,SSR/SSG 支持 | +| Next.js | 14+ | React 框架,静态导出 (`output: 'export'`) | | TypeScript | 5.x | 类型安全 | | TailwindCSS | 3.x | 原子化 CSS | | Shadcn/ui | latest | UI 组件库 | -| React Query | 5.x | 数据请求管理 | -| Zustand | latest | 状态管理 | +| next-themes | latest | 暗黑模式切换 | -### 2.2 移动端(App / 小程序) +### 2.2 移动端(规划中) | 技术 | 说明 | |------|------| -| Uni-app / Taro | 跨端框架 | -| Vue 3 / React | 视框架而定 | -| Pinia / Zustand | 状态管理 | -| uView / NutUI | 移动端组件库 | - -> 推荐 Uni-app + Vue 3,对微信小程序适配最成熟。 +| 微信小程序 | 后续规划 | +| Taro / uni-app | 评估中 | ### 2.3 后端 @@ -79,64 +74,41 @@ |------|------|------| | Node.js | 20 LTS | 运行时 | | NestJS | 10.x | Node.js 后端框架 | -| Prisma | 5.x | ORM | -| JWT | - | 鉴权 | -| Zod | latest | 数据校验 | - -或备选方案: - -| 技术 | 说明 | -|------|------| -| Go + Gin / Fiber | 高性能,适合 AI 网关 | -| Python + FastAPI | 适合 AI 数据处理任务 | - -> 建议:核心业务用 NestJS,AI 网关用 Go。 +| Prisma | 5.x | ORM(MySQL) | +| JWT | - | 鉴权(Access Token 2h + Refresh Token 7d)| ### 2.4 数据库 | 组件 | 用途 | 部署 | |------|------|------| -| MySQL 8.0 | 业务主库(用户/课程/订单等) | 阿里云 RDS | -| Redis 7 | 缓存/会话/限流计数器 | 阿里云 Redis | -| Elasticsearch 8.x | 内容搜索 | 阿里云 ES | -| 阿里云 OSS | 图片/视频/文件存储 | 阿里云 OSS | -| RocketMQ | 异步任务/消息通知 | 阿里云 RocketMQ | +| MySQL 8.0 | 业务主库(用户/课程/订单等) | 本地 / 云 RDS | +| Redis 7 | 缓存/会话/限流计数器 | 本地 / 云 Redis | ### 2.5 AI 网关 ``` -用户请求 → 网关层 - ├── 请求校验 → 内容安全审核 - ├── 模型路由(基于用户配置/成本/负载) - │ ├── 通义千问 (qwen-max) - │ ├── 文心一言 (ERNIE-4.0) - │ ├── GLM-4 - │ ├── DeepSeek-V3 - │ └── Kimi (moonshot-v1) - ├── 结果缓存(Redis,相同 prompt 命中缓存) - ├── 流式响应处理 - └── 计费/用量统计 +用户请求 → AIGatewayService + ├── 模型路由(基于 modelMap 配置) + │ ├── general → OPENAI_MODEL (default: deepseek/deepseek-v4-flash) + │ ├── deepseek-v4-flash → deepseek/deepseek-v4-flash + │ └── sensenova-6.7-flash-lite → sensenova-6.7-flash-lite + ├── 流式响应 (ReadableStream SSE) + └── 用量统计 ``` -### 2.6 内容安全 - -| 服务 | 用途 | -|------|------| -| 阿里云内容安全 | 文本/图片鉴黄、涉政、违禁检测 | -| 腾讯云天御 | UGC 内容安全审核 | -| 自定义敏感词库 | 行业特定敏感词过滤 | +AI 通过 OpenAI 兼容 API (`api.qnaigc.com`) 统一接入,不直接对接各模型厂商。 --- ## 3. 数据库设计概要 -### 3.1 核心表结构 +### 3.1 核心表结构 (Prisma Schema) ``` users — 用户表 ├── id, phone, email, password_hash, avatar, status, created_at -├── user_profiles — 用户扩展信息 -└── user_learn_records — 学习记录 +├── profiles — 用户扩展信息 +└── learn_records — 学习记录 courses — 课程表 ├── id, title, description, cover, category, price, status @@ -172,15 +144,17 @@ contents — 内容(文章/资讯) admin_users — 管理员表 ├── id, username, password_hash, role, permission, last_login └── admin_logs — 操作日志 + +skills — 技能包表 +├── id, name, description, category, difficulty, system_prompt, icon +└── skill_tasks — 练习任务 ``` -### 3.2 数据库设计原则 +### 3.2 Schema 管理 -- **软删除**:所有业务表增加 `deleted_at` 字段 -- **审计字段**:`created_at`、`updated_at` 必备 -- **索引策略**:覆盖常用查询场景,避免全表扫描 -- **分表策略**:`sandbox_sessions` 按用户 ID 分表 -- **读写分离**:主库写入,从库查询 +- 使用 Prisma Migrate 管理 schema 变更 +- migration 文件位于 `backend/prisma/migrations/` +- 种子数据在 `backend/prisma/seed.ts` --- @@ -206,6 +180,11 @@ GET /api/v1/sandbox/quota — 沙箱额度查询 POST /api/v1/orders/create — 创建订单 GET /api/v1/orders/:id — 订单查询 POST /api/v1/orders/callback — 支付回调 + +GET /api/v1/skills — 技能列表(支持过滤) +GET /api/v1/skills/:id — 技能详情 +GET /api/v1/skills/categories — 技能分类 +GET /api/v1/skills/difficulties — 难度等级 ``` ### 4.2 通用响应格式 @@ -223,68 +202,58 @@ POST /api/v1/orders/callback — 支付回调 } ``` -### 4.3 鉴权方案 - -- **JWT Token**(Access Token 2h + Refresh Token 7d) -- 管理后台:Session + Cookie -- API 签名:管理端 API 需签名校验 - --- ## 5. 部署架构 +### 5.1 当前部署 + ``` ┌─────────────┐ - │ DNS │ - │ (阿里云 DNS)│ + │ PM2 │ + │ (进程管理) │ └──────┬──────┘ │ + ┌───────────┼───────────┐ + │ │ │ + ┌──────▼──┐ ┌─────▼────┐ ┌───▼────┐ + │ Node.js │ │ NestJS │ │ Redis │ + │ server.js│ │ API 服务 │ │ │ + │ (静态) │ │ PM2 │ └────────┘ + └─────────┘ └─────┬────┘ + │ ┌──────▼──────┐ - │ CDN │ - │ (静态资源) │ - └──────┬──────┘ - │ - ┌──────▼──────┐ - │ SLB │ - │ (负载均衡) │ - └──────┬──────┘ - │ - ┌────────────┼────────────┐ - │ │ │ - ┌──────▼──┐ ┌─────▼────┐ ┌────▼────┐ - │ Next.js │ │ NestJS │ │ Admin │ - │ 官网 │ │ API 服务 │ │ Panel │ - └─────────┘ └─────┬────┘ └─────────┘ - │ - ┌───────────┼───────────┐ - │ │ │ - ┌──────▼──┐ ┌────▼───┐ ┌───▼────┐ - │ MySQL │ │ Redis │ │ ES │ - │ RDS │ │ │ │ │ - └─────────┘ └────────┘ └────────┘ + │ MySQL │ + │ 8.0 │ + └─────────────┘ ``` -### 5.1 环境规划 +| 服务 | 端口 | 管理方式 | +|------|------|----------| +| 前端 (静态文件) | 3000 | PM2: `node server.js` | +| 后端 (NestJS) | 4000 | PM2: `node dist/main.js` | +| MySQL | 3306 | 系统服务 | +| Redis | 6379 | 系统服务 | -| 环境 | 用途 | 配置 | -|------|------|------| -| 开发 (dev) | 本地开发 | 个人电脑 / Docker Compose | -| 测试 (staging) | 联调测试 | 阿里云 ECS 2C4G | -| 生产 (production) | 正式运营 | 阿里云 ECS 4C8G × 2 + RDS 2C4G | - -### 5.2 CI/CD 流程 +### 5.2 构建流程 ``` -Git Push → GitHub Actions - ├── Lint & Type Check - ├── Unit Test - ├── Build - └── Deploy to 阿里云 - ├── 构建 Docker 镜像 - ├── Push 到阿里云 CR - └── 滚动更新 ECS/Pod +# 前端 +npm run build → out/ (静态文件) +pm2 restart frontend + +# 后端 +npx nest build → dist/ +pm2 restart backend --update-env ``` +### 5.3 环境 + +| 环境 | 用途 | +|------|------| +| 开发 (dev) | 本地 `npm run dev` / `npm run start:dev` | +| 生产 (production) | PM2 + 静态导出 | + --- ## 6. 性能与安全 @@ -296,18 +265,15 @@ Git Push → GitHub Actions | API 响应时间 (P95) | < 200ms | | 首屏加载时间 | < 1.5s | | AI 对话首 Token 延迟 | < 1s | -| 并发用户 | 支持 1000+ 同时在线 | -| 系统可用性 | 99.9% | +| 静态页面加载 | 即时(CDN 缓存) | ### 6.2 安全措施 - HTTPS 全站加密 -- API 限流(单用户 100次/分钟) -- SQL 注入防护(Prisma ORM 参数化查询) -- XSS/CSRF 防护 +- JWT 鉴权(沙箱等核心 API) +- API 限流 - 密码 bcrypt 加密 - 敏感信息脱敏 -- 定期安全扫描 + 渗透测试 --- @@ -315,8 +281,7 @@ Git Push → GitHub Actions | 阶段 | 任务 | |------|------| -| MVP | 单体应用快速验证 | -| V2 | 服务化拆分 | -| V3 | AI 网关独立部署 | -| V4 | 引入 K8s 容器编排 | -| V5 | 多数据中心容灾 | +| 当前 | 单体 NestJS + Next.js 静态导出 | +| V2 | AI 网关独立部署 | +| V3 | 微信小程序上线 | +| V4 | 服务化拆分 | diff --git a/frontend/src/components/ui/card.tsx b/frontend/src/components/ui/card.tsx index 570c18a..d1fa26a 100644 --- a/frontend/src/components/ui/card.tsx +++ b/frontend/src/components/ui/card.tsx @@ -3,7 +3,7 @@ import { cn } from '@/lib/utils' const Card = React.forwardRef>( ({ className, ...props }, ref) => ( -
+
), ) Card.displayName = 'Card' @@ -24,7 +24,7 @@ CardTitle.displayName = 'CardTitle' const CardDescription = React.forwardRef>( ({ className, ...props }, ref) => ( -

+

), ) CardDescription.displayName = 'CardDescription' diff --git a/frontend/src/components/ui/image-upload.tsx b/frontend/src/components/ui/image-upload.tsx deleted file mode 100644 index 50ff0eb..0000000 --- a/frontend/src/components/ui/image-upload.tsx +++ /dev/null @@ -1,82 +0,0 @@ -'use client'; - -import { useState, useRef } from 'react'; -import { API_BASE } from '@/lib/config'; - -interface ImageUploadProps { - onUploaded: (url: string) => void; - defaultImage?: string; - accept?: string; -} - -export function ImageUpload({ onUploaded, defaultImage, accept = 'image/*' }: ImageUploadProps) { - const [uploading, setUploading] = useState(false); - const [preview, setPreview] = useState(defaultImage || ''); - const [error, setError] = useState(''); - const inputRef = useRef(null); - - async function handleFile(e: React.ChangeEvent) { - const file = e.target.files?.[0]; - if (!file) return; - - const token = localStorage.getItem('token'); - if (!token) { - setError('请先登录'); - return; - } - - setUploading(true); - setError(''); - - const formData = new FormData(); - formData.append('file', file); - - try { - const res = await fetch(`${API_BASE}/upload`, { - method: 'POST', - headers: { Authorization: `Bearer ${token}` }, - body: formData, - }); - - if (!res.ok) { - const err = await res.json(); - throw new Error(err.message || '上传失败'); - } - - const data = await res.json(); - setPreview(`${API_BASE}${data.url}`); - onUploaded(data.url); - } catch (err: any) { - setError(err.message); - } finally { - setUploading(false); - } - } - - return ( -

-
inputRef.current?.click()} - > - {preview ? ( - preview - ) : ( -
- - - -
- )} - {uploading && ( -
-
-
- )} -
- - {error &&

{error}

} -

点击上传图片(最大 5MB,支持 jpg/png/gif/webp)

-
- ); -} diff --git a/frontend/src/components/ui/model-selector.tsx b/frontend/src/components/ui/model-selector.tsx index 31d814d..f3d413f 100644 --- a/frontend/src/components/ui/model-selector.tsx +++ b/frontend/src/components/ui/model-selector.tsx @@ -52,7 +52,7 @@ export function ModelSelector({ value, onChange, className = '' }: ModelSelector
{m.label}
-
{m.provider} · {m.desc}
+
{m.desc}
{m.id === value && ( diff --git a/frontend/src/components/ui/page-layout.tsx b/frontend/src/components/ui/page-layout.tsx deleted file mode 100644 index 8874a9c..0000000 --- a/frontend/src/components/ui/page-layout.tsx +++ /dev/null @@ -1,25 +0,0 @@ -import type { ReactNode } from 'react' - -interface PageLayoutProps { - title: string - description?: string - backHref?: string - children: ReactNode -} - -export function PageLayout({ title, description, backHref, children }: PageLayoutProps) { - return ( -
-
- {backHref && ( - - ← 返回 - - )} -

{title}

- {description &&

{description}

} -
- {children} -
- ) -} diff --git a/frontend/src/components/ui/page-transition.tsx b/frontend/src/components/ui/page-transition.tsx deleted file mode 100644 index 67e31d6..0000000 --- a/frontend/src/components/ui/page-transition.tsx +++ /dev/null @@ -1,15 +0,0 @@ -'use client' - -import { motion } from 'framer-motion' - -export function PageTransition({ children }: { children: React.ReactNode }) { - return ( - - {children} - - ) -} diff --git a/frontend/src/components/ui/section-card.tsx b/frontend/src/components/ui/section-card.tsx deleted file mode 100644 index c671b92..0000000 --- a/frontend/src/components/ui/section-card.tsx +++ /dev/null @@ -1,30 +0,0 @@ -import type { ReactNode } from 'react' - -interface CardProps { - children: ReactNode - className?: string - padding?: 'sm' | 'md' | 'lg' -} - -export function Card({ children, className = '', padding = 'md' }: CardProps) { - const pads = { sm: 'p-4', md: 'p-6', lg: 'p-8' } - return ( -
- {children} -
- ) -} - -interface StatCardProps { - label: string - value: string | number -} - -export function StatCard({ label, value }: StatCardProps) { - return ( -
-
{label}
-
{value}
-
- ) -} diff --git a/frontend/src/lib/assistant-actions.ts b/frontend/src/lib/assistant-actions.ts index b80d003..59f7737 100644 --- a/frontend/src/lib/assistant-actions.ts +++ b/frontend/src/lib/assistant-actions.ts @@ -77,8 +77,7 @@ export async function executeAction(action: AssistantAction): Promise<{ success: export const MODEL_OPTIONS = [ { value: 'general', label: '通用模式' }, { value: 'deepseek-v4-flash', label: 'DeepSeek V4 Flash' }, - { value: 'opencode', label: 'OpenCode Go' }, - { value: 'meituan/longcat-flash-lite', label: '长颈鹿 Flash' }, + { value: 'sensenova-6.7-flash-lite', label: 'SenseNova 6.7 Flash Lite' }, ] export const SKILL_IDS = [ diff --git a/frontend/src/lib/models.ts b/frontend/src/lib/models.ts index 183abbb..5283996 100644 --- a/frontend/src/lib/models.ts +++ b/frontend/src/lib/models.ts @@ -6,10 +6,9 @@ export interface ModelOption { } export const AVAILABLE_MODELS: ModelOption[] = [ - { id: 'general', label: '通用模式', provider: 'OpenAI 兼容', desc: '日常问答,综合能力均衡' }, - { id: 'deepseek-v4-flash', label: 'DeepSeek V4 Flash', provider: '商汤科技', desc: '高速推理,代码生成强' }, - { id: 'sensenova-6.7-flash-lite', label: 'SenseNova 6.7 Flash Lite', provider: '商汤科技', desc: '轻量快速,日常使用' }, - { id: 'sensenova-u1-fast', label: 'SenseNova U1 Fast', provider: '商汤科技', desc: '高性能推理,复杂任务' }, + { id: 'general', label: '通用模式', provider: '', desc: '日常问答,综合能力均衡' }, + { id: 'deepseek-v4-flash', label: 'DeepSeek V4 Flash', provider: '', desc: '高速推理,代码生成强' }, + { id: 'sensenova-6.7-flash-lite', label: 'SenseNova 6.7 Flash Lite', provider: '', desc: '轻量快速,日常使用' }, ]; export const DEFAULT_MODEL = 'general';