From 4de042abcdc8ed2aa0886558193005c5dd3fbfd0 Mon Sep 17 00:00:00 2001 From: yuzhiran-dev Date: Mon, 18 May 2026 12:06:30 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20P3a=20=E4=BB=A3=E7=A0=81=E8=AF=AD?= =?UTF-8?q?=E6=B3=95=E9=AB=98=E4=BA=AE=20(highlight.js=20+=20CodeBlock?= =?UTF-8?q?=E7=BB=84=E4=BB=B6)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/progress.md | 61 ++++++-------------- frontend/package-lock.json | 9 +++ frontend/package.json | 1 + frontend/src/app/globals.css | 3 + frontend/src/app/sandbox/page.tsx | 14 ++++- frontend/src/components/ui/code-block.tsx | 68 +++++++++++++++++++++++ 6 files changed, 112 insertions(+), 44 deletions(-) create mode 100644 frontend/src/components/ui/code-block.tsx diff --git a/docs/progress.md b/docs/progress.md index 851c4a0..78c9d72 100644 --- a/docs/progress.md +++ b/docs/progress.md @@ -1,6 +1,6 @@ # 项目进度追踪 -## 当前阶段:P0 全部完成 ✅ +## 当前阶段:P3 — 体验增强 ### Phase 1-3 — 全部完成 ✅ @@ -13,7 +13,6 @@ | **P0d** | 会员定价页重构(用量可视化) | ✅ | | **P0e** | 模型选择器升级(卡片式 + 能力标签) | ✅ | | **P0f** | 会话分享链接 — HMAC 签名 + 分享查看页 | ✅ | -| **全栈验证** | 后端 13 suites / 92 tests / 前端 61 pages | ✅ | ### P1 — 学习技能包(全部完成 ✅) | 子项 | 说明 | 状态 | @@ -21,53 +20,29 @@ | **后端** | SkillsModule + 8 技能定义 + 过滤/详情 API | ✅ | | **前端** | /skills 市场 + /skills/[id] 详情 + header 入口 | ✅ | | **沙盒集成** | 替代硬编码 SCENES,从 API 动态加载,?skill=xxx 支持 | ✅ | -| **全栈验证** | 后端 92 tests / 前端 70 pages | ✅ | + +### P2 — CSS 统一(全部完成 ✅) +| 子项 | 说明 | 状态 | +|------|------|------| +| **Mobile variables.css** | Design Token 变量定义(Web/Mobile 统一) | ✅ | +| **Mobile 全站 18 页** | 批量替换 hardcoded color → CSS vars | ✅ | +| **PC 响应式** | header/compare/sandbox/member/workshop 移动端修复 | ✅ | + +### P3 — 体验增强(当前阶段) +| 优先级 | 项目 | 说明 | 状态 | +|--------|------|------|------| +| **P3a** | 代码语法高亮 | 沙盒聊天中代码块用 Shiki/prism 渲染 | ⏳ | +| **P3b** | 会话重命名 | 侧边栏对话标题可编辑 | ⏳ | +| **P3c** | LLM 集成层增强 | 模型能力检测 / 用量统计 | ⏳ | +| **P3d** | Mobile 样式复查 | 剩余页面 CSS 变量覆盖率检查 | ⏳ | +| — | 多模态(文件上传/图片理解) | 远期规划 | 📋 | +| — | SDK / 共享 UI 包 | 远期规划 | 📋 | ### 参考来源 - opencode 源码克隆到 `/tmp/opencode-source/`(21 packages, 147MB) -- 关键参考模块: `packages/plugin`(hooks + tool 系统)、`packages/ui/i18n/`(20+ 语言)、`packages/llm`(provider 接口) - -### 新页面 -- `/learning/analytics` — 学情分析看板(对话分析→知识图谱→薄弱环节→推荐) -- `/learning/path` — 分阶段学习路径(4 阶段 12 任务,自动追踪完成进度) -- `/my` 首页添加了学情分析和学习路径入口链接 - -### 验证 -- 后端 13 suites / 92 tests passed ✅ -- 前端 60 页面构建无报错 ✅ - -### 今日修复项(2026-05-15) - -#### 颜色约定修复(18 个文件) -- `page.tsx`(首页)、`dashboard/page.tsx`、`my/*`(4 个)、`models/page.tsx` -- `discover/*`(3 个)、`circles/*`(2 个)、`admin/*`(2 个) -- `courses/[id]/client.tsx`、`contents/[id]/client.tsx` -- `sandbox/compare/page.tsx`、`sandbox/code/page.tsx` -- 全部替换 `text-gray-*`/`bg-gray-*`/`border-gray-*` → CSS 变量 - -#### 模型列表统一 -- 新建 `frontend/src/lib/models.ts` — 单数据源 -- `sandbox/page.tsx`、`sandbox/compare/page.tsx`、`prompts/workshop/page.tsx` 统一导入 - -#### 对比实验室增强 -- 修复硬编码颜色、更新 model ID 为后端支持的值 -- 新增参数调节面板(temperature/top-p/max-tokens) - -#### 清理 -- 删除根目录 9 个残留文件(next-dev*.log、PROGRESS_FINAL* 等) -- 删除 `backend/src/e2e.spec.ts` 占位符 ### 关键决策 - `frontend/src/lib/models.ts` 作为模型列表单数据源 - 所有颜色使用 CSS 变量(`text-foreground`/`text-muted-foreground`/`bg-card`/`bg-muted`/`border-border`) - `nest build` 正常产出(`--tsc` 遗漏 prisma 模块) - 沙盒 JWT 认证保留 - -### 相关文件 -- `backend/src/modules/learning/` — 学习分析模块(service + controller + module) -- `frontend/src/app/learning/analytics/page.tsx` — 学情分析看板页面 -- `frontend/src/app/learning/path/page.tsx` — 学习路径页面 -- `frontend/src/lib/models.ts` — 模型列表单数据源(新建) -- `frontend/src/app/sandbox/compare/page.tsx` — 对比实验室(修复颜色+参数+model) -- `frontend/src/app/page.tsx`, `dashboard/`, `my/`, `discover/`, `circles/`, `admin/`, `courses/`, `contents/` — 颜色修复 -- `docs/产品优化方向.md` — 完整优化方案 diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 9d123c2..cc7dc77 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -27,6 +27,7 @@ "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "framer-motion": "^12.38.0", + "highlight.js": "^11.11.1", "lucide-react": "^1.14.0", "next": "^14.2.35", "next-themes": "^0.4.6", @@ -3769,6 +3770,14 @@ "node": ">= 0.4" } }, + "node_modules/highlight.js": { + "version": "11.11.1", + "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-11.11.1.tgz", + "integrity": "sha512-Xwwo44whKBVCYoliBQwaPvtd/2tYFkRQtXDWj1nackaV2JPXx3L0+Jvd8/qCJ2p+ML0/XVkJ2q+Mr+UVdpJK5w==", + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/html-encoding-sniffer": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-6.0.0.tgz", diff --git a/frontend/package.json b/frontend/package.json index 545ad60..85f25d2 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -43,6 +43,7 @@ "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "framer-motion": "^12.38.0", + "highlight.js": "^11.11.1", "lucide-react": "^1.14.0", "next": "^14.2.35", "next-themes": "^0.4.6", diff --git a/frontend/src/app/globals.css b/frontend/src/app/globals.css index e386d71..eda3db1 100644 --- a/frontend/src/app/globals.css +++ b/frontend/src/app/globals.css @@ -4,6 +4,9 @@ @import url('https://fonts.googleapis.com/css2?family=Noto+Sans+SC:wght@300;400;500;700&display=swap'); +/* highlight.js for code syntax highlighting */ +@import 'highlight.js/styles/atom-one-dark.css'; + @layer base { :root { --background: 0 0% 100%; diff --git a/frontend/src/app/sandbox/page.tsx b/frontend/src/app/sandbox/page.tsx index 23f1855..5967362 100644 --- a/frontend/src/app/sandbox/page.tsx +++ b/frontend/src/app/sandbox/page.tsx @@ -8,6 +8,7 @@ import { getToken, apiFetch } from '@/lib/auth'; import { DEFAULT_MODEL } from '@/lib/models'; import { ModelSelector } from '@/components/ui/model-selector'; import { useT } from '@/i18n'; +import { CodeBlock } from '@/components/ui/code-block'; const API_BASE = process.env.NEXT_PUBLIC_API_URL || 'http://localhost:3000/api/v1'; @@ -412,7 +413,18 @@ function SandboxPage() {
Y
)}
- {msg.content} + {(() => { + const parts = msg.content.split(/(```(?:\w+)?\n[\s\S]*?```)/g) + return parts.map((part, pi) => { + const match = part.match(/^```(\w*)\n([\s\S]*)```$/) + if (match) { + const [, lang, code] = match + return + } + if (pi > 0 && parts[pi-1].startsWith('```')) return null + return {part} + }) + })()}
{msg.role === 'user' && (
diff --git a/frontend/src/components/ui/code-block.tsx b/frontend/src/components/ui/code-block.tsx new file mode 100644 index 0000000..2009ab3 --- /dev/null +++ b/frontend/src/components/ui/code-block.tsx @@ -0,0 +1,68 @@ +'use client' + +import { useRef, useState } from 'react' +import hljs from 'highlight.js/lib/core' +import javascript from 'highlight.js/lib/languages/javascript' +import python from 'highlight.js/lib/languages/python' +import typescript from 'highlight.js/lib/languages/typescript' +import xml from 'highlight.js/lib/languages/xml' +import css from 'highlight.js/lib/languages/css' +import bash from 'highlight.js/lib/languages/bash' +import json from 'highlight.js/lib/languages/json' + +hljs.registerLanguage('javascript', javascript) +hljs.registerLanguage('js', javascript) +hljs.registerLanguage('python', python) +hljs.registerLanguage('py', python) +hljs.registerLanguage('typescript', typescript) +hljs.registerLanguage('ts', typescript) +hljs.registerLanguage('xml', xml) +hljs.registerLanguage('html', xml) +hljs.registerLanguage('css', css) +hljs.registerLanguage('bash', bash) +hljs.registerLanguage('sh', bash) +hljs.registerLanguage('json', json) + +interface CodeBlockProps { + code: string + language?: string +} + +export function CodeBlock({ code, language }: CodeBlockProps) { + const [copied, setCopied] = useState(false) + const codeRef = useRef(null) + + let highlighted = '' + try { + if (language && hljs.getLanguage(language)) { + highlighted = hljs.highlight(code, { language }).value + } else { + highlighted = hljs.highlightAuto(code).value + } + } catch { + highlighted = code + } + + async function copyCode() { + try { + await navigator.clipboard.writeText(code) + setCopied(true) + setTimeout(() => setCopied(false), 2000) + } catch {} + } + + return ( +
+
+ {language || 'code'} + +
+
+        
+      
+
+ ) +}