feat: 优化UI并增加代码编辑器
This commit is contained in:
parent
1a010bb8ce
commit
917feb1c8c
|
|
@ -10,6 +10,7 @@
|
|||
- 📁 **工作目录** - 项目级文件隔离,安全操作
|
||||
- 📊 **Token 统计** - 按日/周/月统计使用量
|
||||
- 🔄 **流式响应** - 实时 SSE 流式输出
|
||||
- 📝 **代码编辑器** - 基于 CodeMirror 6,支持 15+ 语言语法高亮和暗色主题
|
||||
- 💾 **多数据库** - 支持 MySQL、SQLite、PostgreSQL
|
||||
|
||||
## 快速开始
|
||||
|
|
@ -157,5 +158,5 @@ frontend/
|
|||
## 技术栈
|
||||
|
||||
- **后端**: Python 3.11+, Flask, SQLAlchemy
|
||||
- **前端**: Vue 3, Vite
|
||||
- **前端**: Vue 3, Vite, CodeMirror 6
|
||||
- **LLM**: 支持 GLM 等大语言模型
|
||||
|
|
|
|||
|
|
@ -85,6 +85,8 @@ def delete_message(conv_id, msg_id):
|
|||
conv = _get_conv(conv_id)
|
||||
if not conv:
|
||||
return err(404, "conversation not found")
|
||||
if msg_id.startswith("temp_"):
|
||||
return ok(message="deleted")
|
||||
msg = db.session.get(Message, msg_id)
|
||||
if not msg or msg.conversation_id != conv_id:
|
||||
return err(404, "message not found")
|
||||
|
|
|
|||
|
|
@ -8,6 +8,21 @@
|
|||
"name": "nano-claw",
|
||||
"version": "0.1.0",
|
||||
"dependencies": {
|
||||
"@codemirror/lang-cpp": "^6.0.2",
|
||||
"@codemirror/lang-css": "^6.3.1",
|
||||
"@codemirror/lang-go": "^6.0.1",
|
||||
"@codemirror/lang-html": "^6.4.9",
|
||||
"@codemirror/lang-java": "^6.0.1",
|
||||
"@codemirror/lang-javascript": "^6.2.3",
|
||||
"@codemirror/lang-json": "^6.0.1",
|
||||
"@codemirror/lang-markdown": "^6.3.2",
|
||||
"@codemirror/lang-python": "^6.1.7",
|
||||
"@codemirror/lang-rust": "^6.0.1",
|
||||
"@codemirror/lang-sql": "^6.8.0",
|
||||
"@codemirror/lang-xml": "^6.1.0",
|
||||
"@codemirror/lang-yaml": "^6.1.2",
|
||||
"@codemirror/theme-one-dark": "^6.1.2",
|
||||
"codemirror": "^6.0.1",
|
||||
"highlight.js": "^11.11.1",
|
||||
"katex": "^0.16.40",
|
||||
"marked": "^15.0.12",
|
||||
|
|
@ -66,6 +81,268 @@
|
|||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@codemirror/autocomplete": {
|
||||
"version": "6.20.1",
|
||||
"resolved": "https://registry.npmmirror.com/@codemirror/autocomplete/-/autocomplete-6.20.1.tgz",
|
||||
"integrity": "sha512-1cvg3Vz1dSSToCNlJfRA2WSI4ht3K+WplO0UMOgmUYPivCyy2oueZY6Lx7M9wThm7SDUBViRmuT+OG/i8+ON9A==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@codemirror/language": "^6.0.0",
|
||||
"@codemirror/state": "^6.0.0",
|
||||
"@codemirror/view": "^6.17.0",
|
||||
"@lezer/common": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@codemirror/commands": {
|
||||
"version": "6.10.3",
|
||||
"resolved": "https://registry.npmmirror.com/@codemirror/commands/-/commands-6.10.3.tgz",
|
||||
"integrity": "sha512-JFRiqhKu+bvSkDLI+rUhJwSxQxYb759W5GBezE8Uc8mHLqC9aV/9aTC7yJSqCtB3F00pylrLCwnyS91Ap5ej4Q==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@codemirror/language": "^6.0.0",
|
||||
"@codemirror/state": "^6.6.0",
|
||||
"@codemirror/view": "^6.27.0",
|
||||
"@lezer/common": "^1.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@codemirror/lang-cpp": {
|
||||
"version": "6.0.3",
|
||||
"resolved": "https://registry.npmmirror.com/@codemirror/lang-cpp/-/lang-cpp-6.0.3.tgz",
|
||||
"integrity": "sha512-URM26M3vunFFn9/sm6rzqrBzDgfWuDixp85uTY49wKudToc2jTHUrKIGGKs+QWND+YLofNNZpxcNGRynFJfvgA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@codemirror/language": "^6.0.0",
|
||||
"@lezer/cpp": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@codemirror/lang-css": {
|
||||
"version": "6.3.1",
|
||||
"resolved": "https://registry.npmmirror.com/@codemirror/lang-css/-/lang-css-6.3.1.tgz",
|
||||
"integrity": "sha512-kr5fwBGiGtmz6l0LSJIbno9QrifNMUusivHbnA1H6Dmqy4HZFte3UAICix1VuKo0lMPKQr2rqB+0BkKi/S3Ejg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@codemirror/autocomplete": "^6.0.0",
|
||||
"@codemirror/language": "^6.0.0",
|
||||
"@codemirror/state": "^6.0.0",
|
||||
"@lezer/common": "^1.0.2",
|
||||
"@lezer/css": "^1.1.7"
|
||||
}
|
||||
},
|
||||
"node_modules/@codemirror/lang-go": {
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmmirror.com/@codemirror/lang-go/-/lang-go-6.0.1.tgz",
|
||||
"integrity": "sha512-7fNvbyNylvqCphW9HD6WFnRpcDjr+KXX/FgqXy5H5ZS0eC5edDljukm/yNgYkwTsgp2busdod50AOTIy6Jikfg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@codemirror/autocomplete": "^6.0.0",
|
||||
"@codemirror/language": "^6.6.0",
|
||||
"@codemirror/state": "^6.0.0",
|
||||
"@lezer/common": "^1.0.0",
|
||||
"@lezer/go": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@codemirror/lang-html": {
|
||||
"version": "6.4.11",
|
||||
"resolved": "https://registry.npmmirror.com/@codemirror/lang-html/-/lang-html-6.4.11.tgz",
|
||||
"integrity": "sha512-9NsXp7Nwp891pQchI7gPdTwBuSuT3K65NGTHWHNJ55HjYcHLllr0rbIZNdOzas9ztc1EUVBlHou85FFZS4BNnw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@codemirror/autocomplete": "^6.0.0",
|
||||
"@codemirror/lang-css": "^6.0.0",
|
||||
"@codemirror/lang-javascript": "^6.0.0",
|
||||
"@codemirror/language": "^6.4.0",
|
||||
"@codemirror/state": "^6.0.0",
|
||||
"@codemirror/view": "^6.17.0",
|
||||
"@lezer/common": "^1.0.0",
|
||||
"@lezer/css": "^1.1.0",
|
||||
"@lezer/html": "^1.3.12"
|
||||
}
|
||||
},
|
||||
"node_modules/@codemirror/lang-java": {
|
||||
"version": "6.0.2",
|
||||
"resolved": "https://registry.npmmirror.com/@codemirror/lang-java/-/lang-java-6.0.2.tgz",
|
||||
"integrity": "sha512-m5Nt1mQ/cznJY7tMfQTJchmrjdjQ71IDs+55d1GAa8DGaB8JXWsVCkVT284C3RTASaY43YknrK2X3hPO/J3MOQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@codemirror/language": "^6.0.0",
|
||||
"@lezer/java": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@codemirror/lang-javascript": {
|
||||
"version": "6.2.5",
|
||||
"resolved": "https://registry.npmmirror.com/@codemirror/lang-javascript/-/lang-javascript-6.2.5.tgz",
|
||||
"integrity": "sha512-zD4e5mS+50htS7F+TYjBPsiIFGanfVqg4HyUz6WNFikgOPf2BgKlx+TQedI1w6n/IqRBVBbBWmGFdLB/7uxO4A==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@codemirror/autocomplete": "^6.0.0",
|
||||
"@codemirror/language": "^6.6.0",
|
||||
"@codemirror/lint": "^6.0.0",
|
||||
"@codemirror/state": "^6.0.0",
|
||||
"@codemirror/view": "^6.17.0",
|
||||
"@lezer/common": "^1.0.0",
|
||||
"@lezer/javascript": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@codemirror/lang-json": {
|
||||
"version": "6.0.2",
|
||||
"resolved": "https://registry.npmmirror.com/@codemirror/lang-json/-/lang-json-6.0.2.tgz",
|
||||
"integrity": "sha512-x2OtO+AvwEHrEwR0FyyPtfDUiloG3rnVTSZV1W8UteaLL8/MajQd8DpvUb2YVzC+/T18aSDv0H9mu+xw0EStoQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@codemirror/language": "^6.0.0",
|
||||
"@lezer/json": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@codemirror/lang-markdown": {
|
||||
"version": "6.5.0",
|
||||
"resolved": "https://registry.npmmirror.com/@codemirror/lang-markdown/-/lang-markdown-6.5.0.tgz",
|
||||
"integrity": "sha512-0K40bZ35jpHya6FriukbgaleaqzBLZfOh7HuzqbMxBXkbYMJDxfF39c23xOgxFezR+3G+tR2/Mup+Xk865OMvw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@codemirror/autocomplete": "^6.7.1",
|
||||
"@codemirror/lang-html": "^6.0.0",
|
||||
"@codemirror/language": "^6.3.0",
|
||||
"@codemirror/state": "^6.0.0",
|
||||
"@codemirror/view": "^6.0.0",
|
||||
"@lezer/common": "^1.2.1",
|
||||
"@lezer/markdown": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@codemirror/lang-python": {
|
||||
"version": "6.2.1",
|
||||
"resolved": "https://registry.npmmirror.com/@codemirror/lang-python/-/lang-python-6.2.1.tgz",
|
||||
"integrity": "sha512-IRjC8RUBhn9mGR9ywecNhB51yePWCGgvHfY1lWN/Mrp3cKuHr0isDKia+9HnvhiWNnMpbGhWrkhuWOc09exRyw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@codemirror/autocomplete": "^6.3.2",
|
||||
"@codemirror/language": "^6.8.0",
|
||||
"@codemirror/state": "^6.0.0",
|
||||
"@lezer/common": "^1.2.1",
|
||||
"@lezer/python": "^1.1.4"
|
||||
}
|
||||
},
|
||||
"node_modules/@codemirror/lang-rust": {
|
||||
"version": "6.0.2",
|
||||
"resolved": "https://registry.npmmirror.com/@codemirror/lang-rust/-/lang-rust-6.0.2.tgz",
|
||||
"integrity": "sha512-EZaGjCUegtiU7kSMvOfEZpaCReowEf3yNidYu7+vfuGTm9ow4mthAparY5hisJqOHmJowVH3Upu+eJlUji6qqA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@codemirror/language": "^6.0.0",
|
||||
"@lezer/rust": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@codemirror/lang-sql": {
|
||||
"version": "6.10.0",
|
||||
"resolved": "https://registry.npmmirror.com/@codemirror/lang-sql/-/lang-sql-6.10.0.tgz",
|
||||
"integrity": "sha512-6ayPkEd/yRw0XKBx5uAiToSgGECo/GY2NoJIHXIIQh1EVwLuKoU8BP/qK0qH5NLXAbtJRLuT73hx7P9X34iO4w==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@codemirror/autocomplete": "^6.0.0",
|
||||
"@codemirror/language": "^6.0.0",
|
||||
"@codemirror/state": "^6.0.0",
|
||||
"@lezer/common": "^1.2.0",
|
||||
"@lezer/highlight": "^1.0.0",
|
||||
"@lezer/lr": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@codemirror/lang-xml": {
|
||||
"version": "6.1.0",
|
||||
"resolved": "https://registry.npmmirror.com/@codemirror/lang-xml/-/lang-xml-6.1.0.tgz",
|
||||
"integrity": "sha512-3z0blhicHLfwi2UgkZYRPioSgVTo9PV5GP5ducFH6FaHy0IAJRg+ixj5gTR1gnT/glAIC8xv4w2VL1LoZfs+Jg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@codemirror/autocomplete": "^6.0.0",
|
||||
"@codemirror/language": "^6.4.0",
|
||||
"@codemirror/state": "^6.0.0",
|
||||
"@codemirror/view": "^6.0.0",
|
||||
"@lezer/common": "^1.0.0",
|
||||
"@lezer/xml": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@codemirror/lang-yaml": {
|
||||
"version": "6.1.3",
|
||||
"resolved": "https://registry.npmmirror.com/@codemirror/lang-yaml/-/lang-yaml-6.1.3.tgz",
|
||||
"integrity": "sha512-AZ8DJBuXGVHybpBQhmZtgew5//4hv3tdkXnr3vDmOUMJRuB6vn/uuwtmTOTlqEaQFg3hQSVeA90NmvIQyUV6FQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@codemirror/autocomplete": "^6.0.0",
|
||||
"@codemirror/language": "^6.0.0",
|
||||
"@codemirror/state": "^6.0.0",
|
||||
"@lezer/common": "^1.2.0",
|
||||
"@lezer/highlight": "^1.2.0",
|
||||
"@lezer/lr": "^1.0.0",
|
||||
"@lezer/yaml": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@codemirror/language": {
|
||||
"version": "6.12.3",
|
||||
"resolved": "https://registry.npmmirror.com/@codemirror/language/-/language-6.12.3.tgz",
|
||||
"integrity": "sha512-QwCZW6Tt1siP37Jet9Tb02Zs81TQt6qQrZR2H+eGMcFsL1zMrk2/b9CLC7/9ieP1fjIUMgviLWMmgiHoJrj+ZA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@codemirror/state": "^6.0.0",
|
||||
"@codemirror/view": "^6.23.0",
|
||||
"@lezer/common": "^1.5.0",
|
||||
"@lezer/highlight": "^1.0.0",
|
||||
"@lezer/lr": "^1.0.0",
|
||||
"style-mod": "^4.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@codemirror/lint": {
|
||||
"version": "6.9.5",
|
||||
"resolved": "https://registry.npmmirror.com/@codemirror/lint/-/lint-6.9.5.tgz",
|
||||
"integrity": "sha512-GElsbU9G7QT9xXhpUg1zWGmftA/7jamh+7+ydKRuT0ORpWS3wOSP0yT1FOlIZa7mIJjpVPipErsyvVqB9cfTFA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@codemirror/state": "^6.0.0",
|
||||
"@codemirror/view": "^6.35.0",
|
||||
"crelt": "^1.0.5"
|
||||
}
|
||||
},
|
||||
"node_modules/@codemirror/search": {
|
||||
"version": "6.6.0",
|
||||
"resolved": "https://registry.npmmirror.com/@codemirror/search/-/search-6.6.0.tgz",
|
||||
"integrity": "sha512-koFuNXcDvyyotWcgOnZGmY7LZqEOXZaaxD/j6n18TCLx2/9HieZJ5H6hs1g8FiRxBD0DNfs0nXn17g872RmYdw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@codemirror/state": "^6.0.0",
|
||||
"@codemirror/view": "^6.37.0",
|
||||
"crelt": "^1.0.5"
|
||||
}
|
||||
},
|
||||
"node_modules/@codemirror/state": {
|
||||
"version": "6.6.0",
|
||||
"resolved": "https://registry.npmmirror.com/@codemirror/state/-/state-6.6.0.tgz",
|
||||
"integrity": "sha512-4nbvra5R5EtiCzr9BTHiTLc+MLXK2QGiAVYMyi8PkQd3SR+6ixar/Q/01Fa21TBIDOZXgeWV4WppsQolSreAPQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@marijn/find-cluster-break": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@codemirror/theme-one-dark": {
|
||||
"version": "6.1.3",
|
||||
"resolved": "https://registry.npmmirror.com/@codemirror/theme-one-dark/-/theme-one-dark-6.1.3.tgz",
|
||||
"integrity": "sha512-NzBdIvEJmx6fjeremiGp3t/okrLPYT0d9orIc7AFun8oZcRk58aejkqhv6spnz4MLAevrKNPMQYXEWMg4s+sKA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@codemirror/language": "^6.0.0",
|
||||
"@codemirror/state": "^6.0.0",
|
||||
"@codemirror/view": "^6.0.0",
|
||||
"@lezer/highlight": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@codemirror/view": {
|
||||
"version": "6.40.0",
|
||||
"resolved": "https://registry.npmmirror.com/@codemirror/view/-/view-6.40.0.tgz",
|
||||
"integrity": "sha512-WA0zdU7xfF10+5I3HhUUq3kqOx3KjqmtQ9lqZjfK7jtYk4G72YW9rezcSywpaUMCWOMlq+6E0pO1IWg1TNIhtg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@codemirror/state": "^6.6.0",
|
||||
"crelt": "^1.0.6",
|
||||
"style-mod": "^4.1.0",
|
||||
"w3c-keyname": "^2.2.4"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/aix-ppc64": {
|
||||
"version": "0.25.12",
|
||||
"resolved": "https://registry.npmmirror.com/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz",
|
||||
|
|
@ -514,6 +791,167 @@
|
|||
"integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@lezer/common": {
|
||||
"version": "1.5.1",
|
||||
"resolved": "https://registry.npmmirror.com/@lezer/common/-/common-1.5.1.tgz",
|
||||
"integrity": "sha512-6YRVG9vBkaY7p1IVxL4s44n5nUnaNnGM2/AckNgYOnxTG2kWh1vR8BMxPseWPjRNpb5VtXnMpeYAEAADoRV1Iw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@lezer/cpp": {
|
||||
"version": "1.1.5",
|
||||
"resolved": "https://registry.npmmirror.com/@lezer/cpp/-/cpp-1.1.5.tgz",
|
||||
"integrity": "sha512-DIhSXmYtJKLehrjzDFN+2cPt547ySQ41nA8yqcDf/GxMc+YM736xqltFkvADL2M0VebU5I+3+4ks2Vv+Kyq3Aw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@lezer/common": "^1.2.0",
|
||||
"@lezer/highlight": "^1.0.0",
|
||||
"@lezer/lr": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@lezer/css": {
|
||||
"version": "1.3.3",
|
||||
"resolved": "https://registry.npmmirror.com/@lezer/css/-/css-1.3.3.tgz",
|
||||
"integrity": "sha512-RzBo8r+/6QJeow7aPHIpGVIH59xTcJXp399820gZoMo9noQDRVpJLheIBUicYwKcsbOYoBRoLZlf2720dG/4Tg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@lezer/common": "^1.2.0",
|
||||
"@lezer/highlight": "^1.0.0",
|
||||
"@lezer/lr": "^1.3.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@lezer/go": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmmirror.com/@lezer/go/-/go-1.0.1.tgz",
|
||||
"integrity": "sha512-xToRsYxwsgJNHTgNdStpcvmbVuKxTapV0dM0wey1geMMRc9aggoVyKgzYp41D2/vVOx+Ii4hmE206kvxIXBVXQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@lezer/common": "^1.2.0",
|
||||
"@lezer/highlight": "^1.0.0",
|
||||
"@lezer/lr": "^1.3.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@lezer/highlight": {
|
||||
"version": "1.2.3",
|
||||
"resolved": "https://registry.npmmirror.com/@lezer/highlight/-/highlight-1.2.3.tgz",
|
||||
"integrity": "sha512-qXdH7UqTvGfdVBINrgKhDsVTJTxactNNxLk7+UMwZhU13lMHaOBlJe9Vqp907ya56Y3+ed2tlqzys7jDkTmW0g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@lezer/common": "^1.3.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@lezer/html": {
|
||||
"version": "1.3.13",
|
||||
"resolved": "https://registry.npmmirror.com/@lezer/html/-/html-1.3.13.tgz",
|
||||
"integrity": "sha512-oI7n6NJml729m7pjm9lvLvmXbdoMoi2f+1pwSDJkl9d68zGr7a9Btz8NdHTGQZtW2DA25ybeuv/SyDb9D5tseg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@lezer/common": "^1.2.0",
|
||||
"@lezer/highlight": "^1.0.0",
|
||||
"@lezer/lr": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@lezer/java": {
|
||||
"version": "1.1.3",
|
||||
"resolved": "https://registry.npmmirror.com/@lezer/java/-/java-1.1.3.tgz",
|
||||
"integrity": "sha512-yHquUfujwg6Yu4Fd1GNHCvidIvJwi/1Xu2DaKl/pfWIA2c1oXkVvawH3NyXhCaFx4OdlYBVX5wvz2f7Aoa/4Xw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@lezer/common": "^1.2.0",
|
||||
"@lezer/highlight": "^1.0.0",
|
||||
"@lezer/lr": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@lezer/javascript": {
|
||||
"version": "1.5.4",
|
||||
"resolved": "https://registry.npmmirror.com/@lezer/javascript/-/javascript-1.5.4.tgz",
|
||||
"integrity": "sha512-vvYx3MhWqeZtGPwDStM2dwgljd5smolYD2lR2UyFcHfxbBQebqx8yjmFmxtJ/E6nN6u1D9srOiVWm3Rb4tmcUA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@lezer/common": "^1.2.0",
|
||||
"@lezer/highlight": "^1.1.3",
|
||||
"@lezer/lr": "^1.3.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@lezer/json": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmmirror.com/@lezer/json/-/json-1.0.3.tgz",
|
||||
"integrity": "sha512-BP9KzdF9Y35PDpv04r0VeSTKDeox5vVr3efE7eBbx3r4s3oNLfunchejZhjArmeieBH+nVOpgIiBJpEAv8ilqQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@lezer/common": "^1.2.0",
|
||||
"@lezer/highlight": "^1.0.0",
|
||||
"@lezer/lr": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@lezer/lr": {
|
||||
"version": "1.4.8",
|
||||
"resolved": "https://registry.npmmirror.com/@lezer/lr/-/lr-1.4.8.tgz",
|
||||
"integrity": "sha512-bPWa0Pgx69ylNlMlPvBPryqeLYQjyJjqPx+Aupm5zydLIF3NE+6MMLT8Yi23Bd9cif9VS00aUebn+6fDIGBcDA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@lezer/common": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@lezer/markdown": {
|
||||
"version": "1.6.3",
|
||||
"resolved": "https://registry.npmmirror.com/@lezer/markdown/-/markdown-1.6.3.tgz",
|
||||
"integrity": "sha512-jpGm5Ps+XErS+xA4urw7ogEGkeZOahVQF21Z6oECF0sj+2liwZopd2+I8uH5I/vZsRuuze3OxBREIANLf6KKUw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@lezer/common": "^1.5.0",
|
||||
"@lezer/highlight": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@lezer/python": {
|
||||
"version": "1.1.18",
|
||||
"resolved": "https://registry.npmmirror.com/@lezer/python/-/python-1.1.18.tgz",
|
||||
"integrity": "sha512-31FiUrU7z9+d/ElGQLJFXl+dKOdx0jALlP3KEOsGTex8mvj+SoE1FgItcHWK/axkxCHGUSpqIHt6JAWfWu9Rhg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@lezer/common": "^1.2.0",
|
||||
"@lezer/highlight": "^1.0.0",
|
||||
"@lezer/lr": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@lezer/rust": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmmirror.com/@lezer/rust/-/rust-1.0.2.tgz",
|
||||
"integrity": "sha512-Lz5sIPBdF2FUXcWeCu1//ojFAZqzTQNRga0aYv6dYXqJqPfMdCAI0NzajWUd4Xijj1IKJLtjoXRPMvTKWBcqKg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@lezer/common": "^1.2.0",
|
||||
"@lezer/highlight": "^1.0.0",
|
||||
"@lezer/lr": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@lezer/xml": {
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmmirror.com/@lezer/xml/-/xml-1.0.6.tgz",
|
||||
"integrity": "sha512-CdDwirL0OEaStFue/66ZmFSeppuL6Dwjlk8qk153mSQwiSH/Dlri4GNymrNWnUmPl2Um7QfV1FO9KFUyX3Twww==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@lezer/common": "^1.2.0",
|
||||
"@lezer/highlight": "^1.0.0",
|
||||
"@lezer/lr": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@lezer/yaml": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmmirror.com/@lezer/yaml/-/yaml-1.0.4.tgz",
|
||||
"integrity": "sha512-2lrrHqxalACEbxIbsjhqGpSW8kWpUKuY6RHgnSAFZa6qK62wvnPxA8hGOwOoDbwHcOFs5M4o27mjGu+P7TvBmw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@lezer/common": "^1.2.0",
|
||||
"@lezer/highlight": "^1.0.0",
|
||||
"@lezer/lr": "^1.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@marijn/find-cluster-break": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmmirror.com/@marijn/find-cluster-break/-/find-cluster-break-1.0.2.tgz",
|
||||
"integrity": "sha512-l0h88YhZFyKdXIFNfSWpyjStDjGHwZ/U7iobcK1cQQD8sejsONdQtTVU+1wVN1PBw40PiiHB1vA5S7VTfQiP9g==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@rollup/rollup-android-arm-eabi": {
|
||||
"version": "4.60.0",
|
||||
"resolved": "https://registry.npmmirror.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.60.0.tgz",
|
||||
|
|
@ -992,6 +1430,21 @@
|
|||
"dev": true,
|
||||
"license": "Python-2.0"
|
||||
},
|
||||
"node_modules/codemirror": {
|
||||
"version": "6.0.2",
|
||||
"resolved": "https://registry.npmmirror.com/codemirror/-/codemirror-6.0.2.tgz",
|
||||
"integrity": "sha512-VhydHotNW5w1UGK0Qj96BwSk/Zqbp9WbnyK2W/eVMv4QyF41INRGpjUhFJY7/uDNuudSc33a/PKr4iDqRduvHw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@codemirror/autocomplete": "^6.0.0",
|
||||
"@codemirror/commands": "^6.0.0",
|
||||
"@codemirror/language": "^6.0.0",
|
||||
"@codemirror/lint": "^6.0.0",
|
||||
"@codemirror/search": "^6.0.0",
|
||||
"@codemirror/state": "^6.0.0",
|
||||
"@codemirror/view": "^6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/commander": {
|
||||
"version": "8.3.0",
|
||||
"resolved": "https://registry.npmmirror.com/commander/-/commander-8.3.0.tgz",
|
||||
|
|
@ -1001,6 +1454,12 @@
|
|||
"node": ">= 12"
|
||||
}
|
||||
},
|
||||
"node_modules/crelt": {
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmmirror.com/crelt/-/crelt-1.0.6.tgz",
|
||||
"integrity": "sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/csstype": {
|
||||
"version": "3.2.3",
|
||||
"resolved": "https://registry.npmmirror.com/csstype/-/csstype-3.2.3.tgz",
|
||||
|
|
@ -1287,6 +1746,12 @@
|
|||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/style-mod": {
|
||||
"version": "4.1.3",
|
||||
"resolved": "https://registry.npmmirror.com/style-mod/-/style-mod-4.1.3.tgz",
|
||||
"integrity": "sha512-i/n8VsZydrugj3Iuzll8+x/00GH2vnYsk1eomD8QiRrSAeW6ItbCQDtfXCeJHd0iwiNagqjQkvpvREEPtW3IoQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/tinyglobby": {
|
||||
"version": "0.2.15",
|
||||
"resolved": "https://registry.npmmirror.com/tinyglobby/-/tinyglobby-0.2.15.tgz",
|
||||
|
|
@ -1399,6 +1864,12 @@
|
|||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/w3c-keyname": {
|
||||
"version": "2.2.8",
|
||||
"resolved": "https://registry.npmmirror.com/w3c-keyname/-/w3c-keyname-2.2.8.tgz",
|
||||
"integrity": "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==",
|
||||
"license": "MIT"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,6 +9,21 @@
|
|||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"codemirror": "^6.0.1",
|
||||
"@codemirror/theme-one-dark": "^6.1.2",
|
||||
"@codemirror/lang-markdown": "^6.3.2",
|
||||
"@codemirror/lang-javascript": "^6.2.3",
|
||||
"@codemirror/lang-python": "^6.1.7",
|
||||
"@codemirror/lang-html": "^6.4.9",
|
||||
"@codemirror/lang-css": "^6.3.1",
|
||||
"@codemirror/lang-json": "^6.0.1",
|
||||
"@codemirror/lang-yaml": "^6.1.2",
|
||||
"@codemirror/lang-java": "^6.0.1",
|
||||
"@codemirror/lang-cpp": "^6.0.2",
|
||||
"@codemirror/lang-rust": "^6.0.1",
|
||||
"@codemirror/lang-go": "^6.0.1",
|
||||
"@codemirror/lang-sql": "^6.8.0",
|
||||
"@codemirror/lang-xml": "^6.1.0",
|
||||
"highlight.js": "^11.11.1",
|
||||
"katex": "^0.16.40",
|
||||
"marked": "^15.0.12",
|
||||
|
|
|
|||
|
|
@ -83,12 +83,7 @@
|
|||
<div class="create-modal">
|
||||
<div class="modal-header">
|
||||
<h3>创建项目</h3>
|
||||
<button class="btn-icon" @click="showCreateModal = false">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<line x1="18" y1="6" x2="6" y2="18"></line>
|
||||
<line x1="6" y1="6" x2="18" y2="18"></line>
|
||||
</svg>
|
||||
</button>
|
||||
<CloseButton @click="showCreateModal = false" />
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="form-group">
|
||||
|
|
@ -116,6 +111,7 @@ import { ref, shallowRef, computed, onMounted, defineAsyncComponent } from 'vue'
|
|||
import Sidebar from './components/Sidebar.vue'
|
||||
import ChatView from './components/ChatView.vue'
|
||||
import FileExplorer from './components/FileExplorer.vue'
|
||||
import CloseButton from './components/CloseButton.vue'
|
||||
|
||||
const SettingsPanel = defineAsyncComponent(() => import('./components/SettingsPanel.vue'))
|
||||
const StatsPanel = defineAsyncComponent(() => import('./components/StatsPanel.vue'))
|
||||
|
|
@ -181,13 +177,10 @@ const newProjectDesc = ref('')
|
|||
const creatingProject = ref(false)
|
||||
|
||||
function togglePanel(panel) {
|
||||
if (panel === 'settings') {
|
||||
showSettings.value = !showSettings.value
|
||||
if (showSettings.value) showStats.value = false
|
||||
} else {
|
||||
showStats.value = !showStats.value
|
||||
if (showStats.value) showSettings.value = false
|
||||
}
|
||||
const ref = panel === 'settings' ? showSettings : showStats
|
||||
const other = panel === 'settings' ? showStats : showSettings
|
||||
ref.value = !ref.value
|
||||
if (ref.value) other.value = false
|
||||
}
|
||||
|
||||
const currentConv = computed(() =>
|
||||
|
|
@ -629,31 +622,8 @@ onMounted(() => {
|
|||
font-size: 14px;
|
||||
}
|
||||
|
||||
.modal-overlay {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 100;
|
||||
}
|
||||
/* modal-overlay, modal-content, btn-icon, form-group, modal-footer, btn-secondary, btn-primary now in global.css */
|
||||
|
||||
.modal-content {
|
||||
border-radius: 16px;
|
||||
width: 90%;
|
||||
max-width: 520px;
|
||||
max-height: 80vh;
|
||||
overflow-y: auto;
|
||||
padding: 24px;
|
||||
background: color-mix(in srgb, var(--bg-primary) 75%, transparent);
|
||||
backdrop-filter: blur(40px);
|
||||
-webkit-backdrop-filter: blur(40px);
|
||||
border: 1px solid var(--border-medium);
|
||||
box-shadow: 0 25px 60px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
/* -- Create project modal -- */
|
||||
.create-modal {
|
||||
background: var(--bg-primary);
|
||||
border: 1px solid var(--border-medium);
|
||||
|
|
@ -662,104 +632,4 @@ onMounted(() => {
|
|||
max-width: 440px;
|
||||
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
.btn-icon {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
border: none;
|
||||
background: none;
|
||||
color: var(--text-tertiary);
|
||||
cursor: pointer;
|
||||
border-radius: 6px;
|
||||
transition: all 0.15s;
|
||||
}
|
||||
|
||||
.btn-icon:hover {
|
||||
background: var(--bg-hover);
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.form-group {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.form-group label {
|
||||
display: block;
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
color: var(--text-secondary);
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
|
||||
.form-group input,
|
||||
.form-group textarea {
|
||||
width: 100%;
|
||||
padding: 8px 12px;
|
||||
background: var(--bg-secondary);
|
||||
border: 1px solid var(--border-light);
|
||||
border-radius: 8px;
|
||||
color: var(--text-primary);
|
||||
font-size: 14px;
|
||||
outline: none;
|
||||
box-sizing: border-box;
|
||||
transition: border-color 0.2s;
|
||||
}
|
||||
|
||||
.form-group input:focus,
|
||||
.form-group textarea:focus {
|
||||
border-color: var(--accent-primary);
|
||||
}
|
||||
|
||||
.form-group textarea {
|
||||
resize: vertical;
|
||||
min-height: 60px;
|
||||
font-family: inherit;
|
||||
}
|
||||
|
||||
.modal-footer {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 8px;
|
||||
padding: 12px 20px;
|
||||
border-top: 1px solid var(--border-light);
|
||||
}
|
||||
|
||||
.btn-secondary {
|
||||
padding: 8px 16px;
|
||||
background: var(--bg-secondary);
|
||||
border: 1px solid var(--border-light);
|
||||
border-radius: 8px;
|
||||
color: var(--text-secondary);
|
||||
font-size: 13px;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.btn-secondary:hover {
|
||||
background: var(--bg-hover);
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
padding: 8px 16px;
|
||||
background: var(--accent-primary);
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
color: #fff;
|
||||
font-size: 13px;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.btn-primary:hover {
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
.btn-primary:disabled {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -260,12 +260,14 @@ watch(() => props.conversation?.id, () => {
|
|||
}
|
||||
|
||||
.thinking-badge {
|
||||
background: var(--success-bg);
|
||||
color: var(--success-color);
|
||||
background: rgba(245, 158, 11, 0.12);
|
||||
color: #d97706;
|
||||
}
|
||||
|
||||
|
||||
|
||||
[data-theme="dark"] .thinking-badge {
|
||||
background: rgba(245, 158, 11, 0.18);
|
||||
color: #fbbf24;
|
||||
}
|
||||
|
||||
.messages-container {
|
||||
flex: 1 1 auto;
|
||||
|
|
@ -309,7 +311,7 @@ watch(() => props.conversation?.id, () => {
|
|||
padding: 0 16px;
|
||||
}
|
||||
|
||||
/* .message-bubble, .avatar, .message-body now in global.css */
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,168 @@
|
|||
<template>
|
||||
<div ref="container" class="code-editor-wrap"></div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted, onUnmounted, watch } from 'vue'
|
||||
import { EditorView, keymap } from '@codemirror/view'
|
||||
import { EditorState, Compartment } from '@codemirror/state'
|
||||
import { basicSetup } from 'codemirror'
|
||||
import { indentWithTab } from '@codemirror/commands'
|
||||
import { oneDarkHighlightStyle } from '@codemirror/theme-one-dark'
|
||||
import { syntaxHighlighting } from '@codemirror/language'
|
||||
import { markdown } from '@codemirror/lang-markdown'
|
||||
import { javascript } from '@codemirror/lang-javascript'
|
||||
import { python } from '@codemirror/lang-python'
|
||||
import { html } from '@codemirror/lang-html'
|
||||
import { css } from '@codemirror/lang-css'
|
||||
import { json } from '@codemirror/lang-json'
|
||||
import { yaml } from '@codemirror/lang-yaml'
|
||||
import { java } from '@codemirror/lang-java'
|
||||
import { cpp } from '@codemirror/lang-cpp'
|
||||
import { rust } from '@codemirror/lang-rust'
|
||||
import { go } from '@codemirror/lang-go'
|
||||
import { sql } from '@codemirror/lang-sql'
|
||||
import { xml } from '@codemirror/lang-xml'
|
||||
|
||||
const EXT_MAP = {
|
||||
md: markdown, markdown: markdown, mdx: markdown,
|
||||
js: () => javascript(), jsx: () => javascript({ jsx: true }),
|
||||
ts: () => javascript({ typescript: true }),
|
||||
tsx: () => javascript({ jsx: true, typescript: true }),
|
||||
py: python, pyw: python,
|
||||
html: html, htm: html, vue: html, svelte: html,
|
||||
css: css, scss: css, less: css,
|
||||
json: json, jsonc: json,
|
||||
yaml: yaml, yml: yaml,
|
||||
java: java,
|
||||
c: cpp, h: cpp, cpp: cpp, cc: cpp, cxx: cpp, hpp: cpp,
|
||||
rs: rust,
|
||||
go: go,
|
||||
sql: sql,
|
||||
xml: xml, svg: xml, xsl: xml,
|
||||
}
|
||||
|
||||
function langForFile(filename) {
|
||||
if (!filename) return []
|
||||
const ext = filename.split('.').pop().toLowerCase()
|
||||
const fn = EXT_MAP[ext]
|
||||
if (!fn) return []
|
||||
const result = typeof fn === 'function' ? fn() : fn
|
||||
return Array.isArray(result) ? result : [result]
|
||||
}
|
||||
|
||||
const props = defineProps({
|
||||
modelValue: { type: String, default: '' },
|
||||
filename: { type: String, default: '' },
|
||||
dark: { type: Boolean, default: false },
|
||||
})
|
||||
|
||||
const emit = defineEmits(['update:modelValue', 'save'])
|
||||
|
||||
const container = ref(null)
|
||||
let view = null
|
||||
let skipEmit = false
|
||||
|
||||
const themeComp = new Compartment()
|
||||
const langComp = new Compartment()
|
||||
|
||||
// Custom dark theme matching app's --bg-primary / --bg-secondary
|
||||
const appDarkTheme = EditorView.theme({
|
||||
'&': { backgroundColor: '#1a1a1a', color: '#f0f0f0' },
|
||||
'.cm-gutters': {
|
||||
backgroundColor: '#141414',
|
||||
color: '#606060',
|
||||
border: 'none',
|
||||
borderRight: '1px solid rgba(255,255,255,0.06)',
|
||||
},
|
||||
'.cm-activeLineGutter': { backgroundColor: 'rgba(255,255,255,0.04)', color: '#a0a0a0' },
|
||||
'.cm-activeLine': { backgroundColor: 'rgba(255,255,255,0.03)' },
|
||||
'&.cm-focused .cm-cursor': { borderLeftColor: '#3b82f6', borderLeftWidth: '2px' },
|
||||
'&.cm-focused .cm-selectionBackground, .cm-selectionBackground, &.cm-focused .cm-content ::selection': {
|
||||
backgroundColor: 'rgba(59,130,246,0.25) !important',
|
||||
},
|
||||
'.cm-searchMatch': { backgroundColor: 'rgba(250,204,21,0.3)' },
|
||||
'&.cm-focused .cm-searchMatch': { backgroundColor: 'rgba(250,204,21,0.45)' },
|
||||
'.cm-panels': { backgroundColor: '#1a1a1a', borderColor: 'rgba(255,255,255,0.08)' },
|
||||
'.cm-panels input, .cm-panels button, .cm-panels select': {
|
||||
backgroundColor: '#141414',
|
||||
color: '#f0f0f0',
|
||||
border: '1px solid rgba(255,255,255,0.1)',
|
||||
},
|
||||
'.cm-tooltip': {
|
||||
backgroundColor: '#141414',
|
||||
border: '1px solid rgba(255,255,255,0.1)',
|
||||
color: '#f0f0f0',
|
||||
},
|
||||
'.cm-tooltip-autocomplete > ul > li': { padding: '4px 8px' },
|
||||
'.cm-tooltip-autocomplete > ul > li[aria-selected]': {
|
||||
backgroundColor: 'rgba(59,130,246,0.2)',
|
||||
color: '#f0f0f0',
|
||||
},
|
||||
}, { dark: true })
|
||||
|
||||
const editorTheme = EditorView.theme({
|
||||
'&': { height: '100%' },
|
||||
'.cm-scroller': { overflow: 'auto', height: '100%' },
|
||||
'.cm-gutters': { minWidth: '40px' },
|
||||
'.cm-content': {
|
||||
fontFamily: "'JetBrains Mono', 'Fira Code', monospace",
|
||||
fontSize: '14px',
|
||||
lineHeight: '1.7',
|
||||
},
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
view = new EditorView({
|
||||
state: EditorState.create({
|
||||
doc: props.modelValue,
|
||||
extensions: [
|
||||
basicSetup,
|
||||
editorTheme,
|
||||
keymap.of([
|
||||
indentWithTab,
|
||||
{ key: 'Mod-s', run: () => { emit('save'); return true } },
|
||||
]),
|
||||
EditorView.updateListener.of(update => {
|
||||
if (update.docChanged && !skipEmit) {
|
||||
emit('update:modelValue', update.state.doc.toString())
|
||||
}
|
||||
}),
|
||||
EditorView.lineWrapping,
|
||||
themeComp.of(props.dark ? [appDarkTheme, syntaxHighlighting(oneDarkHighlightStyle)] : []),
|
||||
langComp.of(langForFile(props.filename)),
|
||||
],
|
||||
}),
|
||||
parent: container.value,
|
||||
})
|
||||
})
|
||||
|
||||
watch(() => props.modelValue, val => {
|
||||
if (!view || val === view.state.doc.toString()) return
|
||||
skipEmit = true
|
||||
view.dispatch({ changes: { from: 0, to: view.state.doc.length, insert: val } })
|
||||
skipEmit = false
|
||||
})
|
||||
|
||||
watch(() => props.dark, isDark => {
|
||||
view?.dispatch({
|
||||
effects: themeComp.reconfigure(
|
||||
isDark ? [appDarkTheme, syntaxHighlighting(oneDarkHighlightStyle)] : []
|
||||
),
|
||||
})
|
||||
})
|
||||
|
||||
watch(() => props.filename, () => {
|
||||
view?.dispatch({ effects: langComp.reconfigure(langForFile(props.filename)) })
|
||||
})
|
||||
|
||||
onUnmounted(() => view?.destroy())
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.code-editor-wrap {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -54,9 +54,9 @@
|
|||
<div v-if="activeFile" class="file-viewer">
|
||||
<div class="viewer-header">
|
||||
<div class="viewer-breadcrumb">
|
||||
<span v-for="(seg, i) in activeFile.split('/')" :key="i" class="breadcrumb-seg">
|
||||
<span v-for="(seg, i) in breadcrumbSegments" :key="i" class="breadcrumb-seg">
|
||||
{{ seg }}
|
||||
<span v-if="i < activeFile.split('/').length - 1" class="breadcrumb-sep">/</span>
|
||||
<span v-if="i < breadcrumbSegments.length - 1" class="breadcrumb-sep">/</span>
|
||||
</span>
|
||||
</div>
|
||||
<div class="viewer-actions">
|
||||
|
|
@ -93,17 +93,14 @@
|
|||
<span>{{ fileError }}</span>
|
||||
</div>
|
||||
|
||||
<!-- Text / code editor (default mode) -->
|
||||
<div v-else-if="fileType !== 'image'" class="editor-container">
|
||||
<div class="editor-highlight" aria-hidden="true" v-html="editorHighlighted"></div>
|
||||
<textarea
|
||||
ref="editorRef"
|
||||
<!-- Text / code editor (all non-image files including .md) -->
|
||||
<div v-else-if="fileType !== 'image'" class="code-pane">
|
||||
<CodeEditor
|
||||
v-model="editContent"
|
||||
class="file-editor"
|
||||
spellcheck="false"
|
||||
@keydown="onEditorKeydown"
|
||||
@scroll="syncScroll"
|
||||
></textarea>
|
||||
:filename="activeFile"
|
||||
:dark="isDark"
|
||||
@save="saveFile"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Image viewer -->
|
||||
|
|
@ -130,19 +127,19 @@
|
|||
import { ref, computed, watch, onMounted, onUnmounted } from 'vue'
|
||||
import { projectApi } from '../api'
|
||||
import FileTreeItem from './FileTreeItem.vue'
|
||||
import { renderMarkdown } from '../utils/markdown'
|
||||
import CodeEditor from './CodeEditor.vue'
|
||||
import { normalizeFileTree } from '../utils/fileTree'
|
||||
import { useTheme } from '../composables/useTheme'
|
||||
|
||||
const IMAGE_EXTS = new Set(['png', 'jpg', 'jpeg', 'gif', 'bmp', 'webp', 'svg', 'ico'])
|
||||
|
||||
// Treat all text/code files as markdown (code blocks in ``` get auto-highlighted)
|
||||
// For pure code files without markdown, wrap in code fence
|
||||
|
||||
const props = defineProps({
|
||||
projectId: { type: String, required: true },
|
||||
projectName: { type: String, default: '' },
|
||||
})
|
||||
|
||||
const { isDark } = useTheme()
|
||||
|
||||
// -- Tree state --
|
||||
const treeItems = ref([])
|
||||
const loadingTree = ref(false)
|
||||
|
|
@ -153,13 +150,14 @@ const fileError = ref('')
|
|||
const loadingFile = ref(false)
|
||||
const editContent = ref('')
|
||||
const saving = ref(false)
|
||||
const editorRef = ref(null)
|
||||
const imageUrl = ref('')
|
||||
|
||||
// -- File type detection --
|
||||
const isMarkdownFile = computed(() => {
|
||||
return ['md', 'markdown', 'mdx'].includes(fileExt.value)
|
||||
})
|
||||
function releaseImageUrl() {
|
||||
if (imageUrl.value) {
|
||||
URL.revokeObjectURL(imageUrl.value)
|
||||
imageUrl.value = ''
|
||||
}
|
||||
}
|
||||
|
||||
const fileExt = computed(() => {
|
||||
if (!activeFile.value) return ''
|
||||
|
|
@ -167,28 +165,16 @@ const fileExt = computed(() => {
|
|||
return parts.length > 1 ? parts.pop().toLowerCase() : ''
|
||||
})
|
||||
|
||||
const breadcrumbSegments = computed(() => {
|
||||
if (!activeFile.value) return []
|
||||
return activeFile.value.split('/')
|
||||
})
|
||||
|
||||
const fileType = computed(() => {
|
||||
if (IMAGE_EXTS.has(fileExt.value)) return 'image'
|
||||
return 'text'
|
||||
})
|
||||
|
||||
// -- Content rendering --
|
||||
const editorHighlighted = computed(() => {
|
||||
if (!editContent.value) return ''
|
||||
if (isMarkdownFile.value) return renderMarkdown(editContent.value)
|
||||
const lang = fileExt.value || ''
|
||||
return renderMarkdown('```' + lang + '\n' + editContent.value + '\n```')
|
||||
})
|
||||
|
||||
function syncScroll() {
|
||||
const ta = editorRef.value
|
||||
const pre = ta?.previousElementSibling
|
||||
if (pre) {
|
||||
pre.scrollTop = ta.scrollTop
|
||||
pre.scrollLeft = ta.scrollLeft
|
||||
}
|
||||
}
|
||||
|
||||
async function loadTree(path = '') {
|
||||
loadingTree.value = true
|
||||
try {
|
||||
|
|
@ -205,7 +191,7 @@ async function openFile(filepath) {
|
|||
activeFile.value = filepath
|
||||
fileError.value = ''
|
||||
editContent.value = ''
|
||||
imageUrl.value = ''
|
||||
releaseImageUrl()
|
||||
loadingFile.value = true
|
||||
|
||||
const ext = filepath.split('.').pop().toLowerCase()
|
||||
|
|
@ -281,13 +267,6 @@ async function createNewFolder() {
|
|||
}
|
||||
}
|
||||
|
||||
function onEditorKeydown(e) {
|
||||
if (e.key === 's' && (e.ctrlKey || e.metaKey)) {
|
||||
e.preventDefault()
|
||||
saveFile()
|
||||
}
|
||||
}
|
||||
|
||||
// Ctrl+S global shortcut
|
||||
function onGlobalKeydown(e) {
|
||||
if (e.key === 's' && (e.ctrlKey || e.metaKey) && activeFile.value) {
|
||||
|
|
@ -299,7 +278,7 @@ function onGlobalKeydown(e) {
|
|||
watch(() => props.projectId, () => {
|
||||
activeFile.value = null
|
||||
editContent.value = ''
|
||||
imageUrl.value = ''
|
||||
releaseImageUrl()
|
||||
loadTree()
|
||||
})
|
||||
|
||||
|
|
@ -310,6 +289,7 @@ onMounted(() => {
|
|||
|
||||
onUnmounted(() => {
|
||||
document.removeEventListener('keydown', onGlobalKeydown)
|
||||
releaseImageUrl()
|
||||
})
|
||||
</script>
|
||||
|
||||
|
|
@ -459,6 +439,7 @@ onUnmounted(() => {
|
|||
display: flex;
|
||||
gap: 2px;
|
||||
flex-shrink: 0;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.viewer-loading {
|
||||
|
|
@ -480,31 +461,6 @@ onUnmounted(() => {
|
|||
font-size: 13px;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
.file-editor {
|
||||
flex: 1;
|
||||
resize: none;
|
||||
border: none;
|
||||
outline: none;
|
||||
color: var(--text-primary);
|
||||
background: var(--bg-code);
|
||||
tab-size: 4;
|
||||
white-space: pre;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.file-editor::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
}
|
||||
|
||||
.file-editor::-webkit-scrollbar-thumb {
|
||||
background: var(--scrollbar-thumb);
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.viewer-placeholder {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
|
|
@ -516,60 +472,12 @@ onUnmounted(() => {
|
|||
font-size: 13px;
|
||||
}
|
||||
|
||||
/* -- Editor (highlighted textarea overlay) -- */
|
||||
.editor-container {
|
||||
/* -- Code pane -- */
|
||||
.code-pane {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.editor-highlight {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
margin: 0;
|
||||
padding: 20px 24px;
|
||||
overflow: auto;
|
||||
pointer-events: none;
|
||||
color: var(--text-primary);
|
||||
background: transparent;
|
||||
font-family: 'JetBrains Mono', 'Fira Code', monospace;
|
||||
font-size: 14px;
|
||||
line-height: 1.7;
|
||||
white-space: pre;
|
||||
tab-size: 4;
|
||||
}
|
||||
|
||||
/* Strip wrapper styles from markdown-rendered <pre><code> so text aligns with textarea */
|
||||
.editor-highlight :deep(pre),
|
||||
.editor-highlight :deep(code) {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
background: transparent !important;
|
||||
border: none;
|
||||
border-radius: 0;
|
||||
font-size: inherit;
|
||||
font-family: inherit;
|
||||
line-height: inherit;
|
||||
white-space: inherit;
|
||||
tab-size: inherit;
|
||||
word-wrap: normal;
|
||||
}
|
||||
|
||||
.editor-container .file-editor {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
flex: 1;
|
||||
border: none;
|
||||
font-family: 'JetBrains Mono', 'Fira Code', monospace;
|
||||
font-size: 14px;
|
||||
line-height: 1.7;
|
||||
padding: 20px 24px;
|
||||
color: transparent;
|
||||
caret-color: var(--text-primary);
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
/* -- Image viewer -- */
|
||||
.image-viewer {
|
||||
flex: 1;
|
||||
|
|
|
|||
|
|
@ -161,10 +161,6 @@ async function onClick() {
|
|||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.tree-children {
|
||||
/* no extra indent, tree-item handles it via tree-indent */
|
||||
}
|
||||
|
||||
.tree-loading {
|
||||
padding: 4px 0 4px 40px;
|
||||
color: var(--text-tertiary);
|
||||
|
|
|
|||
|
|
@ -92,8 +92,6 @@ function copyContent() {
|
|||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* .message-bubble, .avatar, .message-body now in global.css */
|
||||
|
||||
.attachments-list {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
|
|
@ -115,8 +113,8 @@ function copyContent() {
|
|||
}
|
||||
|
||||
.attachment-icon {
|
||||
background: rgba(139, 92, 246, 0.15);
|
||||
color: #8b5cf6;
|
||||
background: var(--attachment-bg);
|
||||
color: var(--attachment-color);
|
||||
padding: 2px 6px;
|
||||
border-radius: 4px;
|
||||
font-size: 11px;
|
||||
|
|
|
|||
|
|
@ -306,8 +306,8 @@ textarea::placeholder {
|
|||
height: 36px;
|
||||
border-radius: 8px;
|
||||
border: none;
|
||||
background: rgba(139, 92, 246, 0.12);
|
||||
color: #8b5cf6;
|
||||
background: var(--attachment-bg);
|
||||
color: var(--attachment-color);
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
|
@ -322,23 +322,23 @@ textarea::placeholder {
|
|||
}
|
||||
|
||||
.btn-tool.active {
|
||||
background: var(--success-bg);
|
||||
color: var(--success-color);
|
||||
background: var(--tool-bg);
|
||||
color: var(--tool-color);
|
||||
}
|
||||
|
||||
.btn-tool.active:hover:not(:disabled) {
|
||||
background: var(--success-color);
|
||||
background: var(--tool-color);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-upload:hover:not(:disabled) {
|
||||
background: #8b5cf6;
|
||||
background: var(--attachment-color);
|
||||
color: white;
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
.btn-upload:active:not(:disabled) {
|
||||
background: #7c3aed;
|
||||
background: var(--attachment-color-hover);
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -236,7 +236,7 @@ watch(() => props.streamingContent?.length, () => {
|
|||
50% { opacity: 1; }
|
||||
}
|
||||
|
||||
/* Thinking and tool call step headers */
|
||||
/* Step header (shared by thinking and tool_call) */
|
||||
.thinking .step-header,
|
||||
.tool_call .step-header {
|
||||
display: flex;
|
||||
|
|
@ -261,7 +261,7 @@ watch(() => props.streamingContent?.length, () => {
|
|||
}
|
||||
|
||||
.tool_call .step-header svg:first-child {
|
||||
color: #a855f7;
|
||||
color: var(--tool-color);
|
||||
}
|
||||
|
||||
.step-label {
|
||||
|
|
@ -317,7 +317,7 @@ watch(() => props.streamingContent?.length, () => {
|
|||
.loading-dots {
|
||||
font-size: 16px;
|
||||
font-weight: 700;
|
||||
color: var(--accent-primary);
|
||||
color: var(--tool-color);
|
||||
animation: pulse 1s ease-in-out infinite;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -217,14 +217,11 @@
|
|||
<script setup>
|
||||
import { ref, computed, onMounted } from 'vue'
|
||||
import { statsApi } from '../api'
|
||||
import { useTheme } from '../composables/useTheme'
|
||||
import { formatNumber } from '../utils/format'
|
||||
import CloseButton from './CloseButton.vue'
|
||||
|
||||
defineEmits(['close'])
|
||||
|
||||
const { isDark } = useTheme()
|
||||
|
||||
const periods = [
|
||||
{ value: 'daily', label: '今日' },
|
||||
{ value: 'weekly', label: '本周' },
|
||||
|
|
@ -236,7 +233,9 @@ const stats = ref(null)
|
|||
const loading = ref(false)
|
||||
const hoveredPoint = ref(null)
|
||||
|
||||
const accentColor = computed(() => isDark.value ? '#60a5fa' : '#2563eb')
|
||||
const accentColor = computed(() => {
|
||||
return getComputedStyle(document.documentElement).getPropertyValue('--accent-primary').trim() || '#2563eb'
|
||||
})
|
||||
|
||||
const chartWidth = 320
|
||||
const chartHeight = 140
|
||||
|
|
|
|||
|
|
@ -22,6 +22,16 @@
|
|||
--accent-primary-light: rgba(37, 99, 235, 0.08);
|
||||
--accent-primary-medium: rgba(37, 99, 235, 0.15);
|
||||
|
||||
--tool-color: #5478FF;
|
||||
--tool-color-hover: #3d5ce0;
|
||||
--tool-bg: rgba(84, 120, 255, 0.18);
|
||||
--tool-bg-hover: rgba(84, 120, 255, 0.28);
|
||||
--tool-border: rgba(84, 120, 255, 0.22);
|
||||
|
||||
--attachment-color: #ca8a04;
|
||||
--attachment-color-hover: #a16207;
|
||||
--attachment-bg: rgba(202, 138, 4, 0.15);
|
||||
|
||||
--success-color: #059669;
|
||||
--success-bg: rgba(16, 185, 129, 0.1);
|
||||
--danger-color: #ef4444;
|
||||
|
|
@ -57,6 +67,16 @@
|
|||
--accent-primary-light: rgba(59, 130, 246, 0.15);
|
||||
--accent-primary-medium: rgba(59, 130, 246, 0.25);
|
||||
|
||||
--tool-color: #5478FF;
|
||||
--tool-color-hover: #7a96ff;
|
||||
--tool-bg: rgba(84, 120, 255, 0.28);
|
||||
--tool-bg-hover: rgba(84, 120, 255, 0.40);
|
||||
--tool-border: rgba(84, 120, 255, 0.32);
|
||||
|
||||
--attachment-color: #facc15;
|
||||
--attachment-color-hover: #fde047;
|
||||
--attachment-bg: rgba(250, 204, 21, 0.22);
|
||||
|
||||
--success-color: #34d399;
|
||||
--success-bg: rgba(52, 211, 153, 0.15);
|
||||
--danger-color: #f87171;
|
||||
|
|
@ -215,8 +235,6 @@ body {
|
|||
overflow-x: auto;
|
||||
}
|
||||
|
||||
/* (merged above with .md-content pre selectors) */
|
||||
|
||||
.code-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
|
@ -417,6 +435,20 @@ input[type="range"]::-moz-range-thumb {
|
|||
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
.modal-content {
|
||||
border-radius: 16px;
|
||||
width: 90%;
|
||||
max-width: 520px;
|
||||
max-height: 80vh;
|
||||
overflow-y: auto;
|
||||
padding: 24px;
|
||||
background: color-mix(in srgb, var(--bg-primary) 75%, transparent);
|
||||
backdrop-filter: blur(40px);
|
||||
-webkit-backdrop-filter: blur(40px);
|
||||
border: 1px solid var(--border-medium);
|
||||
box-shadow: 0 25px 60px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.modal-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
|
@ -665,6 +697,7 @@ input[type="range"]::-moz-range-thumb {
|
|||
padding-right: 40px;
|
||||
}
|
||||
|
||||
|
||||
.form-group select:hover {
|
||||
border-color: var(--accent-primary);
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue