Compare commits
59 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
629fd0d586 | ||
|
9adffd981f | ||
|
dd0e01a114 | ||
|
9c0da9915c | ||
|
26837559a5 | ||
|
74ba466141 | ||
|
d3a0b05910 | ||
|
f6bd08da49 | ||
|
901bc13526 | ||
|
c937694d8b | ||
|
635e792e22 | ||
|
121dfabbd1 | ||
|
b47669f3e4 | ||
|
6f386709e2 | ||
|
2b4885ae2d | ||
|
6bb9247c6c | ||
|
f9763778fa | ||
|
cb6c3c225b | ||
|
ecb90d9035 | ||
|
e21c52cb22 | ||
|
a9d1f1a94f | ||
|
50af75d347 | ||
|
e0e41d7e21 | ||
|
e640b1254d | ||
|
6fb71b01f0 | ||
|
37a11fbb8b | ||
|
165140162a | ||
|
e9fcbd36d4 | ||
|
17020e8755 | ||
|
efbf2a3eff | ||
|
df0bf51bdf | ||
|
48404fb316 | ||
|
30aa0faaa1 | ||
|
8d6a9b39bb | ||
|
1104fb2733 | ||
|
ef0e315bdc | ||
|
3fb66b4c36 | ||
|
3cbf4454da | ||
|
6a597da44f | ||
|
dba196d777 | ||
|
c5fa739a95 | ||
|
70d1f40333 | ||
|
2866bcc7af | ||
|
2a57034c9d | ||
|
79a03ab6fc | ||
|
50f9e4354f | ||
|
8f27ca2e4e | ||
|
ce333714b7 | ||
|
7b8879a7a8 | ||
|
c50bb49b37 | ||
|
970ffdac15 | ||
|
da162be01d | ||
|
6d79d42925 | ||
|
f617a05483 | ||
|
4c5d5cfe99 | ||
|
51188b1428 | ||
|
a56e46a98d | ||
|
e8471f1802 | ||
|
691575e449 |
2
.gitignore
vendored
2
.gitignore
vendored
@ -2,7 +2,7 @@
|
|||||||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||||
# settings
|
# settings
|
||||||
.vscode
|
.vscode
|
||||||
|
video.mp4
|
||||||
# dependencies
|
# dependencies
|
||||||
/node_modules
|
/node_modules
|
||||||
/.pnp
|
/.pnp
|
||||||
|
@ -10,6 +10,8 @@ Page Assist supports Chromium-based browsers like Chrome, Brave, and Edge, as we
|
|||||||
|
|
||||||
[](https://chrome.google.com/webstore/detail/page-assist/jfgfiigpkhlkbnfnbobbkinehhfdhndo)
|
[](https://chrome.google.com/webstore/detail/page-assist/jfgfiigpkhlkbnfnbobbkinehhfdhndo)
|
||||||
[](https://addons.mozilla.org/en-US/firefox/addon/page-assist/)
|
[](https://addons.mozilla.org/en-US/firefox/addon/page-assist/)
|
||||||
|
[](https://microsoftedge.microsoft.com/addons/detail/page-assist-a-web-ui-fo/ogkogooadflifpmmidmhjedogicnhooa)
|
||||||
|
|
||||||
|
|
||||||
Checkout the Demo (v1.0.0):
|
Checkout the Demo (v1.0.0):
|
||||||
|
|
||||||
@ -47,7 +49,9 @@ cd page-assist
|
|||||||
2. Install the dependencies
|
2. Install the dependencies
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
export PATH="/Users/huaqiancai/.bun/bin/:$PATH"
|
||||||
bun install
|
bun install
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
3. Build the extension (by default it will build for Chrome)
|
3. Build the extension (by default it will build for Chrome)
|
||||||
|
BIN
operation/1.png
Normal file
BIN
operation/1.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 659 KiB |
BIN
operation/2.png
Normal file
BIN
operation/2.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 763 KiB |
BIN
operation/3.png
Normal file
BIN
operation/3.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 941 KiB |
BIN
operation/4.png
Normal file
BIN
operation/4.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 610 KiB |
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "pageassist",
|
"name": "pageassist",
|
||||||
"displayName": "Page Assist - A Web UI for Local AI Models",
|
"displayName": "IoD Bot - A Web UI for Local AI Models",
|
||||||
"version": "1.0.9",
|
"version": "1.0.9",
|
||||||
"description": "Use your locally running AI models to assist you in your web browsing.",
|
"description": "Use your locally running AI models to assist you in your web browsing.",
|
||||||
"author": "n4ze3m",
|
"author": "n4ze3m",
|
||||||
@ -35,6 +35,7 @@
|
|||||||
"cheerio": "^1.0.0-rc.12",
|
"cheerio": "^1.0.0-rc.12",
|
||||||
"d3-dsv": "2",
|
"d3-dsv": "2",
|
||||||
"dayjs": "^1.11.10",
|
"dayjs": "^1.11.10",
|
||||||
|
"framer-motion": "^12.23.12",
|
||||||
"html-to-text": "^9.0.5",
|
"html-to-text": "^9.0.5",
|
||||||
"i18next": "^23.10.1",
|
"i18next": "^23.10.1",
|
||||||
"i18next-browser-languagedetector": "^7.2.0",
|
"i18next-browser-languagedetector": "^7.2.0",
|
||||||
@ -47,6 +48,7 @@
|
|||||||
"property-information": "^6.4.1",
|
"property-information": "^6.4.1",
|
||||||
"pubsub-js": "^1.9.4",
|
"pubsub-js": "^1.9.4",
|
||||||
"react": "18.2.0",
|
"react": "18.2.0",
|
||||||
|
"react-countup": "^6.5.3",
|
||||||
"react-dom": "18.2.0",
|
"react-dom": "18.2.0",
|
||||||
"react-i18next": "^14.1.0",
|
"react-i18next": "^14.1.0",
|
||||||
"react-icons": "^5.2.1",
|
"react-icons": "^5.2.1",
|
||||||
@ -58,6 +60,8 @@
|
|||||||
"rehype-mathjax": "4.0.3",
|
"rehype-mathjax": "4.0.3",
|
||||||
"remark-gfm": "3.0.1",
|
"remark-gfm": "3.0.1",
|
||||||
"remark-math": "5.1.1",
|
"remark-math": "5.1.1",
|
||||||
|
"segmentit": "^2.0.3",
|
||||||
|
"styled-components": "^6.1.19",
|
||||||
"tesseract.js": "^5.1.1",
|
"tesseract.js": "^5.1.1",
|
||||||
"turndown": "^7.1.3",
|
"turndown": "^7.1.3",
|
||||||
"unist-util-visit": "^5.0.0",
|
"unist-util-visit": "^5.0.0",
|
||||||
@ -75,6 +79,7 @@
|
|||||||
"@types/react-dom": "18.2.18",
|
"@types/react-dom": "18.2.18",
|
||||||
"@types/react-speech-recognition": "^3.9.5",
|
"@types/react-speech-recognition": "^3.9.5",
|
||||||
"@types/react-syntax-highlighter": "^15.5.11",
|
"@types/react-syntax-highlighter": "^15.5.11",
|
||||||
|
"@types/styled-components": "^5.1.34",
|
||||||
"@types/turndown": "^5.0.4",
|
"@types/turndown": "^5.0.4",
|
||||||
"autoprefixer": "^10.4.17",
|
"autoprefixer": "^10.4.17",
|
||||||
"cross-env": "^7.0.3",
|
"cross-env": "^7.0.3",
|
||||||
|
@ -109,7 +109,7 @@
|
|||||||
"translate": "ترجمة",
|
"translate": "ترجمة",
|
||||||
"custom": "مخصص"
|
"custom": "مخصص"
|
||||||
},
|
},
|
||||||
"citations": "الاقتباسات",
|
"webCitations": "الاقتباسات",
|
||||||
"segmented": {
|
"segmented": {
|
||||||
"ollama": "نماذج Ollama",
|
"ollama": "نماذج Ollama",
|
||||||
"custom": "نماذج مخصصة"
|
"custom": "نماذج مخصصة"
|
||||||
|
@ -106,7 +106,7 @@
|
|||||||
"translate": "Oversæt",
|
"translate": "Oversæt",
|
||||||
"custom": "Brugerdefineret"
|
"custom": "Brugerdefineret"
|
||||||
},
|
},
|
||||||
"citations": "Citater",
|
"webCitations": "Citater",
|
||||||
"downloadCode": "Download Kode",
|
"downloadCode": "Download Kode",
|
||||||
"date": {
|
"date": {
|
||||||
"pinned": "Fastgjort",
|
"pinned": "Fastgjort",
|
||||||
|
@ -106,7 +106,7 @@
|
|||||||
"translate": "Übersetzen",
|
"translate": "Übersetzen",
|
||||||
"custom": "Benutzerdefiniert"
|
"custom": "Benutzerdefiniert"
|
||||||
},
|
},
|
||||||
"citations": "Zitate",
|
"webCitations": "Zitate",
|
||||||
"downloadCode": "Code herunterladen",
|
"downloadCode": "Code herunterladen",
|
||||||
"date": {
|
"date": {
|
||||||
"pinned": "Angepinnt",
|
"pinned": "Angepinnt",
|
||||||
|
@ -39,6 +39,7 @@
|
|||||||
},
|
},
|
||||||
"copyToClipboard": "Copy to clipboard",
|
"copyToClipboard": "Copy to clipboard",
|
||||||
"webSearch": "Searching the web",
|
"webSearch": "Searching the web",
|
||||||
|
"iodSearch": "Searching the Internet of Data",
|
||||||
"regenerate": "Regenerate",
|
"regenerate": "Regenerate",
|
||||||
"edit": "Edit",
|
"edit": "Edit",
|
||||||
"delete": "Delete",
|
"delete": "Delete",
|
||||||
@ -136,7 +137,8 @@
|
|||||||
"translate": "Translate",
|
"translate": "Translate",
|
||||||
"custom": "Custom"
|
"custom": "Custom"
|
||||||
},
|
},
|
||||||
"citations": "Citations",
|
"webCitations": "Web Citations",
|
||||||
|
"iodCitations": "Internet of Data Citations",
|
||||||
"segmented": {
|
"segmented": {
|
||||||
"ollama": "Ollama Models",
|
"ollama": "Ollama Models",
|
||||||
"custom": "Custom Models"
|
"custom": "Custom Models"
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
"selectAPrompt": "Select a Prompt",
|
"selectAPrompt": "Select a Prompt",
|
||||||
"githubRepository": "GitHub Repository",
|
"githubRepository": "GitHub Repository",
|
||||||
"settings": "Settings",
|
"settings": "Settings",
|
||||||
|
"metering": "Metering",
|
||||||
"sidebarTitle": "Chat History",
|
"sidebarTitle": "Chat History",
|
||||||
"error": "Error",
|
"error": "Error",
|
||||||
"somethingWentWrong": "Something went wrong",
|
"somethingWentWrong": "Something went wrong",
|
||||||
|
@ -20,6 +20,7 @@
|
|||||||
},
|
},
|
||||||
"tooltip": {
|
"tooltip": {
|
||||||
"searchInternet": "Search Internet",
|
"searchInternet": "Search Internet",
|
||||||
|
"searchIod": "Search Internet of Data",
|
||||||
"speechToText": "Speech to Text",
|
"speechToText": "Speech to Text",
|
||||||
"uploadImage": "Upload Image",
|
"uploadImage": "Upload Image",
|
||||||
"stopStreaming": "Stop Streaming",
|
"stopStreaming": "Stop Streaming",
|
||||||
|
@ -105,7 +105,7 @@
|
|||||||
"rephrase": "Reformular",
|
"rephrase": "Reformular",
|
||||||
"translate": "Traducir"
|
"translate": "Traducir"
|
||||||
},
|
},
|
||||||
"citations": "Citas",
|
"webCitations": "Citas",
|
||||||
"downloadCode": "Descargar Código",
|
"downloadCode": "Descargar Código",
|
||||||
"date": {
|
"date": {
|
||||||
"pinned": "Fijado",
|
"pinned": "Fijado",
|
||||||
|
@ -99,7 +99,7 @@
|
|||||||
},
|
},
|
||||||
"advanced": "تنظیمات بیشتر مدل"
|
"advanced": "تنظیمات بیشتر مدل"
|
||||||
},
|
},
|
||||||
"citations": "منابع",
|
"webCitations": "منابع",
|
||||||
"downloadCode": "دانلود کد",
|
"downloadCode": "دانلود کد",
|
||||||
"date": {
|
"date": {
|
||||||
"pinned": "پین شده",
|
"pinned": "پین شده",
|
||||||
|
@ -105,7 +105,7 @@
|
|||||||
"rephrase": "Reformuler",
|
"rephrase": "Reformuler",
|
||||||
"translate": "Traduire"
|
"translate": "Traduire"
|
||||||
},
|
},
|
||||||
"citations": "Citations",
|
"webCitations": "Citations",
|
||||||
"downloadCode": "Télécharger le code",
|
"downloadCode": "Télécharger le code",
|
||||||
"date": {
|
"date": {
|
||||||
"pinned": "Épinglé",
|
"pinned": "Épinglé",
|
||||||
|
@ -105,7 +105,7 @@
|
|||||||
"rephrase": "Riformulare",
|
"rephrase": "Riformulare",
|
||||||
"translate": "Tradurre"
|
"translate": "Tradurre"
|
||||||
},
|
},
|
||||||
"citations": "Citazioni",
|
"webCitations": "Citazioni",
|
||||||
"downloadCode": "Scarica Codice",
|
"downloadCode": "Scarica Codice",
|
||||||
"date": {
|
"date": {
|
||||||
"pinned": "Fissato",
|
"pinned": "Fissato",
|
||||||
|
@ -105,8 +105,7 @@
|
|||||||
"rephrase": "言い換え",
|
"rephrase": "言い換え",
|
||||||
"translate": "翻訳"
|
"translate": "翻訳"
|
||||||
},
|
},
|
||||||
"citations": "万维网引用",
|
"webCitations": "引用",
|
||||||
"iodcitations":"数联网引用",
|
|
||||||
"downloadCode": "コードをダウンロード",
|
"downloadCode": "コードをダウンロード",
|
||||||
"date": {
|
"date": {
|
||||||
"pinned": "固定",
|
"pinned": "固定",
|
||||||
|
@ -105,7 +105,7 @@
|
|||||||
"rephrase": "다르게 표현",
|
"rephrase": "다르게 표현",
|
||||||
"translate": "번역"
|
"translate": "번역"
|
||||||
},
|
},
|
||||||
"citations": "인용",
|
"webCitations": "인용",
|
||||||
"downloadCode": "코드 다운로드",
|
"downloadCode": "코드 다운로드",
|
||||||
"date": {
|
"date": {
|
||||||
"pinned": "고정됨",
|
"pinned": "고정됨",
|
||||||
|
@ -104,7 +104,7 @@
|
|||||||
"rephrase": "പുനഃരൂപീകരിക്കുക",
|
"rephrase": "പുനഃരൂപീകരിക്കുക",
|
||||||
"translate": "വിവർത്തനം ചെയ്യുക"
|
"translate": "വിവർത്തനം ചെയ്യുക"
|
||||||
},
|
},
|
||||||
"citations": "ഉദ്ധരണികൾ",
|
"webCitations": "ഉദ്ധരണികൾ",
|
||||||
"downloadCode": "കോഡ് ഡൗൺലോഡ് ചെയ്യുക",
|
"downloadCode": "കോഡ് ഡൗൺലോഡ് ചെയ്യുക",
|
||||||
"date": {
|
"date": {
|
||||||
"pinned": "പിൻ ചെയ്തത്",
|
"pinned": "പിൻ ചെയ്തത്",
|
||||||
|
@ -106,7 +106,7 @@
|
|||||||
"translate": "Oversett",
|
"translate": "Oversett",
|
||||||
"custom": "Egendefinert"
|
"custom": "Egendefinert"
|
||||||
},
|
},
|
||||||
"citations": "Sitater",
|
"webCitations": "Sitater",
|
||||||
"downloadCode": "Last ned kode",
|
"downloadCode": "Last ned kode",
|
||||||
"date": {
|
"date": {
|
||||||
"pinned": "Festet",
|
"pinned": "Festet",
|
||||||
|
@ -105,7 +105,7 @@
|
|||||||
"rephrase": "Reformular",
|
"rephrase": "Reformular",
|
||||||
"translate": "Traduzir"
|
"translate": "Traduzir"
|
||||||
},
|
},
|
||||||
"citations": "Citações",
|
"webCitations": "Citações",
|
||||||
"downloadCode": "Baixar Código",
|
"downloadCode": "Baixar Código",
|
||||||
"date": {
|
"date": {
|
||||||
"pinned": "Fixado",
|
"pinned": "Fixado",
|
||||||
|
@ -105,7 +105,7 @@
|
|||||||
"rephrase": "Перефразировать",
|
"rephrase": "Перефразировать",
|
||||||
"translate": "Перевести"
|
"translate": "Перевести"
|
||||||
},
|
},
|
||||||
"citations": "Цитаты",
|
"webCitations": "Цитаты",
|
||||||
"downloadCode": "Скачать код",
|
"downloadCode": "Скачать код",
|
||||||
"date": {
|
"date": {
|
||||||
"pinned": "Закреплено",
|
"pinned": "Закреплено",
|
||||||
|
@ -106,7 +106,7 @@
|
|||||||
"translate": "Översätt",
|
"translate": "Översätt",
|
||||||
"custom": "Custom"
|
"custom": "Custom"
|
||||||
},
|
},
|
||||||
"citations": "Citat",
|
"webCitations": "Citat",
|
||||||
"segmented": {
|
"segmented": {
|
||||||
"ollama": "Ollama-modeller",
|
"ollama": "Ollama-modeller",
|
||||||
"custom": "Custom modeller"
|
"custom": "Custom modeller"
|
||||||
|
@ -106,7 +106,7 @@
|
|||||||
"translate": "Перекласти",
|
"translate": "Перекласти",
|
||||||
"custom": "Власне"
|
"custom": "Власне"
|
||||||
},
|
},
|
||||||
"citations": "Цитати",
|
"webCitations": "Цитати",
|
||||||
"segmented": {
|
"segmented": {
|
||||||
"ollama": "Моделі Ollama",
|
"ollama": "Моделі Ollama",
|
||||||
"custom": "Власні моделі"
|
"custom": "Власні моделі"
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"pageAssist": "Page Assist",
|
"pageAssist": "IoD Bot",
|
||||||
"selectAModel": "选择一个模型",
|
"selectAModel": "选择一个模型",
|
||||||
"save": "保存",
|
"save": "保存",
|
||||||
"saved": "已保存",
|
"saved": "已保存",
|
||||||
@ -38,7 +38,8 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"copyToClipboard": "复制到剪贴板",
|
"copyToClipboard": "复制到剪贴板",
|
||||||
"webSearch": "搜索网络",
|
"webSearch": "搜索中...",
|
||||||
|
"iodSearch": "搜索数联网",
|
||||||
"regenerate": "重新生成",
|
"regenerate": "重新生成",
|
||||||
"edit": "编辑",
|
"edit": "编辑",
|
||||||
"delete": "删除",
|
"delete": "删除",
|
||||||
@ -105,8 +106,8 @@
|
|||||||
"rephrase": "重述",
|
"rephrase": "重述",
|
||||||
"translate": "翻译"
|
"translate": "翻译"
|
||||||
},
|
},
|
||||||
"citations": "万维网引用",
|
"webCitations": "万维网引用",
|
||||||
"iodcitations":"数联网引用",
|
"iodCitations": "数联网引用",
|
||||||
"downloadCode": "下载代码",
|
"downloadCode": "下载代码",
|
||||||
"date": {
|
"date": {
|
||||||
"pinned": "已置顶",
|
"pinned": "已置顶",
|
||||||
|
@ -1,15 +1,17 @@
|
|||||||
{
|
{
|
||||||
"newChat": "新聊天",
|
"projectTitle": "数联网科创智能体",
|
||||||
"selectAPrompt": "本地回答",
|
"newChat": "新对话",
|
||||||
|
"selectAPrompt": "选择一个提示词",
|
||||||
"githubRepository": "GitHub 仓库",
|
"githubRepository": "GitHub 仓库",
|
||||||
"settings": "设置",
|
"settings": "设置",
|
||||||
"sidebarTitle": "聊天历史",
|
"metering": "计量",
|
||||||
|
"sidebarTitle": "对话历史",
|
||||||
"error": "错误",
|
"error": "错误",
|
||||||
"somethingWentWrong": "出现了错误",
|
"somethingWentWrong": "出现了错误",
|
||||||
"validationSelectModel": "请选择一个模型以继续",
|
"validationSelectModel": "请选择一个模型以继续",
|
||||||
"deleteHistoryConfirmation": "你确定要删除这个历史记录吗?",
|
"deleteHistoryConfirmation": "你确定要删除这个历史记录吗?",
|
||||||
"editHistoryTitle": "输入一个新的标题",
|
"editHistoryTitle": "输入一个新的标题",
|
||||||
"temporaryChat": "临时聊天",
|
"temporaryChat": "临时对话",
|
||||||
"more": {
|
"more": {
|
||||||
"copy": {
|
"copy": {
|
||||||
"group": "复制",
|
"group": "复制",
|
||||||
|
@ -19,7 +19,8 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"tooltip": {
|
"tooltip": {
|
||||||
"searchInternet": "搜索互联网",
|
"searchInternet": "深度搜索",
|
||||||
|
"searchIod": "搜索数联网",
|
||||||
"speechToText": "语音到文本",
|
"speechToText": "语音到文本",
|
||||||
"uploadImage": "上传图片",
|
"uploadImage": "上传图片",
|
||||||
"stopStreaming": "停止流媒体",
|
"stopStreaming": "停止流媒体",
|
||||||
|
@ -88,7 +88,7 @@
|
|||||||
"system": {
|
"system": {
|
||||||
"heading": "系统设置",
|
"heading": "系统设置",
|
||||||
"deleteChatHistory": {
|
"deleteChatHistory": {
|
||||||
"label": "系统重置",
|
"label": "清除最近对话",
|
||||||
"button": "全部重置",
|
"button": "全部重置",
|
||||||
"confirm": "您确定要执行系统重置吗?这将清除所有数据且无法撤消。"
|
"confirm": "您确定要执行系统重置吗?这将清除所有数据且无法撤消。"
|
||||||
},
|
},
|
||||||
@ -316,6 +316,10 @@
|
|||||||
"title": "管理知识",
|
"title": "管理知识",
|
||||||
"heading": "配置知识库"
|
"heading": "配置知识库"
|
||||||
},
|
},
|
||||||
|
"iodSettings": {
|
||||||
|
"title": "数联网 设置",
|
||||||
|
"heading": "配置数联网"
|
||||||
|
},
|
||||||
"rag": {
|
"rag": {
|
||||||
"title": "RAG 设置",
|
"title": "RAG 设置",
|
||||||
"ragSettings": {
|
"ragSettings": {
|
||||||
|
BIN
src/assets/logo.png
Normal file
BIN
src/assets/logo.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 224 KiB |
@ -6,7 +6,7 @@
|
|||||||
|
|
||||||
.arimo {
|
.arimo {
|
||||||
font-family: "Arimo", sans-serif;
|
font-family: "Arimo", sans-serif;
|
||||||
font-weight: 500;
|
/*font-weight: 500;*/
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
41
src/components/Common/DataNavigation.tsx
Normal file
41
src/components/Common/DataNavigation.tsx
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
import React from "react"
|
||||||
|
import { Typography } from "antd"
|
||||||
|
import { ChevronRightIcon } from "@heroicons/react/24/outline"
|
||||||
|
|
||||||
|
const { Title } = Typography
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
Header: React.ReactNode
|
||||||
|
showButton?: boolean
|
||||||
|
onClick?: () => void
|
||||||
|
}
|
||||||
|
|
||||||
|
export const DataNavigation: React.FC<Props> = ({
|
||||||
|
Header,
|
||||||
|
showButton = true,
|
||||||
|
onClick
|
||||||
|
}) => {
|
||||||
|
return (
|
||||||
|
<div className="flex items-center justify-between bg-white dark:bg-gray-800 rounded-lg">
|
||||||
|
{/* 左侧部分 */}
|
||||||
|
<div className="flex items-center">
|
||||||
|
<Title
|
||||||
|
level={3}
|
||||||
|
className="flex items-center"
|
||||||
|
style={{ marginBottom: 0, color: "#1F2937", fontSize: "18px" }}>
|
||||||
|
{Header}
|
||||||
|
</Title>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 右侧部分 */}
|
||||||
|
{showButton && (
|
||||||
|
<div
|
||||||
|
className="flex items-center text-[#3a3a3a] cursor-pointer space-x-0.5 hover:text-[#3581e3] transition-colors duration-200"
|
||||||
|
onClick={onClick}>
|
||||||
|
<span className="text-[12px]">更多</span>
|
||||||
|
<ChevronRightIcon className="w-4 h-4" />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
@ -12,7 +12,7 @@ import { preprocessLaTeX } from "@/utils/latex"
|
|||||||
|
|
||||||
function Markdown({
|
function Markdown({
|
||||||
message,
|
message,
|
||||||
className = "prose break-words dark:prose-invert prose-p:leading-relaxed prose-pre:p-0 dark:prose-dark"
|
className = "prose-lg break-words dark:prose-invert prose-p:leading-relaxed prose-pre:p-0 dark:prose-dark"
|
||||||
}: {
|
}: {
|
||||||
message: string
|
message: string
|
||||||
className?: string
|
className?: string
|
||||||
|
@ -11,6 +11,11 @@ export const PageAssistProvider = ({
|
|||||||
const [controller, setController] = React.useState<AbortController | null>(
|
const [controller, setController] = React.useState<AbortController | null>(
|
||||||
null
|
null
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const [iodLoading, setIodLoading] = React.useState<boolean>(false)
|
||||||
|
const [currentMessageId, setCurrentMessageId] = React.useState<string>('')
|
||||||
|
|
||||||
|
|
||||||
const [embeddingController, setEmbeddingController] =
|
const [embeddingController, setEmbeddingController] =
|
||||||
React.useState<AbortController | null>(null)
|
React.useState<AbortController | null>(null)
|
||||||
|
|
||||||
@ -20,6 +25,12 @@ export const PageAssistProvider = ({
|
|||||||
messages,
|
messages,
|
||||||
setMessages,
|
setMessages,
|
||||||
|
|
||||||
|
iodLoading,
|
||||||
|
setIodLoading,
|
||||||
|
|
||||||
|
currentMessageId,
|
||||||
|
setCurrentMessageId,
|
||||||
|
|
||||||
controller,
|
controller,
|
||||||
setController,
|
setController,
|
||||||
|
|
||||||
|
139
src/components/Common/Playground/Data.tsx
Normal file
139
src/components/Common/Playground/Data.tsx
Normal file
@ -0,0 +1,139 @@
|
|||||||
|
import React, { useMemo } from "react"
|
||||||
|
import { DataNavigation } from "@/components/Common/DataNavigation.tsx"
|
||||||
|
import { Card, Skeleton } from "antd"
|
||||||
|
import { useMessageOption } from "@/hooks/useMessageOption.tsx"
|
||||||
|
import { IodRegistryEntry } from "@/types/iod.ts"
|
||||||
|
import { useIodPlaygroundContext } from "@/components/Option/Playground/PlaygroundIod.tsx"
|
||||||
|
|
||||||
|
// import { Drawer } from './Drawer.tsx'
|
||||||
|
|
||||||
|
const defaultData: IodRegistryEntry[] = [
|
||||||
|
{
|
||||||
|
name: "固态电池固体电解质材料数据集",
|
||||||
|
doId: "CSTR:16666.11.nbsdc.9bjqrscd",
|
||||||
|
description: "国家基础学科公共科学数据中心"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "固体颗粒物与流体耦合",
|
||||||
|
doId: "CSTR:16666.11.nbsdc.xyzbycl7",
|
||||||
|
description: "清华大学"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
type HeaderProps = {
|
||||||
|
title: string
|
||||||
|
showButton?: boolean
|
||||||
|
onClick?: () => void
|
||||||
|
}
|
||||||
|
const Header: React.FC<HeaderProps> = ({
|
||||||
|
title,
|
||||||
|
showButton = true,
|
||||||
|
onClick
|
||||||
|
}) => (
|
||||||
|
<DataNavigation
|
||||||
|
Header={
|
||||||
|
<div className="flex items-center gap-0.5 text-[#3581e3]">
|
||||||
|
<svg
|
||||||
|
className="icon"
|
||||||
|
viewBox="0 0 1024 1024"
|
||||||
|
version="1.1"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
p-id="3572"
|
||||||
|
width="18"
|
||||||
|
height="18">
|
||||||
|
<path
|
||||||
|
d="M877.714286 54.857143H754.285714V9.142857c0-5.028571-4.114286-9.142857-9.142857-9.142857h-64c-5.028571 0-9.142857 4.114286-9.142857 9.142857v45.714286H498.285714V9.142857c0-5.028571-4.114286-9.142857-9.142857-9.142857h-64c-5.028571 0-9.142857 4.114286-9.142857 9.142857v45.714286H292.571429c-20.228571 0-36.571429 16.342857-36.571429 36.571428v137.142858h-109.714286c-20.228571 0-36.571429 16.342857-36.571428 36.571428v722.285714c0 20.228571 16.342857 36.571429 36.571428 36.571429h585.142857c20.228571 0 36.571429-16.342857 36.571429-36.571429v-109.714285h109.714286c20.228571 0 36.571429-16.342857 36.571428-36.571429V91.428571c0-20.228571-16.342857-36.571429-36.571428-36.571428zM685.714286 941.714286H192V310.857143h249.142857v198.857143c0 25.257143 20.457143 45.714286 45.714286 45.714285h198.857143v386.285715z m0-459.428572H514.285714V310.857143h0.228572L685.714286 482.057143v0.228571z m146.285714 313.142857h-64V448L548.571429 228.571429H338.285714v-91.428572h77.714286v36.571429c0 5.028571 4.114286 9.142857 9.142857 9.142857h64c5.028571 0 9.142857-4.114286 9.142857-9.142857v-36.571429h173.714286v36.571429c0 5.028571 4.114286 9.142857 9.142857 9.142857h64c5.028571 0 9.142857-4.114286 9.142857-9.142857v-36.571429h77.714286v658.285714z"
|
||||||
|
p-id="3573"
|
||||||
|
fill="#3581e3"></path>
|
||||||
|
</svg>
|
||||||
|
{title}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
showButton={showButton}
|
||||||
|
onClick={onClick}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
|
||||||
|
type MainProps = {
|
||||||
|
loading: boolean
|
||||||
|
data: IodRegistryEntry[]
|
||||||
|
truncate?: boolean
|
||||||
|
}
|
||||||
|
const Main: React.FC<MainProps> = ({ data, loading, truncate = true }) => (
|
||||||
|
<div className="space-y-1.5 flex-1 overflow-y-auto">
|
||||||
|
{data.map((item, index) => {
|
||||||
|
return (
|
||||||
|
<Card
|
||||||
|
className="[&_.ant-card-body]:!p-2 !bg-[gb(248, 248, 248)] border !border-[#e9e9e9]"
|
||||||
|
key={item.doId}>
|
||||||
|
{loading ? (
|
||||||
|
<Skeleton title={false} active />
|
||||||
|
) : (
|
||||||
|
<div className="flex flex-col gap-0.5">
|
||||||
|
<h3
|
||||||
|
className={`text-base font-medium mb-1 text-[#222222] break-all ${truncate ? "line-clamp-2" : ""}`}
|
||||||
|
title={item.name}>
|
||||||
|
{item.name}
|
||||||
|
</h3>
|
||||||
|
<p
|
||||||
|
className={`text-sm text-[#383838] break-all ${truncate ? "line-clamp-2" : ""}`}
|
||||||
|
title={item.doId}>
|
||||||
|
数字对象标识:{item.doId}
|
||||||
|
</p>
|
||||||
|
<p
|
||||||
|
className={`text-[#828282] text-xs break-all ${truncate ? "truncate" : ""}`}
|
||||||
|
title={item.description}>
|
||||||
|
{item.description}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</Card>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
className?: string
|
||||||
|
}
|
||||||
|
export const PlaygroundData: React.FC<Props> = ({ className }) => {
|
||||||
|
const { iodLoading } = useMessageOption()
|
||||||
|
|
||||||
|
const {
|
||||||
|
setShowPlayground,
|
||||||
|
setDetailHeader,
|
||||||
|
setDetailMain,
|
||||||
|
currentIodMessage
|
||||||
|
} = useIodPlaygroundContext()
|
||||||
|
|
||||||
|
const data = useMemo<IodRegistryEntry[]>(() => {
|
||||||
|
return currentIodMessage ? currentIodMessage.data?.data ?? [] : defaultData
|
||||||
|
}, [currentIodMessage])
|
||||||
|
|
||||||
|
const title = useMemo(() => {
|
||||||
|
return currentIodMessage ? "推荐数据" : "热点数据"
|
||||||
|
}, [currentIodMessage])
|
||||||
|
|
||||||
|
const showMore = () => {
|
||||||
|
setShowPlayground(false)
|
||||||
|
setDetailHeader(
|
||||||
|
<Header
|
||||||
|
title={title}
|
||||||
|
showButton={false}
|
||||||
|
onClick={() => setShowPlayground(false)}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
setDetailMain(<Main loading={iodLoading && Boolean(currentIodMessage)} data={data} truncate={false} />)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Card className={`${className}`} hoverable>
|
||||||
|
<div className="h-full flex flex-col gap-2 relative">
|
||||||
|
{/* 数据导航 */}
|
||||||
|
<Header title={title} onClick={showMore} />
|
||||||
|
{/* 数据列表 */}
|
||||||
|
<Main loading={iodLoading && Boolean(currentIodMessage)} data={data.slice(0, 3)} />
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
)
|
||||||
|
}
|
91
src/components/Common/Playground/Drawer.tsx
Normal file
91
src/components/Common/Playground/Drawer.tsx
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
// Drawer.tsx
|
||||||
|
import React, { useEffect } from "react"
|
||||||
|
import styled from "styled-components"
|
||||||
|
import { shadow } from "pdfjs-dist"
|
||||||
|
|
||||||
|
interface DrawerProps {
|
||||||
|
open: boolean
|
||||||
|
onClose: () => void
|
||||||
|
children: React.ReactNode
|
||||||
|
width?: string | number
|
||||||
|
overlay?: boolean
|
||||||
|
keydown?: boolean
|
||||||
|
shadow?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
const DrawerOverlay = styled.div<{ isOpen: boolean }>`
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
background-color: rgba(0, 0, 0, 0.5);
|
||||||
|
opacity: ${({ isOpen }) => (isOpen ? 1 : 0)};
|
||||||
|
visibility: ${({ isOpen }) => (isOpen ? "visible" : "hidden")};
|
||||||
|
transition:
|
||||||
|
opacity 0.3s ease,
|
||||||
|
visibility 0.3s ease;
|
||||||
|
z-index: 1000;
|
||||||
|
`
|
||||||
|
|
||||||
|
const DrawerContainer = styled.div<{
|
||||||
|
isOpen: boolean
|
||||||
|
width: string | number
|
||||||
|
shadow: boolean
|
||||||
|
}>`
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
height: 100%;
|
||||||
|
width: ${({ width }) => (typeof width === "number" ? `${width}px` : width)};
|
||||||
|
background: #ffffff;
|
||||||
|
box-shadow: ${shadow ? "-2px 0 8px rgba(0, 0, 0, 0.15)" : ""};
|
||||||
|
transform: translateX(${({ isOpen }) => (isOpen ? "0" : "100%")});
|
||||||
|
transition: transform 0.3s ease;
|
||||||
|
z-index: 9999;
|
||||||
|
overflow-y: auto;
|
||||||
|
`
|
||||||
|
|
||||||
|
export const Drawer: React.FC<DrawerProps> = ({
|
||||||
|
open,
|
||||||
|
onClose,
|
||||||
|
children,
|
||||||
|
overlay = true,
|
||||||
|
keydown = true,
|
||||||
|
shadow = true,
|
||||||
|
width = "300px"
|
||||||
|
}) => {
|
||||||
|
// 处理 Escape 键关闭抽屉
|
||||||
|
useEffect(() => {
|
||||||
|
const handleEscape = (e: KeyboardEvent) => {
|
||||||
|
if (e.key === "Escape" && open) {
|
||||||
|
onClose()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (keydown) {
|
||||||
|
document.addEventListener("keydown", handleEscape)
|
||||||
|
}
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
if (keydown) {
|
||||||
|
document.removeEventListener("keydown", handleEscape)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [open, onClose, keydown])
|
||||||
|
|
||||||
|
// 处理点击遮罩层关闭抽屉
|
||||||
|
const handleOverlayClick = (e: React.MouseEvent) => {
|
||||||
|
if (e.target === e.currentTarget) {
|
||||||
|
onClose()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{overlay && <DrawerOverlay isOpen={open} onClick={handleOverlayClick} />}
|
||||||
|
<DrawerContainer isOpen={open} width={width}>
|
||||||
|
{children}
|
||||||
|
</DrawerContainer>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
@ -1,7 +1,8 @@
|
|||||||
import { useForm } from "@mantine/form"
|
import { useForm } from "@mantine/form"
|
||||||
import React from "react"
|
import React, { useEffect, useState } from "react"
|
||||||
import { useTranslation } from "react-i18next"
|
import { useTranslation } from "react-i18next"
|
||||||
import useDynamicTextareaSize from "~/hooks/useDynamicTextareaSize"
|
import useDynamicTextareaSize from "~/hooks/useDynamicTextareaSize"
|
||||||
|
import TextArea from "antd/es/input/TextArea"
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
value: string
|
value: string
|
||||||
@ -14,6 +15,14 @@ export const EditMessageForm = (props: Props) => {
|
|||||||
const [isComposing, setIsComposing] = React.useState(false)
|
const [isComposing, setIsComposing] = React.useState(false)
|
||||||
const textareaRef = React.useRef<HTMLTextAreaElement>(null)
|
const textareaRef = React.useRef<HTMLTextAreaElement>(null)
|
||||||
const { t } = useTranslation("common")
|
const { t } = useTranslation("common")
|
||||||
|
const [value, setValue] = useState(props.value);
|
||||||
|
|
||||||
|
useEffect(
|
||||||
|
() => {
|
||||||
|
setValue(props.value)
|
||||||
|
},
|
||||||
|
[props.value]
|
||||||
|
);
|
||||||
|
|
||||||
const form = useForm({
|
const form = useForm({
|
||||||
initialValues: {
|
initialValues: {
|
||||||
@ -29,46 +38,27 @@ export const EditMessageForm = (props: Props) => {
|
|||||||
return (
|
return (
|
||||||
<form
|
<form
|
||||||
onSubmit={form.onSubmit((data) => {
|
onSubmit={form.onSubmit((data) => {
|
||||||
if (isComposing) return
|
|
||||||
props.onClose()
|
props.onClose()
|
||||||
props.onSumbit(data.message, true)
|
props.onSumbit(value, true)
|
||||||
})}
|
})}
|
||||||
className="flex flex-col gap-2">
|
className="flex flex-col gap-2 w-96 ml-auto">
|
||||||
<textarea
|
<TextArea
|
||||||
{...form.getInputProps("message")}
|
|
||||||
onCompositionStart={() => setIsComposing(true)}
|
|
||||||
onCompositionEnd={() => setIsComposing(false)}
|
|
||||||
required
|
required
|
||||||
rows={1}
|
rows={2}
|
||||||
|
value={value}
|
||||||
style={{ minHeight: "60px" }}
|
style={{ minHeight: "60px" }}
|
||||||
tabIndex={0}
|
tabIndex={0}
|
||||||
|
onChange={(e) => {
|
||||||
|
setValue(e.target.value)
|
||||||
|
}}
|
||||||
placeholder={t("editMessage.placeholder")}
|
placeholder={t("editMessage.placeholder")}
|
||||||
ref={textareaRef}
|
|
||||||
className="w-full bg-transparent focus-within:outline-none focus:ring-0 focus-visible:ring-0 ring-0 dark:ring-0 border-0 dark:text-gray-100"
|
|
||||||
/>
|
/>
|
||||||
<div className="flex flex-wrap gap-2 mt-2">
|
<div className="flex flex-wrap gap-2 mt-2">
|
||||||
<div
|
<div
|
||||||
className={`w-full flex ${
|
className={`w-full flex ${
|
||||||
!props.isBot ? "justify-between" : "justify-end"
|
!props.isBot ? "justify-end" : "justify-end"
|
||||||
}`}>
|
}`}>
|
||||||
{!props.isBot && (
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onClick={() => {
|
|
||||||
props.onSumbit(form.values.message, false)
|
|
||||||
props.onClose()
|
|
||||||
}}
|
|
||||||
aria-label={t("save")}
|
|
||||||
className="border border-gray-600 px-2 py-1.5 rounded-lg text-gray-700 dark:text-gray-300 focus:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-gray-500 hover:bg-gray-100 dark:hover:bg-gray-900 text-sm">
|
|
||||||
{t("save")}
|
|
||||||
</button>
|
|
||||||
)}
|
|
||||||
<div className="flex space-x-2">
|
<div className="flex space-x-2">
|
||||||
<button
|
|
||||||
aria-label={t("save")}
|
|
||||||
className="bg-black px-2 py-1.5 rounded-lg text-white focus:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-gray-500 hover:bg-gray-900 text-sm">
|
|
||||||
{props.isBot ? t("save") : t("saveAndSubmit")}
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<button
|
<button
|
||||||
onClick={props.onClose}
|
onClick={props.onClose}
|
||||||
@ -76,6 +66,12 @@ export const EditMessageForm = (props: Props) => {
|
|||||||
className="border dark:border-gray-600 px-2 py-1.5 rounded-lg text-gray-700 dark:text-gray-300 focus:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-gray-500 hover:bg-gray-100 dark:hover:bg-gray-900 text-sm">
|
className="border dark:border-gray-600 px-2 py-1.5 rounded-lg text-gray-700 dark:text-gray-300 focus:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-gray-500 hover:bg-gray-100 dark:hover:bg-gray-900 text-sm">
|
||||||
{t("cancel")}
|
{t("cancel")}
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
|
<button
|
||||||
|
aria-label={t("save")}
|
||||||
|
className="bg-[#0057ff] px-2 py-1.5 rounded-lg text-white focus:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-gray-500 text-sm">
|
||||||
|
{props.isBot ? t("save") : t("saveAndSubmit")}
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>{" "}
|
</div>{" "}
|
||||||
|
482
src/components/Common/Playground/IodRelevant.tsx
Normal file
482
src/components/Common/Playground/IodRelevant.tsx
Normal file
@ -0,0 +1,482 @@
|
|||||||
|
import React, { useEffect, useMemo, useState } from "react"
|
||||||
|
import { Avatar, Card } from "antd"
|
||||||
|
import { AnimatePresence, motion } from "framer-motion" // 使用 CSS-in-JS 方式
|
||||||
|
import styled, { keyframes } from "styled-components"
|
||||||
|
import CountUp from "react-countup"
|
||||||
|
import { TalentPoolIcon } from "@/components/Icons/TalentPool .tsx"
|
||||||
|
import { ResearchPaperIcon } from "@/components/Icons/ResearchPaper.tsx"
|
||||||
|
import { DataProjectIcon } from "@/components/Icons/DataProject.tsx"
|
||||||
|
import { DatasetIcon } from "@/components/Icons/Dataset.tsx"
|
||||||
|
import { TechCompanyIcon } from "@/components/Icons/TechCompany.tsx"
|
||||||
|
import { ResearchInstitutesIcon } from "@/components/Icons/ResearchInstitutes.tsx"
|
||||||
|
import { NSDCIcon } from "@/components/Icons/NSDC.tsx"
|
||||||
|
import { useIodPlaygroundContext } from "@/components/Option/Playground/PlaygroundIod.tsx"
|
||||||
|
import { totalSearchResults } from "@/services/search.ts"
|
||||||
|
|
||||||
|
const rotate = keyframes`
|
||||||
|
0% {
|
||||||
|
transform: translate(-50%, -50%) rotate(0deg);
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
transform: translate(-50%, -50%) rotate(360deg);
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
const breathe = keyframes`
|
||||||
|
0% {
|
||||||
|
box-shadow: 0 0 5px rgba(37, 231, 232, 0.3);
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
box-shadow: 0 0 20px rgba(37, 231, 232, 0.8);
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
box-shadow: 0 0 5px rgba(37, 231, 232, 0.3);
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
// 花瓣 /* ${(props) => (props.playing ? "running" : "paused")}; */
|
||||||
|
const CircleElement = styled.div<{ delay: number }>`
|
||||||
|
position: absolute;
|
||||||
|
width: 300px;
|
||||||
|
height: 160px;
|
||||||
|
background: #3b82f6; // blue-500
|
||||||
|
opacity: 0.2;
|
||||||
|
border-radius: 50%;
|
||||||
|
top: 55%;
|
||||||
|
left: 50%;
|
||||||
|
animation:
|
||||||
|
${rotate} 6s linear infinite,
|
||||||
|
${breathe} 2s infinite alternate;
|
||||||
|
animation-delay: ${(props) => props.delay}s;
|
||||||
|
animation-play-state: running;
|
||||||
|
animation-duration: 3s; /* 添加动画总持续时间 */
|
||||||
|
animation-fill-mode: forwards; /* 保持动画结束时的状态 */
|
||||||
|
`
|
||||||
|
|
||||||
|
const FrostedGlassCard = styled(Card)`
|
||||||
|
background: rgba(255, 255, 255, 0.25) !important;
|
||||||
|
backdrop-filter: blur(10px);
|
||||||
|
-webkit-backdrop-filter: blur(10px);
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.18);
|
||||||
|
`
|
||||||
|
|
||||||
|
const SuccessIcon = React.forwardRef<
|
||||||
|
SVGSVGElement,
|
||||||
|
React.SVGProps<SVGSVGElement>
|
||||||
|
>((props, ref) => {
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
fill="none"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
className="text-green-500"
|
||||||
|
ref={ref}
|
||||||
|
{...props}>
|
||||||
|
<path
|
||||||
|
d="M9 12L11 14L15 10M21 12C21 16.9706 16.9706 21 12 21C7.02944 21 3 16.9706 3 12C3 7.02944 7.02944 3 12 3C16.9706 3 21 7.02944 21 12Z"
|
||||||
|
stroke="currentColor"
|
||||||
|
strokeWidth="2"
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
const LoadingIcon = React.forwardRef<
|
||||||
|
SVGSVGElement,
|
||||||
|
React.SVGProps<SVGSVGElement>
|
||||||
|
>((props, ref) => {
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
className="icon animate-spin"
|
||||||
|
viewBox="0 0 1024 1024"
|
||||||
|
version="1.1"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
p-id="29588"
|
||||||
|
ref={ref}
|
||||||
|
{...props}>
|
||||||
|
<path
|
||||||
|
d="M483.712 888.064a52.437333 52.437333 0 1 1 52.48 52.352 52.394667 52.394667 0 0 1-52.48-52.352z m-235.434667-53.76a65.578667 65.578667 0 1 1 46.421334 19.242667 65.962667 65.962667 0 0 1-46.378667-19.242667z m499.584-16.597333a41.984 41.984 0 0 1 59.264-59.434667 42.282667 42.282667 0 0 1 0 59.434667 41.941333 41.941333 0 0 1-59.264 0zM112.853333 546.602667a81.92 81.92 0 1 1 81.92 81.92 81.834667 81.834667 0 0 1-81.92-81.877334z m731.008 0a33.536 33.536 0 1 1 33.493334 33.578666 33.578667 33.578667 0 0 1-33.450667-33.536zM222.208 377.6a102.4 102.4 0 1 1 72.533333 29.866667 102.869333 102.869333 0 0 1-72.533333-29.824z m536.32-53.504a26.666667 26.666667 0 1 1 18.816 7.936 26.368 26.368 0 0 1-18.773333-7.893333zM414.378667 205.184a121.642667 121.642667 0 1 1 121.813333 121.6A121.728 121.728 0 0 1 414.378667 205.226667z"
|
||||||
|
p-id="29589"
|
||||||
|
fill="#4284f6"></path>
|
||||||
|
</svg>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
const SearchIcon = () => {
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
className="icon"
|
||||||
|
viewBox="0 0 1024 1024"
|
||||||
|
version="1.1"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
p-id="2585"
|
||||||
|
width="22px"
|
||||||
|
height="22px">
|
||||||
|
<path
|
||||||
|
d="M913.365333 842.794667l-188.16-188.16a347.648 347.648 0 0 0 69.674667-209.194667c0-192.682667-156.757333-349.44-349.44-349.44s-349.44 156.757333-349.44 349.44 156.757333 349.44 349.44 349.44a347.648 347.648 0 0 0 209.152-69.674667l188.16 188.16a49.962667 49.962667 0 0 0 70.613333-70.570666zM195.84 445.44a249.6 249.6 0 1 1 249.6 249.6 249.898667 249.898667 0 0 1-249.6-249.6z"
|
||||||
|
fill="#08307f"
|
||||||
|
p-id="2586"></path>
|
||||||
|
</svg>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 自定义统计卡片组件
|
||||||
|
const StatCard: React.FC<{
|
||||||
|
number: number
|
||||||
|
unit?: string
|
||||||
|
label: string
|
||||||
|
decimals?: number
|
||||||
|
icon: React.ReactNode
|
||||||
|
}> = ({ number, unit, label, decimals, icon }) => {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className="flex flex-col items-center justify-center p-3 rounded-xl shadow-sm bg-[rgba(240,245,255,0.3)] backdrop-blur-sm border border-[rgba(200,220,255,0.25)]
|
||||||
|
">
|
||||||
|
<Avatar size={40} className="!bg-[#3581e3b3]" icon={icon} />
|
||||||
|
|
||||||
|
<div className="text-lg font-bold text-[#f00000]">
|
||||||
|
<CountUp
|
||||||
|
end={number}
|
||||||
|
duration={2.5}
|
||||||
|
separator=","
|
||||||
|
decimals={decimals}
|
||||||
|
/>
|
||||||
|
{unit}
|
||||||
|
</div>
|
||||||
|
<div className="text-sm text-[#3581e3] mt-1 flex items-center gap-2">
|
||||||
|
{" "}
|
||||||
|
{label}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const StatisticGrid: React.FC = () => {
|
||||||
|
return (
|
||||||
|
<div className="p-6">
|
||||||
|
{/* 第一行:3 个卡片 */}
|
||||||
|
<div className="grid grid-cols-3 gap-6 mb-6">
|
||||||
|
<StatCard
|
||||||
|
icon={<NSDCIcon className="w-6 h-6 text-white" color="#3581e3" />}
|
||||||
|
number={11}
|
||||||
|
unit="家"
|
||||||
|
label="国家科学数据中心"
|
||||||
|
/>
|
||||||
|
<StatCard
|
||||||
|
icon={
|
||||||
|
<ResearchInstitutesIcon
|
||||||
|
className="w-6 h-6 text-white"
|
||||||
|
color="#3581e3"
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
number={763}
|
||||||
|
unit="家"
|
||||||
|
label="高等院校和科研机构"
|
||||||
|
/>
|
||||||
|
<StatCard
|
||||||
|
icon={
|
||||||
|
<TechCompanyIcon className="w-6 h-6 text-white" color="#3581e3" />
|
||||||
|
}
|
||||||
|
number={2.1}
|
||||||
|
decimals={1}
|
||||||
|
unit="万"
|
||||||
|
label="科技型企业"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 第二行:4 个卡片 */}
|
||||||
|
<div className="grid grid-cols-4 gap-6">
|
||||||
|
<StatCard
|
||||||
|
icon={<DatasetIcon className="w-6 h-6 text-white" color="#3581e3" />}
|
||||||
|
number={537163}
|
||||||
|
label="数据集"
|
||||||
|
/>
|
||||||
|
<StatCard
|
||||||
|
icon={
|
||||||
|
<DataProjectIcon className="w-6 h-6 text-white" color="#3581e3" />
|
||||||
|
}
|
||||||
|
number={183729}
|
||||||
|
label="数据项目"
|
||||||
|
/>
|
||||||
|
<StatCard
|
||||||
|
icon={
|
||||||
|
<ResearchPaperIcon className="w-6 h-6 text-white" color="#3581e3" />
|
||||||
|
}
|
||||||
|
number={1380026}
|
||||||
|
label="数据论文"
|
||||||
|
/>
|
||||||
|
<StatCard
|
||||||
|
icon={
|
||||||
|
<TalentPoolIcon className="w-6 h-6 text-white" color="#3581e3" />
|
||||||
|
}
|
||||||
|
number={2}
|
||||||
|
unit="万"
|
||||||
|
label="科创人才"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
className?: string
|
||||||
|
}
|
||||||
|
export const PlaygroundIodRelevant: React.FC<Props> = ({ className }) => {
|
||||||
|
const { iodLoading, iodSearch } = useMessageOption()
|
||||||
|
|
||||||
|
const { currentIodMessage } = useIodPlaygroundContext()
|
||||||
|
|
||||||
|
const showSearchData = useMemo(() => {
|
||||||
|
return currentIodMessage && !iodLoading
|
||||||
|
}, [currentIodMessage, iodLoading])
|
||||||
|
|
||||||
|
const [count, setCount] = useState<number>(0)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
totalSearchResults().then((res) => {
|
||||||
|
setCount(res)
|
||||||
|
})
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
const getMinNum = (n1: number) => {
|
||||||
|
return Math.min(n1, count)
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = useMemo(() => {
|
||||||
|
const loading = iodSearch && iodLoading
|
||||||
|
const text = loading ? "正" : "已"
|
||||||
|
const text2 = loading ? "进行" : "完成"
|
||||||
|
const text3 = loading ? "……" : ""
|
||||||
|
const duration = loading ? 2.5 : 0
|
||||||
|
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
title: (
|
||||||
|
<p className="font-extrabold">
|
||||||
|
{text}在
|
||||||
|
<span className="text-[#f00000]">
|
||||||
|
<CountUp end={29} duration={duration} separator="," />个
|
||||||
|
</span>
|
||||||
|
国家和省部级科学数据中心、
|
||||||
|
<span className="text-[#f00000]">
|
||||||
|
{" "}
|
||||||
|
<CountUp end={55} duration={duration} separator="," />所
|
||||||
|
</span>
|
||||||
|
高等院校的
|
||||||
|
<span className="text-[#f00000]">
|
||||||
|
<CountUp
|
||||||
|
decimals={1}
|
||||||
|
end={53.7}
|
||||||
|
duration={duration}
|
||||||
|
separator=","
|
||||||
|
/>
|
||||||
|
万个
|
||||||
|
</span>
|
||||||
|
科学数据集中{text2}搜索{text3}
|
||||||
|
</p>
|
||||||
|
),
|
||||||
|
description: showSearchData ? (
|
||||||
|
<p>
|
||||||
|
已发现
|
||||||
|
<span className="text-green-700">
|
||||||
|
{" "}
|
||||||
|
<CountUp
|
||||||
|
end={currentIodMessage?.data.total ?? 0}
|
||||||
|
duration={2.5}
|
||||||
|
separator=","
|
||||||
|
/>
|
||||||
|
个{" "}
|
||||||
|
</span>
|
||||||
|
数据集,引用 {getMinNum(currentIodMessage?.data.total ?? 0)}个 数据集作为参考
|
||||||
|
</p>
|
||||||
|
) : (
|
||||||
|
""
|
||||||
|
)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: (
|
||||||
|
<p className="font-extrabold">
|
||||||
|
{text}在
|
||||||
|
<span className="text-[#f00000]">
|
||||||
|
<CountUp end={138} duration={duration} separator="," />
|
||||||
|
万篇
|
||||||
|
</span>
|
||||||
|
数据论文、
|
||||||
|
<span className="text-[#f00000]">
|
||||||
|
<CountUp
|
||||||
|
end={18.3}
|
||||||
|
decimals={1}
|
||||||
|
duration={duration}
|
||||||
|
separator=","
|
||||||
|
/>
|
||||||
|
万个
|
||||||
|
</span>
|
||||||
|
数据项目中{text2}搜索{text3}
|
||||||
|
</p>
|
||||||
|
),
|
||||||
|
description: showSearchData ? (
|
||||||
|
<p>
|
||||||
|
已发现
|
||||||
|
<span className="text-green-700">
|
||||||
|
{" "}
|
||||||
|
<CountUp
|
||||||
|
end={currentIodMessage?.scenario.total ?? 0}
|
||||||
|
duration={2.5}
|
||||||
|
separator=","
|
||||||
|
/>
|
||||||
|
个{" "}
|
||||||
|
</span>
|
||||||
|
场景,引用 {getMinNum(currentIodMessage?.scenario.total ?? 0)}个 场景作为参考
|
||||||
|
</p>
|
||||||
|
) : (
|
||||||
|
""
|
||||||
|
)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: (
|
||||||
|
<p className="font-extrabold">
|
||||||
|
{text}在
|
||||||
|
<span className="text-[#f00000]">
|
||||||
|
<CountUp end={763} duration={duration} separator="," />家
|
||||||
|
</span>
|
||||||
|
高等院校和科研机构、
|
||||||
|
<span className="text-[#f00000]">
|
||||||
|
{" "}
|
||||||
|
<CountUp
|
||||||
|
end={2.1}
|
||||||
|
decimals={1}
|
||||||
|
duration={duration}
|
||||||
|
separator=","
|
||||||
|
/>
|
||||||
|
万
|
||||||
|
</span>
|
||||||
|
家科技型企业、
|
||||||
|
<span className="text-[#f00000]">
|
||||||
|
{" "}
|
||||||
|
<CountUp end={2} duration={duration} separator="," />万
|
||||||
|
</span>
|
||||||
|
科技人才中{text2}搜索{text3}
|
||||||
|
</p>
|
||||||
|
),
|
||||||
|
description: showSearchData ? (
|
||||||
|
<p>
|
||||||
|
已发现
|
||||||
|
<span className="text-green-700">
|
||||||
|
{" "}
|
||||||
|
<CountUp
|
||||||
|
end={currentIodMessage?.organization.total ?? 0}
|
||||||
|
duration={2.5}
|
||||||
|
separator=","
|
||||||
|
/>
|
||||||
|
个{" "}
|
||||||
|
</span>
|
||||||
|
组织,引用 {getMinNum(currentIodMessage?.organization.total ?? 0)}个 组织作为参考
|
||||||
|
</p>
|
||||||
|
) : (
|
||||||
|
""
|
||||||
|
)
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}, [showSearchData, iodLoading, count])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Card
|
||||||
|
hoverable
|
||||||
|
variant="outlined"
|
||||||
|
className={`${className} translate-y-[-2px] !bg-[#d0e6ff] shadow-md`}>
|
||||||
|
<div className="h-full flex flex-col relative">
|
||||||
|
{/* 花瓣效果 */}
|
||||||
|
<div
|
||||||
|
className={`absolute inset-0 pointer-events-none z-0 overflow-hidden ${showSearchData ? "" : ""}`}>
|
||||||
|
<div className="absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2 w-64 h-64">
|
||||||
|
<CircleElement delay={0} />
|
||||||
|
<CircleElement delay={1} />
|
||||||
|
<CircleElement delay={2} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Header */}
|
||||||
|
<div className="p-3">
|
||||||
|
<h2 className="text-xl font-semibold text-[#1a3c87] flex justify-center items-center">
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<SearchIcon />
|
||||||
|
{currentIodMessage ? "科创数联网深度搜索" : "科创数联网连接资源"}
|
||||||
|
</div>
|
||||||
|
{/*<button className="bg-[#2563eb1a] text-[#08307f] font-medium py-1 px-3 rounded-full text-sm hover:bg-[#2563eb1a] transition-colors float-right">*/}
|
||||||
|
{/* {data.length}个结果*/}
|
||||||
|
{/*</button>*/}
|
||||||
|
</h2>
|
||||||
|
<p className="text-sm text-[#1a3c87] mt-1 text-center">
|
||||||
|
{currentIodMessage
|
||||||
|
? "下面是在科创数联网上进行深度搜索得到的相关数据、场景和团队"
|
||||||
|
: "下面是科创数联网连接的数据、场景和团队"}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Content */}
|
||||||
|
<div className="space-y-2 flex-1 overflow-y-auto">
|
||||||
|
{currentIodMessage ? (
|
||||||
|
<AnimatePresence mode="wait">
|
||||||
|
<motion.div
|
||||||
|
key="search-results"
|
||||||
|
initial={{ opacity: 0, y: 10 }}
|
||||||
|
animate={{ opacity: 1, y: 0 }}
|
||||||
|
exit={{ opacity: 0, y: -10 }}
|
||||||
|
transition={{ duration: 0.3 }}
|
||||||
|
className="space-y-2 flex-1 overflow-y-auto">
|
||||||
|
{data.map((item, index) => (
|
||||||
|
<FrostedGlassCard
|
||||||
|
className="[&_.ant-card-body]:!p-3 [&_.ant-card-body]:h-full shadow-md min-h-[88px]"
|
||||||
|
key={index}>
|
||||||
|
<div
|
||||||
|
className={`flex flex-col gap-2 h-full items-start ${showSearchData ? "justify-start" : "justify-center"}`}>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<div>
|
||||||
|
{iodSearch && iodLoading ? (
|
||||||
|
<LoadingIcon
|
||||||
|
width={showSearchData ? 16 : 22}
|
||||||
|
height={showSearchData ? 16 : 22}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<SuccessIcon
|
||||||
|
width={showSearchData ? 16 : 22}
|
||||||
|
height={showSearchData ? 16 : 22}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className={`text-gray-700 ${showSearchData ? "text-sm" : "text-lg"}`}>
|
||||||
|
{item.title}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{item.description && (
|
||||||
|
<div className="flex-1">
|
||||||
|
<div className="text-xs text-gray-500 mt-1 pl-7">
|
||||||
|
{item.description}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</FrostedGlassCard>
|
||||||
|
))}
|
||||||
|
</motion.div>
|
||||||
|
</AnimatePresence>
|
||||||
|
) : (
|
||||||
|
<AnimatePresence mode="wait">
|
||||||
|
<motion.div
|
||||||
|
key="statistic-grid"
|
||||||
|
initial={{ opacity: 0, y: 10 }}
|
||||||
|
animate={{ opacity: 1, y: 0 }}
|
||||||
|
exit={{ opacity: 0, y: -10 }}
|
||||||
|
transition={{ duration: 0.3 }}
|
||||||
|
className="flex-1 overflow-y-auto">
|
||||||
|
<StatisticGrid />
|
||||||
|
</motion.div>
|
||||||
|
</AnimatePresence>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
)
|
||||||
|
}
|
@ -1,26 +1,34 @@
|
|||||||
import Markdown from "../../Common/Markdown"
|
import Markdown from "../../Common/Markdown"
|
||||||
import React from "react"
|
import React from "react"
|
||||||
import { Tag, Image, Tooltip, Collapse, Popover } from "antd"
|
import { Collapse, Image, Popover, Tag, Tooltip } from "antd"
|
||||||
import { WebSearch } from "./WebSearch"
|
import { WebSearch } from "./WebSearch"
|
||||||
import {
|
import {
|
||||||
|
ArrowUpSquare,
|
||||||
CheckIcon,
|
CheckIcon,
|
||||||
ClipboardIcon,
|
ClipboardIcon,
|
||||||
InfoIcon,
|
InfoIcon,
|
||||||
|
MessageSquareShare,
|
||||||
Pen,
|
Pen,
|
||||||
PlayIcon,
|
PlayIcon,
|
||||||
RotateCcw,
|
RotateCcw,
|
||||||
Square
|
Square,
|
||||||
|
Star,
|
||||||
|
ThumbsDown,
|
||||||
|
ThumbsUp
|
||||||
} from "lucide-react"
|
} from "lucide-react"
|
||||||
import { EditMessageForm } from "./EditMessageForm"
|
import { EditMessageForm } from "./EditMessageForm"
|
||||||
import { useTranslation } from "react-i18next"
|
import { useTranslation } from "react-i18next"
|
||||||
import { MessageSource } from "./MessageSource"
|
import { MessageSource } from "./MessageSource"
|
||||||
import { useTTS } from "@/hooks/useTTS"
|
import { useTTS } from "@/hooks/useTTS"
|
||||||
import { tagColors } from "@/utils/color"
|
import { tagColors } from "@/utils/color"
|
||||||
import { removeModelSuffix } from "@/db/models"
|
|
||||||
import { GenerationInfo } from "./GenerationInfo"
|
import { GenerationInfo } from "./GenerationInfo"
|
||||||
import { parseReasoning, } from "@/libs/reasoning"
|
import { parseReasoning } from "@/libs/reasoning"
|
||||||
import { humanizeMilliseconds } from "@/utils/humanize-milliseconds"
|
import { humanizeMilliseconds } from "@/utils/humanize-milliseconds"
|
||||||
|
import { AllIodRegistryEntry } from "@/types/iod.ts"
|
||||||
|
import { PiNetwork } from "react-icons/pi"
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
|
id?: string
|
||||||
message: string
|
message: string
|
||||||
message_type?: string
|
message_type?: string
|
||||||
hideCopy?: boolean
|
hideCopy?: boolean
|
||||||
@ -36,50 +44,31 @@ type Props = {
|
|||||||
isProcessing: boolean
|
isProcessing: boolean
|
||||||
webSearch?: {}
|
webSearch?: {}
|
||||||
isSearchingInternet?: boolean
|
isSearchingInternet?: boolean
|
||||||
sources?: any[]
|
webSources?: any[]
|
||||||
iodSources?:any[]
|
iodSources?: AllIodRegistryEntry
|
||||||
hideEditAndRegenerate?: boolean
|
hideEditAndRegenerate?: boolean
|
||||||
onSourceClick?: (source: any) => void
|
onSourceClick?: (source: any) => void
|
||||||
isTTSEnabled?: boolean
|
isTTSEnabled?: boolean
|
||||||
generationInfo?: any
|
generationInfo?: any
|
||||||
isStreaming: boolean
|
isStreaming: boolean
|
||||||
reasoningTimeTaken?: number
|
reasoningTimeTaken?: number
|
||||||
|
iodSearch?: boolean
|
||||||
|
setCurrentMessageId: (id: string) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
export const PlaygroundMessage = (props: Props) => {
|
export const PlaygroundMessage: React.FC<Props> = (props) => {
|
||||||
const [isBtnPressed, setIsBtnPressed] = React.useState(false)
|
const [isBtnPressed, setIsBtnPressed] = React.useState(false)
|
||||||
const [editMode, setEditMode] = React.useState(false)
|
const [editMode, setEditMode] = React.useState(false)
|
||||||
|
|
||||||
const { t } = useTranslation("common")
|
const { t } = useTranslation("common")
|
||||||
const { cancel, isSpeaking, speak } = useTTS()
|
const { cancel, isSpeaking, speak } = useTTS()
|
||||||
return (
|
return (
|
||||||
<div className="group relative flex w-full max-w-3xl flex-col items-end justify-center pb-2 md:px-4 lg:w-4/5 text-gray-800 dark:text-gray-100">
|
<div className="group relative flex w-full flex-col items-end justify-center pb-2 md:px-4 text-gray-800 dark:text-gray-100">
|
||||||
{/* <div className="text-base md:max-w-2xl lg:max-w-xl xl:max-w-3xl flex lg:px-0 m-auto w-full"> */}
|
{/* <div className="text-base md:max-w-2xl lg:max-w-xl xl:max-w-3xl flex lg:px-0 m-auto w-full"> */}
|
||||||
<div className="flex flex-row gap-4 md:gap-6 my-2 m-auto w-full">
|
<div
|
||||||
<div className="w-8 flex flex-col relative items-end">
|
className={`flex flex-row gap-1 md:gap-1 my-2 m-auto w-full ${props.isBot ? "" : "flex-row-reverse"}`}>
|
||||||
<div className="relative h-7 w-7 p-1 rounded-sm text-white flex items-center justify-center text-opacity-100r">
|
<div className="flex flex-col gap-2">
|
||||||
{props.isBot ? (
|
<span className="text-xs font-bold text-gray-800 dark:text-white"></span>
|
||||||
!props.botAvatar ? (
|
|
||||||
<div className="absolute h-8 w-8 rounded-full bg-gradient-to-r from-green-300 to-purple-400"></div>
|
|
||||||
) : (
|
|
||||||
props.botAvatar
|
|
||||||
)
|
|
||||||
) : !props.userAvatar ? (
|
|
||||||
<div className="absolute h-8 w-8 rounded-full from-blue-400 to-blue-600 bg-gradient-to-r"></div>
|
|
||||||
) : (
|
|
||||||
props.userAvatar
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="flex w-[calc(100%-50px)] flex-col gap-2 lg:w-[calc(100%-115px)]">
|
|
||||||
<span className="text-xs font-bold text-gray-800 dark:text-white">
|
|
||||||
{props.isBot
|
|
||||||
? props.name === "chrome::gemini-nano::page-assist"
|
|
||||||
? "Gemini Nano"
|
|
||||||
: removeModelSuffix(
|
|
||||||
props.name?.replaceAll(/accounts\/[^\/]+\/models\//g, "")
|
|
||||||
)
|
|
||||||
: "You"}
|
|
||||||
</span>
|
|
||||||
|
|
||||||
{props.isBot &&
|
{props.isBot &&
|
||||||
props.isSearchingInternet &&
|
props.isSearchingInternet &&
|
||||||
@ -93,7 +82,7 @@ export const PlaygroundMessage = (props: Props) => {
|
|||||||
</Tag>
|
</Tag>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-grow flex-col">
|
<div className={`flex flex-grow flex-col w-full`}>
|
||||||
{!editMode ? (
|
{!editMode ? (
|
||||||
props.isBot ? (
|
props.isBot ? (
|
||||||
<>
|
<>
|
||||||
@ -102,6 +91,7 @@ export const PlaygroundMessage = (props: Props) => {
|
|||||||
return (
|
return (
|
||||||
<Collapse
|
<Collapse
|
||||||
key={i}
|
key={i}
|
||||||
|
defaultActiveKey={["reasoning"]}
|
||||||
className="border-none !mb-3"
|
className="border-none !mb-3"
|
||||||
items={[
|
items={[
|
||||||
{
|
{
|
||||||
@ -131,11 +121,17 @@ export const PlaygroundMessage = (props: Props) => {
|
|||||||
})}
|
})}
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
|
// <p
|
||||||
|
// className={`bg-[#f1f3f4] font-normal text-[#000000d9] px-4 py-2.5 rounded-2xl prose-lg dark:prose-invert whitespace-pre-line prose-p:leading-relaxed prose-pre:p-0 dark:prose-dark ${
|
||||||
|
// props.message_type && "italic dark:text-gray-400"
|
||||||
|
// } flex flex-row-reverse`}>
|
||||||
|
// {props.message}
|
||||||
|
// </p>
|
||||||
<p
|
<p
|
||||||
className={`prose dark:prose-invert whitespace-pre-line prose-p:leading-relaxed prose-pre:p-0 dark:prose-dark ${
|
className={`bg-[#2563eb] font-normal rounded-tr-none
|
||||||
props.message_type &&
|
text-white px-4 py-2.5 rounded-2xl prose-lg dark:prose-invert whitespace-pre-line prose-p:leading-relaxed prose-pre:p-0 dark:prose-dark ${
|
||||||
"italic text-gray-500 dark:text-gray-400 text-sm"
|
props.message_type && "italic dark:text-gray-400"
|
||||||
}`}>
|
} flex flex-row-reverse`}>
|
||||||
{props.message}
|
{props.message}
|
||||||
</p>
|
</p>
|
||||||
)
|
)
|
||||||
@ -166,26 +162,27 @@ export const PlaygroundMessage = (props: Props) => {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{props.isBot && props?.iodSources && props?.iodSources.length > 0 && (
|
{props.isBot && props?.webSources && props?.webSources.length > 0 && (
|
||||||
<Collapse
|
<Collapse
|
||||||
className="mt-6"
|
className="mt-6"
|
||||||
ghost
|
ghost
|
||||||
|
// defaultActiveKey={['webSources']}
|
||||||
items={[
|
items={[
|
||||||
{
|
{
|
||||||
key: "1",
|
key: "webSources",
|
||||||
label: (
|
label: (
|
||||||
<div className="italic text-gray-500 dark:text-gray-400">
|
<div className="italic text-gray-500 dark:text-gray-400">
|
||||||
{t("iodcitations")}
|
{t("webCitations")}
|
||||||
</div>
|
</div>
|
||||||
),
|
),
|
||||||
children: (
|
children: (
|
||||||
<div className="block">
|
<div className="mb-3 flex flex-wrap gap-2">
|
||||||
{props?.iodSources?.map((source, index) => (
|
{props?.webSources?.map((source, index) => (
|
||||||
<MessageSource
|
<MessageSource
|
||||||
onSourceClick={props.onSourceClick}
|
onSourceClick={props.onSourceClick}
|
||||||
key={index}
|
key={index}
|
||||||
|
index={index}
|
||||||
source={source}
|
source={source}
|
||||||
index = {index}
|
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
@ -194,26 +191,34 @@ export const PlaygroundMessage = (props: Props) => {
|
|||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{props.isBot && props?.sources && props?.sources.length > 0 && (
|
{props.isBot &&
|
||||||
|
props?.iodSources &&
|
||||||
|
Object.values(props?.iodSources)
|
||||||
|
.map((item) => item.data)
|
||||||
|
.flat().length > 0 && (
|
||||||
<Collapse
|
<Collapse
|
||||||
className="mt-6"
|
className="mt-6"
|
||||||
ghost
|
ghost
|
||||||
|
// defaultActiveKey={['iod']}
|
||||||
items={[
|
items={[
|
||||||
{
|
{
|
||||||
key: "1",
|
key: "iod",
|
||||||
label: (
|
label: (
|
||||||
<div className="italic text-gray-500 dark:text-gray-400">
|
<div className="italic text-gray-500 dark:text-gray-400">
|
||||||
{t("citations")}
|
{t("iodCitations")}
|
||||||
</div>
|
</div>
|
||||||
),
|
),
|
||||||
children: (
|
children: (
|
||||||
<div className="block">
|
<div className="mb-3 flex flex-wrap gap-2">
|
||||||
{props?.sources?.map((source, index) => (
|
{Object.values(props?.iodSources)
|
||||||
|
.map((item) => item.data)
|
||||||
|
.flat()
|
||||||
|
?.map((source, index) => (
|
||||||
<MessageSource
|
<MessageSource
|
||||||
onSourceClick={props.onSourceClick}
|
onSourceClick={props.onSourceClick}
|
||||||
key={index}
|
key={index}
|
||||||
|
index={index}
|
||||||
source={source}
|
source={source}
|
||||||
index = {index}
|
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
@ -234,7 +239,7 @@ export const PlaygroundMessage = (props: Props) => {
|
|||||||
// : "flex"
|
// : "flex"
|
||||||
}`}>
|
}`}>
|
||||||
{props.isTTSEnabled && (
|
{props.isTTSEnabled && (
|
||||||
<Tooltip title={t("tts")}>
|
<Tooltip title={t("tts")} className="hidden">
|
||||||
<button
|
<button
|
||||||
aria-label={t("tts")}
|
aria-label={t("tts")}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
@ -257,6 +262,18 @@ export const PlaygroundMessage = (props: Props) => {
|
|||||||
)}
|
)}
|
||||||
{props.isBot && (
|
{props.isBot && (
|
||||||
<>
|
<>
|
||||||
|
{/*数联网搜索*/}
|
||||||
|
{props.iodSearch && (
|
||||||
|
<Tooltip title="数联网信息">
|
||||||
|
<button
|
||||||
|
onClick={() => props.setCurrentMessageId(props.id)}
|
||||||
|
aria-label="数联网信息"
|
||||||
|
className="flex items-center justify-center w-6 h-6 rounded-full bg-gray-100 dark:bg-gray-800 hover:bg-gray-200 dark:hover:bg-gray-700 transition-colors duration-200 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-gray-500">
|
||||||
|
<PiNetwork className="w-3 h-3 text-gray-400 group-hover:text-gray-500" />
|
||||||
|
</button>
|
||||||
|
</Tooltip>
|
||||||
|
)}
|
||||||
|
|
||||||
{!props.hideCopy && (
|
{!props.hideCopy && (
|
||||||
<Tooltip title={t("copyToClipboard")}>
|
<Tooltip title={t("copyToClipboard")}>
|
||||||
<button
|
<button
|
||||||
@ -280,6 +297,7 @@ export const PlaygroundMessage = (props: Props) => {
|
|||||||
|
|
||||||
{props.generationInfo && (
|
{props.generationInfo && (
|
||||||
<Popover
|
<Popover
|
||||||
|
className="hidden"
|
||||||
content={
|
content={
|
||||||
<GenerationInfo generationInfo={props.generationInfo} />
|
<GenerationInfo generationInfo={props.generationInfo} />
|
||||||
}
|
}
|
||||||
@ -305,8 +323,8 @@ export const PlaygroundMessage = (props: Props) => {
|
|||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
{!props.hideEditAndRegenerate && (
|
{(!props.hideEditAndRegenerate && !props.isBot) && (
|
||||||
<Tooltip title={t("edit")}>
|
<Tooltip title={t("edit")} className="hidden">
|
||||||
<button
|
<button
|
||||||
onClick={() => setEditMode(true)}
|
onClick={() => setEditMode(true)}
|
||||||
aria-label={t("edit")}
|
aria-label={t("edit")}
|
||||||
@ -315,6 +333,51 @@ export const PlaygroundMessage = (props: Props) => {
|
|||||||
</button>
|
</button>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
)}
|
)}
|
||||||
|
{
|
||||||
|
<Tooltip title="收藏" className="hidden">
|
||||||
|
<button
|
||||||
|
aria-label="收藏"
|
||||||
|
className="flex items-center justify-center w-6 h-6 rounded-full bg-gray-100 dark:bg-gray-800 hover:bg-gray-200 dark:hover:bg-gray-700 transition-colors duration-200 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-gray-500">
|
||||||
|
<Star className="w-3 h-3 text-gray-400 group-hover:text-gray-500" />
|
||||||
|
</button>
|
||||||
|
</Tooltip>
|
||||||
|
}
|
||||||
|
{
|
||||||
|
<Tooltip title="发布语用" className="hidden">
|
||||||
|
<button
|
||||||
|
aria-label="发布语用"
|
||||||
|
className="flex items-center justify-center w-6 h-6 rounded-full bg-gray-100 dark:bg-gray-800 hover:bg-gray-200 dark:hover:bg-gray-700 transition-colors duration-200 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-gray-500">
|
||||||
|
<ArrowUpSquare className="w-3 h-3 text-gray-400 group-hover:text-gray-500" />
|
||||||
|
</button>
|
||||||
|
</Tooltip>
|
||||||
|
}
|
||||||
|
{
|
||||||
|
<Tooltip title="发布对话" className="hidden">
|
||||||
|
<button
|
||||||
|
aria-label="发布对话"
|
||||||
|
className="flex items-center justify-center w-6 h-6 rounded-full bg-gray-100 dark:bg-gray-800 hover:bg-gray-200 dark:hover:bg-gray-700 transition-colors duration-200 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-gray-500">
|
||||||
|
<MessageSquareShare className="w-3 h-3 text-gray-400 group-hover:text-gray-500" />
|
||||||
|
</button>
|
||||||
|
</Tooltip>
|
||||||
|
}
|
||||||
|
{
|
||||||
|
<Tooltip title="点赞" className="hidden">
|
||||||
|
<button
|
||||||
|
aria-label="点赞"
|
||||||
|
className="flex items-center justify-center w-6 h-6 rounded-full bg-gray-100 dark:bg-gray-800 hover:bg-gray-200 dark:hover:bg-gray-700 transition-colors duration-200 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-gray-500">
|
||||||
|
<ThumbsUp className="w-3 h-3 text-gray-400 group-hover:text-gray-500" />
|
||||||
|
</button>
|
||||||
|
</Tooltip>
|
||||||
|
}
|
||||||
|
{
|
||||||
|
<Tooltip title="点踩" className="hidden">
|
||||||
|
<button
|
||||||
|
aria-label="点踩"
|
||||||
|
className="flex items-center justify-center w-6 h-6 rounded-full bg-gray-100 dark:bg-gray-800 hover:bg-gray-200 dark:hover:bg-gray-700 transition-colors duration-200 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-gray-500">
|
||||||
|
<ThumbsDown className="w-3 h-3 text-gray-400 group-hover:text-gray-500" />
|
||||||
|
</button>
|
||||||
|
</Tooltip>
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
// add invisible div to prevent layout shift
|
// add invisible div to prevent layout shift
|
||||||
|
@ -1,6 +1,9 @@
|
|||||||
|
import { useState } from "react"
|
||||||
|
import type React from "react"
|
||||||
import { KnowledgeIcon } from "@/components/Option/Knowledge/KnowledgeIcon"
|
import { KnowledgeIcon } from "@/components/Option/Knowledge/KnowledgeIcon"
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
|
index: number
|
||||||
source: {
|
source: {
|
||||||
name?: string
|
name?: string
|
||||||
url?: string
|
url?: string
|
||||||
@ -8,42 +11,72 @@ type Props = {
|
|||||||
type?: string
|
type?: string
|
||||||
pageContent?: string
|
pageContent?: string
|
||||||
content?: string
|
content?: string
|
||||||
|
doId?: string
|
||||||
|
description?: string
|
||||||
}
|
}
|
||||||
key: number
|
|
||||||
onSourceClick?: (source: any) => void
|
onSourceClick?: (source: any) => void
|
||||||
index: number
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const MessageSource: React.FC<Props> = ({ source, key, onSourceClick, index}) => {
|
export const MessageSource: React.FC<Props> = ({
|
||||||
|
index,
|
||||||
|
source,
|
||||||
|
onSourceClick
|
||||||
|
}) => {
|
||||||
|
// Add state for tracking and content visibility
|
||||||
|
const [showContent, setShowContent] = useState(false)
|
||||||
|
|
||||||
if (source?.mode === "rag" || source?.mode === "chat") {
|
if (source?.mode === "rag" || source?.mode === "chat") {
|
||||||
return (
|
return (
|
||||||
<div className="block items-center gap-1 text-xs text-gray-800 dark:text-gray-100 mb-1">
|
<button
|
||||||
<span className="text-xs font-medium">[{index + 1}]</span> {/* 显示序号 */}
|
onClick={() => {
|
||||||
<span className="text-xs">{source.name}</span>
|
onSourceClick && onSourceClick(source)
|
||||||
<a
|
|
||||||
href={source?.url}
|
|
||||||
target="_blank"
|
|
||||||
className="text-xs text-blue-500 hover:underline"
|
|
||||||
onClick={(e) => {
|
|
||||||
e.preventDefault(); // 阻止默认的链接行为
|
|
||||||
onSourceClick && onSourceClick(source); // 调用自定义点击事件
|
|
||||||
}}
|
}}
|
||||||
>
|
className="inline-flex gap-2 cursor-pointer transition-shadow duration-300 ease-in-out hover:shadow-lg items-center rounded-md bg-gray-100 p-1 text-xs text-gray-800 border border-gray-300 dark:bg-gray-800 dark:border-gray-700 dark:text-gray-100 opacity-80 hover:opacity-100">
|
||||||
{source.url}
|
<KnowledgeIcon type={source.type} className="h-3 w-3" />
|
||||||
</a>
|
<span className="text-xs">{source.name}</span>
|
||||||
</div>
|
</button>
|
||||||
);
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const onContextMenu = (e: React.MouseEvent) => {
|
||||||
|
e.preventDefault()
|
||||||
|
e.stopPropagation
|
||||||
|
setShowContent(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="block items-center gap-1 text-xs text-gray-800 dark:text-gray-100 mb-1">
|
<div className="block items-center gap-1 text-xs text-gray-800 dark:text-gray-100 mb-1">
|
||||||
<span className="text-xs font-medium">[{index + 1}]</span> {/* 显示序号 */}
|
<span className="text-xs font-medium"></span>{" "}
|
||||||
<a
|
<a
|
||||||
href={source?.url}
|
href={source?.url}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
className="text-xs text-blue-500 hover:underline"
|
onContextMenu={onContextMenu}
|
||||||
>
|
className="inline-block cursor-pointer transition-shadow duration-300 ease-in-out hover:shadow-lg items-center rounded-md bg-gray-100 p-1 text-xs text-gray-800 border border-gray-300 dark:bg-gray-800 dark:border-gray-700 dark:text-gray-100 opacity-80 hover:opacity-100">
|
||||||
{source.name}
|
{source.doId ? (
|
||||||
|
<>
|
||||||
|
<span className="text-xs">
|
||||||
|
[{index + 1}] doid: {source.doId}
|
||||||
|
</span>
|
||||||
|
<br />
|
||||||
|
<span className="text-xs">{source.name}</span>
|
||||||
|
{showContent && (
|
||||||
|
<div className="mt-2 p-2 border-t border-gray-200 dark:border-gray-700">
|
||||||
|
{source.content || source.pageContent || source.description}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<span className="text-xs">
|
||||||
|
[{index + 1}] {source.name}
|
||||||
|
</span>
|
||||||
|
{showContent && (
|
||||||
|
<div className="mt-2 p-2 border-t border-gray-200 dark:border-gray-700">
|
||||||
|
{source.content || source.pageContent}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
150
src/components/Common/Playground/Scene.tsx
Normal file
150
src/components/Common/Playground/Scene.tsx
Normal file
@ -0,0 +1,150 @@
|
|||||||
|
import React, { useMemo } from "react"
|
||||||
|
import { DataNavigation } from "@/components/Common/DataNavigation.tsx"
|
||||||
|
import { Card, Skeleton } from "antd"
|
||||||
|
import { IodRegistryEntry } from "@/types/iod.ts"
|
||||||
|
import { useIodPlaygroundContext } from "@/components/Option/Playground/PlaygroundIod.tsx"
|
||||||
|
|
||||||
|
const defaultData: IodRegistryEntry[] = [
|
||||||
|
{
|
||||||
|
name: "绿色化工工艺项目",
|
||||||
|
description:
|
||||||
|
"基于生物基原料,采用repeal2.0可降解材料技术,开发新型环保材料。",
|
||||||
|
doId: "CSTR:13552.11.01.61.2021.742"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "智能农业解决方案",
|
||||||
|
description: "利用物联网技术,实现精准农业管理,提高农作物产量。",
|
||||||
|
doId: "CSTR:14542.11.01.61.2031.528"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "新能源汽车电池技术",
|
||||||
|
description: "研发高能量密度、长寿命的新型电池材料,推动电动汽车发展。",
|
||||||
|
doId: "CSTR:147842.11.04.91.2031.680"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "碳捕集与封存技术",
|
||||||
|
description: "开发高效的碳捕集技术,减少工业排放,助力碳中和目标。",
|
||||||
|
doId: "CSTR:14242.19.11.61.2131.428"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
type HeaderProps = {
|
||||||
|
title: string
|
||||||
|
showButton?: boolean
|
||||||
|
onClick?: () => void
|
||||||
|
}
|
||||||
|
const Header: React.FC<HeaderProps> = ({
|
||||||
|
title,
|
||||||
|
showButton = true,
|
||||||
|
onClick
|
||||||
|
}) => (
|
||||||
|
<DataNavigation
|
||||||
|
Header={
|
||||||
|
<div className="flex items-center text-[#4ab01a] gap-1">
|
||||||
|
<svg
|
||||||
|
className="icon"
|
||||||
|
viewBox="0 0 1025 1024"
|
||||||
|
version="1.1"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
p-id="6235"
|
||||||
|
width="18"
|
||||||
|
height="18">
|
||||||
|
<path
|
||||||
|
d="M980.34571 1.143792c-4.850903 0-9.824354 0.888481-14.797806 2.930966L229.773215 299.724504H20.428686c-11.233669 0-20.424853 9.446494-20.424853 21.180572V702.584302c0 11.74429 9.191184 21.180572 20.424853 21.180573h129.820365c-4.728353 14.808018-7.271248 30.51473-7.271248 46.46654 0 84.119757 68.678568 152.543014 153.176184 152.543014 70.721053 0 130.330986-47.998404 147.93721-112.847312l521.569043 209.59984c4.983664 1.919936 9.957116 2.930966 14.808019 2.930967 21.568645 0 40.839493-18.127057 40.839493-42.371358V43.525362C1021.195415 19.270849 1002.047116 1.143792 980.34571 1.143792zM296.153987 831.250663c-33.833769 0-61.274559-27.308028-61.274558-61.009035 0-14.297397 4.983664-27.951411 14.042086-38.807221l108.374269 43.525362c-2.553107 31.403211-28.972654 56.290895-61.141797 56.290894z m633.12959 74.550713L263.984844 638.501326l-16.462431-6.638077H91.915671V391.626129h155.606742l16.462431-6.638077 665.298733-267.30005v788.113374z m0 0"
|
||||||
|
fill="#4ab01a"
|
||||||
|
p-id="6236"></path>
|
||||||
|
</svg>
|
||||||
|
{title}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
showButton={showButton}
|
||||||
|
onClick={onClick}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
|
||||||
|
type MainProps = {
|
||||||
|
loading: boolean
|
||||||
|
data: IodRegistryEntry[]
|
||||||
|
truncate?: boolean
|
||||||
|
}
|
||||||
|
const Main: React.FC<MainProps> = ({ data, loading, truncate = true }) => (
|
||||||
|
<div className="space-y-1.5 flex-1 overflow-y-auto">
|
||||||
|
{data.map((item, index) => {
|
||||||
|
return (
|
||||||
|
<Card
|
||||||
|
className="[&_.ant-card-body]:!p-2 !bg-[gb(248, 248, 248)] border !border-[#e9e9e9]"
|
||||||
|
key={item.doId}>
|
||||||
|
{loading ? (
|
||||||
|
<Skeleton title={false} active />
|
||||||
|
) : (
|
||||||
|
<div className="flex flex-col gap-0.5">
|
||||||
|
<h3
|
||||||
|
className={`text-base font-medium mb-1 text-[#222222] break-all ${truncate ? "line-clamp-2" : ""}`}
|
||||||
|
title={item.name}>
|
||||||
|
{item.name}
|
||||||
|
</h3>
|
||||||
|
<p
|
||||||
|
className={`text-sm text-[#383838] break-all ${truncate ? "line-clamp-2" : ""}`}
|
||||||
|
title={item.doId}>
|
||||||
|
数字对象标识:{item.doId}
|
||||||
|
</p>
|
||||||
|
<p
|
||||||
|
className={`text-[#828282] text-xs break-all ${truncate ? "truncate" : ""}`}
|
||||||
|
title={item.description}>
|
||||||
|
{item.description}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</Card>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
className?: string
|
||||||
|
}
|
||||||
|
export const PlaygroundScene: React.FC<Props> = ({ className }) => {
|
||||||
|
const { iodLoading } = useMessageOption()
|
||||||
|
|
||||||
|
const {
|
||||||
|
setShowPlayground,
|
||||||
|
setDetailHeader,
|
||||||
|
setDetailMain,
|
||||||
|
currentIodMessage
|
||||||
|
} = useIodPlaygroundContext()
|
||||||
|
|
||||||
|
const data = useMemo<IodRegistryEntry[]>(() => {
|
||||||
|
return currentIodMessage
|
||||||
|
? currentIodMessage.scenario?.data ?? []
|
||||||
|
: defaultData
|
||||||
|
}, [currentIodMessage])
|
||||||
|
|
||||||
|
const title = useMemo(() => {
|
||||||
|
return currentIodMessage ? "推荐场景" : "热点场景"
|
||||||
|
}, [currentIodMessage])
|
||||||
|
|
||||||
|
const showMore = () => {
|
||||||
|
setShowPlayground(false)
|
||||||
|
setDetailHeader(
|
||||||
|
<Header
|
||||||
|
title={title}
|
||||||
|
showButton={false}
|
||||||
|
onClick={() => setShowPlayground(false)}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
setDetailMain(<Main loading={iodLoading && Boolean(currentIodMessage)} data={data} truncate={false} />)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Card className={`${className}`} hoverable>
|
||||||
|
<div className="h-full flex flex-col gap-2 relative">
|
||||||
|
{/* 数据导航 */}
|
||||||
|
<Header title={title} onClick={showMore} />
|
||||||
|
|
||||||
|
{/* 数据列表 */}
|
||||||
|
<Main loading={iodLoading && Boolean(currentIodMessage)} data={data.slice(0, 3)} />
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
)
|
||||||
|
}
|
158
src/components/Common/Playground/Team.tsx
Normal file
158
src/components/Common/Playground/Team.tsx
Normal file
@ -0,0 +1,158 @@
|
|||||||
|
import React, { useMemo } from "react"
|
||||||
|
import { DataNavigation } from "@/components/Common/DataNavigation.tsx"
|
||||||
|
import { Card, Skeleton } from "antd"
|
||||||
|
import { IodRegistryEntry } from "@/types/iod.ts"
|
||||||
|
import { useIodPlaygroundContext } from "@/components/Option/Playground/PlaygroundIod.tsx"
|
||||||
|
|
||||||
|
const defaultData: IodRegistryEntry[] = [
|
||||||
|
{
|
||||||
|
name: "北京大学",
|
||||||
|
description:
|
||||||
|
"北大是常为新的,改进的运动的先锋,要使中国向着好的,往上的道路走。",
|
||||||
|
doId: "12100000400002259P"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "长三角先进材料研究院",
|
||||||
|
description: "由江苏省人民政府联合中国科学院、中国钢研科技集团和中国",
|
||||||
|
doId: "91320507MAEKWL5Y2L"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "伊利诺伊大学香槟分校(UIUC)",
|
||||||
|
description: "创建于1867年,坐落于伊利诺伊州双子城厄巴纳–香槟市,",
|
||||||
|
doId: "bdware.org/uiuc"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
type HeaderProps = {
|
||||||
|
title: string
|
||||||
|
showButton?: boolean
|
||||||
|
onClick?: () => void
|
||||||
|
}
|
||||||
|
const Header: React.FC<HeaderProps> = ({
|
||||||
|
title,
|
||||||
|
showButton = true,
|
||||||
|
onClick
|
||||||
|
}) => (
|
||||||
|
<DataNavigation
|
||||||
|
Header={
|
||||||
|
<div className="flex items-center text-[#BE0BAC] gap-1">
|
||||||
|
<svg
|
||||||
|
className="icon"
|
||||||
|
viewBox="0 0 1024 1024"
|
||||||
|
version="1.1"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
p-id="7272"
|
||||||
|
width="18"
|
||||||
|
height="18">
|
||||||
|
<path
|
||||||
|
d="M824.2 699.9c-25.4-25.4-54.7-45.7-86.4-60.4C783.1 602.8 812 546.8 812 484c0-110.8-92.4-201.7-203.2-200-109.1 1.7-197 90.6-197 200 0 62.8 29 118.8 74.2 155.5-31.7 14.7-60.9 34.9-86.4 60.4C345 754.6 314 826.8 312 903.8c-0.1 4.5 3.5 8.2 8 8.2h56c4.3 0 7.9-3.4 8-7.7 1.9-58 25.4-112.3 66.7-153.5C493.8 707.7 551.1 684 612 684c60.9 0 118.2 23.7 161.3 66.8C814.5 792 838 846.3 840 904.3c0.1 4.3 3.7 7.7 8 7.7h56c4.5 0 8.1-3.7 8-8.2-2-77-33-149.2-87.8-203.9zM612 612c-34.2 0-66.4-13.3-90.5-37.5-24.5-24.5-37.9-57.1-37.5-91.8 0.3-32.8 13.4-64.5 36.3-88 24-24.6 56.1-38.3 90.4-38.7 33.9-0.3 66.8 12.9 91 36.6 24.8 24.3 38.4 56.8 38.4 91.4 0 34.2-13.3 66.3-37.5 90.5-24.2 24.2-56.4 37.5-90.6 37.5z"
|
||||||
|
p-id="7273"
|
||||||
|
fill="#BE0BAC"></path>
|
||||||
|
<path
|
||||||
|
d="M361.5 510.4c-0.9-8.7-1.4-17.5-1.4-26.4 0-15.9 1.5-31.4 4.3-46.5 0.7-3.6-1.2-7.3-4.5-8.8-13.6-6.1-26.1-14.5-36.9-25.1-25.8-25.2-39.7-59.3-38.7-95.4 0.9-32.1 13.8-62.6 36.3-85.6 24.7-25.3 57.9-39.1 93.2-38.7 31.9 0.3 62.7 12.6 86 34.4 7.9 7.4 14.7 15.6 20.4 24.4 2 3.1 5.9 4.4 9.3 3.2 17.6-6.1 36.2-10.4 55.3-12.4 5.6-0.6 8.8-6.6 6.3-11.6-32.5-64.3-98.9-108.7-175.7-109.9-110.9-1.7-203.3 89.2-203.3 199.9 0 62.8 28.9 118.8 74.2 155.5-31.8 14.7-61.1 35-86.5 60.4-54.8 54.7-85.8 126.9-87.8 204-0.1 4.5 3.5 8.2 8 8.2h56.1c4.3 0 7.9-3.4 8-7.7 1.9-58 25.4-112.3 66.7-153.5 29.4-29.4 65.4-49.8 104.7-59.7 3.9-1 6.5-4.7 6-8.7z"
|
||||||
|
p-id="7274"
|
||||||
|
fill="#BE0BAC"></path>
|
||||||
|
</svg>
|
||||||
|
{title}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
showButton={showButton}
|
||||||
|
onClick={onClick}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
|
||||||
|
type MainProps = {
|
||||||
|
loading: boolean
|
||||||
|
data: IodRegistryEntry[]
|
||||||
|
truncate?: boolean
|
||||||
|
// 水平展示三个还是按列展示(页面和详情展示不一样)
|
||||||
|
flat?: boolean
|
||||||
|
}
|
||||||
|
const Main: React.FC<MainProps> = ({
|
||||||
|
data,
|
||||||
|
loading,
|
||||||
|
truncate = true,
|
||||||
|
flat = true
|
||||||
|
}) => (
|
||||||
|
<div
|
||||||
|
className={`${flat ? "grid grid-cols-3 gap-3" : "space-y-1.5"} flex-1 overflow-y-auto`}>
|
||||||
|
{data.map((item) => {
|
||||||
|
return (
|
||||||
|
<Card
|
||||||
|
className="[&_.ant-card-body]:!p-2 !bg-[gb(248, 248, 248)] border !border-[#e9e9e9]"
|
||||||
|
key={item.doId}>
|
||||||
|
{loading ? (
|
||||||
|
<Skeleton title={false} active />
|
||||||
|
) : (
|
||||||
|
<div className="flex flex-col gap-0.5">
|
||||||
|
<h3
|
||||||
|
className={`text-base font-medium mb-1 text-[#222222] break-all ${truncate ? "line-clamp-2" : ""}`}
|
||||||
|
title={item.name}>
|
||||||
|
{item.name}
|
||||||
|
</h3>
|
||||||
|
<p
|
||||||
|
className={`text-sm text-[#383838] break-all ${truncate ? "line-clamp-2" : ""}`}
|
||||||
|
title={item.doId}>
|
||||||
|
数字对象标识:{item.doId}
|
||||||
|
</p>
|
||||||
|
<p
|
||||||
|
className={`text-[#828282] text-xs break-all ${truncate ? "truncate" : ""}`}
|
||||||
|
title={item.description}>
|
||||||
|
{item.description}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</Card>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
className?: string
|
||||||
|
}
|
||||||
|
export const PlaygroundTeam: React.FC<Props> = ({ className }) => {
|
||||||
|
const { iodLoading } = useMessageOption()
|
||||||
|
|
||||||
|
const {
|
||||||
|
setShowPlayground,
|
||||||
|
setDetailHeader,
|
||||||
|
setDetailMain,
|
||||||
|
currentIodMessage
|
||||||
|
} = useIodPlaygroundContext()
|
||||||
|
|
||||||
|
const data = useMemo<IodRegistryEntry[]>(() => {
|
||||||
|
return currentIodMessage
|
||||||
|
? currentIodMessage.organization?.data ?? []
|
||||||
|
: defaultData
|
||||||
|
}, [currentIodMessage])
|
||||||
|
|
||||||
|
const title = useMemo(() => {
|
||||||
|
return currentIodMessage ? "推荐团队" : "热点团队"
|
||||||
|
}, [currentIodMessage])
|
||||||
|
|
||||||
|
const showMore = () => {
|
||||||
|
setShowPlayground(false)
|
||||||
|
setDetailHeader(
|
||||||
|
<Header
|
||||||
|
title={title}
|
||||||
|
showButton={false}
|
||||||
|
onClick={() => setShowPlayground(false)}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
setDetailMain(
|
||||||
|
<Main loading={iodLoading && Boolean(currentIodMessage)} data={data} truncate={false} flat={false} />
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Card className={`${className}`} hoverable>
|
||||||
|
<div className="h-full flex flex-col gap-2 relative">
|
||||||
|
{/* 数据导航 */}
|
||||||
|
<Header title={title} onClick={showMore} />
|
||||||
|
{/* 数据列表 */}
|
||||||
|
<Main loading={iodLoading && Boolean(currentIodMessage)} data={data.slice(0, 3)} />
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
)
|
||||||
|
}
|
46
src/components/Common/Playground/TokenStatistics.tsx
Normal file
46
src/components/Common/Playground/TokenStatistics.tsx
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
import { DataNavigation } from "@/components/Common/DataNavigation.tsx"
|
||||||
|
import { Card, Descriptions, DescriptionsProps, Drawer, List, Spin } from "antd"
|
||||||
|
import { useCallback, useMemo, useState } from "react"
|
||||||
|
import { useMessageOption } from "@/hooks/useMessageOption.tsx"
|
||||||
|
import { useStoreMessageOption } from "@/store/option.tsx"
|
||||||
|
|
||||||
|
export const PlaygroundTokenStatistics = () => {
|
||||||
|
const { currentMeteringEntry } = useStoreMessageOption()
|
||||||
|
|
||||||
|
const items = useMemo<DescriptionsProps["items"]>(() => {
|
||||||
|
const { data } = currentMeteringEntry
|
||||||
|
return [
|
||||||
|
// {
|
||||||
|
// key: "relatedDataCount",
|
||||||
|
// label: "关联数据个数",
|
||||||
|
// children: data.relatedDataCount
|
||||||
|
// },
|
||||||
|
{
|
||||||
|
key: "iodTokenCount",
|
||||||
|
label: "数联网引用token总数",
|
||||||
|
children: data.iodTokenCount ?? 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "modelInputTokenCount",
|
||||||
|
label: "大模型输入token数量",
|
||||||
|
children: data.modelInputTokenCount ?? 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "modelOutputTokenCount",
|
||||||
|
label: "大模型输出token数量",
|
||||||
|
children: data.modelOutputTokenCount ?? 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}, [currentMeteringEntry])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Card
|
||||||
|
style={{ marginBottom: "1rem" }}
|
||||||
|
className="h-full"
|
||||||
|
title={<DataNavigation title="Token统计" showButton={false} />}>
|
||||||
|
<Spin spinning={currentMeteringEntry.loading}>
|
||||||
|
<Descriptions layout="horizontal" items={items} column={2} />
|
||||||
|
</Spin>
|
||||||
|
</Card>
|
||||||
|
)
|
||||||
|
}
|
@ -1,15 +1,13 @@
|
|||||||
import { Form, Image, Input, Modal, Tooltip, message } from "antd"
|
import { Form, Image, Input, message, Modal } from "antd"
|
||||||
import { Share } from "lucide-react"
|
|
||||||
import { useState } from "react"
|
|
||||||
import type { Message } from "~/store/option"
|
|
||||||
import Markdown from "./Markdown"
|
|
||||||
import React from "react"
|
import React from "react"
|
||||||
|
import Markdown from "./Markdown"
|
||||||
import { useMutation } from "@tanstack/react-query"
|
import { useMutation } from "@tanstack/react-query"
|
||||||
import { getPageShareUrl } from "~/services/ollama"
|
import { getPageShareUrl } from "~/services/ollama"
|
||||||
import { cleanUrl } from "~/libs/clean-url"
|
import { cleanUrl } from "~/libs/clean-url"
|
||||||
import { getTitleById, getUserId, saveWebshare } from "@/db"
|
import { getTitleById, getUserId, saveWebshare } from "@/db"
|
||||||
import { useTranslation } from "react-i18next"
|
import { useTranslation } from "react-i18next"
|
||||||
import fetcher from "@/libs/fetcher"
|
import fetcher from "@/libs/fetcher"
|
||||||
|
import { Message } from "@/types/message.ts"
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
messages: Message[]
|
messages: Message[]
|
||||||
|
21
src/components/Icons/Battery.tsx
Normal file
21
src/components/Icons/Battery.tsx
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
import React from "react"
|
||||||
|
|
||||||
|
export const BatteryIcon = React.forwardRef<
|
||||||
|
SVGSVGElement,
|
||||||
|
React.SVGProps<SVGSVGElement>
|
||||||
|
>((props, ref) => {
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
className="icon"
|
||||||
|
viewBox="0 0 1024 1024"
|
||||||
|
version="1.1"
|
||||||
|
fill="currentColor"
|
||||||
|
fillRule="evenodd"
|
||||||
|
ref={ref}
|
||||||
|
{...props}>
|
||||||
|
<path
|
||||||
|
d="M604.16 112.64c13.568-35.0208-5.9904-57.7024-24.1152-80.8448H452.7616C436.2752 56.32 419.84 80.4864 434.176 112.64zM194.56 888.3712a213.4528 213.4528 0 0 0 21.504 2.304c195.7888 0 391.5776 0 587.3152 0.6656 25.1904 0 29.4912-10.24 29.3888-32.1024-0.6144-202.0864-3.1232-633.7024-3.1232-633.7024H194.56zM597.3504 307.712l7.1168 4.2496c-25.6 73.1136-50.8928 146.2272-78.4384 225.28h139.0592L437.9648 824.32l-8.6016-2.7648c17.92-71.0144 35.84-142.0288 54.9888-217.7536l-134.656-5.7856zM192.6656 926.72c-4.096 41.8304 14.7456 64.4096 54.3744 64.8192 66.56 0.6656 133.12 0 200.0384 0 105.8304 0 211.6608 0.3072 317.44 0 44.6464 0 69.6832-25.856 64.512-64.8704zM777.4208 141.0048c-20.992-1.6896-42.24-0.4096-63.3856-0.4096H257.4848c-41.3696 0-57.9584 12.3904-66.0992 49.3568h641.2288c-6.5024-32.5632-26.368-46.592-55.1936-48.9472z"
|
||||||
|
p-id="50919"></path>
|
||||||
|
</svg>
|
||||||
|
)
|
||||||
|
})
|
26
src/components/Icons/Bell.tsx
Normal file
26
src/components/Icons/Bell.tsx
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
import React from "react"
|
||||||
|
|
||||||
|
export const BellIcon = React.forwardRef<
|
||||||
|
SVGSVGElement,
|
||||||
|
React.SVGProps<SVGSVGElement>
|
||||||
|
>((props, ref) => {
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
className="icon"
|
||||||
|
viewBox="0 0 1024 1024"
|
||||||
|
version="1.1"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
p-id="76534"
|
||||||
|
fill="currentColor"
|
||||||
|
fillRule="evenodd"
|
||||||
|
ref={ref}
|
||||||
|
{...props}>
|
||||||
|
<path
|
||||||
|
d="M593.861938 788.582269 424.670537 788.582269c-9.444093 0-18.437931 3.931542-24.695448 10.902304-6.313799 6.970762-9.441023 16.32378-8.547677 25.675776 2.860141 29.191856 16.32378 56.238862 38.009685 76.018348 21.772886 20.016893 50.161447 31.0379 79.889515 31.0379 29.696346 0 58.084906-11.022031 79.830163-30.977525 21.714558-19.839861 35.178197-46.885843 38.068014-76.255755 0.595564-9.473769-2.534729-18.707061-8.638751-25.498744C612.299869 792.513812 603.306031 788.582269 593.861938 788.582269zM555.020304 863.825974c-25.082258 22.877033-66.604954 22.817682-91.567485 0.060375-7.596002-6.970762-13.404288-15.429411-17.157775-24.723078l125.82266 0C568.394916 848.51629 562.643935 856.914564 555.020304 863.825974z"
|
||||||
|
p-id="76535"></path>
|
||||||
|
<path
|
||||||
|
d="M818.608631 648.343271l-62.763462-82.927711 0-36.22197 0-13.046131L755.845169 410.432767c0-70.745251-24.215518-136.337131-68.182892-184.682209-26.003234-28.625968-57.310264-49.715285-93.055372-62.821791-3.306302-18.944468-12.720719-36.251645-26.926256-49.207725-32.050973-29.251208-85.104283-29.251208-117.095905 0-14.356986 13.046131-23.77038 30.382984-26.986631 49.2681-35.71441 13.046131-67.022463 34.135448-93.025697 62.791092-43.937698 48.434106-68.183915 114.025986-68.183915 184.652534l0.179079 154.686035-62.315254 82.45085c-8.757454 9.353019-13.582343 21.506826-13.582343 34.256198l0 40.331567c0 27.643594 22.460548 50.042743 50.042743 50.042743l544.812313 0c27.610848 0 50.011021-22.400173 50.011021-50.042743l0-40.331567C831.535035 669.075455 826.739822 656.921647 818.608631 648.343271zM535.776008 149.881612c-7.387247-0.655939-19.301602-1.906419-26.569122-1.906419-7.29822 0-19.689435 1.251503-27.048029 1.906419C494.578724 129.627313 526.542716 133.379777 535.776008 149.881612zM237.426992 722.156394l-0.119727-40.034808 62.315254-82.449827c8.698103-9.354042 13.524015-21.447475 13.524015-34.256198L313.146535 410.432767c0-58.056254 19.540032-111.553679 54.986335-150.634766 17.574261-19.361977 38.307468-34.374902 61.540611-44.681642 48.851615-21.745257 110.302175-21.745257 159.096485 0 23.321148 10.425444 43.99398 25.438369 61.538565 44.681642 35.449373 39.081087 54.958706 92.578512 54.958706 150.634766l0 105.715717 0 13.046131 0 36.22197c0 12.867052 4.825912 25.081235 12.95608 33.539884l62.791092 82.868359 0.508583 39.795355L237.426992 722.156394z"
|
||||||
|
p-id="76536"></path>
|
||||||
|
</svg>
|
||||||
|
)
|
||||||
|
})
|
26
src/components/Icons/Check.tsx
Normal file
26
src/components/Icons/Check.tsx
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
import React from "react"
|
||||||
|
|
||||||
|
export const CheckIcon = React.forwardRef<
|
||||||
|
SVGSVGElement,
|
||||||
|
React.SVGProps<SVGSVGElement>
|
||||||
|
>((props, ref) => {
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
className="icon"
|
||||||
|
viewBox="0 0 1024 1024"
|
||||||
|
version="1.1"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
p-id="41530"
|
||||||
|
fill="currentColor"
|
||||||
|
fillRule="evenodd"
|
||||||
|
ref={ref}
|
||||||
|
{...props}>
|
||||||
|
<path
|
||||||
|
d="M334.935114 642.334328l-185.247485-227.447917c-16.445757-20.169324-13.342784-49.957864 6.826541-66.713918L571.071355 10.569036c20.169324-16.445757 49.957864-13.342784 66.713919 6.826541l185.247484 227.447916c16.445757 20.169324 13.342784 49.957864-6.82654 66.713919L401.649033 649.160868c-20.479621 16.445757-50.268162 13.342784-66.713919-6.82654zM189.71598 693.843679L39.53209 509.216788c-14.273676-17.376648-11.481-43.131324 5.895648-57.404999l5.585352-4.654459c17.376648-14.273676 43.131324-11.481 57.404999 5.895648l150.494188 184.62689c14.273676 17.376648 11.481 43.131324-5.895649 57.405l-5.585351 4.654459c-17.686946 14.273676-43.441621 11.481-57.715297-5.895648zM877.024488 1024H275.668331a44.372513 44.372513 0 1 1 0-88.745026h601.356157a44.372513 44.372513 0 1 1 0 88.745026z"
|
||||||
|
p-id="41531"></path>
|
||||||
|
<path
|
||||||
|
d="M564.555112 345.06952l-77.264026-94.950972c-16.445757-20.169324-13.342784-49.957864 6.82654-66.713919l199.521161-162.595782c20.169324-16.445757 49.957864-13.342784 66.713918 6.82654l77.264026 94.950972c16.445757 20.169324 13.342784 49.957864-6.82654 66.713919l-199.521161 162.595782c-20.169324 16.445757-50.268162 13.342784-66.713918-6.82654zM646.163301 1020.58673l-94.950973-79.746405c51.509351-61.438864 77.574324-137.771999 72.919865-215.346322-4.344162-76.022837-37.85627-143.977945-94.330378-191.143134l79.746405-94.950972c82.849378 69.506594 131.87635 168.491431 138.392593 278.957268 6.205946 109.224648-29.78854 216.587512-101.777512 302.229565z"
|
||||||
|
p-id="41532"></path>
|
||||||
|
</svg>
|
||||||
|
)
|
||||||
|
})
|
24
src/components/Icons/Collect.tsx
Normal file
24
src/components/Icons/Collect.tsx
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
import React from "react"
|
||||||
|
|
||||||
|
export const CollectIcon = React.forwardRef<
|
||||||
|
SVGSVGElement,
|
||||||
|
React.SVGProps<SVGSVGElement>
|
||||||
|
>((props, ref) => {
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
className="icon"
|
||||||
|
viewBox="0 0 1024 1024"
|
||||||
|
version="1.1"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
p-id="73631"
|
||||||
|
fill="currentColor"
|
||||||
|
fillRule="evenodd"
|
||||||
|
ref={ref}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M536.934 860.314c-26.828-14.797-70.502-14.695-97.177 0L251.238 964.096c-53.606 29.542-88.78 2.56-78.54-59.802l35.993-219.801c5.12-31.335-8.448-74.752-30.054-96.768L26.163 431.975c-43.417-44.289-29.696-87.655 30.003-96.769l210.74-32c30.003-4.608 65.28-31.539 78.643-59.852l94.259-199.936c26.829-56.884 70.451-56.679 97.126 0l94.208 199.987c13.466 28.467 48.896 55.296 78.695 59.853l210.739 32.05c60.006 9.114 73.216 52.583 30.054 96.718L798.106 587.674c-21.71 22.17-35.124 65.69-30.055 96.819l35.994 219.801c10.24 62.567-25.14 89.19-78.541 59.802l-188.57-103.782z"
|
||||||
|
p-id="73632"></path>
|
||||||
|
</svg>
|
||||||
|
)
|
||||||
|
})
|
24
src/components/Icons/DataProject.tsx
Normal file
24
src/components/Icons/DataProject.tsx
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
import React from "react"
|
||||||
|
|
||||||
|
export const DataProjectIcon = React.forwardRef<
|
||||||
|
SVGSVGElement,
|
||||||
|
React.SVGProps<SVGSVGElement>
|
||||||
|
>((props, ref) => {
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
className="icon"
|
||||||
|
viewBox="0 0 1024 1024"
|
||||||
|
version="1.1"
|
||||||
|
fill="currentColor"
|
||||||
|
fillRule="evenodd"
|
||||||
|
ref={ref}
|
||||||
|
{...props}>
|
||||||
|
<path
|
||||||
|
d="M669.538462 315.076923a185.659077 185.659077 0 0 1 122.171076 325.474462 354.500923 354.500923 0 0 1 232.093539 321.378461l0.196923 11.421539c0 27.963077-22.685538 50.648615-50.688 50.648615H365.764923a50.688 50.688 0 0 1-50.412308-45.489231L315.076923 973.312a354.579692 354.579692 0 0 1 232.290462-332.8A185.659077 185.659077 0 0 1 669.538462 315.076923z m-263.404308 161.910154a267.815385 267.815385 0 0 0-1.024 23.748923l0.196923 11.027692c1.378462 32.846769 8.782769 64.630154 21.464615 93.971693l2.087385 4.489846v2.756923l-1.220923 0.866461A433.742769 433.742769 0 0 0 249.619692 866.461538H126.936615A87.512615 87.512615 0 0 1 39.384615 778.948923v-214.449231c0-48.324923 39.187692-87.512615 87.512616-87.512615h279.236923zM341.346462 0c48.324923 0 87.512615 39.187692 87.512615 87.512615V389.513846H126.897231A87.512615 87.512615 0 0 1 39.384615 301.961846V87.512615C39.384615 39.187692 78.572308 0 126.897231 0h214.449231z m476.947692 0C866.697846 0 905.846154 39.187692 905.846154 87.512615v294.4A264.428308 264.428308 0 0 0 516.332308 285.144615V87.512615C516.371692 39.187692 555.559385 0 603.884308 0h214.44923z"
|
||||||
|
p-id="11293"></path>
|
||||||
|
<path
|
||||||
|
d="M24.024615 24.615385L23.630769 22.646154l0.393846 1.969231zM24.024615 1001.353846l-0.393846-1.969231 0.393846 1.969231zM1000.763077 24.615385l-0.393846-1.969231 0.393846 1.969231zM1000.763077 1001.353846l-0.393846-1.969231 0.393846 1.969231z"
|
||||||
|
p-id="11294"></path>
|
||||||
|
</svg>
|
||||||
|
)
|
||||||
|
})
|
27
src/components/Icons/Dataset.tsx
Normal file
27
src/components/Icons/Dataset.tsx
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
import React from "react"
|
||||||
|
|
||||||
|
export const DatasetIcon = React.forwardRef<
|
||||||
|
SVGSVGElement,
|
||||||
|
React.SVGProps<SVGSVGElement>
|
||||||
|
>((props, ref) => {
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
className="icon"
|
||||||
|
viewBox="0 0 1153 1024"
|
||||||
|
version="1.1"
|
||||||
|
fill="currentColor"
|
||||||
|
fillRule="evenodd"
|
||||||
|
ref={ref}
|
||||||
|
{...props}>
|
||||||
|
<path
|
||||||
|
d="M849.992624 307.054752c-56.549976 0-109.613995 0.871489-162.581182 0a39.701182 39.701182 0 0 0-40.282175 23.239716c-15.29948 27.403499-31.857778 54.226005-49.190733 80.370686A36.118392 36.118392 0 0 1 576.054468 426.061466C416.378251 441.167281 257.670355 439.908463 104.966052 380.35669a323.419385 323.419385 0 0 1-45.123782-22.852387C19.850591 334.167754-2.420804 300.857494 0.871489 252.344586a735.92435 735.92435 0 0 0 0-77.465721A98.478298 98.478298 0 0 1 46.285768 87.148936c51.030544-36.312057 109.80766-53.257683 169.940426-65.071206C384.810969-11.329362 552.427423-8.811726 716.55792 44.252293a419.767376 419.767376 0 0 1 93.927186 48.416076c28.274988 18.01078 39.894846 46.963593 39.313854 81.04851-0.387329 41.444161 0.193664 83.178818 0.193664 133.337873zM724.594988 957.766809c-52.967187 10.554704-105.740709 22.755556-159.095224 31.373617a981.587518 981.587518 0 0 1-293.595083 0 625.535697 625.535697 0 0 1-193.664303-55.581655c-52.289362-25.951017-82.791489-63.618723-77.465721-125.881797 3.195461-40.088511 0.580993-80.56435 0.580993-122.976832 58.099291 78.724539 144.279905 96.832151 229.879527 113.487281 111.16331 21.399905 223.391773 28.274988 335.717069 5.713097 14.137494-2.808132 21.496738 2.517636 28.178156 15.202648 16.267801 30.59896 35.634232 59.454941 51.417872 90.150733a58.099291 58.099291 0 0 0 50.159055 34.762742 189.50052 189.50052 0 0 1 27.113002 6.29409zM1.161986 397.689645c25.66052 44.54279 61.972577 65.071206 101.092766 81.242175A723.820331 723.820331 0 0 0 358.27896 531.027518c65.749031 1.936643 131.691726-5.616265 197.537588-7.456076a35.5374 35.5374 0 0 1 26.338346 13.556501 751.514326 751.514326 0 0 1 45.414279 79.692861c3.58279 7.262411 0 18.301277-1.258818 27.500331 0 2.808132-3.970118 5.035272-5.422601 7.843404-19.36643 38.732861-49.481229 56.646809-94.701844 60.32643-142.343262 11.426194-281.491064 1.936643-416.37825-47.350922a320.804917 320.804917 0 0 1-43.574469-20.818912C21.980898 619.725768-2.808132 584.575697 0.580993 530.737021c2.7113-41.734657 0.580993-83.856643 0.580993-133.047376zM1017.899574 477.57617c0-32.148274-0.774657-59.551773 0.580993-87.148936 0-6.100426 7.746572-12.104019 12.685012-17.429787 11.135697-11.910355 13.846998-26.822506 1.258818-35.731064-7.64974-5.4226-30.59896-4.454279-33.213428 0.580993-15.202648 29.049645-45.607943 34.375414-68.653995 50.546383a312.864681 312.864681 0 0 1-30.017967 17.139291c-9.683215-15.29948-19.36643-29.72747-27.306667-45.123783a21.399905 21.399905 0 0 1 1.646147-17.429787c18.591773-33.794421 37.474043-67.782506 58.099291-100.511773a32.729267 32.729267 0 0 1 22.561891-13.556501c37.086714-1.35565 74.27026-1.452482 111.260142 0a35.731064 35.731064 0 0 1 24.789031 14.137494c20.818913 31.567281 40.572671 64.006052 58.873948 96.832151a35.150071 35.150071 0 0 1 0 27.984492c-15.008983 29.049645-33.213428 57.227801-48.416076 86.567943-8.908558 17.429787-20.334752 25.176359-39.894846 23.046052a405.242553 405.242553 0 0 0-44.252294 0.096832zM900.539007 842.439716c28.37182 16.267801 55.000662 30.986288 80.951679 46.866762 4.551111 2.808132 5.906761 10.457872 9.683215 14.912151a76.594232 76.594232 0 0 0 24.014373 22.271395 25.370024 25.370024 0 0 0 20.915745-12.781844 72.430449 72.430449 0 0 0-8.811726-31.179953c-2.323972-6.197258-9.102222-11.523026-9.683215-17.429787-0.968322-29.049645 0-58.099291 0-90.44123 19.36643 0 38.055035-0.677825 56.162648 0a22.271395 22.271395 0 0 1 14.331158 10.264208c20.141087 33.891253 39.991678 67.782506 58.099291 102.738913a29.049645 29.049645 0 0 1-0.580993 23.917541c-17.720284 33.31026-36.312057 66.330024-56.25948 98.478298a32.148274 32.148274 0 0 1-22.658723 12.491348q-55.484823 2.033475-111.16331 0a32.148274 32.148274 0 0 1-22.368227-12.685012q-30.405296-47.931915-57.324633-97.994137a34.084917 34.084917 0 0 1 0-25.854185A401.272435 401.272435 0 0 1 900.539007 842.439716z"
|
||||||
|
p-id="6804"></path>
|
||||||
|
<path
|
||||||
|
d="M875.362648 419.96104c-26.628842 15.493144-51.32104 30.308463-76.691064 43.961797-4.551111 2.420804-12.200851-1.646147-17.817116 0-10.748369 2.517636-24.692199 4.06695-30.308463 11.329362-4.357447 5.713097 1.258818 19.36643 3.776454 29.630638 1.742979 7.165579 8.037069 13.750165 8.327565 20.72208 0.968322 29.049645 0 58.099291 0 90.92539-19.36643 0-37.667707 0.968322-55.678487-0.580993a25.176359 25.176359 0 0 1-15.008984-12.781844c-19.36643-34.278582-38.732861-68.653995-56.549976-103.900898a36.118392 36.118392 0 0 1 0.774657-28.37182c16.267801-31.373617 33.600757-62.359905 52.870355-91.990544a40.669504 40.669504 0 0 1 27.790827-16.267801c32.051442-2.033475 64.393381 0 96.832151-1.258818 20.334752-1.065154 33.794421 4.551111 41.928322 23.820709a381.22818 381.22818 0 0 0 19.753759 34.762742zM763.037352 639.092199c0 33.503924 0.774657 62.55357 0 91.40955 0 7.64974-8.424397 14.621655-10.36104 22.561892s-6.003593 22.658723-1.452482 27.984491 18.591773 7.262411 29.049645 9.00539a242.080378 242.080378 0 0 1 32.826099 3.292294 134.790355 134.790355 0 0 1 31.664114 17.332955c10.845201 7.359243 30.695792 18.785437 29.049645 23.530212a193.664303 193.664303 0 0 1-27.113002 50.352719c-2.130307 3.195461-10.264208 3.292293-15.589976 3.292293-38.732861 0-77.465721 1.065154-116.198582 0a34.375414 34.375414 0 0 1-23.723877-14.524822c-19.36643-30.695792-37.474043-62.069409-53.838676-94.314516a38.732861 38.732861 0 0 1 0-30.405295c14.427991-29.049645 32.632435-55.581655 46.866761-84.340804 9.683215-19.36643 21.303073-28.274988 43.090307-25.079527A246.050496 246.050496 0 0 0 763.037352 639.092199zM1017.899574 752.095319c0-31.373617-0.580993-58.680284 0-85.890118a30.017967 30.017967 0 0 1 9.683216-17.139291c15.589976-14.912151 15.686809-28.178156 1.452482-42.993475a40.088511 40.088511 0 0 1-11.038865-23.433381c-1.452482-25.466856-0.580993-51.127376-0.580993-79.983357 20.72208 0 38.732861-1.161986 57.130969 0.677825a29.630638 29.630638 0 0 1 17.236123 14.137495c19.36643 31.276785 39.410686 62.747234 56.937305 95.282836a38.055035 38.055035 0 0 1 0 30.211632c-15.202648 30.114799-33.891253 58.099291-49.384397 88.504586-8.037069 15.589976-17.623452 22.94922-35.5374 21.012577a450.075839 450.075839 0 0 0-45.89844-0.387329z"
|
||||||
|
p-id="6805"></path>
|
||||||
|
<path
|
||||||
|
d="M875.45948 551.943262c-32.535603 68.84766-35.924728 70.881135-103.51357 62.650402 0-31.276785-1.161986-63.328227 1.258818-95.089172 0-4.744775 20.334752-14.234326 26.435177-11.329362 25.951017 11.813522 50.159054 27.984492 75.819575 43.768132zM879.816927 697.966147c-27.693995 16.461466-54.03234 33.213428-81.532672 47.641418-14.427991 7.64974-25.66052 0.580993-26.338345-16.074137-1.161986-24.885863 0-49.771726 0-74.754421a19.947423 19.947423 0 0 1 4.066951-13.266005c10.554704-10.264208 71.074799 0 78.724539 12.58818s15.783641 27.693995 25.079527 43.864965zM907.510922 834.693144c39.410686-73.398771 16.945626-60.035934 102.15792-58.099291 0 31.470449 0.580993 63.134563-0.968322 94.701844 0 3.292293-15.493144 10.457872-20.044255 8.230733-27.209835-13.750165-53.354515-29.340142-81.145343-44.833286zM909.060236 416.378251c25.079527-14.912151 48.997069-29.533806 73.398771-43.283972 15.686809-8.908558 25.757352 0 26.725674 14.427991 1.936643 29.049645 0.580993 57.518298 0.580993 85.599621-56.356312 14.331158-71.55896 5.809929-100.705438-56.74364zM1009.765674 504.882837v87.148936c0 14.234326-9.683215 18.688605-21.399906 12.200851-25.854184-14.234326-51.127376-29.824303-76.206903-44.542789 14.331158-54.613333 29.921135-63.618723 97.606809-54.806998zM904.218629 568.114232c26.628842 15.880473 52.095697 31.083121 77.465721 46.479432 11.329362 6.778251 12.394515 14.718487 0.677825 21.884066-25.273191 15.589976-50.836879 30.695792-75.819575 45.704776-42.025154-43.090307-42.509314-58.680284-2.323971-114.068274zM1009.765674 749.965012c-64.780709 7.359243-66.233191 6.487754-99.252955-55.097494 26.628842-15.29948 53.160851-31.276785 80.56435-45.704776 10.845201-5.616265 18.688605-0.580993 18.688605 12.975509zM883.883877 709.973333c39.798014 60.616927 38.732861 46.479433-1.258818 110.098156L788.213712 764.973995zM791.31234 488.421371c7.359243-5.809929 10.264208-8.618061 13.556502-10.651536 24.789031-14.718487 49.674894-29.049645 74.27026-43.671301 36.796217 38.151868 37.086714 54.322837 0.580993 106.999527z"
|
||||||
|
p-id="6806"></path>
|
||||||
|
</svg>
|
||||||
|
)
|
||||||
|
})
|
33
src/components/Icons/Iod.tsx
Normal file
33
src/components/Icons/Iod.tsx
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
import React from "react"
|
||||||
|
|
||||||
|
export const IodIcon = React.forwardRef<
|
||||||
|
SVGSVGElement,
|
||||||
|
React.SVGProps<SVGSVGElement>
|
||||||
|
>((props, ref) => {
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
className="icon"
|
||||||
|
viewBox="0 0 1088 1024"
|
||||||
|
version="1.1"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
fill="currentColor"
|
||||||
|
fillRule="evenodd"
|
||||||
|
p-id="32289"
|
||||||
|
{...props}
|
||||||
|
ref={ref}
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M853.333333 458.666667h21.333334v21.333333h-21.333334zM680.533333 217.6h21.333334v21.333333h-21.333334zM740.266667 264.533333h21.333333v21.333334h-21.333333zM629.333333 177.066667h21.333334v21.333333h-21.333334zM398.933333 177.066667h21.333334v21.333333h-21.333334zM343.466667 217.6h21.333333v21.333333h-21.333333zM285.866667 264.533333h21.333333v21.333334h-21.333333zM174.933333 458.666667h21.333334v21.333333h-21.333334zM174.933333 520.533333h21.333334v21.333334h-21.333334zM174.933333 576h21.333334v21.333333h-21.333334zM292.266667 759.466667h21.333333v21.333333h-21.333333zM347.733333 800h21.333334v21.333333h-21.333334zM403.2 846.933333h21.333333v21.333334h-21.333333zM629.333333 846.933333h21.333334v21.333334h-21.333334zM454.4 514.133333h21.333333v21.333334h-21.333333zM512 514.133333h21.333333v21.333334h-21.333333zM567.466667 514.133333h21.333333v21.333334h-21.333333zM680.533333 800h21.333334v21.333333h-21.333334zM740.266667 759.466667h21.333333v21.333333h-21.333333zM853.333333 520.533333h21.333334v21.333334h-21.333334zM853.333333 576h21.333334v21.333333h-21.333334zM509.866667 189.866667h21.333333v147.2h-21.333333zM509.866667 708.266667h21.333333v147.2h-21.333333zM225.92 672.170667l127.488-73.6 10.666667 18.474666-127.488 73.6zM223.509333 375.125333l10.666667-18.474666 127.466667 73.6-10.666667 18.474666zM677.312 422.186667l132.309333-64.554667 9.344 19.2-132.309333 64.512zM684.544 618.517333l10.986667-18.282666 126.186666 75.797333-10.986666 18.304z"
|
||||||
|
p-id="32290"></path>
|
||||||
|
<path
|
||||||
|
d="M520.533333 590.933333c-93.866667 0-189.866667-23.466667-189.866666-66.133333s96-66.133333 189.866666-66.133333 189.866667 23.466667 189.866667 66.133333-93.866667 66.133333-189.866667 66.133333z m0-110.933333c-102.4 0-168.533333 27.733333-168.533333 44.8s66.133333 44.8 168.533333 44.8c102.4 0 168.533333-27.733333 168.533334-44.8s-64-44.8-168.533334-44.8z"
|
||||||
|
p-id="32291"></path>
|
||||||
|
<path
|
||||||
|
d="M520.533333 714.666667c-36.266667 0-57.6-68.266667-64-130.133334l21.333334-2.133333c8.533333 76.8 29.866667 110.933333 42.666666 110.933333 12.8 0 34.133333-34.133333 42.666667-113.066666l21.333333 2.133333c-6.4 64-25.6 132.266667-64 132.266667z m44.8-243.2c-8.533333-78.933333-29.866667-115.2-42.666666-115.2-12.8 0-34.133333 36.266667-42.666667 113.066666l-21.333333-2.133333c6.4-64 27.733333-132.266667 64-132.266667 38.4 0 57.6 70.4 64 134.4l-21.333334 2.133334zM520.533333 209.066667c-36.266667 0-66.133333-29.866667-66.133333-66.133334 0-36.266667 29.866667-66.133333 66.133333-66.133333 36.266667 0 66.133333 29.866667 66.133334 66.133333 2.133333 36.266667-27.733333 66.133333-66.133334 66.133334z m0-113.066667c-25.6 0-44.8 21.333333-44.8 44.8 0 25.6 21.333333 44.8 44.8 44.8 25.6 0 44.8-21.333333 44.8-44.8 2.133333-23.466667-19.2-44.8-44.8-44.8zM857.6 407.466667c-36.266667 0-66.133333-29.866667-66.133333-66.133334 0-36.266667 29.866667-66.133333 66.133333-66.133333s66.133333 29.866667 66.133333 66.133333c2.133333 36.266667-27.733333 66.133333-66.133333 66.133334z m0-113.066667c-25.6 0-44.8 21.333333-44.8 44.8 0 25.6 21.333333 44.8 44.8 44.8s44.8-21.333333 44.8-44.8c2.133333-23.466667-19.2-44.8-44.8-44.8zM857.6 776.533333c-36.266667 0-66.133333-29.866667-66.133333-66.133333 0-36.266667 29.866667-66.133333 66.133333-66.133333s66.133333 29.866667 66.133333 66.133333c2.133333 34.133333-27.733333 66.133333-66.133333 66.133333z m0-113.066666c-25.6 0-44.8 21.333333-44.8 44.8s21.333333 44.8 44.8 44.8 44.8-21.333333 44.8-44.8-19.2-44.8-44.8-44.8zM520.533333 974.933333c-36.266667 0-66.133333-29.866667-66.133333-66.133333 0-36.266667 29.866667-66.133333 66.133333-66.133333 36.266667 0 66.133333 29.866667 66.133334 66.133333 2.133333 36.266667-27.733333 66.133333-66.133334 66.133333z m0-113.066666c-25.6 0-44.8 21.333333-44.8 44.8s21.333333 44.8 44.8 44.8c25.6 0 44.8-21.333333 44.8-44.8s-19.2-44.8-44.8-44.8zM183.466667 407.466667c-36.266667 0-66.133333-29.866667-66.133334-66.133334 0-36.266667 29.866667-66.133333 66.133334-66.133333 36.266667 0 66.133333 29.866667 66.133333 66.133333 2.133333 36.266667-27.733333 66.133333-66.133333 66.133334z m0-113.066667c-25.6 0-44.8 21.333333-44.8 44.8 0 25.6 21.333333 44.8 44.8 44.8 25.6 0 44.8-21.333333 44.8-44.8 2.133333-23.466667-19.2-44.8-44.8-44.8zM183.466667 776.533333c-36.266667 0-66.133333-29.866667-66.133334-66.133333 0-36.266667 29.866667-66.133333 66.133334-66.133333 36.266667 0 66.133333 29.866667 66.133333 66.133333 2.133333 34.133333-27.733333 66.133333-66.133333 66.133333z m0-113.066666c-25.6 0-44.8 21.333333-44.8 44.8s21.333333 44.8 44.8 44.8c25.6 0 44.8-21.333333 44.8-44.8s-19.2-44.8-44.8-44.8z"
|
||||||
|
p-id="32292"></path>
|
||||||
|
<path
|
||||||
|
d="M514.133333 731.733333c-117.333333 0-215.466667-96-215.466666-215.466666 0-117.333333 96-215.466667 215.466666-215.466667 117.333333 0 215.466667 96 215.466667 215.466667s-96 215.466667-215.466667 215.466666z m0-386.133333c-93.866667 0-172.8 76.8-172.8 172.8s76.8 172.8 172.8 172.8c93.866667 0 172.8-76.8 172.8-172.8s-76.8-172.8-172.8-172.8z"
|
||||||
|
p-id="32293"></path>
|
||||||
|
</svg>
|
||||||
|
)
|
||||||
|
})
|
22
src/components/Icons/MedicineBottleFill.tsx
Normal file
22
src/components/Icons/MedicineBottleFill.tsx
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
import React from "react"
|
||||||
|
|
||||||
|
export const MedicineBottleFillIcon = React.forwardRef<
|
||||||
|
SVGSVGElement,
|
||||||
|
React.SVGProps<SVGSVGElement>
|
||||||
|
>((props, ref) => {
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
className="icon"
|
||||||
|
viewBox="0 0 1024 1024"
|
||||||
|
version="1.1"
|
||||||
|
p-id="25925"
|
||||||
|
fill="currentColor"
|
||||||
|
fillRule="evenodd"
|
||||||
|
ref={ref}
|
||||||
|
{...props}>
|
||||||
|
<path
|
||||||
|
d="M725.333333 213.333333v85.333334a128 128 0 0 1 128 128v469.333333a42.666667 42.666667 0 0 1-42.666666 42.666667H213.333333a42.666667 42.666667 0 0 1-42.666666-42.666667V426.666667a128 128 0 0 1 128-128V213.333333h426.666666z m-170.666666 256h-85.333334v85.333334H384v85.333333h85.290667L469.333333 725.333333h85.333334l-0.042667-85.333333H640v-85.333333h-85.333333v-85.333334z m256-384v85.333334H213.333333V85.333333h597.333334z"
|
||||||
|
p-id="25926"></path>
|
||||||
|
</svg>
|
||||||
|
)
|
||||||
|
})
|
26
src/components/Icons/NSDC.tsx
Normal file
26
src/components/Icons/NSDC.tsx
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
import React from "react"
|
||||||
|
|
||||||
|
export const NSDCIcon = React.forwardRef<
|
||||||
|
SVGSVGElement,
|
||||||
|
React.SVGProps<SVGSVGElement>
|
||||||
|
>((props, ref) => {
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
className="icon"
|
||||||
|
viewBox="0 0 1024 1024"
|
||||||
|
version="1.1"
|
||||||
|
fill="currentColor"
|
||||||
|
fillRule="evenodd"
|
||||||
|
ref={ref}
|
||||||
|
{...props}>
|
||||||
|
<path
|
||||||
|
d="M482.233219 569.858314a74.216 74.216 0 1 0 56.802467-137.133287 74.216 74.216 0 1 0-56.802467 137.133287Z"
|
||||||
|
fill=""
|
||||||
|
p-id="7898"></path>
|
||||||
|
<path
|
||||||
|
d="M952.472 447.816c-25.08-25.224-59-45.952-99.736-63.784-81.616-35.808-191.784-59.872-317.32-66.536a1387.864 1387.864 0 0 0-49-1.16c28.56-38.272 56.824-72.624 83.496-101.04 52.328-55.376 100.896-84.224 123.216-85.088 9.712-0.288 15.224 1.448 19.856 4.352 9.568 6.376 22.176 21.456 21.456 47.256-0.584 19.856-8.984 58.128-19.568 81.76l67.696 30.152c15.656-34.648 25.08-75.528 26.096-109.88 1.304-50.88-23.776-91.032-55.232-111.472-18.992-12.176-41.312-17.104-63.056-16.384-62.912 2.464-116.984 47.544-174.384 108.432-40.592 42.912-81.904 96.112-122.64 154.24-28.264 2.176-55.952 5.072-82.336 9.28-13.48-45.52-22.472-86.544-23.048-115.096-0.432-25.656 2.176-46.824 6.52-60.304 4.496-13.624 8.984-17.688 12.464-19.424 2.752-1.448 5.656-2.32 10-2.608 20.008-1.592 60.88 6.96 100.312 43.776l50.592-54.216C421.32 76.584 370.88 57.88 328.984 56a233.76 233.76 0 0 0-17.544 0.288l-0.144 0.144a103.392 103.392 0 0 0-38.128 10.44v0.144c-25.512 12.904-41.024 37.256-49.288 62.624-8.264 25.512-10.584 53.632-10.144 84.368 0.728 38.128 9.568 82.624 22.616 129.016-22.32 5.656-43.488 12.032-63.056 19.136-39.576 14.496-73.208 31.744-99.008 53.632-25.952 21.888-46.096 50.88-46.096 85.528 0 39.864 21.6 72.912 47.544 95.672 26.096 22.904 57.256 38.416 87.408 50.304l26.96-69.288c-25.08-9.712-49.432-22.76-65.376-36.672-15.8-13.92-22.328-25.8-22.328-40.008 0-5.8 3.624-15.368 19.568-28.848 15.944-13.336 42.76-28.12 76.832-40.592 17.976-6.52 38.272-12.464 59.872-17.688 9.424 25.8 19.856 51.464 30.584 76.688-57.84 123.216-76.832 241.216-75.528 316.736 0.872 50.736 19.424 95.96 56.968 120.32 24.352 15.656 55.232 23.48 87.408 19.136 32.04-4.352 64.656-19.424 99.736-44.504l-42.912-60.304c-28.704 20.44-51.168 29.136-66.976 31.312-15.8 2.032-25.656-0.576-36.816-7.976-11.888-7.68-22.616-24.936-23.192-59.288-0.872-50.448 10.584-133.512 43.632-223.968 6.088 11.888 12.032 23.632 17.976 34.648h-0.144c39.576 73.352 99.008 161.2 161.2 228.168 31.024 33.488 62.48 61.752 95.096 80.6 31.6 18.12 69.144 27.832 103.792 12.176 30.584-10.584 49.576-37.832 58.856-67.264 9.568-30.728 12.032-66.68 9.424-107.416-5.512-81.32-33.048-181.344-86.104-279.488l-65.232 35.08c47.984 89.152 72.624 180.912 77.264 249.336 2.32 34.352-0.728 62.768-6.232 80.16-5.36 17.392-10.872 20.152-11.6 20.44l-3.48 0.872-3.04 1.592c-4.208 2.176-14.784 2.752-36.528-9.856-21.888-12.608-49.72-36.528-77.696-66.68C509.04 734.256 451.2 649.312 414.96 581.904c-14.352-26.672-29.136-57.552-43.488-90.312a694.256 694.256 0 0 1 33.632-57.84c9.28-14.496 18.848-28.416 28.416-42.472 10.872-0.288 21.6-1.304 32.616-1.304 21.312 0 43.056 0.584 65.376 1.736 118.288 6.088 221.496 29.568 291.512 60.304 34.936 15.224 61.32 32.472 76.832 48.128 15.656 15.656 19.568 27.104 18.992 36.384 0 2.464-4.352 13.048-22.032 24.936-17.832 11.888-46.384 23.336-80.888 29.568l12.904 73.064c42.76-7.68 80.016-21.456 109.152-41.024 29.28-19.568 53.2-46.68 55.088-82.776 1.728-35.224-15.52-67.408-40.6-92.48z m-615.936-43.784a34.84 34.84 0 0 1-1.592-4.2c1.448-0.144 2.896-0.288 4.208-0.432-0.88 1.592-1.752 3.04-2.616 4.632z"
|
||||||
|
fill=""
|
||||||
|
p-id="7899"></path>
|
||||||
|
</svg>
|
||||||
|
)
|
||||||
|
})
|
23
src/components/Icons/NewBottle.tsx
Normal file
23
src/components/Icons/NewBottle.tsx
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
import React from "react"
|
||||||
|
|
||||||
|
export const NewBottleIcon = React.forwardRef<
|
||||||
|
SVGSVGElement,
|
||||||
|
React.SVGProps<SVGSVGElement>
|
||||||
|
>((props, ref) => {
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
className="icon"
|
||||||
|
viewBox="0 0 1024 1024"
|
||||||
|
version="1.1"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
p-id="40222"
|
||||||
|
fill="currentColor"
|
||||||
|
fillRule="evenodd"
|
||||||
|
ref={ref}
|
||||||
|
{...props}>
|
||||||
|
<path
|
||||||
|
d="M848.818342 511.548501l-319.661376 308.373898c-14.899471 16.705467-35.216931 27.541446-56.888888 30.70194 16.705467-17.608466 29.798942-38.828924 37.474426-61.40388l175.633157-164.345679-105.199294-105.650794L717.883598 397.770723c4.96649-4.514991 8.126984-10.835979 8.126984-17.608466s-3.160494-13.093474-8.126984-17.608465c-9.029982-10.38448-24.832451-11.738977-35.216931-2.708995L542.250441 478.589065l-30.70194-30.70194v-30.70194L632.098765 295.731922s92.557319-92.557319 199.562611 13.544974c107.45679 106.102293 16.253968 201.820106 16.253968 201.820106h0.902998z m-339.075838 74.948853v-74.948853l30.70194-30.70194 38.828925 38.828924-69.530865 66.821869z m-200.465608 294.828925C216.719577 881.326279 139.964727 819.470899 139.964727 758.067019v-492.134038c0-61.40388 80.818342-123.259259 169.312169-123.25926S478.589065 204.077601 478.589065 265.932981v492.134038c0 61.40388-76.75485 123.259259-169.312169 123.25926zM447.887125 263.223986C425.763668 206.335097 370.229277 169.312169 309.276896 170.666667c-60.952381-1.354497-116.938272 35.216931-139.061728 92.557319v246.067019h61.40388v215.816579c-1.354497 11.738977 4.514991 23.026455 14.447971 29.347442 9.932981 6.320988 23.026455 6.320988 32.959436 0s15.802469-17.608466 14.447972-29.347442v-213.559083h153.961199l0.451499-248.324515z m-184.663139 30.70194c8.126984 0 16.253968 3.160494 22.123457 9.029982 5.869489 5.869489 9.029982 13.996473 9.029982 22.123457v184.663139H232.522046V326.433862c0-8.126984 2.708995-16.253968 9.029982-22.123456 5.869489-5.869489 13.996473-9.029982 22.123457-9.029983l-0.451499-1.354497z m0 0"
|
||||||
|
p-id="40223"></path>
|
||||||
|
</svg>
|
||||||
|
)
|
||||||
|
})
|
23
src/components/Icons/NotCollect.tsx
Normal file
23
src/components/Icons/NotCollect.tsx
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
import React from "react"
|
||||||
|
|
||||||
|
export const NotCollectIcon = React.forwardRef<
|
||||||
|
SVGSVGElement,
|
||||||
|
React.SVGProps<SVGSVGElement>
|
||||||
|
>((props, ref) => {
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
className="icon"
|
||||||
|
viewBox="0 0 1059 1024"
|
||||||
|
version="1.1"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
p-id="73488"
|
||||||
|
fill="currentColor"
|
||||||
|
fillRule="evenodd"
|
||||||
|
ref={ref}
|
||||||
|
{...props}>
|
||||||
|
<path
|
||||||
|
d="M253.488042 1024c-16.9 0-33.2875-5.1125-47.6125-15.3625-26.625-18.425-39.425-49.6625-34.3125-81.925l40.9625-251.9c1.5375-10.2375-1.5375-20.475-8.7-27.65L28.213042 466.4375c-22.0125-22.525-29.1875-55.3-19.45-84.9875 9.725-29.7 35.325-51.2 66.05-55.8125l237.575-36.35c10.75-1.5375 19.4625-8.1875 24.0625-17.925L441.388042 48.125c13.825-29.7 42.5-48.125 75.2625-48.125s61.4375 18.4375 75.2625 48.125l104.45 223.2375c4.6125 9.725 13.825 16.375 24.0625 17.925L958.000542 325.625a82.355 82.355 0 0 1 66.05 55.8125c10.2375 29.7 2.5625 62.4625-19.45 84.9875l-175.625 180.7375c-7.1625 7.175-10.2375 17.925-8.7 27.65l40.9625 251.9c5.125 31.75-8.1875 63.4875-34.3 81.925-26.1125 18.4375-59.9 20.4875-88.0625 4.6125l-206.85-114.6875c-9.725-5.1125-20.9875-5.1125-30.7125 0l-207.3625 115.2c-12.8125 6.65-26.6375 10.2375-40.4625 10.2375zM516.650542 51.2c-12.8 0-23.55 7.1625-29.1875 18.4375L383.525542 292.875c-11.775 25.0875-35.325 43.0125-62.975 47.1l-237.575 36.35c-12.2875 2.05-21.5 9.7375-25.6 21.5-4.1 11.775-1.025 24.0625 7.675 32.775L240.688042 611.325c18.4375 18.95 26.625 45.5625 22.525 71.675L222.250542 934.9125c-2.05 12.8 3.075 24.575 13.3125 31.7375 10.2375 7.175 23.0375 7.6875 33.7875 1.5375l207.3625-115.2c25.0875-13.825 55.3-13.825 80.3875 0l207.3625 115.2c10.75 6.1375 23.55 5.625 33.8-1.5375 10.2375-7.1625 15.3625-18.95 13.3125-31.7375L770.625542 683.0125c-4.1-26.1125 4.1-52.7375 22.525-71.675l175.625-180.7375c8.7-8.7 11.2625-20.9875 7.675-32.775-4.0875-11.775-13.3125-19.9625-25.6-21.5l-237.5625-36.35c-27.65-4.0875-51.2-22.0125-62.975-47.1L545.838042 69.6375c-5.625-11.2625-16.375-18.4375-29.1875-18.4375z m0 0"
|
||||||
|
p-id="73489"></path>
|
||||||
|
</svg>
|
||||||
|
)
|
||||||
|
})
|
24
src/components/Icons/ResearchInstitutes.tsx
Normal file
24
src/components/Icons/ResearchInstitutes.tsx
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
import React from "react"
|
||||||
|
|
||||||
|
export const ResearchInstitutesIcon = React.forwardRef<
|
||||||
|
SVGSVGElement,
|
||||||
|
React.SVGProps<SVGSVGElement>
|
||||||
|
>((props, ref) => {
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
className="icon"
|
||||||
|
viewBox="0 0 1024 1024"
|
||||||
|
version="1.1"
|
||||||
|
fill="currentColor"
|
||||||
|
fillRule="evenodd"
|
||||||
|
ref={ref}
|
||||||
|
{...props}>
|
||||||
|
<path
|
||||||
|
d="M977.997713 356.640616l-433.994529-149.255836c-27.084774-9.776045-21.395041-9.833246-48.245647-0.11619L59.169291 355.289237c-26.854181 9.717056-26.682578 25.531403 0.402196 35.336048l103.741547 35.223434c-45.948661 44.509693-48.937424 90.77296-49.513011 144.425909-17.825328 6.844482-30.363118 24.094223-30.363118 44.2505 0 18.515317 10.580437 34.531656 26.051577 42.325321-7.30388 54.571743-28.409339 116.85135-90.284962 190.658788 30.650912 23.692027 46.408058 31.600094 70.100085 39.479561 86.488232-37.150399 75.964996-135.85824 69.234916-234.076295 11.905002-8.626658 19.611078-22.601629 19.611078-38.387376 0-16.933346-8.912664-31.744885-22.195858-40.139163 1.494382-52.587576 12.938199-99.657023 52.27297-130.107731 0.344995-0.832993 1.206588-1.52477 2.93335-2.24336l298.340069-120.53189c11.098823-4.458119 23.634826 0.920582 28.062557 12.019405l0.402196 0.949183c4.431306 11.070222-0.918794 23.634826-12.017617 28.062557l-252.164391 100.808198 225.655204 76.540584c27.027572 9.802858 21.389678 9.861846 48.188446 0.141215L978.34092 392.007052c26.857756-9.747444 26.684365-25.561791-0.400408-35.337836L977.997713 356.640616zM977.997713 356.640616"
|
||||||
|
p-id="5109"></path>
|
||||||
|
<path
|
||||||
|
d="M498.801714 597.128809l-273.092884-92.610549 0 69.665713c14.260977 13.056177 22.140444 31.802086 22.140444 52.676953 0 18.74591-6.554901 35.797233-17.653724 48.563829 3.621552 10.925431 9.890447 21.622058 18.976502 24.959391 158.946079 87.866423 378.616606 86.88864 555.332599-8.885851 13.109803-10.898618 23.288043-24.412405 23.288043-37.493607l0-153.370748-280.570155 96.611059c-26.798768 9.690243-21.334264 9.659855-48.363624-0.11619L498.801714 597.128809zM498.801714 597.128809"
|
||||||
|
p-id="5110"></path>
|
||||||
|
</svg>
|
||||||
|
)
|
||||||
|
})
|
21
src/components/Icons/ResearchPaper.tsx
Normal file
21
src/components/Icons/ResearchPaper.tsx
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
import React from "react"
|
||||||
|
|
||||||
|
export const ResearchPaperIcon = React.forwardRef<
|
||||||
|
SVGSVGElement,
|
||||||
|
React.SVGProps<SVGSVGElement>
|
||||||
|
>((props, ref) => {
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
className="icon"
|
||||||
|
viewBox="0 0 1024 1024"
|
||||||
|
version="1.1"
|
||||||
|
fill="currentColor"
|
||||||
|
fillRule="evenodd"
|
||||||
|
ref={ref}
|
||||||
|
{...props}>
|
||||||
|
<path
|
||||||
|
d="M715.648 196.928V0H308.352v118.144h220.544l11.904 11.52 203.648 196.992 11.904 11.52v528.32H960V236.288h-244.352v-39.36zM756.352 0v196.928H960L756.352 0zM471.296 157.568v236.288h244.352V1024H64V157.568h407.296zM512 354.496h203.648L512 157.568v196.928z m-136.064 47.424H163.328c-6.4 0-11.52 8-11.52 17.792 0 9.792 5.12 17.792 11.52 17.792h212.608c6.4 0 11.52-8 11.52-17.792 0-9.792-5.12-17.792-11.52-17.792z m100.352 189.888H168.512c-9.216 0-16.704 7.936-16.704 17.728 0 9.856 7.488 17.792 16.704 17.792h307.776c9.216 0 16.64-7.936 16.64-17.792 0-9.792-7.424-17.728-16.64-17.728z m-302.336 225.344H581.76c12.224 0 22.144-7.936 22.144-17.728 0-9.856-9.92-17.792-22.144-17.792H173.952c-12.224 0-22.144 7.936-22.144 17.792 0 9.792 9.92 17.728 22.144 17.728z"
|
||||||
|
p-id="10242"></path>
|
||||||
|
</svg>
|
||||||
|
)
|
||||||
|
})
|
22
src/components/Icons/Setting.tsx
Normal file
22
src/components/Icons/Setting.tsx
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
import React from "react"
|
||||||
|
|
||||||
|
export const SettingIcon = React.forwardRef<
|
||||||
|
SVGSVGElement,
|
||||||
|
React.SVGProps<SVGSVGElement>
|
||||||
|
>((props, ref) => {
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
className="icon"
|
||||||
|
viewBox="0 0 1084 1024"
|
||||||
|
version="1.1"
|
||||||
|
p-id="10420"
|
||||||
|
fill="currentColor"
|
||||||
|
fillRule="evenodd"
|
||||||
|
ref={ref}
|
||||||
|
{...props}>
|
||||||
|
<path
|
||||||
|
d="M1072.147851 406.226367c-6.331285-33.456782-26.762037-55.073399-52.047135-55.073399-0.323417 0-0.651455 0.003081-0.830105 0.009241l-4.655674 0c-73.124722 0-132.618162-59.491899-132.618162-132.618162 0-23.731152 11.447443-50.336101 11.546009-50.565574 13.104573-29.498767 3.023185-65.672257-23.427755-84.127081l-1.601687-1.127342-134.400039-74.661726-1.700252-0.745401c-8.753836-3.805547-18.334698-5.735272-28.479231-5.735272-20.789593 0-41.235746 8.344174-54.683758 22.306575-14.741683 15.216028-65.622973 58.649474-104.721083 58.649474-39.450789 0-90.633935-44.286652-105.438762-59.784516-13.518857-14.247316-34.128258-22.753199-55.127302-22.753199-9.945862 0-19.354234 1.861961-27.958682 5.531982l-1.746455 0.74078-139.141957 76.431283-1.643269 1.139662c-26.537186 18.437884-36.675557 54.579032-23.584845 84.062398 0.115506 0.264895 11.579891 26.725075 11.579891 50.634877 0 73.126262-59.491899 132.618162-132.618162 132.618162l-4.581749 0c-0.318797-0.00616-0.636055-0.01078-0.951772-0.01078-25.260456 0-45.672728 21.618157-52.002472 55.0811-0.462025 2.453354-11.313456 60.622322-11.313456 106.117939 0 45.494078 10.85143 103.659965 11.314996 106.119479 6.334365 33.458322 26.758957 55.076479 52.036353 55.076479 0.320337 0 0.651455-0.00616 0.842426-0.012321l4.655674 0c73.126262 0 132.618162 59.491899 132.618162 132.616622 0 23.760413-11.444363 50.333021-11.546009 50.565574-13.093793 29.474125-3.041666 65.646075 23.395414 84.151722l1.569346 1.093459 131.838879 73.726895 1.675611 0.7377c8.750757 3.84251 18.305437 5.790715 28.397607 5.790715 21.082208 0 41.676209-8.706094 55.0888-23.290689 18.724339-20.347588 69.527086-62.362616 107.04815-62.362616 40.625872 0 92.72537 47.100385 107.759669 63.583903 13.441852 14.831008 34.176001 23.689571 55.470741 23.695731l0.00616 0c9.895039 0 19.27877-1.883523 27.893999-5.598205l1.711034-0.73924 136.659342-75.531873 1.617088-1.128882c26.492523-18.456365 36.601633-54.600594 23.538642-84.016195-0.115506-0.267974-11.595291-27.082374-11.595291-50.67646 0-73.124722 59.49344-132.616622 132.618162-132.616622l4.517066-0.00154c0.300316 0.00616 0.599092 0.009241 0.899409 0.009241 25.331299-0.00154 45.785153-21.619697 52.107197-55.054918 0.112426-0.589852 11.325776-59.507301 11.325776-106.14104C1083.464388 466.640776 1072.609877 408.67356 1072.147851 406.226367zM377.486862 945.656142l-115.32764-64.487932c5.082277-13.052211 15.437801-43.51815 15.437801-75.017486 0-109.382917-84.176364-199.816642-192.587488-208.134635-2.647404-15.427021-8.873963-54.967133-8.873963-85.667166 0-30.65691 6.223479-70.232445 8.869343-85.671786 108.415744-8.311832 192.592108-98.745557 192.592108-208.134635 0-31.416171-10.300081-61.797405-15.371577-74.854236l122.721583-67.40331c0.003081 0 0.00462 0.00154 0.007701 0.00154 4.423121 4.518606 22.121764 22.080182 46.558275 39.493911 39.929754 28.46229 77.952885 42.894416 113.014434 42.894416 34.716571 0 72.437845-14.151831 112.115025-42.06431 24.282503-17.07953 41.896442-34.302288 46.308782-38.74543 0.009241-0.00154 0.018481-0.00462 0.026182-0.00616l118.301542 65.726159c-5.077657 13.055291-15.416239 43.499669-15.416239 74.958962 0 109.389077 84.174824 199.822802 192.590568 208.134635 2.645865 15.462442 8.872423 55.107281 8.872423 85.671786 0 30.687711-6.223479 70.241685-8.869343 85.673326C890.042174 606.334084 805.86427 696.767809 805.86427 806.158426c0 31.450053 10.317022 61.851309 15.393138 74.903519l-119.783103 66.198965c-5.168521-5.490399-22.603811-23.363073-46.740005-41.288109-40.701336-30.224145-79.662378-45.549521-115.800446-45.549521-35.79155 0-74.458435 15.038919-114.927219 44.694774C400.22004 922.554885 382.666163 940.255068 377.486862 945.656142zM731.271848 511.646647c0-105.803762-86.081448-191.88059-191.888289-191.88059-105.803762 0-191.88059 86.076827-191.88059 191.88059 0 105.803762 86.076827 191.882129 191.88059 191.882129C645.19194 703.528777 731.271848 617.450409 731.271848 511.646647zM539.383558 395.903184c63.825696 0 115.751164 51.922387 115.751164 115.743463 0 63.825696-51.925468 115.751164-115.751164 115.751164-63.821076 0-115.743463-51.925468-115.743463-115.751164C423.640095 447.824031 475.562482 395.903184 539.383558 395.903184z"
|
||||||
|
p-id="10421"></path>
|
||||||
|
</svg>
|
||||||
|
)
|
||||||
|
})
|
23
src/components/Icons/Share.tsx
Normal file
23
src/components/Icons/Share.tsx
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
import React from "react"
|
||||||
|
|
||||||
|
export const ShareIcon = React.forwardRef<
|
||||||
|
SVGSVGElement,
|
||||||
|
React.SVGProps<SVGSVGElement>
|
||||||
|
>((props, ref) => {
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
className="icon"
|
||||||
|
viewBox="0 0 1024 1024"
|
||||||
|
version="1.1"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
p-id="75461"
|
||||||
|
fill="currentColor"
|
||||||
|
fillRule="evenodd"
|
||||||
|
ref={ref}
|
||||||
|
{...props}>
|
||||||
|
<path
|
||||||
|
d="M1009.777778 503.466667l-443.733334-455.111111c-5.688889-5.688889-11.377778 0-11.377777 5.688888v267.377778C8.533333 409.6 2.844444 918.755556 17.066667 932.977778c0 0 45.511111-48.355556 164.977777-113.777778 85.333333-48.355556 224.711111-85.333333 369.777778-102.4v261.688889c0 8.533333 11.377778 11.377778 14.222222 5.688889l443.733334-480.711111z m-398.222222 358.4v-199.111111l-36.977778-2.844445c-221.866667 8.533333-378.311111 73.955556-497.777778 156.444445 76.8-275.911111 267.377778-403.911111 466.488889-438.044445l68.266667-2.844444v-199.111111l312.888888 312.888888s8.533333 5.688889 8.533334 14.222223-8.533333 14.222222-8.533334 14.222222l-312.888888 344.177778z"
|
||||||
|
p-id="75462"></path>
|
||||||
|
</svg>
|
||||||
|
)
|
||||||
|
})
|
23
src/components/Icons/Ship.tsx
Normal file
23
src/components/Icons/Ship.tsx
Normal file
File diff suppressed because one or more lines are too long
23
src/components/Icons/Ship1.tsx
Normal file
23
src/components/Icons/Ship1.tsx
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
import React from "react"
|
||||||
|
|
||||||
|
export const Ship1Icon = React.forwardRef<
|
||||||
|
SVGSVGElement,
|
||||||
|
React.SVGProps<SVGSVGElement>
|
||||||
|
>((props, ref) => {
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
className="icon"
|
||||||
|
viewBox="0 0 1024 1024"
|
||||||
|
version="1.1"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
p-id="42511"
|
||||||
|
fill="currentColor"
|
||||||
|
fillRule="evenodd"
|
||||||
|
ref={ref}
|
||||||
|
{...props}>
|
||||||
|
<path
|
||||||
|
d="M532.48 241.777778h-28.444444a151.608889 151.608889 0 0 0-48.924445 8.533333l-142.222222 76.231111V199.111111a42.382222 42.382222 0 0 1 42.382222-42.097778H455.111111V102.115556a42.666667 42.666667 0 0 1 43.52-42.382223H540.444444a42.666667 42.666667 0 0 1 42.382223 42.382223v59.164444h101.546666a42.666667 42.666667 0 0 1 42.382223 42.382222v131.413334l-156.728889-85.333334a209.351111 209.351111 0 0 0-38.115556-8.533333z m309.191111 461.653333a768 768 0 0 0-220.16 85.333333c-113.777778 63.715556-118.613333 50.915556-207.644444 8.533334a961.422222 961.422222 0 0 0-203.093334-76.231111L151.608889 631.466667c-63.715556-80.497778-76.231111-119.466667-42.382222-139.946667L455.111111 309.475556a127.431111 127.431111 0 0 1 122.88 0l347.022222 182.044444c21.333333 17.066667 38.115556 55.182222-25.315555 139.946667z m122.595556 186.311111a42.097778 42.097778 0 0 1-42.382223 42.097778c-8.248889 0-12.515556 0-16.782222-3.982222a199.111111 199.111111 0 0 0-105.813333-34.133334c-63.431111 0-143.928889 59.448889-143.928889 59.448889S612.977778 995.555556 512 995.555556a220.728889 220.728889 0 0 1-143.928889-42.382223s-89.031111-63.431111-143.928889-59.448889a129.706667 129.706667 0 0 0-106.097778 34.133334 25.884444 25.884444 0 0 1-16.782222 3.982222 42.097778 42.097778 0 0 1-42.382222-42.097778 46.933333 46.933333 0 0 1 21.048889-42.382222s119.182222-136.248889 304.355555 0c0 0 59.448889 42.382222 101.546667 42.382222h42.382222a216.462222 216.462222 0 0 0 101.546667-42.382222c50.915556-33.848889 178.062222-118.613333 304.924444 0a47.502222 47.502222 0 0 1 28.444445 42.382222z m0 0"
|
||||||
|
p-id="42512"></path>
|
||||||
|
</svg>
|
||||||
|
)
|
||||||
|
})
|
24
src/components/Icons/TalentPool .tsx
Normal file
24
src/components/Icons/TalentPool .tsx
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
import React from "react"
|
||||||
|
|
||||||
|
export const TalentPoolIcon = React.forwardRef<
|
||||||
|
SVGSVGElement,
|
||||||
|
React.SVGProps<SVGSVGElement>
|
||||||
|
>((props, ref) => {
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
className="icon"
|
||||||
|
viewBox="0 0 1024 1024"
|
||||||
|
version="1.1"
|
||||||
|
fill="currentColor"
|
||||||
|
fillRule="evenodd"
|
||||||
|
ref={ref}
|
||||||
|
{...props}>
|
||||||
|
<path
|
||||||
|
d="M194.7 296.1H571v50.6H194.7zM679.7 294.1c-15 1.9-26.2 13.1-26.2 28.1v1.9c1.8 15 13.1 26.2 28.1 26.2 15-1.9 26.2-13.2 26.2-28.1v-1.9c-1.9-14.9-13.2-26.2-28.1-26.2z"
|
||||||
|
p-id="19241"></path>
|
||||||
|
<path
|
||||||
|
d="M932.4 86.4c-18.7-15-43.1-22.5-67.4-22.5H279c-26.2 0-50.5 7.5-67.4 22.5-18.7 16.9-29.9 39.4-29.9 61.9v41H163c-26.2 0-50.5 7.5-67.4 22.4C75 230.5 63.8 253 63.8 277.4v598c0 24.3 11.2 46.8 29.9 61.8s43.1 22.5 67.4 22.5h584.2c26.2 0 50.6-7.5 67.4-22.5 18.7-16.8 31.8-39.3 31.8-61.8v-30h16.9c26.2 0 50.5-7.5 67.4-22.5 18.7-16.8 30-39.4 30-61.8v-611c3.6-24.4-7.6-46.8-26.4-63.7z m-345.5 711c-2.3 48.8-57.3 48.8-138.8 49-81.5-0.2-136.5-0.2-138.8-49-2.3-48.8 30-90.6 54.9-111 24.9-20.4 43-19.4 43-19.4l40.9-0.1 40.9 0.1s18.2-0.9 43 19.4c24.9 20.4 57.2 62.2 54.9 111zM368.5 578.5c0-43.9 35.6-79.6 79.6-79.6s79.6 35.6 79.6 79.6S492 658 448.1 658s-79.6-35.6-79.6-79.5z m412.3-197.9H128.2v-86.3c0-18.8 2.5-40.8 40.7-40.8h575.2c14.2 0 36.7 15 36.7 33.8v93.3z m117.1 363.6c0 3.8-6.4 22.5-18.6 31-12.2 8.4-30.1 6.5-32 6.5h-2.9V277.3c0-34.3-12.2-86.2-102.8-86.2H242.3v-30c0-3.8 0-7.5 1.9-11.3 1.9-3.8 3.8-5.6 7.5-9.4 9.4-9.4 22.5-15 39.3-15h548.2c26.2 0 58.7 15 58.7 33.7v585.1z"
|
||||||
|
p-id="19242"></path>
|
||||||
|
</svg>
|
||||||
|
)
|
||||||
|
})
|
22
src/components/Icons/TechCompany.tsx
Normal file
22
src/components/Icons/TechCompany.tsx
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
import React from "react"
|
||||||
|
|
||||||
|
export const TechCompanyIcon = React.forwardRef<
|
||||||
|
SVGSVGElement,
|
||||||
|
React.SVGProps<SVGSVGElement>
|
||||||
|
>((props, ref) => {
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
className="icon"
|
||||||
|
viewBox="0 0 1024 1024"
|
||||||
|
version="1.1"
|
||||||
|
fill="currentColor"
|
||||||
|
fillRule="evenodd"
|
||||||
|
ref={ref}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M968 905.6h-48.64V357.376c0-36.992-28.608-66.816-63.808-66.816h-128v615.168h-48V135.68c0-36.864-28.544-66.816-63.872-66.816H168.448c-35.328 0-63.872 29.952-63.872 66.816v769.92H56a24.32 24.32 0 0 0-24 24.64 24.32 24.32 0 0 0 24 24.64h576.128v0.128h287.168v-0.128h48.64a24.32 24.32 0 0 0 24.064-24.64 24.32 24.32 0 0 0-24-24.64zM440.192 265.92h95.808v73.856H440.192V265.856z m0 196.864h95.808v73.856H440.192V462.72z m0 196.928h95.808v73.856H440.192v-73.856z m-192-393.792h96v73.856h-96V265.856z m0 196.864h96v73.856h-96V462.72z m0 196.928h96v73.856h-96v-73.856z"
|
||||||
|
p-id="9056"></path>
|
||||||
|
</svg>
|
||||||
|
)
|
||||||
|
})
|
@ -1,248 +1,157 @@
|
|||||||
import { useStorage } from "@plasmohq/storage/hook"
|
import React, { useMemo, useState } from "react"
|
||||||
import {
|
import { useOptionLayoutContext } from "@/components/Layouts/Layout.tsx"
|
||||||
BrainCog,
|
import { PanelLeftIcon } from "lucide-react"
|
||||||
ChevronLeft,
|
import { Button, Tooltip } from "antd"
|
||||||
ChevronRight,
|
import { PlusOutlined } from "@ant-design/icons"
|
||||||
CogIcon,
|
import { useMessageOption } from "@/hooks/useMessageOption.tsx"
|
||||||
ComputerIcon,
|
|
||||||
GithubIcon,
|
|
||||||
PanelLeftIcon,
|
|
||||||
ZapIcon
|
|
||||||
} from "lucide-react"
|
|
||||||
import { useTranslation } from "react-i18next"
|
import { useTranslation } from "react-i18next"
|
||||||
import { useLocation, NavLink } from "react-router-dom"
|
import { NavLink, useLocation } from "react-router-dom"
|
||||||
import { SelectedKnowledge } from "../Option/Knowledge/SelectedKnowledge"
|
import logo from "@/assets/logo.png"
|
||||||
import { ModelSelect } from "../Common/ModelSelect"
|
import { BellIcon } from "@/components/Icons/Bell.tsx"
|
||||||
import { PromptSelect } from "../Common/PromptSelect"
|
import { ShareIcon } from "@/components/Icons/Share.tsx"
|
||||||
import { useQuery } from "@tanstack/react-query"
|
import { NotCollectIcon } from "@/components/Icons/NotCollect.tsx"
|
||||||
import { fetchChatModels } from "~/services/ollama"
|
import { CollectIcon } from "@/components/Icons/Collect.tsx"
|
||||||
import { useMessageOption } from "~/hooks/useMessageOption"
|
import { SettingIcon } from "@/components/Icons/Setting.tsx"
|
||||||
import { Select, Tooltip } from "antd"
|
|
||||||
import { getAllPrompts } from "@/db"
|
|
||||||
import { ProviderIcons } from "../Common/ProviderIcon"
|
|
||||||
import { NewChat } from "./NewChat"
|
|
||||||
import { PageAssistSelect } from "../Select"
|
|
||||||
import { MoreOptions } from "./MoreOptions"
|
|
||||||
type Props = {
|
|
||||||
setSidebarOpen: (open: boolean) => void
|
|
||||||
setOpenModelSettings: (open: boolean) => void
|
|
||||||
}
|
|
||||||
|
|
||||||
export const Header: React.FC<Props> = ({
|
type Props = {}
|
||||||
setOpenModelSettings,
|
|
||||||
setSidebarOpen
|
|
||||||
}) => {
|
|
||||||
const { t, i18n } = useTranslation(["option", "common"])
|
|
||||||
const isRTL = i18n?.dir() === "rtl"
|
|
||||||
|
|
||||||
const [shareModeEnabled] = useStorage("shareMode", false)
|
export const Header: React.FC<Props> = ({}) => {
|
||||||
const [hideCurrentChatModelSettings] = useStorage(
|
const location = useLocation()
|
||||||
"hideCurrentChatModelSettings",
|
|
||||||
false
|
|
||||||
)
|
|
||||||
const {
|
|
||||||
selectedModel,
|
|
||||||
setSelectedModel,
|
|
||||||
clearChat,
|
|
||||||
selectedSystemPrompt,
|
|
||||||
setSelectedQuickPrompt,
|
|
||||||
setSelectedSystemPrompt,
|
|
||||||
messages,
|
|
||||||
streaming,
|
|
||||||
historyId,
|
|
||||||
temporaryChat
|
|
||||||
} = useMessageOption()
|
|
||||||
const {
|
|
||||||
data: models,
|
|
||||||
isLoading: isModelsLoading,
|
|
||||||
refetch
|
|
||||||
} = useQuery({
|
|
||||||
queryKey: ["fetchModel"],
|
|
||||||
queryFn: () => fetchChatModels({ returnEmpty: true }),
|
|
||||||
refetchIntervalInBackground: false,
|
|
||||||
placeholderData: (prev) => prev
|
|
||||||
})
|
|
||||||
|
|
||||||
const { data: prompts, isLoading: isPromptLoading } = useQuery({
|
const { showOptionSidebar, setShowOptionSidebar } = useOptionLayoutContext()
|
||||||
queryKey: ["fetchAllPromptsLayout"],
|
|
||||||
queryFn: getAllPrompts
|
|
||||||
})
|
|
||||||
|
|
||||||
const { pathname } = useLocation()
|
const showLeft = useMemo<boolean>(() => {
|
||||||
|
console.log(location.pathname)
|
||||||
const getPromptInfoById = (id: string) => {
|
if (location.pathname.includes("/settings")) {
|
||||||
return prompts?.find((prompt) => prompt.id === id)
|
return true
|
||||||
}
|
}
|
||||||
|
return showOptionSidebar
|
||||||
|
}, [location.pathname, showOptionSidebar])
|
||||||
|
|
||||||
const handlePromptChange = (value?: string) => {
|
const { t } = useTranslation(["option", "common", "settings"])
|
||||||
if (!value) {
|
|
||||||
setSelectedSystemPrompt(undefined)
|
|
||||||
setSelectedQuickPrompt(undefined)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
const prompt = getPromptInfoById(value)
|
|
||||||
if (prompt?.is_system) {
|
|
||||||
setSelectedSystemPrompt(prompt.id)
|
|
||||||
} else {
|
|
||||||
setSelectedSystemPrompt(undefined)
|
|
||||||
setSelectedQuickPrompt(prompt!.content)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
const { clearChat } = useMessageOption()
|
||||||
|
|
||||||
|
// 是否隐藏logo
|
||||||
|
const hideLogo = useMemo(() => {
|
||||||
|
return localStorage.getItem("hideLogo") === "true"
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
const [collect, setCollect] = useState<boolean>(false)
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={`absolute top-0 z-10 flex h-14 w-full flex-row items-center justify-center p-3 overflow-x-auto lg:overflow-x-visible bg-gray-50 border-b dark:bg-[#171717] dark:border-gray-600 ${
|
className={`h-[60px] absolute inset-0 pl-5 z-10 flex items-center transition-all duration-300 ease-in-out ${showOptionSidebar && !location.pathname.includes("/settings") ? "left-[300px]" : ""}`}>
|
||||||
temporaryChat && "!bg-gray-200 dark:!bg-black"
|
{/*控制侧边栏显示隐藏与新建对话*/}
|
||||||
}`}>
|
{!showLeft && (
|
||||||
<div className="flex gap-2 items-center">
|
<div className="flex items-center gap-3">
|
||||||
{pathname !== "/" && (
|
|
||||||
<div>
|
|
||||||
<NavLink
|
|
||||||
to="/"
|
|
||||||
className="text-gray-500 items-center dark:text-gray-400 hover:text-gray-600 dark:hover:text-gray-300 transition-colors">
|
|
||||||
{isRTL ? (
|
|
||||||
<ChevronRight className={`w-8 h-8`} />
|
|
||||||
) : (
|
|
||||||
<ChevronLeft className={`w-8 h-8`} />
|
|
||||||
)}
|
|
||||||
</NavLink>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
<div>
|
|
||||||
<button
|
<button
|
||||||
className="text-gray-500 dark:text-gray-400"
|
className="text-gray-500 dark:text-gray-400"
|
||||||
onClick={() => setSidebarOpen(true)}>
|
onClick={() => {
|
||||||
|
setShowOptionSidebar(!showOptionSidebar)
|
||||||
|
}}>
|
||||||
<PanelLeftIcon className="w-6 h-6" />
|
<PanelLeftIcon className="w-6 h-6" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
<Button
|
||||||
<NewChat clearChat={clearChat} />
|
color="cyan"
|
||||||
<span className="text-lg font-thin text-zinc-300 dark:text-zinc-600">
|
variant="filled"
|
||||||
{"/"}
|
shape="round"
|
||||||
</span>
|
style={{
|
||||||
<div className="hidden lg:block">
|
color: "#0057ff",
|
||||||
<Select
|
background: "#0057ff0f",
|
||||||
className="w-80"
|
border: "1px solid #0066ff26"
|
||||||
placeholder={t("common:selectAModel")}
|
|
||||||
// loadingText={t("common:selectAModel")}
|
|
||||||
value={selectedModel}
|
|
||||||
onChange={(e) => {
|
|
||||||
setSelectedModel(e)
|
|
||||||
localStorage.setItem("selectedModel", e)
|
|
||||||
}}
|
}}
|
||||||
filterOption={(input, option) => {
|
onClick={clearChat}>
|
||||||
//@ts-ignore
|
<div className="flex items-center justify-between w-full">
|
||||||
return (
|
<div className="flex items-center">
|
||||||
option?.label?.props["data-title"]
|
<PlusOutlined
|
||||||
?.toLowerCase()
|
className="text-sm"
|
||||||
?.indexOf(input.toLowerCase()) >= 0
|
style={{ fontSize: "16px", fontWeight: 500 }}
|
||||||
)
|
|
||||||
}}
|
|
||||||
showSearch
|
|
||||||
loading={isModelsLoading}
|
|
||||||
options={models?.map((model) => ({
|
|
||||||
label: (
|
|
||||||
<span
|
|
||||||
key={model.model}
|
|
||||||
data-title={model.name}
|
|
||||||
className="flex flex-row gap-3 items-center ">
|
|
||||||
<ProviderIcons
|
|
||||||
provider={model?.provider}
|
|
||||||
className="w-5 h-5"
|
|
||||||
/>
|
|
||||||
<span className="line-clamp-2">{model.name}</span>
|
|
||||||
</span>
|
|
||||||
),
|
|
||||||
value: model.model
|
|
||||||
}))}
|
|
||||||
size="large"
|
|
||||||
// onRefresh={() => {
|
|
||||||
// refetch()
|
|
||||||
// }}
|
|
||||||
/>
|
/>
|
||||||
|
<span>{t("newChat")}</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="lg:hidden">
|
|
||||||
<ModelSelect />
|
|
||||||
</div>
|
</div>
|
||||||
<span className="text-lg font-thin text-zinc-300 dark:text-zinc-600">
|
</Button>
|
||||||
{"/"}
|
</div>
|
||||||
</span>
|
|
||||||
<div className="hidden lg:block">
|
|
||||||
<Select
|
|
||||||
size="large"
|
|
||||||
loading={isPromptLoading}
|
|
||||||
showSearch
|
|
||||||
placeholder={t("selectAPrompt")}
|
|
||||||
className="w-60"
|
|
||||||
allowClear
|
|
||||||
onChange={handlePromptChange}
|
|
||||||
value={selectedSystemPrompt}
|
|
||||||
filterOption={(input, option) =>
|
|
||||||
//@ts-ignore
|
|
||||||
option.label.key.toLowerCase().indexOf(input.toLowerCase()) >= 0
|
|
||||||
}
|
|
||||||
options={prompts?.map((prompt) => ({
|
|
||||||
label: (
|
|
||||||
<span
|
|
||||||
key={prompt.title}
|
|
||||||
className="flex flex-row gap-3 items-center">
|
|
||||||
{prompt.is_system ? (
|
|
||||||
<ComputerIcon className="w-4 h-4" />
|
|
||||||
) : (
|
|
||||||
<ZapIcon className="w-4 h-4" />
|
|
||||||
)}
|
)}
|
||||||
{prompt.title}
|
{location.pathname.includes("/settings") && (
|
||||||
</span>
|
<h2 className="text-xl font-bold text-zinc-700 dark:text-zinc-300 mr-3">
|
||||||
),
|
|
||||||
value: prompt.id
|
|
||||||
}))}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="lg:hidden">
|
|
||||||
<PromptSelect
|
|
||||||
selectedSystemPrompt={selectedSystemPrompt}
|
|
||||||
setSelectedSystemPrompt={setSelectedSystemPrompt}
|
|
||||||
setSelectedQuickPrompt={setSelectedQuickPrompt}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<SelectedKnowledge />
|
|
||||||
</div>
|
|
||||||
<div className="flex flex-1 justify-end px-4">
|
|
||||||
<div className="ml-4 flex items-center md:ml-6">
|
|
||||||
<div className="flex gap-4 items-center">
|
|
||||||
{messages.length > 0 && !streaming && (
|
|
||||||
<MoreOptions
|
|
||||||
shareModeEnabled={shareModeEnabled}
|
|
||||||
historyId={historyId}
|
|
||||||
messages={messages}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
{!hideCurrentChatModelSettings && (
|
|
||||||
<Tooltip title={t("common:currentChatModelSettings")}>
|
|
||||||
<button
|
|
||||||
onClick={() => setOpenModelSettings(true)}
|
|
||||||
className="!text-gray-500 dark:text-gray-300 hover:text-gray-600 dark:hover:text-gray-300 transition-colors">
|
|
||||||
<BrainCog className="w-6 h-6" />
|
|
||||||
</button>
|
|
||||||
</Tooltip>
|
|
||||||
)}
|
|
||||||
<Tooltip title={t("githubRepository")}>
|
|
||||||
<a
|
|
||||||
href="https://github.com/n4ze3m/page-assist"
|
|
||||||
target="_blank"
|
|
||||||
className="!text-gray-500 hidden lg:block dark:text-gray-400 hover:text-gray-600 dark:hover:text-gray-300 transition-colors">
|
|
||||||
<GithubIcon className="w-6 h-6" />
|
|
||||||
</a>
|
|
||||||
</Tooltip>
|
|
||||||
<Tooltip title={t("settings")}>
|
|
||||||
<NavLink
|
<NavLink
|
||||||
to="/settings"
|
to="/"
|
||||||
className="!text-gray-500 dark:text-gray-400 hover:text-gray-600 dark:hover:text-gray-300 transition-colors">
|
className="!text-gray-500 dark:text-gray-400 flex items-center gap-2 hover:text-gray-600 dark:hover:text-gray-300 transition-colors">
|
||||||
<CogIcon className="w-6 h-6" />
|
{!hideLogo && <img src={logo} alt="logo" className="w-8" />}
|
||||||
|
<p>
|
||||||
|
<span className="text-[#d30100]">数联网</span>科创智能体
|
||||||
|
</p>
|
||||||
|
</NavLink>
|
||||||
|
</h2>
|
||||||
|
)}
|
||||||
|
{/* 项目标题 */}
|
||||||
|
<div
|
||||||
|
className={`
|
||||||
|
absolute left-1/2 transform -translate-x-1/2
|
||||||
|
w-[600px] h-[60px] dark:bg-black
|
||||||
|
flex items-center justify-center
|
||||||
|
transition-[top] drop-shadow
|
||||||
|
${showOptionSidebar ? "-top-[60px]" : "-top-[2px] delay-200"}
|
||||||
|
`}>
|
||||||
|
<svg
|
||||||
|
className="icon"
|
||||||
|
viewBox="0 0 8960 1024"
|
||||||
|
version="1.1"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
p-id="9634"
|
||||||
|
width="100%"
|
||||||
|
height="55">
|
||||||
|
<path
|
||||||
|
d="M8960 0c-451.52 181.184-171.2 1024-992 1024H992C171.232 1024 451.392 181.184 0 0h8960z"
|
||||||
|
fill="#ffffff"
|
||||||
|
p-id="9635"></path>
|
||||||
|
</svg>
|
||||||
|
<h2 className="flex items-center gap-3 text-xl font-bold text-zinc-700 dark:text-zinc-300 mr-3 absolute left-1/2 transform -translate-x-1/2">
|
||||||
|
{!hideLogo && <img src={logo} alt="logo" className="w-8" />}
|
||||||
|
<p>
|
||||||
|
<span className="text-[#d30100]">数联网</span>科创智能体
|
||||||
|
</p>
|
||||||
|
</h2>
|
||||||
|
</div>
|
||||||
|
{/*设置框*/}
|
||||||
|
<div className="flex items-center gap-1 ml-auto pr-5">
|
||||||
|
<Tooltip title="收藏">
|
||||||
|
{collect ? (
|
||||||
|
<Button
|
||||||
|
color="default"
|
||||||
|
variant="text"
|
||||||
|
className="!px-[5px]"
|
||||||
|
onClick={() => setCollect(false)}>
|
||||||
|
<CollectIcon className="!text-gray-500 dark:text-gray-400 hover:text-gray-600 dark:hover:text-gray-300 transition-colors w-5 h-5 cursor-pointer" />
|
||||||
|
</Button>
|
||||||
|
) : (
|
||||||
|
<Button
|
||||||
|
color="default"
|
||||||
|
variant="text"
|
||||||
|
className="!px-[5px]"
|
||||||
|
onClick={() => setCollect(true)}>
|
||||||
|
<NotCollectIcon className="!text-gray-500 dark:text-gray-400 hover:text-gray-600 dark:hover:text-gray-300 transition-colors w-5 h-5 cursor-pointer" />
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</Tooltip>
|
||||||
|
<Tooltip title="分享">
|
||||||
|
<Button color="default" variant="text" className="!px-[5px]">
|
||||||
|
<ShareIcon className="!text-gray-500 dark:text-gray-400 hover:text-gray-600 dark:hover:text-gray-300 transition-colors w-5 h-5 cursor-pointer" />
|
||||||
|
</Button>
|
||||||
|
</Tooltip>
|
||||||
|
<Tooltip title="消息">
|
||||||
|
<Button color="default" variant="text" className="!px-[5px]">
|
||||||
|
<BellIcon className="!text-gray-500 dark:text-gray-400 hover:text-gray-600 dark:hover:text-gray-300 transition-colors w-5 h-5 cursor-pointer" />
|
||||||
|
</Button>
|
||||||
|
</Tooltip>
|
||||||
|
|
||||||
|
<Tooltip title={t("settings")}>
|
||||||
|
<NavLink to="/settings">
|
||||||
|
<SettingIcon className="!text-gray-500 dark:text-gray-400 hover:text-gray-600 dark:hover:text-gray-300 transition-colors w-5 h-5 cursor-pointer" />
|
||||||
</NavLink>
|
</NavLink>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -1,104 +1,86 @@
|
|||||||
import React, { useState } from "react"
|
import React, { useContext, useState } from "react"
|
||||||
|
|
||||||
import { Sidebar } from "../Option/Sidebar"
|
|
||||||
import { Drawer, Tooltip } from "antd"
|
|
||||||
|
|
||||||
import { useTranslation } from "react-i18next"
|
|
||||||
|
|
||||||
import { CurrentChatModelSettings } from "../Common/Settings/CurrentChatModelSettings"
|
import { CurrentChatModelSettings } from "../Common/Settings/CurrentChatModelSettings"
|
||||||
import { Header } from "./Header"
|
import { Header } from "./Header.tsx"
|
||||||
import { EraserIcon } from "lucide-react"
|
import IodVideo from "@/components/Option/VideoPlayer"
|
||||||
import { PageAssitDatabase } from "@/db"
|
|
||||||
import { useMessageOption } from "@/hooks/useMessageOption"
|
interface OptionLayoutContextType {
|
||||||
import { useQueryClient } from "@tanstack/react-query"
|
showOptionSidebar: boolean
|
||||||
import { useStoreChatModelSettings } from "@/store/model"
|
setShowOptionSidebar: (show: boolean) => void
|
||||||
|
showVideo: boolean
|
||||||
|
setShowVideo: (show: boolean) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
const OptionLayoutContext = React.createContext<OptionLayoutContextType>({
|
||||||
|
showOptionSidebar: true,
|
||||||
|
setShowOptionSidebar: () => {},
|
||||||
|
showVideo: true,
|
||||||
|
setShowVideo: () => {}
|
||||||
|
})
|
||||||
|
|
||||||
|
// 创建自定义 hook 以便子组件使用
|
||||||
|
export const useOptionLayoutContext = () => {
|
||||||
|
const context = useContext(OptionLayoutContext)
|
||||||
|
if (context === undefined) {
|
||||||
|
throw new Error(
|
||||||
|
"useOptionLayoutContext must be used within a OptionLayoutProvider"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return context
|
||||||
|
}
|
||||||
|
|
||||||
|
const OptionLayoutProvider = ({ children }: { children: React.ReactNode }) => {
|
||||||
|
const [showHistory, setShowHistory] = useState(true)
|
||||||
|
|
||||||
|
const [showVideo, setShowVideo] = useState<boolean>(false)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<OptionLayoutContext.Provider
|
||||||
|
value={{
|
||||||
|
showOptionSidebar: showHistory,
|
||||||
|
setShowOptionSidebar: setShowHistory,
|
||||||
|
showVideo,
|
||||||
|
setShowVideo
|
||||||
|
}}>
|
||||||
|
{children}
|
||||||
|
</OptionLayoutContext.Provider>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const OptionLayoutMain: React.FC<{ children: React.ReactNode }> = ({
|
||||||
|
children
|
||||||
|
}) => {
|
||||||
|
const { showVideo } = useOptionLayoutContext()
|
||||||
|
|
||||||
|
if (showVideo) {
|
||||||
|
return <IodVideo />
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Header />
|
||||||
|
{children}
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
export default function OptionLayout({
|
export default function OptionLayout({
|
||||||
children
|
children
|
||||||
}: {
|
}: {
|
||||||
children: React.ReactNode
|
children: React.ReactNode
|
||||||
}) {
|
}) {
|
||||||
const [sidebarOpen, setSidebarOpen] = useState(false)
|
|
||||||
const { t } = useTranslation(["option", "common", "settings"])
|
|
||||||
const [openModelSettings, setOpenModelSettings] = useState(false)
|
const [openModelSettings, setOpenModelSettings] = useState(false)
|
||||||
const {
|
|
||||||
setMessages,
|
|
||||||
setHistory,
|
|
||||||
setHistoryId,
|
|
||||||
historyId,
|
|
||||||
clearChat,
|
|
||||||
setSelectedModel,
|
|
||||||
temporaryChat,
|
|
||||||
setSelectedSystemPrompt
|
|
||||||
} = useMessageOption()
|
|
||||||
|
|
||||||
const queryClient = useQueryClient()
|
|
||||||
const { setSystemPrompt } = useStoreChatModelSettings()
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex h-full w-full">
|
<div className="flex h-full w-full">
|
||||||
<main className="relative h-dvh w-full">
|
<main className="relative h-dvh w-full">
|
||||||
<div className="relative z-10 w-full">
|
{/*<div className="relative z-10 w-full">*/}
|
||||||
<Header
|
{/*</div>*/}
|
||||||
setSidebarOpen={setSidebarOpen}
|
|
||||||
setOpenModelSettings={setOpenModelSettings}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
{/* <div className="relative flex h-full flex-col items-center"> */}
|
{/* <div className="relative flex h-full flex-col items-center"> */}
|
||||||
{children}
|
<OptionLayoutProvider>
|
||||||
|
<OptionLayoutMain>{children}</OptionLayoutMain>
|
||||||
|
</OptionLayoutProvider>
|
||||||
{/* </div> */}
|
{/* </div> */}
|
||||||
<Drawer
|
|
||||||
title={
|
|
||||||
<div className="flex items-center justify-between">
|
|
||||||
{t("sidebarTitle")}
|
|
||||||
|
|
||||||
<Tooltip
|
|
||||||
title={t(
|
|
||||||
"settings:generalSettings.system.deleteChatHistory.label"
|
|
||||||
)}
|
|
||||||
placement="right">
|
|
||||||
<button
|
|
||||||
onClick={async () => {
|
|
||||||
const confirm = window.confirm(
|
|
||||||
t(
|
|
||||||
"settings:generalSettings.system.deleteChatHistory.confirm"
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
if (confirm) {
|
|
||||||
const db = new PageAssitDatabase()
|
|
||||||
await db.deleteAllChatHistory()
|
|
||||||
await queryClient.invalidateQueries({
|
|
||||||
queryKey: ["fetchChatHistory"]
|
|
||||||
})
|
|
||||||
clearChat()
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
className="text-gray-600 hover:text-gray-800 dark:text-gray-300 dark:hover:text-gray-100">
|
|
||||||
<EraserIcon className="size-5" />
|
|
||||||
</button>
|
|
||||||
</Tooltip>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
placement="left"
|
|
||||||
closeIcon={null}
|
|
||||||
onClose={() => setSidebarOpen(false)}
|
|
||||||
open={sidebarOpen}>
|
|
||||||
<Sidebar
|
|
||||||
onClose={() => setSidebarOpen(false)}
|
|
||||||
setMessages={setMessages}
|
|
||||||
setHistory={setHistory}
|
|
||||||
setHistoryId={setHistoryId}
|
|
||||||
setSelectedModel={setSelectedModel}
|
|
||||||
setSelectedSystemPrompt={setSelectedSystemPrompt}
|
|
||||||
clearChat={clearChat}
|
|
||||||
historyId={historyId}
|
|
||||||
setSystemPrompt={setSystemPrompt}
|
|
||||||
temporaryChat={temporaryChat}
|
|
||||||
history={history}
|
|
||||||
/>
|
|
||||||
</Drawer>
|
|
||||||
|
|
||||||
<CurrentChatModelSettings
|
<CurrentChatModelSettings
|
||||||
open={openModelSettings}
|
open={openModelSettings}
|
||||||
setOpen={setOpenModelSettings}
|
setOpen={setOpenModelSettings}
|
||||||
|
@ -1,17 +1,18 @@
|
|||||||
import {
|
import {
|
||||||
|
BlocksIcon,
|
||||||
BookIcon,
|
BookIcon,
|
||||||
BrainCircuitIcon,
|
BrainCircuitIcon,
|
||||||
OrbitIcon,
|
|
||||||
ShareIcon,
|
|
||||||
BlocksIcon,
|
|
||||||
InfoIcon,
|
|
||||||
CombineIcon,
|
|
||||||
ChromeIcon,
|
ChromeIcon,
|
||||||
CpuIcon
|
CombineIcon,
|
||||||
|
CpuIcon,
|
||||||
|
InfoIcon,
|
||||||
|
OrbitIcon,
|
||||||
|
ShareIcon
|
||||||
} from "lucide-react"
|
} from "lucide-react"
|
||||||
import { useTranslation } from "react-i18next"
|
import { useTranslation } from "react-i18next"
|
||||||
import { Link, useLocation } from "react-router-dom"
|
import { Link, useLocation } from "react-router-dom"
|
||||||
import { OllamaIcon } from "../Icons/Ollama"
|
import { OllamaIcon } from "../Icons/Ollama"
|
||||||
|
import { IodIcon } from "../Icons/Iod.tsx"
|
||||||
import { BetaTag } from "../Common/Beta"
|
import { BetaTag } from "../Common/Beta"
|
||||||
|
|
||||||
function classNames(...classes: string[]) {
|
function classNames(...classes: string[]) {
|
||||||
@ -82,6 +83,12 @@ export const SettingsLayout = ({ children }: { children: React.ReactNode }) => {
|
|||||||
icon={OllamaIcon}
|
icon={OllamaIcon}
|
||||||
current={location.pathname}
|
current={location.pathname}
|
||||||
/>
|
/>
|
||||||
|
<LinkComponent
|
||||||
|
href="/settings/iod"
|
||||||
|
name={t("iodSettings.title")}
|
||||||
|
icon={IodIcon}
|
||||||
|
current={location.pathname}
|
||||||
|
/>
|
||||||
{import.meta.env.BROWSER === "chrome" && (
|
{import.meta.env.BROWSER === "chrome" && (
|
||||||
<LinkComponent
|
<LinkComponent
|
||||||
href="/settings/chrome"
|
href="/settings/chrome"
|
||||||
|
264
src/components/Layouts/_Header.tsx
Normal file
264
src/components/Layouts/_Header.tsx
Normal file
@ -0,0 +1,264 @@
|
|||||||
|
import { useStorage } from "@plasmohq/storage/hook"
|
||||||
|
import {
|
||||||
|
BrainCog,
|
||||||
|
ChevronLeft,
|
||||||
|
ChevronRight,
|
||||||
|
CogIcon,
|
||||||
|
ComputerIcon,
|
||||||
|
GaugeCircle,
|
||||||
|
GithubIcon,
|
||||||
|
PanelLeftIcon,
|
||||||
|
ZapIcon
|
||||||
|
} from "lucide-react"
|
||||||
|
import { useTranslation } from "react-i18next"
|
||||||
|
import { useLocation, NavLink } from "react-router-dom"
|
||||||
|
import { SelectedKnowledge } from "../Option/Knowledge/SelectedKnowledge"
|
||||||
|
import { ModelSelect } from "../Common/ModelSelect"
|
||||||
|
import { PromptSelect } from "../Common/PromptSelect"
|
||||||
|
import { useQuery } from "@tanstack/react-query"
|
||||||
|
import { fetchChatModels } from "~/services/ollama"
|
||||||
|
import { useMessageOption } from "~/hooks/useMessageOption"
|
||||||
|
import { Select, Tooltip } from "antd"
|
||||||
|
import { getAllPrompts } from "@/db"
|
||||||
|
import { ProviderIcons } from "../Common/ProviderIcon"
|
||||||
|
import { NewChat } from "./NewChat"
|
||||||
|
import { MoreOptions } from "./MoreOptions"
|
||||||
|
type Props = {
|
||||||
|
sidebarOpen: boolean
|
||||||
|
setSidebarOpen: () => void
|
||||||
|
setOpenModelSettings: (open: boolean) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Header: React.FC<Props> = ({
|
||||||
|
setOpenModelSettings,
|
||||||
|
setSidebarOpen,
|
||||||
|
sidebarOpen
|
||||||
|
}) => {
|
||||||
|
const { t, i18n } = useTranslation(["option", "common"])
|
||||||
|
const isRTL = i18n?.dir() === "rtl"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const [shareModeEnabled] = useStorage("shareMode", false)
|
||||||
|
const [hideCurrentChatModelSettings] = useStorage(
|
||||||
|
"hideCurrentChatModelSettings",
|
||||||
|
false
|
||||||
|
)
|
||||||
|
const {
|
||||||
|
selectedModel,
|
||||||
|
setSelectedModel,
|
||||||
|
clearChat,
|
||||||
|
selectedSystemPrompt,
|
||||||
|
setSelectedQuickPrompt,
|
||||||
|
setSelectedSystemPrompt,
|
||||||
|
messages,
|
||||||
|
streaming,
|
||||||
|
historyId,
|
||||||
|
temporaryChat
|
||||||
|
} = useMessageOption()
|
||||||
|
const {
|
||||||
|
data: models,
|
||||||
|
isLoading: isModelsLoading,
|
||||||
|
} = useQuery({
|
||||||
|
queryKey: ["fetchModel"],
|
||||||
|
queryFn: () => fetchChatModels({ returnEmpty: true }),
|
||||||
|
refetchIntervalInBackground: false,
|
||||||
|
placeholderData: (prev) => prev
|
||||||
|
})
|
||||||
|
|
||||||
|
const { data: prompts, isLoading: isPromptLoading } = useQuery({
|
||||||
|
queryKey: ["fetchAllPromptsLayout"],
|
||||||
|
queryFn: getAllPrompts
|
||||||
|
})
|
||||||
|
|
||||||
|
const { pathname } = useLocation()
|
||||||
|
|
||||||
|
const getPromptInfoById = (id: string) => {
|
||||||
|
return prompts?.find((prompt) => prompt.id === id)
|
||||||
|
}
|
||||||
|
|
||||||
|
const handlePromptChange = (value?: string) => {
|
||||||
|
if (!value) {
|
||||||
|
setSelectedSystemPrompt(undefined)
|
||||||
|
setSelectedQuickPrompt(undefined)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const prompt = getPromptInfoById(value)
|
||||||
|
if (prompt?.is_system) {
|
||||||
|
setSelectedSystemPrompt(prompt.id)
|
||||||
|
} else {
|
||||||
|
setSelectedSystemPrompt(undefined)
|
||||||
|
setSelectedQuickPrompt(prompt!.content)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={`absolute top-0 z-10 flex h-14 w-full flex-row items-center justify-center p-3 overflow-x-auto lg:overflow-x-visible bg-gray-50 border-b dark:bg-[#171717] dark:border-gray-600 ${
|
||||||
|
temporaryChat && "!bg-gray-200 dark:!bg-black"
|
||||||
|
}`}>
|
||||||
|
<div className="flex gap-2 items-center">
|
||||||
|
{pathname !== "/" && (
|
||||||
|
<div>
|
||||||
|
<NavLink
|
||||||
|
to="/"
|
||||||
|
className="text-gray-500 items-center dark:text-gray-400 hover:text-gray-600 dark:hover:text-gray-300 transition-colors">
|
||||||
|
{isRTL ? (
|
||||||
|
<ChevronRight className={`w-8 h-8`} />
|
||||||
|
) : (
|
||||||
|
<ChevronLeft className={`w-8 h-8`} />
|
||||||
|
)}
|
||||||
|
</NavLink>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<div style={{width: sidebarOpen ? "288px" : "205px"}} className="flex items-center justify-between transition-all duration-300 ease-in-out">
|
||||||
|
<h2
|
||||||
|
className="text-xl font-bold text-zinc-700 dark:text-zinc-300 mr-3"
|
||||||
|
style={{ lineHeight: "0" }}>
|
||||||
|
<span className="text-[#d30100]">数联网</span>科创智能体
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
<button
|
||||||
|
className="text-gray-500 dark:text-gray-400"
|
||||||
|
onClick={() => setSidebarOpen()}>
|
||||||
|
<PanelLeftIcon className="w-6 h-6" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<NewChat clearChat={clearChat} />
|
||||||
|
<span className="text-lg font-thin text-zinc-300 dark:text-zinc-600">
|
||||||
|
{"/"}
|
||||||
|
</span>
|
||||||
|
<div className="hidden lg:block">
|
||||||
|
<Select
|
||||||
|
className="w-80"
|
||||||
|
placeholder={t("common:selectAModel")}
|
||||||
|
// loadingText={t("common:selectAModel")}
|
||||||
|
value={selectedModel}
|
||||||
|
onChange={(e) => {
|
||||||
|
setSelectedModel(e)
|
||||||
|
localStorage.setItem("selectedModel", e)
|
||||||
|
}}
|
||||||
|
filterOption={(input, option) => {
|
||||||
|
//@ts-ignore
|
||||||
|
return (
|
||||||
|
option?.label?.props["data-title"]
|
||||||
|
?.toLowerCase()
|
||||||
|
?.indexOf(input.toLowerCase()) >= 0
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
showSearch
|
||||||
|
loading={isModelsLoading}
|
||||||
|
options={models?.map((model) => ({
|
||||||
|
label: (
|
||||||
|
<span
|
||||||
|
key={model.model}
|
||||||
|
data-title={model.name}
|
||||||
|
className="flex flex-row gap-3 items-center ">
|
||||||
|
<ProviderIcons
|
||||||
|
provider={model?.provider}
|
||||||
|
className="w-5 h-5"
|
||||||
|
/>
|
||||||
|
<span className="line-clamp-2">{model.name}</span>
|
||||||
|
</span>
|
||||||
|
),
|
||||||
|
value: model.model
|
||||||
|
}))}
|
||||||
|
size="large"
|
||||||
|
// onRefresh={() => {
|
||||||
|
// refetch()
|
||||||
|
// }}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="lg:hidden">
|
||||||
|
<ModelSelect />
|
||||||
|
</div>
|
||||||
|
<span className="text-lg font-thin text-zinc-300 dark:text-zinc-600">
|
||||||
|
{"/"}
|
||||||
|
</span>
|
||||||
|
<div className="hidden lg:block">
|
||||||
|
<Select
|
||||||
|
size="large"
|
||||||
|
loading={isPromptLoading}
|
||||||
|
showSearch
|
||||||
|
placeholder={t("selectAPrompt")}
|
||||||
|
className="w-60"
|
||||||
|
allowClear
|
||||||
|
onChange={handlePromptChange}
|
||||||
|
value={selectedSystemPrompt}
|
||||||
|
filterOption={(input, option) =>
|
||||||
|
//@ts-ignore
|
||||||
|
option.label.key.toLowerCase().indexOf(input.toLowerCase()) >= 0
|
||||||
|
}
|
||||||
|
options={prompts?.map((prompt) => ({
|
||||||
|
label: (
|
||||||
|
<span
|
||||||
|
key={prompt.title}
|
||||||
|
className="flex flex-row gap-3 items-center">
|
||||||
|
{prompt.is_system ? (
|
||||||
|
<ComputerIcon className="w-4 h-4" />
|
||||||
|
) : (
|
||||||
|
<ZapIcon className="w-4 h-4" />
|
||||||
|
)}
|
||||||
|
{prompt.title}
|
||||||
|
</span>
|
||||||
|
),
|
||||||
|
value: prompt.id
|
||||||
|
}))}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="lg:hidden">
|
||||||
|
<PromptSelect
|
||||||
|
selectedSystemPrompt={selectedSystemPrompt}
|
||||||
|
setSelectedSystemPrompt={setSelectedSystemPrompt}
|
||||||
|
setSelectedQuickPrompt={setSelectedQuickPrompt}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<SelectedKnowledge />
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-1 justify-end px-4">
|
||||||
|
<div className="ml-4 flex items-center md:ml-6">
|
||||||
|
<div className="flex gap-4 items-center">
|
||||||
|
{messages.length > 0 && !streaming && (
|
||||||
|
<MoreOptions
|
||||||
|
shareModeEnabled={shareModeEnabled}
|
||||||
|
historyId={historyId}
|
||||||
|
messages={messages}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{!hideCurrentChatModelSettings && (
|
||||||
|
<Tooltip title={t("common:currentChatModelSettings")}>
|
||||||
|
<button
|
||||||
|
onClick={() => setOpenModelSettings(true)}
|
||||||
|
className="!text-gray-500 dark:text-gray-300 hover:text-gray-600 dark:hover:text-gray-300 transition-colors">
|
||||||
|
<BrainCog className="w-6 h-6" />
|
||||||
|
</button>
|
||||||
|
</Tooltip>
|
||||||
|
)}
|
||||||
|
<Tooltip title={t("githubRepository")}>
|
||||||
|
<a
|
||||||
|
href="https://github.com/n4ze3m/page-assist"
|
||||||
|
target="_blank"
|
||||||
|
className="!text-gray-500 hidden lg:block dark:text-gray-400 hover:text-gray-600 dark:hover:text-gray-300 transition-colors">
|
||||||
|
<GithubIcon className="w-6 h-6" />
|
||||||
|
</a>
|
||||||
|
</Tooltip>
|
||||||
|
<Tooltip title={t("settings")}>
|
||||||
|
<NavLink
|
||||||
|
to="/settings"
|
||||||
|
className="!text-gray-500 dark:text-gray-400 hover:text-gray-600 dark:hover:text-gray-300 transition-colors">
|
||||||
|
<CogIcon className="w-6 h-6" />
|
||||||
|
</NavLink>
|
||||||
|
</Tooltip>
|
||||||
|
<Tooltip title={t("metering")}>
|
||||||
|
<NavLink
|
||||||
|
to="/metering"
|
||||||
|
className="!text-gray-500 dark:text-gray-400 hover:text-gray-600 dark:hover:text-gray-300 transition-colors">
|
||||||
|
<GaugeCircle className="w-6 h-6" />
|
||||||
|
</NavLink>
|
||||||
|
</Tooltip>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
165
src/components/Option/Metering/detail.tsx
Normal file
165
src/components/Option/Metering/detail.tsx
Normal file
@ -0,0 +1,165 @@
|
|||||||
|
import React, { useMemo } from "react"
|
||||||
|
import { MeteringEntry, useStoreMessageOption } from "@/store/option"
|
||||||
|
import { Card, List, Table, Tag, Space, TableProps, Tooltip } from "antd"
|
||||||
|
import { NavLink } from "react-router-dom"
|
||||||
|
import { formatDate } from "@/utils/date"
|
||||||
|
|
||||||
|
const columns: TableProps<MeteringEntry>["columns"] = [
|
||||||
|
{
|
||||||
|
title: '序号',
|
||||||
|
key: 'index',
|
||||||
|
width: 100,
|
||||||
|
render: (_text, _record, index) => index + 1, // 索引从0开始,+1后从1显示
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "问题",
|
||||||
|
dataIndex: "queryContent",
|
||||||
|
key: "queryContent"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "提示词全文",
|
||||||
|
dataIndex: "prompt",
|
||||||
|
key: "prompt",
|
||||||
|
ellipsis: {
|
||||||
|
showTitle: false
|
||||||
|
},
|
||||||
|
render: (prompt) => (
|
||||||
|
<Tooltip placement="topLeft" title={prompt}>
|
||||||
|
{prompt}
|
||||||
|
</Tooltip>
|
||||||
|
),
|
||||||
|
width: "10%"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "思维链",
|
||||||
|
key: "cot",
|
||||||
|
dataIndex: "cot",
|
||||||
|
ellipsis: {
|
||||||
|
showTitle: false
|
||||||
|
},
|
||||||
|
render: (responseContent) => (
|
||||||
|
<Tooltip placement="topLeft" title={responseContent}>
|
||||||
|
{responseContent}
|
||||||
|
</Tooltip>
|
||||||
|
),
|
||||||
|
width: "10%"
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
title: "回答",
|
||||||
|
dataIndex: "responseContent",
|
||||||
|
key: "responseContent",
|
||||||
|
ellipsis: {
|
||||||
|
showTitle: false
|
||||||
|
},
|
||||||
|
render: (responseContent) => (
|
||||||
|
<Tooltip placement="topLeft" title={responseContent}>
|
||||||
|
{responseContent}
|
||||||
|
</Tooltip>
|
||||||
|
),
|
||||||
|
width: "10%"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "关联数据个数",
|
||||||
|
dataIndex: "relatedDataCount",
|
||||||
|
key: "relatedDataCount"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "数联网token",
|
||||||
|
dataIndex: "iodTokenCount",
|
||||||
|
key: "iodTokenCount"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "大模型token",
|
||||||
|
key: "largeModelToken",
|
||||||
|
dataIndex: "largeModelToken",
|
||||||
|
render: (_, record) => {
|
||||||
|
return (
|
||||||
|
<div>{record.modelInputTokenCount + record.modelOutputTokenCount}</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "日期",
|
||||||
|
dataIndex: "date",
|
||||||
|
key: "date",
|
||||||
|
render: (date) => {
|
||||||
|
return <div>{formatDate(new Date(date))}</div>
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "耗时",
|
||||||
|
key: "timeTaken",
|
||||||
|
dataIndex: "timeTaken"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "操作",
|
||||||
|
key: "action",
|
||||||
|
render: (_, record) => (
|
||||||
|
<Space size="middle">
|
||||||
|
{/* <a>Invite {record.name}</a> */}
|
||||||
|
|
||||||
|
<NavLink to={`/metering/list/${record.id}`}>
|
||||||
|
<a>详情</a>
|
||||||
|
</NavLink>
|
||||||
|
</Space>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
export const MeteringDetail = () => {
|
||||||
|
const { meteringEntries } = useStoreMessageOption()
|
||||||
|
|
||||||
|
const data = useMemo(
|
||||||
|
() => [
|
||||||
|
{
|
||||||
|
key: "对话数量",
|
||||||
|
value: meteringEntries.length
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "数联网输入token数",
|
||||||
|
value: meteringEntries.reduce((acc, cur) => {
|
||||||
|
for (const item of cur.iodKeywords) {
|
||||||
|
acc += item.length
|
||||||
|
}
|
||||||
|
return acc
|
||||||
|
}, 0)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "数联网输出token数",
|
||||||
|
value: meteringEntries.reduce((acc, cur) => acc + cur.iodTokenCount, 0)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "大模型输入token数",
|
||||||
|
value: meteringEntries.reduce(
|
||||||
|
(acc, cur) => acc + cur.modelInputTokenCount,
|
||||||
|
0
|
||||||
|
)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "大模型输出token数",
|
||||||
|
value: meteringEntries.reduce(
|
||||||
|
(acc, cur) => acc + cur.modelOutputTokenCount,
|
||||||
|
0
|
||||||
|
)
|
||||||
|
}
|
||||||
|
],
|
||||||
|
[meteringEntries]
|
||||||
|
)
|
||||||
|
return (
|
||||||
|
<div className="p-4 pt-[4rem]">
|
||||||
|
<List
|
||||||
|
grid={{ gutter: 16, column: 5 }}
|
||||||
|
dataSource={data}
|
||||||
|
split={false}
|
||||||
|
renderItem={(item) => (
|
||||||
|
<List.Item>
|
||||||
|
<Card title={item.key}>{item.value}</Card>
|
||||||
|
</List.Item>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Table<MeteringEntry> columns={columns} dataSource={meteringEntries} />
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
220
src/components/Option/Metering/listDetail.tsx
Normal file
220
src/components/Option/Metering/listDetail.tsx
Normal file
@ -0,0 +1,220 @@
|
|||||||
|
import {
|
||||||
|
Card,
|
||||||
|
List,
|
||||||
|
Table,
|
||||||
|
Space,
|
||||||
|
TableProps,
|
||||||
|
Divider,
|
||||||
|
Typography,
|
||||||
|
Tooltip
|
||||||
|
} from "antd"
|
||||||
|
import { useParams } from "react-router-dom"
|
||||||
|
import { useStoreMessageOption } from "@/store/option.tsx"
|
||||||
|
import { useMemo } from "react"
|
||||||
|
|
||||||
|
interface DataType {
|
||||||
|
key: string
|
||||||
|
name: string
|
||||||
|
doId: number
|
||||||
|
data_space: string
|
||||||
|
content: string
|
||||||
|
tokenCount: number
|
||||||
|
}
|
||||||
|
|
||||||
|
const columns: TableProps<DataType>["columns"] = [
|
||||||
|
{
|
||||||
|
title: "序号",
|
||||||
|
key: "index",
|
||||||
|
width: 100,
|
||||||
|
render: (_text, _record, index) => index + 1 // 索引从0开始,+1后从1显示
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "标识",
|
||||||
|
dataIndex: "doId",
|
||||||
|
key: "doId",
|
||||||
|
width: 350
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "提供方",
|
||||||
|
dataIndex: "data_space",
|
||||||
|
key: "data_space",
|
||||||
|
width: 250
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "token数",
|
||||||
|
key: "tokenCount",
|
||||||
|
dataIndex: "tokenCount",
|
||||||
|
width: 120
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "内容",
|
||||||
|
key: "content",
|
||||||
|
dataIndex: "content",
|
||||||
|
ellipsis: {
|
||||||
|
showTitle: false
|
||||||
|
},
|
||||||
|
render: (content) => (
|
||||||
|
<Tooltip placement="topLeft" title={content}>
|
||||||
|
{content}
|
||||||
|
</Tooltip>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
export const ListDetail = () => {
|
||||||
|
const { meteringEntries } = useStoreMessageOption()
|
||||||
|
const { id } = useParams()
|
||||||
|
const record = useMemo(
|
||||||
|
() => meteringEntries.find((item) => item.id === id),
|
||||||
|
[meteringEntries]
|
||||||
|
)
|
||||||
|
|
||||||
|
const modelData = useMemo(
|
||||||
|
() => [
|
||||||
|
{
|
||||||
|
key: "数联网引用token总数",
|
||||||
|
value: record.iodTokenCount
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "大模型输入token数",
|
||||||
|
value: record.modelInputTokenCount
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "大模型输出token数",
|
||||||
|
value: record.modelOutputTokenCount
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "模型",
|
||||||
|
value: record.model
|
||||||
|
}
|
||||||
|
],
|
||||||
|
[record]
|
||||||
|
)
|
||||||
|
|
||||||
|
const inputTokenData = useMemo(
|
||||||
|
() => [
|
||||||
|
{
|
||||||
|
key: "内容:",
|
||||||
|
value: record.queryContent
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "token数量:",
|
||||||
|
value: record.queryContent.length
|
||||||
|
}
|
||||||
|
],
|
||||||
|
[record]
|
||||||
|
)
|
||||||
|
const keywordsData = useMemo(
|
||||||
|
() => [
|
||||||
|
{
|
||||||
|
key: "token数量:",
|
||||||
|
value: record.iodKeywords.reduce((acc, cur) => acc + cur.length, 0)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "内容:",
|
||||||
|
value: record.iodKeywords.join(", ")
|
||||||
|
}
|
||||||
|
],
|
||||||
|
[record]
|
||||||
|
)
|
||||||
|
const responseContent = useMemo(
|
||||||
|
() => [
|
||||||
|
{
|
||||||
|
key: "token数量:",
|
||||||
|
value: record.modelResponseContent.length
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "内容:",
|
||||||
|
value: record.modelResponseContent
|
||||||
|
}
|
||||||
|
],
|
||||||
|
[record]
|
||||||
|
)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="p-[1rem] pt-[4rem]">
|
||||||
|
<List
|
||||||
|
grid={{ gutter: 16, column: 4 }}
|
||||||
|
dataSource={modelData}
|
||||||
|
renderItem={(item) => (
|
||||||
|
<List.Item>
|
||||||
|
<Card title={item.key}>{item.value}</Card>
|
||||||
|
</List.Item>
|
||||||
|
)}
|
||||||
|
style={{ marginBottom: "2rem" }}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Space direction="vertical" className="w-full" size={10}>
|
||||||
|
<Divider orientation="left">数联网引用数据</Divider>
|
||||||
|
<Table<DataType> columns={columns} dataSource={record.iodData} />
|
||||||
|
</Space>
|
||||||
|
|
||||||
|
<Space direction="vertical" className="w-full" size={10}>
|
||||||
|
<Divider orientation="left">输入token详情</Divider>
|
||||||
|
<List
|
||||||
|
bordered
|
||||||
|
header={<div>问题</div>}
|
||||||
|
dataSource={inputTokenData}
|
||||||
|
renderItem={(item) => (
|
||||||
|
<List.Item style={{ justifyContent: "flex-start" }}>
|
||||||
|
<Typography.Paragraph
|
||||||
|
style={{ marginBottom: 0 }}
|
||||||
|
className="mr-1">
|
||||||
|
{item.key}
|
||||||
|
</Typography.Paragraph>
|
||||||
|
<Tooltip
|
||||||
|
placement="topLeft"
|
||||||
|
style={{ marginBottom: 0 }}
|
||||||
|
title={item.value}>
|
||||||
|
{item.value}
|
||||||
|
</Tooltip>
|
||||||
|
</List.Item>
|
||||||
|
)}
|
||||||
|
style={{ marginBottom: "1rem" }}
|
||||||
|
/>
|
||||||
|
</Space>
|
||||||
|
|
||||||
|
<Space direction="vertical" className="w-full" size={10}>
|
||||||
|
<Divider orientation="left">输出token详情</Divider>
|
||||||
|
<List
|
||||||
|
bordered
|
||||||
|
dataSource={keywordsData}
|
||||||
|
header={<div>数联网搜索关键词</div>}
|
||||||
|
renderItem={(item) => (
|
||||||
|
<List.Item style={{ justifyContent: "flex-start" }}>
|
||||||
|
<Typography.Text className="mr-1" style={{ marginBottom: 0 }}>
|
||||||
|
{item.key}
|
||||||
|
</Typography.Text>
|
||||||
|
<Tooltip
|
||||||
|
style={{ marginBottom: 0 }}
|
||||||
|
placement="topLeft"
|
||||||
|
title={item.value}>
|
||||||
|
{item.value}
|
||||||
|
</Tooltip>
|
||||||
|
</List.Item>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<List
|
||||||
|
bordered
|
||||||
|
dataSource={responseContent}
|
||||||
|
header={<div>回答</div>}
|
||||||
|
renderItem={(item) => (
|
||||||
|
<List.Item
|
||||||
|
style={{ justifyContent: "flex-start", alignItems: "center" }}>
|
||||||
|
<Typography.Text
|
||||||
|
className="mt-0 mr-1 w-20"
|
||||||
|
style={{ marginBottom: 0 }}>
|
||||||
|
{item.key}
|
||||||
|
</Typography.Text>
|
||||||
|
<Typography.Paragraph
|
||||||
|
style={{ marginBottom: 0 }}
|
||||||
|
ellipsis={{ tooltip: item.value, rows: 2, expandable: true }}>
|
||||||
|
{item.value}
|
||||||
|
</Typography.Paragraph>
|
||||||
|
</List.Item>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</Space>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
@ -1,8 +1,11 @@
|
|||||||
import React from "react"
|
import React from "react"
|
||||||
|
|
||||||
import { PlaygroundForm } from "./PlaygroundForm"
|
import { PlaygroundForm } from "./PlaygroundForm"
|
||||||
import { PlaygroundChat } from "./PlaygroundChat"
|
import { PlaygroundChat } from "./PlaygroundChat"
|
||||||
|
import { PlaygroundSidebar } from "./PlaygroundSidebar.tsx"
|
||||||
import { useMessageOption } from "@/hooks/useMessageOption"
|
import { useMessageOption } from "@/hooks/useMessageOption"
|
||||||
import { webUIResumeLastChat } from "@/services/app"
|
import { webUIResumeLastChat } from "@/services/app"
|
||||||
|
|
||||||
import {
|
import {
|
||||||
formatToChatHistory,
|
formatToChatHistory,
|
||||||
formatToMessage,
|
formatToMessage,
|
||||||
@ -13,6 +16,7 @@ import { getLastUsedChatSystemPrompt } from "@/services/model-settings"
|
|||||||
import { useStoreChatModelSettings } from "@/store/model"
|
import { useStoreChatModelSettings } from "@/store/model"
|
||||||
import { useSmartScroll } from "@/hooks/useSmartScroll"
|
import { useSmartScroll } from "@/hooks/useSmartScroll"
|
||||||
import { ChevronDown } from "lucide-react"
|
import { ChevronDown } from "lucide-react"
|
||||||
|
import { PlaygroundIod } from "@/components/Option/Playground/PlaygroundIod.tsx"
|
||||||
|
|
||||||
export const Playground = () => {
|
export const Playground = () => {
|
||||||
const drop = React.useRef<HTMLDivElement>(null)
|
const drop = React.useRef<HTMLDivElement>(null)
|
||||||
@ -132,17 +136,20 @@ export const Playground = () => {
|
|||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
ref={drop}
|
ref={drop}
|
||||||
className={`relative flex h-full flex-col items-center ${
|
className={`relative flex gap-3 h-full items-center ${
|
||||||
dropState === "dragging" ? "bg-gray-100 dark:bg-gray-800" : ""
|
dropState === "dragging" ? "bg-gray-100 dark:bg-gray-800" : ""
|
||||||
} bg-white dark:bg-[#171717]`}>
|
} bg-white dark:bg-[#171717]`}>
|
||||||
|
<PlaygroundSidebar />
|
||||||
|
<div className="h-full flex-1 overflow-x-hidden prose-lg flex flex-col items-center [&>*]:max-w-[848px] pt-[60px]">
|
||||||
<div
|
<div
|
||||||
ref={containerRef}
|
ref={containerRef}
|
||||||
className="custom-scrollbar bg-bottom-mask-light dark:bg-bottom-mask-dark mask-bottom-fade will-change-mask flex h-full w-full flex-col items-center overflow-x-hidden overflow-y-auto px-5">
|
className="custom-scrollbar flex h-auto w-full flex-col items-center px-5">
|
||||||
<PlaygroundChat />
|
<PlaygroundChat />
|
||||||
</div>
|
</div>
|
||||||
<div className="absolute bottom-0 w-full">
|
<div
|
||||||
|
className={`${messages.length ? "absolute" : "relative"} bottom-0 w-full`}>
|
||||||
{!isAtBottom && (
|
{!isAtBottom && (
|
||||||
<div className="fixed bottom-36 z-20 left-0 right-0 flex justify-center">
|
<div className="absolute bottom-36 z-20 left-0 right-0 flex justify-center">
|
||||||
<button
|
<button
|
||||||
onClick={scrollToBottom}
|
onClick={scrollToBottom}
|
||||||
className="bg-gray-50 shadow border border-gray-200 dark:border-none dark:bg-white/20 p-1.5 rounded-full pointer-events-auto">
|
className="bg-gray-50 shadow border border-gray-200 dark:border-none dark:bg-white/20 p-1.5 rounded-full pointer-events-auto">
|
||||||
@ -153,5 +160,7 @@ export const Playground = () => {
|
|||||||
<PlaygroundForm dropedFile={dropedFile} />
|
<PlaygroundForm dropedFile={dropedFile} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<PlaygroundIod />
|
||||||
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -11,22 +11,24 @@ export const PlaygroundChat = () => {
|
|||||||
regenerateLastMessage,
|
regenerateLastMessage,
|
||||||
isSearchingInternet,
|
isSearchingInternet,
|
||||||
editMessage,
|
editMessage,
|
||||||
ttsEnabled
|
ttsEnabled,
|
||||||
|
setCurrentMessageId,
|
||||||
} = useMessageOption()
|
} = useMessageOption()
|
||||||
const [isSourceOpen, setIsSourceOpen] = React.useState(false)
|
const [isSourceOpen, setIsSourceOpen] = React.useState(false)
|
||||||
const [source, setSource] = React.useState<any>(null)
|
const [source, setSource] = React.useState<any>(null)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="relative flex w-full flex-col items-center pt-16 pb-4">
|
<div className="relative flex w-full flex-col items-center pb-4">
|
||||||
{messages.length === 0 && (
|
{messages.length === 0 && (
|
||||||
<div className="mt-32 w-full">
|
<div className="mt-3 w-full">
|
||||||
<PlaygroundEmpty />
|
<PlaygroundEmpty />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{messages.map((message, index) => (
|
{messages.map((message, index) => (
|
||||||
<PlaygroundMessage
|
<PlaygroundMessage
|
||||||
key={index}
|
key={index}
|
||||||
|
id={message.id}
|
||||||
isBot={message.isBot}
|
isBot={message.isBot}
|
||||||
message={message.message}
|
message={message.message}
|
||||||
name={message.name}
|
name={message.name}
|
||||||
@ -36,7 +38,7 @@ export const PlaygroundChat = () => {
|
|||||||
onRengerate={regenerateLastMessage}
|
onRengerate={regenerateLastMessage}
|
||||||
isProcessing={streaming}
|
isProcessing={streaming}
|
||||||
isSearchingInternet={isSearchingInternet}
|
isSearchingInternet={isSearchingInternet}
|
||||||
sources={message.sources}
|
webSources={message.webSources}
|
||||||
iodSources={message.iodSources}
|
iodSources={message.iodSources}
|
||||||
onEditFormSubmit={(value, isSend) => {
|
onEditFormSubmit={(value, isSend) => {
|
||||||
editMessage(index, value, !message.isBot, isSend)
|
editMessage(index, value, !message.isBot, isSend)
|
||||||
@ -49,11 +51,12 @@ export const PlaygroundChat = () => {
|
|||||||
generationInfo={message?.generationInfo}
|
generationInfo={message?.generationInfo}
|
||||||
isStreaming={streaming}
|
isStreaming={streaming}
|
||||||
reasoningTimeTaken={message?.reasoning_time_taken}
|
reasoningTimeTaken={message?.reasoning_time_taken}
|
||||||
|
setCurrentMessageId={setCurrentMessageId}
|
||||||
|
iodSearch={message.iodSearch}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
<div className="w-full pb-[157px]"></div>
|
{messages.length !== 0 && <div className="w-full pb-[157px]"></div>}
|
||||||
|
|
||||||
<MessageSourcePopup
|
<MessageSourcePopup
|
||||||
open={isSourceOpen}
|
open={isSourceOpen}
|
||||||
setOpen={setIsSourceOpen}
|
setOpen={setIsSourceOpen}
|
||||||
|
@ -1,130 +1,40 @@
|
|||||||
import { cleanUrl } from "@/libs/clean-url"
|
import { useMessageOption } from "@/hooks/useMessageOption.tsx"
|
||||||
import { useStorage } from "@plasmohq/storage/hook"
|
import { useMutation, useQueryClient } from "@tanstack/react-query"
|
||||||
import { useQuery } from "@tanstack/react-query"
|
import { qaPrompt } from "@/libs/playground.tsx"
|
||||||
import { RotateCcw } from "lucide-react"
|
|
||||||
import { useEffect, useState } from "react"
|
|
||||||
import { Trans, useTranslation } from "react-i18next"
|
|
||||||
import {
|
|
||||||
getOllamaURL,
|
|
||||||
isOllamaRunning,
|
|
||||||
setOllamaURL as saveOllamaURL
|
|
||||||
} from "~/services/ollama"
|
|
||||||
|
|
||||||
export const PlaygroundEmpty = () => {
|
export const PlaygroundEmpty = () => {
|
||||||
const [ollamaURL, setOllamaURL] = useState<string>("")
|
const { onSubmit } = useMessageOption()
|
||||||
const { t } = useTranslation(["playground", "common"])
|
|
||||||
|
|
||||||
const [checkOllamaStatus] = useStorage("checkOllamaStatus", true)
|
const queryClient = useQueryClient()
|
||||||
|
|
||||||
const {
|
const { mutateAsync: sendMessage } = useMutation({
|
||||||
data: ollamaInfo,
|
mutationFn: onSubmit,
|
||||||
status: ollamaStatus,
|
onSuccess: () => {
|
||||||
refetch,
|
queryClient.invalidateQueries({
|
||||||
isRefetching
|
queryKey: ["fetchChatHistory"]
|
||||||
} = useQuery({
|
})
|
||||||
queryKey: ["ollamaStatus"],
|
|
||||||
queryFn: async () => {
|
|
||||||
const ollamaURL = await getOllamaURL()
|
|
||||||
const isOk = await isOllamaRunning()
|
|
||||||
|
|
||||||
if (ollamaURL) {
|
|
||||||
saveOllamaURL(ollamaURL)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
|
||||||
isOk,
|
|
||||||
ollamaURL
|
|
||||||
}
|
|
||||||
},
|
|
||||||
enabled: checkOllamaStatus
|
|
||||||
})
|
})
|
||||||
|
|
||||||
useEffect(() => {
|
function handleQuestion(message: string) {
|
||||||
if (ollamaInfo?.ollamaURL) {
|
void sendMessage({ message, image: "" })
|
||||||
setOllamaURL(ollamaInfo.ollamaURL)
|
|
||||||
}
|
}
|
||||||
}, [ollamaInfo])
|
|
||||||
|
|
||||||
|
|
||||||
if (!checkOllamaStatus) {
|
|
||||||
return (
|
return (
|
||||||
<div className="mx-auto sm:max-w-xl px-4 mt-10">
|
<div className="w-full pb-4 pt-[20%] grid grid-cols-3 gap-3">
|
||||||
<div className="rounded-lg justify-center items-center flex flex-col border p-8 bg-gray-50 dark:bg-[#262626] dark:border-gray-600">
|
{qaPrompt.map((item, index) => (
|
||||||
<h1 className="text-sm font-medium text-center text-gray-500 dark:text-gray-400 flex gap-3 items-center justify-center">
|
<div
|
||||||
<span >👋</span>
|
key={item.id}
|
||||||
<span className="text-gray-700 dark:text-gray-300">
|
className="p-6 bg-gradient-to-br from-blue-50/90 via-indigo-50/90 to-purple-50/90 backdrop-blur-xl border border-white/60 shadow-xl rounded-2xl cursor-pointer hover:shadow-blue-200/40 hover:from-blue-100/90 hover:to-indigo-100/90 transition-all duration-500 hover:-translate-y-1"
|
||||||
{t("welcome")}
|
onClick={() => handleQuestion(item.title)}>
|
||||||
</span>
|
<div className="flex items-center">
|
||||||
</h1>
|
<div className="text-blue-500 mr-2 w-10">{item.icon}</div>
|
||||||
|
<div className="text-sm text-gray-800">
|
||||||
|
{item.title}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
<div className="mx-auto sm:max-w-xl px-4 mt-10">
|
|
||||||
<div className="rounded-lg justify-center items-center flex flex-col border p-8 bg-gray-50 dark:bg-[#262626] dark:border-gray-600">
|
|
||||||
{(ollamaStatus === "pending" || isRefetching) && (
|
|
||||||
<div className="inline-flex items-center space-x-2">
|
|
||||||
<div className="w-3 h-3 bg-blue-500 rounded-full animate-pulse"></div>
|
|
||||||
<p className="dark:text-gray-400 text-gray-900">
|
|
||||||
{t("ollamaState.searching")}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
{!isRefetching && ollamaStatus === "success" ? (
|
|
||||||
ollamaInfo.isOk ? (
|
|
||||||
<div className="inline-flex items-center space-x-2">
|
|
||||||
<div className="w-3 h-3 bg-green-500 rounded-full animate-pulse"></div>
|
|
||||||
<p className="dark:text-gray-400 text-gray-900">
|
|
||||||
{t("ollamaState.running")}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<div className="flex flex-col space-y-2 justify-center items-center">
|
|
||||||
<div className="inline-flex space-x-2">
|
|
||||||
<div className="w-3 h-3 bg-red-500 rounded-full animate-pulse"></div>
|
|
||||||
<p className="dark:text-gray-400 text-gray-900">
|
|
||||||
{t("ollamaState.notRunning")}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<input
|
|
||||||
className="bg-gray-100 dark:bg-[#262626] dark:text-gray-100 rounded-md px-4 py-2 mt-2 w-full"
|
|
||||||
type="url"
|
|
||||||
value={ollamaURL}
|
|
||||||
onChange={(e) => setOllamaURL(e.target.value)}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<button
|
|
||||||
onClick={() => {
|
|
||||||
saveOllamaURL(ollamaURL)
|
|
||||||
refetch()
|
|
||||||
}}
|
|
||||||
className="inline-flex mt-4 items-center rounded-md border border-transparent bg-black px-2 py-2 text-sm font-medium leading-4 text-white shadow-sm hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 dark:bg-white dark:text-gray-800 dark:hover:bg-gray-100 dark:focus:ring-gray-500 dark:focus:ring-offset-gray-100 disabled:opacity-50 ">
|
|
||||||
<RotateCcw className="h-4 w-4 mr-3" />
|
|
||||||
{t("common:retry")}
|
|
||||||
</button>
|
|
||||||
|
|
||||||
{ollamaURL &&
|
|
||||||
cleanUrl(ollamaURL) !== "http://127.0.0.1:11434" && (
|
|
||||||
<p className="text-xs text-gray-500 dark:text-gray-400 mb-4 text-center">
|
|
||||||
<Trans
|
|
||||||
i18nKey="playground:ollamaState.connectionError"
|
|
||||||
components={{
|
|
||||||
anchor: (
|
|
||||||
<a
|
|
||||||
href="https://github.com/n4ze3m/page-assist/blob/main/docs/connection-issue.md"
|
|
||||||
target="__blank"
|
|
||||||
className="text-blue-600 dark:text-blue-400"></a>
|
|
||||||
)
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</p>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
) : null}
|
|
||||||
</div>
|
</div>
|
||||||
|
))}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -1,19 +1,26 @@
|
|||||||
import { useForm } from "@mantine/form"
|
import { useForm } from "@mantine/form"
|
||||||
import { useMutation, useQueryClient } from "@tanstack/react-query"
|
import { useMutation, useQueryClient } from "@tanstack/react-query"
|
||||||
import React from "react"
|
import React, { useMemo } from "react"
|
||||||
import useDynamicTextareaSize from "~/hooks/useDynamicTextareaSize"
|
import useDynamicTextareaSize from "~/hooks/useDynamicTextareaSize"
|
||||||
import { toBase64 } from "~/libs/to-base64"
|
import { toBase64 } from "~/libs/to-base64"
|
||||||
import { useMessageOption } from "~/hooks/useMessageOption"
|
import { useMessageOption } from "~/hooks/useMessageOption"
|
||||||
import { Checkbox, Dropdown, Switch, Tooltip } from "antd"
|
import {
|
||||||
import { Image } from "antd"
|
Button,
|
||||||
|
Checkbox,
|
||||||
|
Dropdown,
|
||||||
|
Image,
|
||||||
|
MenuProps,
|
||||||
|
Switch,
|
||||||
|
Tooltip
|
||||||
|
} from "antd"
|
||||||
import { useWebUI } from "~/store/webui"
|
import { useWebUI } from "~/store/webui"
|
||||||
import { defaultEmbeddingModelForRag } from "~/services/ollama"
|
import { defaultEmbeddingModelForRag } from "~/services/ollama"
|
||||||
import { ImageIcon, MicIcon, StopCircleIcon, X } from "lucide-react"
|
import { ImageIcon, MicIcon, StopCircleIcon, X } from "lucide-react"
|
||||||
import { getVariable } from "@/utils/select-variable"
|
import { getVariable } from "@/utils/select-variable"
|
||||||
import { useTranslation } from "react-i18next"
|
import { useTranslation } from "react-i18next"
|
||||||
import { KnowledgeSelect } from "../Knowledge/KnowledgeSelect"
|
// import { KnowledgeSelect } from "../Knowledge/KnowledgeSelect"
|
||||||
import { useSpeechRecognition } from "@/hooks/useSpeechRecognition"
|
import { useSpeechRecognition } from "@/hooks/useSpeechRecognition"
|
||||||
import { PiGlobe } from "react-icons/pi"
|
import { PiGlobe, PiNetwork } from "react-icons/pi"
|
||||||
import { handleChatInputKeyDown } from "@/utils/key-down"
|
import { handleChatInputKeyDown } from "@/utils/key-down"
|
||||||
import { getIsSimpleInternetSearch } from "@/services/search"
|
import { getIsSimpleInternetSearch } from "@/services/search"
|
||||||
|
|
||||||
@ -34,6 +41,8 @@ export const PlaygroundForm = ({ dropedFile }: Props) => {
|
|||||||
streaming: isSending,
|
streaming: isSending,
|
||||||
webSearch,
|
webSearch,
|
||||||
setWebSearch,
|
setWebSearch,
|
||||||
|
iodSearch,
|
||||||
|
setIodSearch,
|
||||||
selectedQuickPrompt,
|
selectedQuickPrompt,
|
||||||
textareaRef,
|
textareaRef,
|
||||||
setSelectedQuickPrompt,
|
setSelectedQuickPrompt,
|
||||||
@ -126,7 +135,6 @@ export const PlaygroundForm = ({ dropedFile }: Props) => {
|
|||||||
}
|
}
|
||||||
}, [transcript])
|
}, [transcript])
|
||||||
|
|
||||||
/*
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
if (selectedQuickPrompt) {
|
if (selectedQuickPrompt) {
|
||||||
const word = getVariable(selectedQuickPrompt)
|
const word = getVariable(selectedQuickPrompt)
|
||||||
@ -143,7 +151,7 @@ export const PlaygroundForm = ({ dropedFile }: Props) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [selectedQuickPrompt])
|
}, [selectedQuickPrompt])
|
||||||
*/
|
|
||||||
const queryClient = useQueryClient()
|
const queryClient = useQueryClient()
|
||||||
|
|
||||||
const { mutateAsync: sendMessage } = useMutation({
|
const { mutateAsync: sendMessage } = useMutation({
|
||||||
@ -205,14 +213,47 @@ export const PlaygroundForm = ({ dropedFile }: Props) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
const iodSearchItems = useMemo<MenuProps["items"]>(() => {
|
||||||
<div className="flex w-full flex-col items-center p-2 pt-1 pb-4">
|
return [
|
||||||
<div className="relative z-10 flex w-full flex-col items-center justify-center gap-2 text-base">
|
{
|
||||||
<div className="relative flex w-full flex-row justify-center gap-2 lg:w-4/5">
|
key: 0,
|
||||||
|
label: (
|
||||||
<div
|
<div
|
||||||
className={` bg-neutral-50 dark:bg-[#262626] relative w-full max-w-[48rem] p-1 backdrop-blur-lg duration-100 border border-gray-300 rounded-xl dark:border-gray-600
|
onClick={() => {
|
||||||
${temporaryChat ? "!bg-gray-200 dark:!bg-black " : ""}
|
setIodSearch(true)
|
||||||
`}>
|
}}>
|
||||||
|
<p
|
||||||
|
className={`${iodSearch ? "text-[#0057ff]" : "text-[#000000d9]"} flex items-center gap-1 mb-1`}>
|
||||||
|
<PiNetwork className="h-5 w-5" />开
|
||||||
|
</p>
|
||||||
|
<p className="text-[#00000080]">输出带数联网的回答</p>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 1,
|
||||||
|
label: (
|
||||||
|
<div
|
||||||
|
onClick={() => {
|
||||||
|
setIodSearch(false)
|
||||||
|
}}>
|
||||||
|
<p
|
||||||
|
className={`${!iodSearch ? "text-[#0057ff]" : "text-[#000000d9]"} flex items-center gap-1 mb-1`}>
|
||||||
|
<PiNetwork className="h-5 w-5" /> 关闭
|
||||||
|
</p>
|
||||||
|
<p className="text-[#00000080]">快速直接回答</p>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}, [iodSearch])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex w-full flex-col items-center pt-1 px-5 pb-4">
|
||||||
|
<div className="relative z-10 flex w-full flex-col items-center justify-center gap-2 text-base">
|
||||||
|
<div className="relative flex w-full flex-row justify-center gap-2 lg:w-5/5">
|
||||||
|
<div
|
||||||
|
className={`shadow-xl relative w-full max-w-[65rem] p-1 rounded-xl bg-gradient-to-br from-white/90 via-blue-50/90 to-cyan-50/90 backdrop-blur-lg border border-blue-100/70 cursor-pointer hover:shadow-blue-100/60 transition-all duration-500`}>
|
||||||
<div
|
<div
|
||||||
className={`border-b border-gray-200 dark:border-gray-600 relative ${
|
className={`border-b border-gray-200 dark:border-gray-600 relative ${
|
||||||
form.values.image.length === 0 ? "hidden" : "block"
|
form.values.image.length === 0 ? "hidden" : "block"
|
||||||
@ -233,8 +274,7 @@ export const PlaygroundForm = ({ dropedFile }: Props) => {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<div
|
<div className={`flex bg-transparent `}>
|
||||||
className={`flex bg-transparent `}>
|
|
||||||
<form
|
<form
|
||||||
onSubmit={form.onSubmit(async (value) => {
|
onSubmit={form.onSubmit(async (value) => {
|
||||||
stopListening()
|
stopListening()
|
||||||
@ -300,26 +340,73 @@ export const PlaygroundForm = ({ dropedFile }: Props) => {
|
|||||||
{...form.getInputProps("message")}
|
{...form.getInputProps("message")}
|
||||||
/>
|
/>
|
||||||
<div className="mt-2 flex justify-between items-center">
|
<div className="mt-2 flex justify-between items-center">
|
||||||
<div className="flex !justify-end gap-3">
|
<div className="flex">
|
||||||
|
{!selectedKnowledge && (
|
||||||
|
<div>
|
||||||
|
{/* 展示隐藏深度搜索*/}
|
||||||
|
<Tooltip
|
||||||
|
title={t("tooltip.searchInternet")}
|
||||||
|
className="hidden">
|
||||||
|
<div className="inline-flex items-center gap-2">
|
||||||
|
<PiGlobe
|
||||||
|
className={`h-5 w-5 dark:text-gray-300 `}
|
||||||
|
/>
|
||||||
|
<Switch
|
||||||
|
value={webSearch}
|
||||||
|
onChange={(e) => setWebSearch(e)}
|
||||||
|
checkedChildren={t("form.webSearch.on")}
|
||||||
|
unCheckedChildren={t("form.webSearch.off")}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</Tooltip>
|
||||||
|
<Dropdown
|
||||||
|
menu={{ items: iodSearchItems }}
|
||||||
|
placement="bottom"
|
||||||
|
trigger={["click"]}
|
||||||
|
arrow>
|
||||||
|
<Button
|
||||||
|
color="default"
|
||||||
|
variant="filled"
|
||||||
|
size="large"
|
||||||
|
className="w-full mt-4 hover:!bg-[#0057ff1a]"
|
||||||
|
style={
|
||||||
|
iodSearch
|
||||||
|
? {
|
||||||
|
color: "#0057ff",
|
||||||
|
background: "#0057ff0f",
|
||||||
|
border: "1px solid #0066ff26"
|
||||||
|
}
|
||||||
|
: {}
|
||||||
|
}>
|
||||||
|
<PiNetwork className="h-5 w-5" />
|
||||||
|
数联网深度搜索{iodSearch ? ":开" : ""}
|
||||||
|
</Button>
|
||||||
|
</Dropdown>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div className="flex !justify-end gap-1">
|
||||||
{!selectedKnowledge && (
|
{!selectedKnowledge && (
|
||||||
<Tooltip title={t("tooltip.uploadImage")}>
|
<Tooltip title={t("tooltip.uploadImage")}>
|
||||||
<button
|
<Button
|
||||||
type="button"
|
color="default"
|
||||||
|
variant="text"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
inputRef.current?.click()
|
inputRef.current?.click()
|
||||||
}}
|
}}
|
||||||
className={`flex items-center justify-center dark:text-gray-300 ${
|
className={`!px-[5px] flex items-center justify-center dark:text-gray-300 ${
|
||||||
chatMode === "rag" ? "hidden" : "block"
|
chatMode === "rag" ? "hidden" : "block"
|
||||||
}`}>
|
}`}>
|
||||||
<ImageIcon className="h-5 w-5" />
|
<ImageIcon strokeWidth={1} className="h-5 w-5" />
|
||||||
</button>
|
</Button>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{browserSupportsSpeechRecognition && (
|
{browserSupportsSpeechRecognition && (
|
||||||
<Tooltip title={t("tooltip.speechToText")}>
|
<Tooltip title={t("tooltip.speechToText")}>
|
||||||
<button
|
<Button
|
||||||
type="button"
|
color="default"
|
||||||
|
variant="text"
|
||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
if (isListening) {
|
if (isListening) {
|
||||||
stopSpeechRecognition()
|
stopSpeechRecognition()
|
||||||
@ -331,40 +418,43 @@ export const PlaygroundForm = ({ dropedFile }: Props) => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
className={`flex items-center justify-center dark:text-gray-300`}>
|
className={`flex items-center justify-center dark:text-gray-300 !px-[5px]`}>
|
||||||
{!isListening ? (
|
{!isListening ? (
|
||||||
<MicIcon className="h-5 w-5" />
|
<MicIcon strokeWidth={1} className="h-5 w-5" />
|
||||||
) : (
|
) : (
|
||||||
<div className="relative">
|
<div className="relative">
|
||||||
<span className="animate-ping absolute inline-flex h-3 w-3 rounded-full bg-red-400 opacity-75"></span>
|
<span className="animate-ping absolute inline-flex h-3 w-3 rounded-full bg-red-400 opacity-75"></span>
|
||||||
<MicIcon className="h-5 w-5" />
|
<MicIcon
|
||||||
|
strokeWidth={1}
|
||||||
|
className="h-5 w-5"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</button>
|
</Button>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
)}
|
)}
|
||||||
<KnowledgeSelect />
|
{/*<KnowledgeSelect />*/}
|
||||||
|
|
||||||
{!isSending ? (
|
{!isSending ? (
|
||||||
<Dropdown.Button
|
<Dropdown.Button
|
||||||
|
type="default"
|
||||||
htmlType="submit"
|
htmlType="submit"
|
||||||
disabled={isSending}
|
disabled={isSending}
|
||||||
className="!justify-end !w-auto"
|
// icon={
|
||||||
icon={
|
// <svg
|
||||||
<svg
|
// xmlns="http://www.w3.org/2000/svg"
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
// fill="none"
|
||||||
fill="none"
|
// viewBox="0 0 24 24"
|
||||||
viewBox="0 0 24 24"
|
// strokeWidth={1.5}
|
||||||
strokeWidth={1.5}
|
// stroke="currentColor"
|
||||||
stroke="currentColor"
|
// className="w-5 h-5">
|
||||||
className="w-5 h-5">
|
// <path
|
||||||
<path
|
// strokeLinecap="round"
|
||||||
strokeLinecap="round"
|
// strokeLinejoin="round"
|
||||||
strokeLinejoin="round"
|
// d="m19.5 8.25-7.5 7.5-7.5-7.5"
|
||||||
d="m19.5 8.25-7.5 7.5-7.5-7.5"
|
// />
|
||||||
/>
|
// </svg>
|
||||||
</svg>
|
// }
|
||||||
}
|
|
||||||
menu={{
|
menu={{
|
||||||
items: [
|
items: [
|
||||||
{
|
{
|
||||||
@ -394,20 +484,6 @@ export const PlaygroundForm = ({ dropedFile }: Props) => {
|
|||||||
]
|
]
|
||||||
}}>
|
}}>
|
||||||
<div className="inline-flex gap-2">
|
<div className="inline-flex gap-2">
|
||||||
{sendWhenEnter ? (
|
|
||||||
<svg
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
fill="none"
|
|
||||||
stroke="currentColor"
|
|
||||||
strokeLinecap="round"
|
|
||||||
strokeLinejoin="round"
|
|
||||||
strokeWidth="2"
|
|
||||||
className="h-5 w-5"
|
|
||||||
viewBox="0 0 24 24">
|
|
||||||
<path d="M9 10L4 15 9 20"></path>
|
|
||||||
<path d="M20 4v7a4 4 0 01-4 4H4"></path>
|
|
||||||
</svg>
|
|
||||||
) : null}
|
|
||||||
{t("common:submit")}
|
{t("common:submit")}
|
||||||
</div>
|
</div>
|
||||||
</Dropdown.Button>
|
</Dropdown.Button>
|
||||||
|
160
src/components/Option/Playground/PlaygroundIod.tsx
Normal file
160
src/components/Option/Playground/PlaygroundIod.tsx
Normal file
@ -0,0 +1,160 @@
|
|||||||
|
import React, { createContext, useContext, useMemo, useState } from "react"
|
||||||
|
|
||||||
|
import { AnimatePresence, motion } from "framer-motion"
|
||||||
|
|
||||||
|
import { PlaygroundIodRelevant } from "@/components/Common/Playground/IodRelevant.tsx"
|
||||||
|
import { PlaygroundData } from "@/components/Common/Playground/Data.tsx"
|
||||||
|
import { PlaygroundScene } from "@/components/Common/Playground/Scene.tsx"
|
||||||
|
import { PlaygroundTeam } from "@/components/Common/Playground/Team.tsx"
|
||||||
|
import { Card } from "antd"
|
||||||
|
import { CloseOutlined } from "@ant-design/icons"
|
||||||
|
import { useMessageOption } from "@/hooks/useMessageOption.tsx"
|
||||||
|
import { AllIodRegistryEntry } from "@/types/iod.ts"
|
||||||
|
|
||||||
|
// 定义 Context 类型
|
||||||
|
interface IodPlaygroundContextType {
|
||||||
|
showPlayground: boolean
|
||||||
|
setShowPlayground: React.Dispatch<React.SetStateAction<boolean>>
|
||||||
|
detailHeader: React.ReactNode
|
||||||
|
setDetailHeader: React.Dispatch<React.SetStateAction<React.ReactNode>>
|
||||||
|
detailMain: React.ReactNode
|
||||||
|
setDetailMain: React.Dispatch<React.SetStateAction<React.ReactNode>>
|
||||||
|
currentIodMessage?: AllIodRegistryEntry
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建 Context
|
||||||
|
const PlaygroundContext = createContext<IodPlaygroundContextType | undefined>(
|
||||||
|
undefined
|
||||||
|
)
|
||||||
|
|
||||||
|
// 创建自定义 hook 以便子组件使用
|
||||||
|
export const useIodPlaygroundContext = () => {
|
||||||
|
const context = useContext(PlaygroundContext)
|
||||||
|
if (context === undefined) {
|
||||||
|
throw new Error(
|
||||||
|
"usePlaygroundContext must be used within a PlaygroundProvider"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return context
|
||||||
|
}
|
||||||
|
|
||||||
|
const PlaygroundIodProvider: React.FC<{ children: React.ReactNode }> = ({
|
||||||
|
children
|
||||||
|
}) => {
|
||||||
|
const { messages, iodLoading, currentMessageId } = useMessageOption()
|
||||||
|
|
||||||
|
const [showPlayground, setShowPlayground] = useState<boolean>(true)
|
||||||
|
const [detailHeader, setDetailHeader] = useState(<></>)
|
||||||
|
const [detailMain, setDetailMain] = useState(<></>)
|
||||||
|
|
||||||
|
const currentIodMessage = useMemo<AllIodRegistryEntry | undefined>(() => {
|
||||||
|
// loading 返回 undefined是为了避免,数据不足三个的情况
|
||||||
|
if (iodLoading || !messages.length) {
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
console.log(messages)
|
||||||
|
console.log(currentMessageId)
|
||||||
|
// 如果不存在currentMessageId默认返回最后一个message
|
||||||
|
if (!currentMessageId) {
|
||||||
|
const lastMessage = messages.at(-1)
|
||||||
|
// 如果最后一次message没有开启数联网搜索,则返回undefined
|
||||||
|
return lastMessage?.iodSearch ? lastMessage.iodSources : undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
const currentMessage = messages?.find(
|
||||||
|
(message) => message.id === currentMessageId
|
||||||
|
)
|
||||||
|
return currentMessage?.iodSearch ? currentMessage.iodSources : undefined
|
||||||
|
}, [currentMessageId, messages, iodLoading])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<PlaygroundContext.Provider
|
||||||
|
value={{
|
||||||
|
currentIodMessage,
|
||||||
|
showPlayground,
|
||||||
|
setShowPlayground,
|
||||||
|
detailMain,
|
||||||
|
setDetailMain,
|
||||||
|
detailHeader,
|
||||||
|
setDetailHeader
|
||||||
|
}}>
|
||||||
|
{children}
|
||||||
|
</PlaygroundContext.Provider>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 子组件使用修改card的默认样式
|
||||||
|
const classNames =
|
||||||
|
"h-full [&_.ant-card-body]:h-full [&_.ant-card-body]:!p-[20px] overflow-y-hidden !bg-[rgba(240,245,255,0.3)] backdrop-blur-sm border border-white/30 shadow-xl rounded-2xl"
|
||||||
|
// 将原来的返回内容移到这个组件中
|
||||||
|
const PlaygroundContent = () => {
|
||||||
|
const { showPlayground, detailMain, detailHeader, setShowPlayground } =
|
||||||
|
useIodPlaygroundContext()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<AnimatePresence mode="popLayout">
|
||||||
|
{showPlayground ? (
|
||||||
|
<motion.div
|
||||||
|
key="playground"
|
||||||
|
initial={{ x: "100%" }}
|
||||||
|
animate={{ x: 0 }}
|
||||||
|
exit={{ x: "100%" }}
|
||||||
|
transition={{
|
||||||
|
duration: 0.6,
|
||||||
|
ease: "easeInOut"
|
||||||
|
}}
|
||||||
|
className="h-full grid grid-rows-12 gap-3">
|
||||||
|
<div className="w-full row-span-5">
|
||||||
|
<PlaygroundIodRelevant
|
||||||
|
className={classNames
|
||||||
|
.replace("!bg-[rgba(240,245,255,0.3)]", "")
|
||||||
|
.replace("shadow-xl", "")}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="w-full row-span-4 grid grid-cols-2 gap-3 custom-scrollbar">
|
||||||
|
<PlaygroundData className={classNames} />
|
||||||
|
<PlaygroundScene className={classNames} />
|
||||||
|
</div>
|
||||||
|
<div className="w-full row-span-3 pb-3">
|
||||||
|
<PlaygroundTeam className={classNames} />
|
||||||
|
</div>
|
||||||
|
</motion.div>
|
||||||
|
) : (
|
||||||
|
<motion.div
|
||||||
|
key="alternative"
|
||||||
|
initial={{ x: "100%" }}
|
||||||
|
animate={{ x: 0 }}
|
||||||
|
exit={{ x: "100%" }}
|
||||||
|
transition={{
|
||||||
|
duration: 0.6,
|
||||||
|
ease: "easeInOut"
|
||||||
|
}}
|
||||||
|
className="h-full pb-5">
|
||||||
|
<Card className="h-full shadow-xl shadow-gray-500/20 [&_.ant-card-body]:h-full">
|
||||||
|
<div className="flex flex-col h-full">
|
||||||
|
<div className="pb-6 flex items-center justify-between">
|
||||||
|
<div>{detailHeader}</div>
|
||||||
|
<CloseOutlined
|
||||||
|
size={30}
|
||||||
|
className="hover:text-red-500 cursor-pointer transition-colors duration-200 text-xl"
|
||||||
|
onClick={() => setShowPlayground(true)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{detailMain}
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
</motion.div>
|
||||||
|
)}
|
||||||
|
</AnimatePresence>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const PlaygroundIod = () => {
|
||||||
|
return (
|
||||||
|
<div className="w-[36%] h-full pt-16 pr-5 pb-0">
|
||||||
|
<PlaygroundIodProvider>
|
||||||
|
<PlaygroundContent />
|
||||||
|
</PlaygroundIodProvider>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
@ -1,25 +0,0 @@
|
|||||||
import { PencilIcon } from "lucide-react"
|
|
||||||
import { useMessage } from "../../../hooks/useMessage"
|
|
||||||
import { useTranslation } from 'react-i18next';
|
|
||||||
|
|
||||||
export const PlaygroundNewChat = () => {
|
|
||||||
const { setHistory, setMessages, setHistoryId } = useMessage()
|
|
||||||
const { t } = useTranslation('optionChat')
|
|
||||||
|
|
||||||
const handleClick = () => {
|
|
||||||
setHistoryId(null)
|
|
||||||
setMessages([])
|
|
||||||
setHistory([])
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<button
|
|
||||||
onClick={handleClick}
|
|
||||||
className="flex w-full border bg-transparent hover:bg-gray-200 dark:hover:bg-gray-800 text-gray-600 dark:text-gray-400 hover:text-gray-800 dark:hover:text-gray-200 focus:outline-none focus:ring-2 focus:ring-gray-500 focus:ring-offset-2 focus:ring-offset-gray-100 rounded-md p-2 dark:border-gray-800">
|
|
||||||
<PencilIcon className="mx-3 h-5 w-5" aria-hidden="true" />
|
|
||||||
<span className="inline-flex font-semibol text-white text-sm">
|
|
||||||
{t('newChat')}
|
|
||||||
</span>
|
|
||||||
</button>
|
|
||||||
)
|
|
||||||
}
|
|
277
src/components/Option/Playground/PlaygroundSidebar.tsx
Normal file
277
src/components/Option/Playground/PlaygroundSidebar.tsx
Normal file
@ -0,0 +1,277 @@
|
|||||||
|
import { Sidebar } from "@/components/Option/Sidebar.tsx"
|
||||||
|
import React, { useMemo } from "react"
|
||||||
|
import { useMessageOption } from "@/hooks/useMessageOption.tsx"
|
||||||
|
import { useStoreChatModelSettings } from "@/store/model.tsx"
|
||||||
|
import {
|
||||||
|
Button,
|
||||||
|
Card,
|
||||||
|
Divider,
|
||||||
|
Menu,
|
||||||
|
MenuProps,
|
||||||
|
Popover,
|
||||||
|
Select,
|
||||||
|
Tooltip
|
||||||
|
} from "antd"
|
||||||
|
import { PageAssitDatabase } from "@/db"
|
||||||
|
import { EraserIcon, PanelLeftIcon } from "lucide-react"
|
||||||
|
import { useTranslation } from "react-i18next"
|
||||||
|
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"
|
||||||
|
import { useOptionLayoutContext } from "@/components/Layouts/Layout.tsx"
|
||||||
|
import { PlusOutlined, RightOutlined } from "@ant-design/icons"
|
||||||
|
import { qaPrompt } from "@/libs/playground.tsx"
|
||||||
|
import { ProviderIcons } from "@/components/Common/ProviderIcon.tsx"
|
||||||
|
import { fetchChatModels } from "@/services/ollama.ts"
|
||||||
|
import logo from "@/assets/logo.png"
|
||||||
|
|
||||||
|
const ModelIcon = () => {
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
className="icon"
|
||||||
|
viewBox="0 0 1024 1024"
|
||||||
|
version="1.1"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
p-id="9426"
|
||||||
|
width="16"
|
||||||
|
height="16">
|
||||||
|
<path
|
||||||
|
d="M509.952 161.512727c148.945455-82.850909 300.730182-91.229091 371.479273-20.945454s62.324364 221.509818-20.526546 370.501818h-0.465454a429.335273 429.335273 0 0 1 65.163636 284.392727 168.727273 168.727273 0 0 1-44.683636 85.643637 173.754182 173.754182 0 0 1-86.109091 44.683636 435.665455 435.665455 0 0 1-285.277091-65.675636 430.731636 430.731636 0 0 1-282.530909 63.813818 172.218182 172.218182 0 0 1-86.109091-44.683637c-70.283636-69.818182-62.370909-220.206545 19.502545-368.174545-81.966545-148.48-89.786182-298.309818-19.502545-368.686546s220.625455-62.324364 369.058909 19.130182z m291.886545 440.785455a901.818182 901.818182 0 0 1-92.16 106.589091 934.027636 934.027636 0 0 1-108.916363 93.602909 586.891636 586.891636 0 0 0 58.600727 21.410909c74.938182 22.341818 127.069091 19.502545 155.508364-8.843636l-0.465455 0.884363c28.811636-28.392727 31.697455-80.523636 8.843637-155.508363a546.443636 546.443636 0 0 0-21.41091-58.135273z m-582.74909-0.465455a539.927273 539.927273 0 0 0-20.433455 55.854546c-22.295273 75.357091-19.549091 127.022545 8.797091 155.368727s80.151273 31.697455 155.508364 8.936727h-0.558546a539.927273 539.927273 0 0 0 55.854546-20.526545 967.400727 967.400727 0 0 1-199.214546-199.726546z m290.90909-332.753454a851.781818 851.781818 0 0 0-131.258181 108.404363 823.296 823.296 0 0 0-109.847273 133.12 823.854545 823.854545 0 0 0 109.847273 133.12v-0.884363a852.293818 852.293818 0 0 0 131.211636 108.357818 846.754909 846.754909 0 0 0 133.538909-109.800727 856.436364 856.436364 0 0 0 108.962909-131.258182 852.852364 852.852364 0 0 0-108.962909-131.211637 829.998545 829.998545 0 0 0-133.538909-109.847272zM503.994182 418.909091a94.347636 94.347636 0 1 1-35.84 10.705454 92.811636 92.811636 0 0 1 35.84-10.705454z m310.877091-212.340364c-28.253091-28.299636-80.151273-31.557818-155.508364-8.750545a591.592727 591.592727 0 0 0-58.600727 21.876363 933.794909 933.794909 0 0 1 108.869818 93.556364 947.060364 947.060364 0 0 1 92.718545 107.054546 545.326545 545.326545 0 0 0 21.41091-58.181819q33.559273-113.058909-8.843637-155.508363zM363.054545 199.68c-74.938182-22.295273-127.069091-19.549091-155.508363 8.843636v-0.465454c-28.997818 28.392727-31.744 80.523636-8.936727 155.508363a507.345455 507.345455 0 0 0 20.433454 56.273455A976.663273 976.663273 0 0 1 418.909091 220.206545a541.230545 541.230545 0 0 0-55.854546-20.526545z m0 0"
|
||||||
|
fill="#696F85"
|
||||||
|
p-id="9427"></path>
|
||||||
|
</svg>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const PlaygroundSidebar = () => {
|
||||||
|
const { setSystemPrompt } = useStoreChatModelSettings()
|
||||||
|
|
||||||
|
const { showOptionSidebar, setShowOptionSidebar, setShowVideo } = useOptionLayoutContext()
|
||||||
|
|
||||||
|
const {
|
||||||
|
setMessages,
|
||||||
|
setHistory,
|
||||||
|
setHistoryId,
|
||||||
|
historyId,
|
||||||
|
clearChat,
|
||||||
|
selectedModel,
|
||||||
|
setSelectedModel,
|
||||||
|
temporaryChat,
|
||||||
|
setSelectedSystemPrompt,
|
||||||
|
stopStreamingRequest
|
||||||
|
} = useMessageOption()
|
||||||
|
|
||||||
|
const { t } = useTranslation(["option", "common", "settings"])
|
||||||
|
|
||||||
|
const queryClient = useQueryClient()
|
||||||
|
|
||||||
|
type MenuItem = Required<MenuProps>["items"][number]
|
||||||
|
const qaPromptItems = useMemo<MenuItem[]>(() => {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
key: "qaPrompt",
|
||||||
|
label: "热点问题",
|
||||||
|
type: "group" as const,
|
||||||
|
children: qaPrompt.map((item) => {
|
||||||
|
return {
|
||||||
|
key: item.id,
|
||||||
|
label: (
|
||||||
|
<div className="flex items-center gap-2 truncate w-full">
|
||||||
|
<p className="w-5 h-5 [&_.ant-avatar]:!w-full [&_.ant-avatar]:!h-full [&_.ant-avatar]:relative [&_.ant-avatar]:-top-3">
|
||||||
|
{item.icon}
|
||||||
|
</p>
|
||||||
|
<span className="flex-1 truncate" title={item.title}>
|
||||||
|
{item.title}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
const { onSubmit } = useMessageOption()
|
||||||
|
|
||||||
|
const { mutateAsync: sendMessage } = useMutation({
|
||||||
|
mutationFn: onSubmit,
|
||||||
|
onSuccess: () => {
|
||||||
|
queryClient.invalidateQueries({
|
||||||
|
queryKey: ["fetchChatHistory"]
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const onClickQaPromptItem: MenuProps["onClick"] = (e) => {
|
||||||
|
const record = qaPrompt.find((item) => item.id === e.key)
|
||||||
|
void sendMessage({ message: record.title, image: "" })
|
||||||
|
}
|
||||||
|
|
||||||
|
// 大模型
|
||||||
|
const { data: models, isLoading: isModelsLoading } = useQuery({
|
||||||
|
queryKey: ["fetchModel"],
|
||||||
|
queryFn: () => fetchChatModels({ returnEmpty: true }),
|
||||||
|
refetchIntervalInBackground: false,
|
||||||
|
placeholderData: (prev) => prev
|
||||||
|
})
|
||||||
|
|
||||||
|
// 是否隐藏logo
|
||||||
|
const hideLogo = useMemo(() => {
|
||||||
|
return localStorage.getItem("hideLogo") === "true"
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Card
|
||||||
|
className={`flex flex-col [&_.ant-card-body]:h-full w-[300px] overflow-hidden h-full pb-5 transition-all duration-300 ease-in-out backdrop-blur-lg !bg-[#f3f4f6]`}
|
||||||
|
style={{ width: showOptionSidebar ? "300px" : "0" }}>
|
||||||
|
{/*Header*/}
|
||||||
|
<div className="flex flex-col overflow-y-hidden h-full">
|
||||||
|
<div className="flex items-center justify-between transition-all duration-300 ease-in-out w-[250px]">
|
||||||
|
<div className="flex items-center gap-2 cursor-pointer" onClick={() => setShowVideo(true)}>
|
||||||
|
{!hideLogo && <img src={logo} alt="logo" className="w-8" />}
|
||||||
|
<h2 className="text-xl font-bold text-zinc-700 dark:text-zinc-300 mr-3">
|
||||||
|
<span className="text-[#d30100]">数联网</span>科创智能体
|
||||||
|
</h2>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button
|
||||||
|
className="text-gray-500 dark:text-gray-400"
|
||||||
|
onClick={() => {
|
||||||
|
setShowOptionSidebar(!showOptionSidebar)
|
||||||
|
}}>
|
||||||
|
<PanelLeftIcon className="w-6 h-6" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col gap-1">
|
||||||
|
{/*新建对话*/}
|
||||||
|
<Button
|
||||||
|
color="purple"
|
||||||
|
variant="filled"
|
||||||
|
size="large"
|
||||||
|
className="w-full mt-4 hover:!bg-[#0057ff1a]"
|
||||||
|
style={{
|
||||||
|
color: "#0057ff",
|
||||||
|
background: "#0057ff0f",
|
||||||
|
border: "1px solid #0066ff26"
|
||||||
|
}}
|
||||||
|
onClick={clearChat}>
|
||||||
|
<div className="flex items-center justify-between w-full">
|
||||||
|
<div className="flex items-center">
|
||||||
|
<PlusOutlined
|
||||||
|
className="text-sm"
|
||||||
|
style={{ fontSize: "16px", fontWeight: 500 }}
|
||||||
|
/>
|
||||||
|
<span className="font-medium ml-2.5">{t("newChat")}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Button>
|
||||||
|
{/*选择智能体*/}
|
||||||
|
<Popover
|
||||||
|
placement="right"
|
||||||
|
content={
|
||||||
|
<Select
|
||||||
|
className="w-80"
|
||||||
|
placeholder={t("common:selectAModel")}
|
||||||
|
// loadingText={t("common:selectAModel")}
|
||||||
|
value={selectedModel}
|
||||||
|
onChange={(e) => {
|
||||||
|
setSelectedModel(e)
|
||||||
|
localStorage.setItem("selectedModel", e)
|
||||||
|
}}
|
||||||
|
filterOption={(input, option) => {
|
||||||
|
//@ts-ignore
|
||||||
|
return (
|
||||||
|
option?.label?.props["data-title"]
|
||||||
|
?.toLowerCase()
|
||||||
|
?.indexOf(input.toLowerCase()) >= 0
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
showSearch
|
||||||
|
loading={isModelsLoading}
|
||||||
|
options={models?.map((model) => ({
|
||||||
|
label: (
|
||||||
|
<span
|
||||||
|
key={model.model}
|
||||||
|
data-title={model.name}
|
||||||
|
className="flex flex-row gap-3 items-center ">
|
||||||
|
<ProviderIcons
|
||||||
|
provider={model?.provider}
|
||||||
|
className="w-5 h-5"
|
||||||
|
/>
|
||||||
|
<span className="line-clamp-2">{model.name}</span>
|
||||||
|
</span>
|
||||||
|
),
|
||||||
|
value: model.model
|
||||||
|
}))}
|
||||||
|
size="large"
|
||||||
|
// onRefresh={() => {
|
||||||
|
// refetch()
|
||||||
|
// }}
|
||||||
|
/>
|
||||||
|
}>
|
||||||
|
<Button
|
||||||
|
size="large"
|
||||||
|
color="default"
|
||||||
|
variant="text"
|
||||||
|
className="w-full !justify-between !text-[#000000d9] font-normal">
|
||||||
|
<div className="flex items-center gap-2.5">
|
||||||
|
<ModelIcon />
|
||||||
|
<span className="!text-[#000000d9] font-normal text-sm">
|
||||||
|
选择智能体
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<RightOutlined style={{ color: "#0000004d" }} />
|
||||||
|
</Button>
|
||||||
|
</Popover>
|
||||||
|
<Divider size="small" />
|
||||||
|
{/*热门搜索*/}
|
||||||
|
<Menu
|
||||||
|
items={qaPromptItems}
|
||||||
|
onClick={onClickQaPromptItem}
|
||||||
|
className="!bg-[#f3f4f6] !border-r-0"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<Divider size="small" />
|
||||||
|
<div className="pb-1.5 pl-4 text-sm text-[#00000073] flex items-center justify-between pr-2">
|
||||||
|
<span>最近对话</span>
|
||||||
|
<Tooltip
|
||||||
|
title={t("settings:generalSettings.system.deleteChatHistory.label")}
|
||||||
|
placement="right">
|
||||||
|
<button
|
||||||
|
onClick={async () => {
|
||||||
|
const confirm = window.confirm(
|
||||||
|
t("settings:generalSettings.system.deleteChatHistory.confirm")
|
||||||
|
)
|
||||||
|
|
||||||
|
if (confirm) {
|
||||||
|
const db = new PageAssitDatabase()
|
||||||
|
await db.deleteAllChatHistory()
|
||||||
|
await queryClient.invalidateQueries({
|
||||||
|
queryKey: ["fetchChatHistory"]
|
||||||
|
})
|
||||||
|
clearChat()
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
className="text-gray-600 hover:text-gray-800 dark:text-gray-300 dark:hover:text-gray-100">
|
||||||
|
<EraserIcon className="size-5" />
|
||||||
|
</button>
|
||||||
|
</Tooltip>
|
||||||
|
</div>
|
||||||
|
<div className="overflow-y-auto flex-1 pl-7">
|
||||||
|
<Sidebar
|
||||||
|
onClose={() => setShowOptionSidebar(true)}
|
||||||
|
setMessages={setMessages}
|
||||||
|
setHistory={setHistory}
|
||||||
|
setHistoryId={setHistoryId}
|
||||||
|
setSelectedModel={setSelectedModel}
|
||||||
|
setSelectedSystemPrompt={setSelectedSystemPrompt}
|
||||||
|
clearChat={clearChat}
|
||||||
|
historyId={historyId}
|
||||||
|
setSystemPrompt={setSystemPrompt}
|
||||||
|
temporaryChat={temporaryChat}
|
||||||
|
stopStreamingRequest={stopStreamingRequest}
|
||||||
|
history={history}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
)
|
||||||
|
}
|
@ -1,13 +0,0 @@
|
|||||||
<Tooltip title={t("tooltip.searchInternet")}>
|
|
||||||
<div className="inline-flex items-center gap-2">
|
|
||||||
<PiGlobe
|
|
||||||
className={`h-5 w-5 dark:text-gray-300 `}
|
|
||||||
/>
|
|
||||||
<Switch
|
|
||||||
value={webSearch}
|
|
||||||
onChange={(e) => setWebSearch(e)}
|
|
||||||
checkedChildren={t("form.webSearch.on")}
|
|
||||||
unCheckedChildren={t("form.webSearch.off")}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</Tooltip>
|
|
33
src/components/Option/Settings/iod.tsx
Normal file
33
src/components/Option/Settings/iod.tsx
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
import { useTranslation } from "react-i18next"
|
||||||
|
import TextArea from "antd/es/input/TextArea"
|
||||||
|
import { IodDb } from "@/db/iod.ts"
|
||||||
|
import { useState } from "react"
|
||||||
|
|
||||||
|
|
||||||
|
export const IodApp = () => {
|
||||||
|
const { t } = useTranslation("settings")
|
||||||
|
|
||||||
|
const db = IodDb.getInstance()
|
||||||
|
|
||||||
|
const [connection, setConnection] = useState(JSON.stringify(db.getIodConnection(), null, 2))
|
||||||
|
|
||||||
|
const setConnectValWrap = (val: string) => {
|
||||||
|
db.insertIodConnection(JSON.parse(val))
|
||||||
|
setConnection(val)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<dl className="flex flex-col space-y-6 text-sm">
|
||||||
|
<div>
|
||||||
|
<h2 className="text-base font-semibold leading-7 text-gray-900 dark:text-white">
|
||||||
|
{t("iodSettings.heading")}
|
||||||
|
</h2>
|
||||||
|
<div className="border border-b border-gray-200 dark:border-gray-600 mt-3"></div>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col gap-3">
|
||||||
|
<span className="text-gray-700 dark:text-neutral-50">连接配置</span>
|
||||||
|
<TextArea rows={6} placeholder="请输入数联网连接配置" value={connection} onChange={(e) => setConnectValWrap(e.target.value)} />
|
||||||
|
</div>
|
||||||
|
</dl>
|
||||||
|
)
|
||||||
|
}
|
@ -1,21 +1,21 @@
|
|||||||
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"
|
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"
|
||||||
import {
|
import {
|
||||||
PageAssitDatabase,
|
deleteByHistoryId,
|
||||||
formatToChatHistory,
|
formatToChatHistory,
|
||||||
formatToMessage,
|
formatToMessage,
|
||||||
deleteByHistoryId,
|
getPromptById,
|
||||||
updateHistory,
|
PageAssitDatabase,
|
||||||
pinHistory,
|
pinHistory,
|
||||||
getPromptById
|
updateHistory
|
||||||
} from "@/db"
|
} from "@/db"
|
||||||
import { Empty, Skeleton, Dropdown, Menu, Tooltip } from "antd"
|
import { Dropdown, Empty, Menu, Skeleton, Tooltip } from "antd"
|
||||||
import {
|
import {
|
||||||
PencilIcon,
|
BotIcon,
|
||||||
Trash2,
|
|
||||||
MoreVertical,
|
MoreVertical,
|
||||||
|
PencilIcon,
|
||||||
PinIcon,
|
PinIcon,
|
||||||
PinOffIcon,
|
PinOffIcon,
|
||||||
BotIcon
|
Trash2
|
||||||
} from "lucide-react"
|
} from "lucide-react"
|
||||||
import { useNavigate } from "react-router-dom"
|
import { useNavigate } from "react-router-dom"
|
||||||
import { useTranslation } from "react-i18next"
|
import { useTranslation } from "react-i18next"
|
||||||
@ -24,6 +24,7 @@ import {
|
|||||||
getLastUsedChatSystemPrompt,
|
getLastUsedChatSystemPrompt,
|
||||||
lastUsedChatModelEnabled
|
lastUsedChatModelEnabled
|
||||||
} from "@/services/model-settings"
|
} from "@/services/model-settings"
|
||||||
|
import { useMessageOption } from "@/hooks/useMessageOption.tsx"
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
onClose: () => void
|
onClose: () => void
|
||||||
@ -32,8 +33,8 @@ type Props = {
|
|||||||
setHistoryId: (historyId: string) => void
|
setHistoryId: (historyId: string) => void
|
||||||
setSelectedModel: (model: string) => void
|
setSelectedModel: (model: string) => void
|
||||||
setSelectedSystemPrompt: (prompt: string) => void
|
setSelectedSystemPrompt: (prompt: string) => void
|
||||||
setSelectedQuickPrompt: (prompt: string | undefined) => void
|
|
||||||
setSystemPrompt: (prompt: string) => void
|
setSystemPrompt: (prompt: string) => void
|
||||||
|
stopStreamingRequest: () => void
|
||||||
clearChat: () => void
|
clearChat: () => void
|
||||||
temporaryChat: boolean
|
temporaryChat: boolean
|
||||||
historyId: string
|
historyId: string
|
||||||
@ -47,7 +48,7 @@ export const Sidebar = ({
|
|||||||
setHistoryId,
|
setHistoryId,
|
||||||
setSelectedModel,
|
setSelectedModel,
|
||||||
setSelectedSystemPrompt,
|
setSelectedSystemPrompt,
|
||||||
setSelectedQuickPrompt,
|
stopStreamingRequest,
|
||||||
clearChat,
|
clearChat,
|
||||||
historyId,
|
historyId,
|
||||||
setSystemPrompt,
|
setSystemPrompt,
|
||||||
@ -57,6 +58,8 @@ export const Sidebar = ({
|
|||||||
const client = useQueryClient()
|
const client = useQueryClient()
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
|
|
||||||
|
const { setCurrentMessageId } = useMessageOption()
|
||||||
|
|
||||||
const { data: chatHistories, status } = useQuery({
|
const { data: chatHistories, status } = useQuery({
|
||||||
queryKey: ["fetchChatHistory"],
|
queryKey: ["fetchChatHistory"],
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
@ -142,6 +145,41 @@ export const Sidebar = ({
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const handleHistoryClick = async (chat: any) => {
|
||||||
|
const db = new PageAssitDatabase()
|
||||||
|
const history = await db.getChatHistory(chat.id)
|
||||||
|
setHistoryId(chat.id)
|
||||||
|
setCurrentMessageId("")
|
||||||
|
setHistory(formatToChatHistory(history))
|
||||||
|
setMessages(formatToMessage(history))
|
||||||
|
stopStreamingRequest()
|
||||||
|
const isLastUsedChatModel =
|
||||||
|
await lastUsedChatModelEnabled()
|
||||||
|
if (isLastUsedChatModel) {
|
||||||
|
const currentChatModel = await getLastUsedChatModel(
|
||||||
|
chat.id
|
||||||
|
)
|
||||||
|
if (currentChatModel) {
|
||||||
|
setSelectedModel(currentChatModel)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const lastUsedPrompt =
|
||||||
|
await getLastUsedChatSystemPrompt(chat.id)
|
||||||
|
if (lastUsedPrompt) {
|
||||||
|
if (lastUsedPrompt.prompt_id) {
|
||||||
|
const prompt = await getPromptById(
|
||||||
|
lastUsedPrompt.prompt_id
|
||||||
|
)
|
||||||
|
if (prompt) {
|
||||||
|
setSelectedSystemPrompt(lastUsedPrompt.prompt_id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setSystemPrompt(lastUsedPrompt.prompt_content)
|
||||||
|
}
|
||||||
|
navigate("/")
|
||||||
|
onClose()
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={`overflow-y-auto z-99 ${temporaryChat ? "pointer-events-none opacity-50" : ""}`}>
|
className={`overflow-y-auto z-99 ${temporaryChat ? "pointer-events-none opacity-50" : ""}`}>
|
||||||
@ -171,7 +209,11 @@ export const Sidebar = ({
|
|||||||
{group.items.map((chat, index) => (
|
{group.items.map((chat, index) => (
|
||||||
<div
|
<div
|
||||||
key={index}
|
key={index}
|
||||||
className="flex py-2 px-2 items-center gap-3 relative rounded-md truncate hover:pr-4 group transition-opacity duration-300 ease-in-out bg-gray-100 dark:bg-[#232222] dark:text-gray-100 text-gray-800 border hover:bg-gray-200 dark:hover:bg-[#2d2d2d] dark:border-gray-800">
|
className={`
|
||||||
|
flex py-2 px-2 items-center gap-3 relative rounded-md truncate group ease-in-out
|
||||||
|
dark:hover:bg-[#2d2d2d] transition-colors duration-300
|
||||||
|
${historyId === chat.id ? "text-white bg-[#2563eb] hover:bg-[#1d4ed8]" : "dark:text-gray-100 text-gray-800 hover:text-white hover:bg-[#2563eb]"}
|
||||||
|
`}>
|
||||||
{chat?.message_source === "copilot" && (
|
{chat?.message_source === "copilot" && (
|
||||||
<Tooltip title={t("common:sidebarChat")} placement="top">
|
<Tooltip title={t("common:sidebarChat")} placement="top">
|
||||||
<BotIcon className="size-3 text-green-500" />
|
<BotIcon className="size-3 text-green-500" />
|
||||||
@ -179,38 +221,7 @@ export const Sidebar = ({
|
|||||||
)}
|
)}
|
||||||
<button
|
<button
|
||||||
className="flex-1 overflow-hidden break-all text-start truncate w-full"
|
className="flex-1 overflow-hidden break-all text-start truncate w-full"
|
||||||
onClick={async () => {
|
onClick={() => handleHistoryClick(chat)}>
|
||||||
const db = new PageAssitDatabase()
|
|
||||||
const history = await db.getChatHistory(chat.id)
|
|
||||||
setHistoryId(chat.id)
|
|
||||||
setHistory(formatToChatHistory(history))
|
|
||||||
setMessages(formatToMessage(history))
|
|
||||||
const isLastUsedChatModel =
|
|
||||||
await lastUsedChatModelEnabled()
|
|
||||||
if (isLastUsedChatModel) {
|
|
||||||
const currentChatModel = await getLastUsedChatModel(
|
|
||||||
chat.id
|
|
||||||
)
|
|
||||||
if (currentChatModel) {
|
|
||||||
setSelectedModel(currentChatModel)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const lastUsedPrompt =
|
|
||||||
await getLastUsedChatSystemPrompt(chat.id)
|
|
||||||
if (lastUsedPrompt) {
|
|
||||||
if (lastUsedPrompt.prompt_id) {
|
|
||||||
const prompt = await getPromptById(
|
|
||||||
lastUsedPrompt.prompt_id
|
|
||||||
)
|
|
||||||
if (prompt) {
|
|
||||||
setSelectedSystemPrompt(lastUsedPrompt.prompt_id)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
setSystemPrompt(lastUsedPrompt.prompt_content)
|
|
||||||
}
|
|
||||||
navigate("/")
|
|
||||||
onClose()
|
|
||||||
}}>
|
|
||||||
<span className="flex-grow truncate">{chat.title}</span>
|
<span className="flex-grow truncate">{chat.title}</span>
|
||||||
</button>
|
</button>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
@ -267,7 +278,9 @@ export const Sidebar = ({
|
|||||||
trigger={["click"]}
|
trigger={["click"]}
|
||||||
placement="bottomRight">
|
placement="bottomRight">
|
||||||
<button className="text-gray-500 dark:text-gray-400 opacity-80 hover:opacity-100">
|
<button className="text-gray-500 dark:text-gray-400 opacity-80 hover:opacity-100">
|
||||||
<MoreVertical className="w-4 h-4" />
|
<MoreVertical
|
||||||
|
className={`w-4 h-4 group-hover:text-white ${historyId === chat.id ? "text-white" : ""}`}
|
||||||
|
/>
|
||||||
</button>
|
</button>
|
||||||
</Dropdown>
|
</Dropdown>
|
||||||
</div>
|
</div>
|
||||||
|
419
src/components/Option/VideoPlayer/index.tsx
Normal file
419
src/components/Option/VideoPlayer/index.tsx
Normal file
@ -0,0 +1,419 @@
|
|||||||
|
import React, { useEffect, useMemo, useRef, useState } from "react"
|
||||||
|
import iodVideo from "@/public/video.mp4"
|
||||||
|
import { useOptionLayoutContext } from "@/components/Layouts/Layout.tsx"
|
||||||
|
import { createPortal } from "react-dom"
|
||||||
|
import {
|
||||||
|
ExpandOutlined,
|
||||||
|
PauseCircleOutlined,
|
||||||
|
PlayCircleOutlined
|
||||||
|
} from "@ant-design/icons"
|
||||||
|
import logo from "@/assets/logo.png"
|
||||||
|
|
||||||
|
const VideoPlayer = () => {
|
||||||
|
const { setShowVideo } = useOptionLayoutContext()
|
||||||
|
const videoRef = useRef<HTMLVideoElement>(null)
|
||||||
|
const containerRef = useRef<HTMLDivElement>(null)
|
||||||
|
const controlsTimerRef = useRef<NodeJS.Timeout | null>(null)
|
||||||
|
const mouseMoveTimerRef = useRef<NodeJS.Timeout | null>(null)
|
||||||
|
const isPlayingRef = useRef(false)
|
||||||
|
|
||||||
|
const [isPlaying, setIsPlaying] = useState(false)
|
||||||
|
const [currentTime, setCurrentTime] = useState(0)
|
||||||
|
const [duration, setDuration] = useState(0)
|
||||||
|
const [volume, setVolume] = useState(1)
|
||||||
|
const [isMuted, setIsMuted] = useState(false)
|
||||||
|
const [showControls, setShowControls] = useState(false)
|
||||||
|
const [isBuffering, setIsBuffering] = useState(false)
|
||||||
|
|
||||||
|
// 更新 isPlayingRef 当状态变化时
|
||||||
|
useEffect(() => {
|
||||||
|
isPlayingRef.current = isPlaying
|
||||||
|
}, [isPlaying])
|
||||||
|
|
||||||
|
// 格式化时间
|
||||||
|
const formatTime = (seconds: number) => {
|
||||||
|
if (isNaN(seconds)) return "00:00"
|
||||||
|
const minutes = Math.floor(seconds / 60)
|
||||||
|
const secs = Math.floor(seconds % 60)
|
||||||
|
return `${minutes.toString().padStart(2, "0")}:${secs.toString().padStart(2, "0")}`
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理播放/暂停
|
||||||
|
const togglePlayPause = () => {
|
||||||
|
const video = videoRef.current
|
||||||
|
if (!video) return
|
||||||
|
|
||||||
|
if (isPlaying) {
|
||||||
|
video.pause()
|
||||||
|
setIsPlaying(false)
|
||||||
|
} else {
|
||||||
|
video.play().catch((error) => {
|
||||||
|
console.error("播放失败:", error)
|
||||||
|
})
|
||||||
|
setIsPlaying(true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理音量变化
|
||||||
|
const handleVolumeChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
const newVolume = parseFloat(e.target.value)
|
||||||
|
setVolume(newVolume)
|
||||||
|
if (videoRef.current) {
|
||||||
|
videoRef.current.volume = newVolume
|
||||||
|
setIsMuted(newVolume === 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 切换静音
|
||||||
|
const toggleMute = () => {
|
||||||
|
const newMuted = !isMuted
|
||||||
|
setIsMuted(newMuted)
|
||||||
|
if (videoRef.current) {
|
||||||
|
videoRef.current.muted = newMuted
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 进度条点击处理
|
||||||
|
const handleProgressClick = (e: React.MouseEvent<HTMLDivElement>) => {
|
||||||
|
const progressBar = e.currentTarget
|
||||||
|
const clickPosition = e.nativeEvent.offsetX
|
||||||
|
const progressBarWidth = progressBar.offsetWidth
|
||||||
|
if (duration > 0 && videoRef.current) {
|
||||||
|
const newTime = (clickPosition / progressBarWidth) * duration
|
||||||
|
videoRef.current.currentTime = newTime
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 全屏切换
|
||||||
|
const toggleFullscreen = () => {
|
||||||
|
const videoContainer = containerRef.current
|
||||||
|
if (!videoContainer) return
|
||||||
|
|
||||||
|
if (!document.fullscreenElement) {
|
||||||
|
if (videoContainer.requestFullscreen) {
|
||||||
|
videoContainer.requestFullscreen().catch((err) => {
|
||||||
|
console.error("全屏切换失败:", err)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (document.exitFullscreen) {
|
||||||
|
document.exitFullscreen()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleEnded = () => {
|
||||||
|
setIsPlaying(false)
|
||||||
|
setShowVideo(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 控制栏显示/隐藏 - 与原始HTML版本行为完全一致,添加防抖功能
|
||||||
|
const handleMouseMove = (e: React.MouseEvent<HTMLDivElement>) => {
|
||||||
|
// 清除之前的防抖定时器
|
||||||
|
if (mouseMoveTimerRef.current) {
|
||||||
|
clearTimeout(mouseMoveTimerRef.current)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设置新的防抖定时器
|
||||||
|
mouseMoveTimerRef.current = setTimeout(() => {
|
||||||
|
const container = containerRef.current
|
||||||
|
if (!container) return
|
||||||
|
|
||||||
|
const containerHeight = container.offsetHeight
|
||||||
|
const mouseY = e.clientY - container.getBoundingClientRect().top
|
||||||
|
console.log(mouseY > containerHeight - 150)
|
||||||
|
// 如果鼠标在底部150px区域内
|
||||||
|
if (mouseY > containerHeight - 150) {
|
||||||
|
// 清除之前的隐藏定时器
|
||||||
|
if (controlsTimerRef.current) {
|
||||||
|
clearTimeout(controlsTimerRef.current)
|
||||||
|
}
|
||||||
|
// 立即显示控制器
|
||||||
|
setShowControls(true)
|
||||||
|
} else {
|
||||||
|
// 鼠标离开底部区域,设置定时器隐藏控制器
|
||||||
|
if (controlsTimerRef.current) {
|
||||||
|
clearTimeout(controlsTimerRef.current)
|
||||||
|
}
|
||||||
|
controlsTimerRef.current = setTimeout(() => {
|
||||||
|
setShowControls(false)
|
||||||
|
}, 300) // 300ms后隐藏
|
||||||
|
}
|
||||||
|
}, 10) // 10ms 防抖延迟
|
||||||
|
}
|
||||||
|
|
||||||
|
// 鼠标离开整个视频容器时立即隐藏控制器
|
||||||
|
const handleMouseLeave = () => {
|
||||||
|
// 清除防抖定时器
|
||||||
|
if (mouseMoveTimerRef.current) {
|
||||||
|
clearTimeout(mouseMoveTimerRef.current)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 清除控制栏隐藏定时器
|
||||||
|
if (controlsTimerRef.current) {
|
||||||
|
clearTimeout(controlsTimerRef.current)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 立即隐藏控制栏
|
||||||
|
setShowControls(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 视频事件处理
|
||||||
|
useEffect(() => {
|
||||||
|
const video = videoRef.current
|
||||||
|
if (!video) return
|
||||||
|
|
||||||
|
const handleLoadedMetadata = () => {
|
||||||
|
setDuration(video.duration)
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleTimeUpdate = () => {
|
||||||
|
setCurrentTime(video.currentTime)
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleWaiting = () => {
|
||||||
|
setIsBuffering(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
const handlePlaying = () => {
|
||||||
|
setIsBuffering(false)
|
||||||
|
setIsPlaying(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
const handlePause = () => {
|
||||||
|
if (!isBuffering) {
|
||||||
|
setIsPlaying(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
video.addEventListener("loadedmetadata", handleLoadedMetadata)
|
||||||
|
video.addEventListener("timeupdate", handleTimeUpdate)
|
||||||
|
video.addEventListener("waiting", handleWaiting)
|
||||||
|
video.addEventListener("playing", handlePlaying)
|
||||||
|
video.addEventListener("pause", handlePause)
|
||||||
|
video.addEventListener("ended", handleEnded)
|
||||||
|
|
||||||
|
// 组件挂载时尝试播放视频
|
||||||
|
const playVideo = async () => {
|
||||||
|
try {
|
||||||
|
await video.play()
|
||||||
|
setIsPlaying(true)
|
||||||
|
} catch (error) {
|
||||||
|
console.error("自动播放失败:", error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const timer = setTimeout(playVideo, 100)
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
video.removeEventListener("loadedmetadata", handleLoadedMetadata)
|
||||||
|
video.removeEventListener("timeupdate", handleTimeUpdate)
|
||||||
|
video.removeEventListener("waiting", handleWaiting)
|
||||||
|
video.removeEventListener("playing", handlePlaying)
|
||||||
|
video.removeEventListener("pause", handlePause)
|
||||||
|
video.removeEventListener("ended", handleEnded)
|
||||||
|
|
||||||
|
// 清除所有定时器
|
||||||
|
if (controlsTimerRef.current) {
|
||||||
|
clearTimeout(controlsTimerRef.current)
|
||||||
|
}
|
||||||
|
if (mouseMoveTimerRef.current) {
|
||||||
|
clearTimeout(mouseMoveTimerRef.current)
|
||||||
|
}
|
||||||
|
clearTimeout(timer)
|
||||||
|
}
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
// 处理键盘事件
|
||||||
|
const handleKeyDown = (e: KeyboardEvent) => {
|
||||||
|
if (!videoRef.current) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (e.code) {
|
||||||
|
case "Space":
|
||||||
|
e.preventDefault()
|
||||||
|
const video = videoRef.current
|
||||||
|
if (!video) return
|
||||||
|
|
||||||
|
if (isPlayingRef.current) {
|
||||||
|
video.pause()
|
||||||
|
setIsPlaying(false)
|
||||||
|
} else {
|
||||||
|
video.play().catch((error) => {
|
||||||
|
console.error("播放失败:", error)
|
||||||
|
})
|
||||||
|
setIsPlaying(true)
|
||||||
|
}
|
||||||
|
break
|
||||||
|
case "ArrowLeft":
|
||||||
|
e.preventDefault()
|
||||||
|
if (videoRef.current) {
|
||||||
|
videoRef.current.currentTime = Math.max(
|
||||||
|
0,
|
||||||
|
videoRef.current.currentTime - 10
|
||||||
|
)
|
||||||
|
}
|
||||||
|
break
|
||||||
|
case "ArrowRight":
|
||||||
|
e.preventDefault()
|
||||||
|
if (videoRef.current && duration) {
|
||||||
|
videoRef.current.currentTime = Math.min(
|
||||||
|
duration,
|
||||||
|
videoRef.current.currentTime + 10
|
||||||
|
)
|
||||||
|
}
|
||||||
|
break
|
||||||
|
case "ArrowUp":
|
||||||
|
e.preventDefault()
|
||||||
|
setVolume((prev) => {
|
||||||
|
const newVolume = Math.min(1, prev + 0.1)
|
||||||
|
if (videoRef.current) {
|
||||||
|
videoRef.current.volume = newVolume
|
||||||
|
setIsMuted(newVolume === 0)
|
||||||
|
}
|
||||||
|
return newVolume
|
||||||
|
})
|
||||||
|
break
|
||||||
|
case "ArrowDown":
|
||||||
|
e.preventDefault()
|
||||||
|
setVolume((prev) => {
|
||||||
|
const newVolume = Math.max(0, prev - 0.1)
|
||||||
|
if (videoRef.current) {
|
||||||
|
videoRef.current.volume = newVolume
|
||||||
|
setIsMuted(newVolume === 0)
|
||||||
|
}
|
||||||
|
return newVolume
|
||||||
|
})
|
||||||
|
break
|
||||||
|
case "KeyM":
|
||||||
|
e.preventDefault()
|
||||||
|
toggleMute()
|
||||||
|
break
|
||||||
|
case "KeyF":
|
||||||
|
e.preventDefault()
|
||||||
|
toggleFullscreen()
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 键盘事件监听
|
||||||
|
useEffect(() => {
|
||||||
|
document.addEventListener("keydown", handleKeyDown)
|
||||||
|
|
||||||
|
if (containerRef.current) {
|
||||||
|
containerRef.current.tabIndex = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
document.removeEventListener("keydown", handleKeyDown)
|
||||||
|
}
|
||||||
|
}, [duration])
|
||||||
|
|
||||||
|
// 计算进度条百分比
|
||||||
|
const progressPercent = duration ? (currentTime / duration) * 100 : 0
|
||||||
|
|
||||||
|
// 是否隐藏logo
|
||||||
|
const hideLogo = useMemo(() => {
|
||||||
|
return localStorage.getItem("hideLogo") === "true"
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
ref={containerRef}
|
||||||
|
className="relative w-full h-screen bg-black flex justify-center items-center"
|
||||||
|
onMouseMove={handleMouseMove}
|
||||||
|
onMouseLeave={handleMouseLeave}>
|
||||||
|
<video
|
||||||
|
ref={videoRef}
|
||||||
|
className="w-full h-full bg-black [&::-webkit-media-controls]:hidden [&::-webkit-media-controls-start-playback-button]:hidden"
|
||||||
|
onClick={togglePlayPause}
|
||||||
|
playsInline
|
||||||
|
preload="auto">
|
||||||
|
<source src={iodVideo} type="video/mp4" />
|
||||||
|
您的浏览器不支持HTML5视频播放
|
||||||
|
</video>
|
||||||
|
|
||||||
|
{/* 暂停时的遮罩层 */}
|
||||||
|
{!isPlaying && !isBuffering && (
|
||||||
|
<div
|
||||||
|
className="absolute inset-0 bg-black bg-opacity-30 flex items-center justify-center cursor-pointer"
|
||||||
|
onClick={togglePlayPause}>
|
||||||
|
<PlayCircleOutlined className="text-white text-6xl opacity-80" />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* 缓冲提示 */}
|
||||||
|
{isBuffering && (
|
||||||
|
<div className="absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2 text-white text-sm bg-black bg-opacity-50 px-4 py-2 rounded">
|
||||||
|
缓冲中...
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* 控制栏 - 使用与原始HTML相同的类名和行为 */}
|
||||||
|
{createPortal(
|
||||||
|
<div
|
||||||
|
className={`fixed left-0 w-full bg-gradient-to-t from-black to-transparent p-4 transition-all duration-300 ease-in-out flex flex-col gap-2.5 z-50 ${showControls ? "bottom-0" : "-bottom-40"}`}>
|
||||||
|
<div
|
||||||
|
className="flex items-center justify-end gap-2 cursor-pointer"
|
||||||
|
onClick={handleEnded}>
|
||||||
|
{!hideLogo && <img src={logo} alt="logo" className="w-8" />}
|
||||||
|
<h2 className="text-xl font-bold text-white dark:text-zinc-300 mr-3">
|
||||||
|
<span className="text-[#d30100]">数联网</span>科创智能体
|
||||||
|
</h2>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className="w-full h-1.5 bg-white bg-opacity-20 rounded cursor-pointer mb-2.5"
|
||||||
|
onClick={handleProgressClick}>
|
||||||
|
<div
|
||||||
|
className="h-full bg-gradient-to-r from-orange-500 to-pink-600 rounded transition-all duration-100"
|
||||||
|
style={{ width: `${progressPercent}%` }}></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex items-center gap-4">
|
||||||
|
<button
|
||||||
|
className="bg-transparent border-none text-white text-lg cursor-pointer p-1 rounded-full w-12 h-12 flex items-center justify-center hover:bg-white hover:bg-opacity-20 transition-colors"
|
||||||
|
onClick={togglePlayPause}>
|
||||||
|
{isPlaying ? (
|
||||||
|
<PauseCircleOutlined className="text-2xl" />
|
||||||
|
) : (
|
||||||
|
<PlayCircleOutlined className="text-2xl" />
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<span className="text-white text-sm min-w-[100px] text-center">
|
||||||
|
<span>{formatTime(currentTime)}</span> /
|
||||||
|
<span>{formatTime(duration)}</span>
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<div className="flex items-center ml-auto">
|
||||||
|
<button
|
||||||
|
className="bg-transparent border-none text-white text-2xl cursor-pointer p-1 rounded-full w-12 h-12 flex items-center justify-center hover:bg-white hover:bg-opacity-20 transition-colors"
|
||||||
|
onClick={toggleMute}>
|
||||||
|
{isMuted ? "🔇" : "🔊"}
|
||||||
|
</button>
|
||||||
|
<input
|
||||||
|
type="range"
|
||||||
|
min="0"
|
||||||
|
max="1"
|
||||||
|
step="0.1"
|
||||||
|
value={isMuted ? 0 : volume}
|
||||||
|
onChange={handleVolumeChange}
|
||||||
|
className="w-20 h-1.5 ml-2.5"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button
|
||||||
|
className="bg-transparent border-none text-white text-lg cursor-pointer p-1 rounded-full w-12 h-12 flex items-center justify-center hover:bg-white hover:bg-opacity-20 transition-colors"
|
||||||
|
onClick={toggleFullscreen}>
|
||||||
|
<ExpandOutlined className="text-2xl" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>,
|
||||||
|
document.body
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default VideoPlayer
|
@ -39,7 +39,7 @@ export const SidePanelBody = () => {
|
|||||||
message_type={message.messageType}
|
message_type={message.messageType}
|
||||||
isProcessing={streaming}
|
isProcessing={streaming}
|
||||||
isSearchingInternet={isSearchingInternet}
|
isSearchingInternet={isSearchingInternet}
|
||||||
sources={message.sources}
|
webSources={message.webSources}
|
||||||
iodSources={message.iodSources}
|
iodSources={message.iodSources}
|
||||||
onEditFormSubmit={(value) => {
|
onEditFormSubmit={(value) => {
|
||||||
editMessage(index, value, !message.isBot)
|
editMessage(index, value, !message.isBot)
|
||||||
|
@ -5,6 +5,12 @@ interface PageAssistContext {
|
|||||||
messages: Message[]
|
messages: Message[]
|
||||||
setMessages: Dispatch<SetStateAction<Message[]>>
|
setMessages: Dispatch<SetStateAction<Message[]>>
|
||||||
|
|
||||||
|
currentMessageId: string
|
||||||
|
setCurrentMessageId: Dispatch<SetStateAction<string>>
|
||||||
|
|
||||||
|
iodLoading: boolean
|
||||||
|
setIodLoading: Dispatch<SetStateAction<boolean>>
|
||||||
|
|
||||||
controller: AbortController | null
|
controller: AbortController | null
|
||||||
setController: Dispatch<SetStateAction<AbortController>>
|
setController: Dispatch<SetStateAction<AbortController>>
|
||||||
|
|
||||||
@ -16,6 +22,12 @@ export const PageAssistContext = createContext<PageAssistContext>({
|
|||||||
messages: [],
|
messages: [],
|
||||||
setMessages: () => {},
|
setMessages: () => {},
|
||||||
|
|
||||||
|
currentMessageId: "",
|
||||||
|
setCurrentMessageId: () => {},
|
||||||
|
|
||||||
|
iodLoading: false,
|
||||||
|
setIodLoading: () => {},
|
||||||
|
|
||||||
controller: null,
|
controller: null,
|
||||||
setController: () => {},
|
setController: () => {},
|
||||||
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import {
|
import { type ChatHistory as ChatHistoryType } from "~/store/option"
|
||||||
type ChatHistory as ChatHistoryType,
|
import { AllIodRegistryEntry } from "@/types/iod.ts"
|
||||||
type Message as MessageType
|
import { type Message as MessageType } from "@/types/message.ts"
|
||||||
} from "~/store/option"
|
import { getDefaultIodSources } from "@/libs/iod.ts"
|
||||||
|
|
||||||
type HistoryInfo = {
|
type HistoryInfo = {
|
||||||
id: string
|
id: string
|
||||||
@ -29,8 +29,8 @@ type Message = {
|
|||||||
role: string
|
role: string
|
||||||
content: string
|
content: string
|
||||||
images?: string[]
|
images?: string[]
|
||||||
sources?: string[]
|
webSources?: string[]
|
||||||
iodSources?:string[]
|
iodSources?: AllIodRegistryEntry
|
||||||
search?: WebSearch
|
search?: WebSearch
|
||||||
createdAt: number
|
createdAt: number
|
||||||
reasoning_time_taken?: number
|
reasoning_time_taken?: number
|
||||||
@ -239,7 +239,7 @@ export const generateID = () => {
|
|||||||
export const saveHistory = async (
|
export const saveHistory = async (
|
||||||
title: string,
|
title: string,
|
||||||
is_rag?: boolean,
|
is_rag?: boolean,
|
||||||
message_source?: "copilot" | "web-ui",
|
message_source?: "copilot" | "web-ui"
|
||||||
) => {
|
) => {
|
||||||
const id = generateID()
|
const id = generateID()
|
||||||
const createdAt = Date.now()
|
const createdAt = Date.now()
|
||||||
@ -248,38 +248,31 @@ export const saveHistory = async (
|
|||||||
await db.addChatHistory(history)
|
await db.addChatHistory(history)
|
||||||
return history
|
return history
|
||||||
}
|
}
|
||||||
|
export type HistoryMessage = {
|
||||||
export const saveMessage = async (
|
history_id: string
|
||||||
history_id: string,
|
name: string
|
||||||
name: string,
|
role: string
|
||||||
role: string,
|
content: string
|
||||||
content: string,
|
images: string[]
|
||||||
images: string[],
|
iodSearch?: boolean
|
||||||
source?: any[],
|
webSearch?: boolean
|
||||||
iodSource?:any[],
|
webSources?: any[]
|
||||||
time?: number,
|
iodSources?: AllIodRegistryEntry
|
||||||
message_type?: string,
|
createdAt?: number
|
||||||
generationInfo?: any,
|
messageType?: string
|
||||||
|
generationInfo?: any
|
||||||
reasoning_time_taken?: number
|
reasoning_time_taken?: number
|
||||||
) => {
|
}
|
||||||
|
export const saveMessage = async (msg: HistoryMessage): Promise<Message> => {
|
||||||
const id = generateID()
|
const id = generateID()
|
||||||
let createdAt = Date.now()
|
let createdAt = Date.now()
|
||||||
if (time) {
|
if (msg.createdAt) {
|
||||||
createdAt += time
|
createdAt += msg.createdAt
|
||||||
}
|
}
|
||||||
const message = {
|
const message = {
|
||||||
|
...msg,
|
||||||
id,
|
id,
|
||||||
history_id,
|
|
||||||
name,
|
|
||||||
role,
|
|
||||||
content,
|
|
||||||
images,
|
|
||||||
createdAt,
|
createdAt,
|
||||||
sources: source,
|
|
||||||
iodSources:iodSource,
|
|
||||||
messageType: message_type,
|
|
||||||
generationInfo: generationInfo,
|
|
||||||
reasoning_time_taken
|
|
||||||
}
|
}
|
||||||
const db = new PageAssitDatabase()
|
const db = new PageAssitDatabase()
|
||||||
await db.addMessage(message)
|
await db.addMessage(message)
|
||||||
@ -303,11 +296,12 @@ export const formatToMessage = (messages: MessageHistory): MessageType[] => {
|
|||||||
messages.sort((a, b) => a.createdAt - b.createdAt)
|
messages.sort((a, b) => a.createdAt - b.createdAt)
|
||||||
return messages.map((message) => {
|
return messages.map((message) => {
|
||||||
return {
|
return {
|
||||||
|
...message,
|
||||||
isBot: message.role === "assistant",
|
isBot: message.role === "assistant",
|
||||||
message: message.content,
|
message: message.content,
|
||||||
name: message.name,
|
name: message.name,
|
||||||
sources: message?.sources || [],
|
webSources: message?.webSources || [],
|
||||||
iodSources: message?.iodSources || [],
|
iodSources: message?.iodSources || getDefaultIodSources(),
|
||||||
images: message.images || [],
|
images: message.images || [],
|
||||||
generationInfo: message?.generationInfo,
|
generationInfo: message?.generationInfo,
|
||||||
reasoning_time_taken: message?.reasoning_time_taken
|
reasoning_time_taken: message?.reasoning_time_taken
|
||||||
|
75
src/db/iod.ts
Normal file
75
src/db/iod.ts
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
const iodConnection = "iodConnection-g3"
|
||||||
|
|
||||||
|
export const defaultIodConnectionConfig = {
|
||||||
|
gatewayUrl: "tcp://reg01.public.internetofdata.cn:21037",
|
||||||
|
registry: "data/Registry",
|
||||||
|
localRepository: "data/Repository",
|
||||||
|
doBrowser: "http://021.node.internetapi.cn:21030/SCIDE/SCManager"
|
||||||
|
} as const
|
||||||
|
|
||||||
|
export type IodConnectionConfig = {
|
||||||
|
gatewayUrl: string
|
||||||
|
registry: string
|
||||||
|
localRepository: string
|
||||||
|
doBrowser: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export class IodDb {
|
||||||
|
private static instance: IodDb
|
||||||
|
private static iodConnectionConfig: IodConnectionConfig | null = null
|
||||||
|
|
||||||
|
// 单例模式
|
||||||
|
static getInstance(): IodDb {
|
||||||
|
if (!IodDb.instance) {
|
||||||
|
IodDb.instance = new IodDb()
|
||||||
|
}
|
||||||
|
return IodDb.instance
|
||||||
|
}
|
||||||
|
|
||||||
|
insertIodConnection(config: IodConnectionConfig): void {
|
||||||
|
try {
|
||||||
|
localStorage.setItem(iodConnection, JSON.stringify(config))
|
||||||
|
IodDb.iodConnectionConfig = config
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to save IOD connection config:', error)
|
||||||
|
throw new Error('Failed to save IOD connection configuration')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getIodConnection(): IodConnectionConfig {
|
||||||
|
// 如果已经有缓存,直接返回
|
||||||
|
if (IodDb.iodConnectionConfig) {
|
||||||
|
return IodDb.iodConnectionConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const val = localStorage.getItem(iodConnection)
|
||||||
|
if (!val) {
|
||||||
|
return defaultIodConnectionConfig
|
||||||
|
}
|
||||||
|
IodDb.iodConnectionConfig = JSON.parse(val)
|
||||||
|
return IodDb.iodConnectionConfig
|
||||||
|
} catch (error) {
|
||||||
|
console.warn('Failed to parse IOD connection config, using default:', error)
|
||||||
|
return defaultIodConnectionConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// 添加清除配置的方法
|
||||||
|
clearIodConnection(): void {
|
||||||
|
try {
|
||||||
|
localStorage.removeItem(iodConnection)
|
||||||
|
IodDb.iodConnectionConfig = null
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to clear IOD connection config:', error)
|
||||||
|
throw new Error('Failed to clear IOD connection configuration')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getIodConfig() {
|
||||||
|
return {
|
||||||
|
connection: this.getIodConnection(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
507
src/entries/auto-deeplink.content.ts
Normal file
507
src/entries/auto-deeplink.content.ts
Normal file
@ -0,0 +1,507 @@
|
|||||||
|
|
||||||
|
|
||||||
|
export default defineContentScript({
|
||||||
|
matches: ['<all_urls>'],
|
||||||
|
main(ctx) {
|
||||||
|
setTimeout(getDeepScript,1000)
|
||||||
|
},
|
||||||
|
});
|
||||||
|
async function getDeepScript(){
|
||||||
|
console.log("getDeepScript!!")
|
||||||
|
const href = document.location.href;
|
||||||
|
let id = "unknown";
|
||||||
|
if (href.startsWith("http://39.105.188.3:3838/topic3/missing/?autoexecute="))
|
||||||
|
id = "id1";
|
||||||
|
if (href.startsWith("http://39.105.188.3:3838/topic3/PKUCausalEfficacy/?autoexecute="))
|
||||||
|
id = "id2";
|
||||||
|
if (href.startsWith("http://39.105.188.3:3838/topic3/ADR23/?autoexecute="))
|
||||||
|
id = "id3";
|
||||||
|
if (idToScript[id]!=undefined){
|
||||||
|
idToScript[id]();
|
||||||
|
}
|
||||||
|
//sendMessageToServiceWorker({});
|
||||||
|
}
|
||||||
|
async function sendMessageToServiceWorker(message) {
|
||||||
|
chrome.runtime.sendMessage({ type: 'retrieveDeepScript', doId:"10.1002/2014JA019817" }, response => {
|
||||||
|
console.log(response);
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const idToScript = {
|
||||||
|
"id1":(function() {
|
||||||
|
// 等待函数
|
||||||
|
function wait(ms) {
|
||||||
|
return new Promise(resolve => setTimeout(resolve, ms));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 主函数
|
||||||
|
async function automate() {
|
||||||
|
try {
|
||||||
|
console.log("开始执行第二部分脚本...");
|
||||||
|
|
||||||
|
// 等待页面加载
|
||||||
|
await wait(2000);
|
||||||
|
|
||||||
|
// 点击"缺失数据填补"链接
|
||||||
|
const dataFillLink = Array.from(document.querySelectorAll('a')).find(a =>
|
||||||
|
a.textContent.includes("缺失数据填补")
|
||||||
|
);
|
||||||
|
if (dataFillLink) {
|
||||||
|
dataFillLink.click();
|
||||||
|
console.log("已点击'缺失数据填补'链接");
|
||||||
|
} else {
|
||||||
|
console.error("未找到'缺失数据填补'链接");
|
||||||
|
}
|
||||||
|
await wait(1000);
|
||||||
|
|
||||||
|
// 点击"选择数字对象"选项卡
|
||||||
|
const numObjTab = Array.from(document.querySelectorAll('[role="tab"]')).find(tab =>
|
||||||
|
tab.textContent.includes("选择数字对象")
|
||||||
|
);
|
||||||
|
if (numObjTab) {
|
||||||
|
numObjTab.click();
|
||||||
|
console.log("已点击'选择数字对象'选项卡");
|
||||||
|
} else {
|
||||||
|
console.error("未找到'选择数字对象'选项卡");
|
||||||
|
}
|
||||||
|
await wait(1000);
|
||||||
|
|
||||||
|
// 点击"多重填补数字对象"
|
||||||
|
const multipleNumObjs = Array.from(document.querySelectorAll('div')).filter(div =>
|
||||||
|
div.textContent.trim() === "多重填补数字对象"
|
||||||
|
);
|
||||||
|
if (multipleNumObjs.length > 1) {
|
||||||
|
multipleNumObjs[1].click();
|
||||||
|
console.log("已点击'多重填补数字对象'");
|
||||||
|
} else if (multipleNumObjs.length > 0) {
|
||||||
|
multipleNumObjs[0].click();
|
||||||
|
console.log("已点击'多重填补数字对象'");
|
||||||
|
} else {
|
||||||
|
console.error("未找到'多重填补数字对象'");
|
||||||
|
}
|
||||||
|
await wait(1000);
|
||||||
|
|
||||||
|
// 选择"围术期处理后"
|
||||||
|
const periOption = Array.from(document.querySelectorAll('[role="option"]')).find(option =>
|
||||||
|
option.textContent.includes("围术期处理后")
|
||||||
|
);
|
||||||
|
if (periOption) {
|
||||||
|
periOption.click();
|
||||||
|
console.log("已选择'围术期处理后'");
|
||||||
|
} else {
|
||||||
|
console.error("未找到'围术期处理后'选项");
|
||||||
|
}
|
||||||
|
await wait(1000);
|
||||||
|
|
||||||
|
// 勾选特定复选框
|
||||||
|
const checkbox = document.querySelector('#col_pro');
|
||||||
|
if (checkbox) {
|
||||||
|
checkbox.checked = true;
|
||||||
|
checkbox.dispatchEvent(new Event('change', { bubbles: true }));
|
||||||
|
console.log("已勾选复选框");
|
||||||
|
} else {
|
||||||
|
console.error("未找到指定复选框");
|
||||||
|
}
|
||||||
|
await wait(1000);
|
||||||
|
|
||||||
|
// 点击"开始填补"按钮
|
||||||
|
const startFillBtn = Array.from(document.querySelectorAll('button')).find(btn =>
|
||||||
|
btn.textContent.includes("开始填补")
|
||||||
|
);
|
||||||
|
if (startFillBtn) {
|
||||||
|
startFillBtn.click();
|
||||||
|
console.log("已点击'开始填补'按钮");
|
||||||
|
} else {
|
||||||
|
console.error("未找到'开始填补'按钮");
|
||||||
|
}
|
||||||
|
await wait(2000);
|
||||||
|
|
||||||
|
// 点击"进入数据分析"按钮
|
||||||
|
const enterAnalysisBtn = Array.from(document.querySelectorAll('button')).find(btn =>
|
||||||
|
btn.textContent.includes("进入数据分析")
|
||||||
|
);
|
||||||
|
if (enterAnalysisBtn) {
|
||||||
|
enterAnalysisBtn.click();
|
||||||
|
console.log("已点击'进入数据分析'按钮");
|
||||||
|
} else {
|
||||||
|
console.error("未找到'进入数据分析'按钮");
|
||||||
|
}
|
||||||
|
await wait(2000);
|
||||||
|
|
||||||
|
// 点击"中心序号"
|
||||||
|
const centerNumDivs = Array.from(document.querySelectorAll('[id^="tab-"][id$="-3"] div')).filter(div =>
|
||||||
|
div.textContent.includes("中心序号")
|
||||||
|
);
|
||||||
|
if (centerNumDivs.length > 1) {
|
||||||
|
centerNumDivs[1].click();
|
||||||
|
console.log("已点击'中心序号'");
|
||||||
|
} else if (centerNumDivs.length > 0) {
|
||||||
|
centerNumDivs[0].click();
|
||||||
|
console.log("已点击'中心序号'");
|
||||||
|
} else {
|
||||||
|
console.error("未找到'中心序号'");
|
||||||
|
}
|
||||||
|
await wait(1000);
|
||||||
|
|
||||||
|
// 选择"术后血红蛋白HB"
|
||||||
|
const hbOption = Array.from(document.querySelectorAll('[role="option"]')).find(option =>
|
||||||
|
option.textContent.includes("术后血红蛋白HB")
|
||||||
|
);
|
||||||
|
if (hbOption) {
|
||||||
|
hbOption.click();
|
||||||
|
console.log("已选择'术后血红蛋白HB'");
|
||||||
|
} else {
|
||||||
|
console.error("未找到'术后血红蛋白HB'选项");
|
||||||
|
}
|
||||||
|
await wait(1000);
|
||||||
|
|
||||||
|
// 点击X变量选择框
|
||||||
|
const xSelector = document.querySelector('[id="X"] > .form-group > div > .selectize-control > .selectize-input');
|
||||||
|
if (xSelector) {
|
||||||
|
xSelector.click();
|
||||||
|
console.log("已点击X变量选择框");
|
||||||
|
} else {
|
||||||
|
console.error("未找到X变量选择框");
|
||||||
|
}
|
||||||
|
await wait(1000);
|
||||||
|
|
||||||
|
// 选择"性别"
|
||||||
|
const genderOption = Array.from(document.querySelectorAll('[role="option"]')).find(option =>
|
||||||
|
option.textContent.includes("性别")
|
||||||
|
);
|
||||||
|
if (genderOption) {
|
||||||
|
genderOption.click();
|
||||||
|
console.log("已选择'性别'");
|
||||||
|
} else {
|
||||||
|
console.error("未找到'性别'选项");
|
||||||
|
}
|
||||||
|
await wait(1000);
|
||||||
|
|
||||||
|
// 点击变量选择区域
|
||||||
|
const varSelectors = Array.from(document.querySelectorAll('div')).filter(div =>
|
||||||
|
div.textContent.includes("1. 根据预览数据选择变量 *选择处理变量(Z")
|
||||||
|
);
|
||||||
|
if (varSelectors.length > 3) {
|
||||||
|
varSelectors[3].click();
|
||||||
|
console.log("已点击变量选择区域");
|
||||||
|
} else if (varSelectors.length > 0) {
|
||||||
|
varSelectors[varSelectors.length - 1].click();
|
||||||
|
console.log("已点击变量选择区域");
|
||||||
|
} else {
|
||||||
|
console.error("未找到变量选择区域");
|
||||||
|
}
|
||||||
|
await wait(1000);
|
||||||
|
|
||||||
|
// 点击"开始分析"按钮
|
||||||
|
const startAnalysisBtn = Array.from(document.querySelectorAll('button')).find(btn =>
|
||||||
|
btn.textContent.includes("开始分析")
|
||||||
|
);
|
||||||
|
if (startAnalysisBtn) {
|
||||||
|
startAnalysisBtn.click();
|
||||||
|
console.log("已点击'开始分析'按钮");
|
||||||
|
} else {
|
||||||
|
console.error("未找到'开始分析'按钮");
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("第二部分脚本执行完成");
|
||||||
|
} catch (error) {
|
||||||
|
console.error("执行过程中出错:", error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 执行自动化操作
|
||||||
|
automate();
|
||||||
|
}),
|
||||||
|
"id2":(function() {
|
||||||
|
function wait(ms) {
|
||||||
|
return new Promise(resolve => setTimeout(resolve, ms));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 主函数
|
||||||
|
async function automate() {
|
||||||
|
try {
|
||||||
|
console.log("开始执行 Case02 第二部分脚本...");
|
||||||
|
|
||||||
|
// 等待页面加载
|
||||||
|
await wait(2000);
|
||||||
|
|
||||||
|
// 点击"准备数据"链接
|
||||||
|
const prepDataLink = Array.from(document.querySelectorAll('a')).find(a =>
|
||||||
|
a.textContent.includes("准备数据")
|
||||||
|
);
|
||||||
|
if (prepDataLink) {
|
||||||
|
prepDataLink.click();
|
||||||
|
console.log("已点击'准备数据'链接");
|
||||||
|
} else {
|
||||||
|
console.error("未找到'准备数据'链接");
|
||||||
|
}
|
||||||
|
await wait(1000);
|
||||||
|
|
||||||
|
// 点击"选择数字对象"选项卡
|
||||||
|
const numObjTab = Array.from(document.querySelectorAll('[role="tab"]')).find(tab =>
|
||||||
|
tab.textContent.includes("选择数字对象")
|
||||||
|
);
|
||||||
|
if (numObjTab) {
|
||||||
|
numObjTab.click();
|
||||||
|
console.log("已点击'选择数字对象'选项卡");
|
||||||
|
} else {
|
||||||
|
console.error("未找到'选择数字对象'选项卡");
|
||||||
|
}
|
||||||
|
await wait(1000);
|
||||||
|
|
||||||
|
// 点击"RS"
|
||||||
|
const rsDivs = Array.from(document.querySelectorAll('div')).filter(div =>
|
||||||
|
div.textContent.trim() === "RS"
|
||||||
|
);
|
||||||
|
if (rsDivs.length > 1) {
|
||||||
|
rsDivs[1].click();
|
||||||
|
console.log("已点击'RS'");
|
||||||
|
} else if (rsDivs.length > 0) {
|
||||||
|
rsDivs[0].click();
|
||||||
|
console.log("已点击'RS'");
|
||||||
|
} else {
|
||||||
|
console.error("未找到'RS'");
|
||||||
|
}
|
||||||
|
await wait(1000);
|
||||||
|
|
||||||
|
// 选择"TQ-B2303-III-01_merged"
|
||||||
|
const mergedOption = Array.from(document.querySelectorAll('[role="option"]')).find(option =>
|
||||||
|
option.textContent.includes("TQ-B2303-III-01_merged")
|
||||||
|
);
|
||||||
|
if (mergedOption) {
|
||||||
|
mergedOption.click();
|
||||||
|
console.log("已选择'TQ-B2303-III-01_merged'");
|
||||||
|
} else {
|
||||||
|
console.error("未找到'TQ-B2303-III-01_merged'选项");
|
||||||
|
}
|
||||||
|
await wait(1000);
|
||||||
|
|
||||||
|
// 点击特定元素
|
||||||
|
const noncomplianceElement = document.querySelector('[id="data\\.goto\\.noncompliance1"]');
|
||||||
|
if (noncomplianceElement) {
|
||||||
|
noncomplianceElement.click();
|
||||||
|
console.log("已点击特定元素");
|
||||||
|
} else {
|
||||||
|
console.error("未找到特定元素");
|
||||||
|
}
|
||||||
|
await wait(1000);
|
||||||
|
|
||||||
|
// 点击"AGEGR1"下拉框
|
||||||
|
const agegr1Combobox = Array.from(document.querySelectorAll('[role="combobox"]')).find(box =>
|
||||||
|
box.getAttribute('name') === "AGEGR1"
|
||||||
|
);
|
||||||
|
if (agegr1Combobox) {
|
||||||
|
agegr1Combobox.click();
|
||||||
|
console.log("已点击'AGEGR1'下拉框");
|
||||||
|
await wait(500);
|
||||||
|
|
||||||
|
// 选择特定选项
|
||||||
|
const agegr1Option = document.querySelector('#bs-select-9-29');
|
||||||
|
if (agegr1Option) {
|
||||||
|
agegr1Option.click();
|
||||||
|
console.log("已选择AGEGR1选项");
|
||||||
|
} else {
|
||||||
|
console.error("未找到AGEGR1选项");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.error("未找到'AGEGR1'下拉框");
|
||||||
|
}
|
||||||
|
await wait(1000);
|
||||||
|
|
||||||
|
// 点击"SUBJID"下拉框
|
||||||
|
const subjidCombobox = Array.from(document.querySelectorAll('[role="combobox"]')).find(box =>
|
||||||
|
box.getAttribute('name') === "SUBJID"
|
||||||
|
);
|
||||||
|
if (subjidCombobox) {
|
||||||
|
subjidCombobox.click();
|
||||||
|
console.log("已点击'SUBJID'下拉框");
|
||||||
|
await wait(500);
|
||||||
|
|
||||||
|
// 选择特定选项
|
||||||
|
const subjidOption = document.querySelector('#bs-select-14-42');
|
||||||
|
if (subjidOption) {
|
||||||
|
subjidOption.click();
|
||||||
|
console.log("已选择SUBJID选项");
|
||||||
|
} else {
|
||||||
|
console.error("未找到SUBJID选项");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.error("未找到'SUBJID'下拉框");
|
||||||
|
}
|
||||||
|
await wait(1000);
|
||||||
|
|
||||||
|
// 点击"Nothing selected"下拉框
|
||||||
|
const nothingSelectedCombobox = Array.from(document.querySelectorAll('[role="combobox"]')).find(box =>
|
||||||
|
box.getAttribute('name') === "Nothing selected"
|
||||||
|
);
|
||||||
|
if (nothingSelectedCombobox) {
|
||||||
|
nothingSelectedCombobox.click();
|
||||||
|
console.log("已点击'Nothing selected'下拉框");
|
||||||
|
await wait(500);
|
||||||
|
|
||||||
|
// 选择特定选项
|
||||||
|
const nothingSelectedOption = document.querySelector('#bs-select-16-6');
|
||||||
|
if (nothingSelectedOption) {
|
||||||
|
nothingSelectedOption.click();
|
||||||
|
console.log("已选择选项");
|
||||||
|
} else {
|
||||||
|
console.error("未找到选项");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.error("未找到'Nothing selected'下拉框");
|
||||||
|
}
|
||||||
|
await wait(1000);
|
||||||
|
|
||||||
|
// 点击"IPI"下拉框
|
||||||
|
const ipiCombobox = Array.from(document.querySelectorAll('[role="combobox"]')).find(box =>
|
||||||
|
box.getAttribute('name') === "IPI"
|
||||||
|
);
|
||||||
|
if (ipiCombobox) {
|
||||||
|
ipiCombobox.click();
|
||||||
|
console.log("已点击'IPI'下拉框");
|
||||||
|
await wait(500);
|
||||||
|
|
||||||
|
// 取消勾选第四个复选框
|
||||||
|
const checkboxes = document.querySelectorAll('[role="checkbox"]');
|
||||||
|
if (checkboxes.length > 3) {
|
||||||
|
checkboxes[3].click(); // 取消勾选
|
||||||
|
console.log("已取消勾选复选框");
|
||||||
|
} else {
|
||||||
|
console.error("未找到足够的复选框");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.error("未找到'IPI'下拉框");
|
||||||
|
}
|
||||||
|
await wait(1000);
|
||||||
|
|
||||||
|
// 点击"计算估计结果"按钮
|
||||||
|
const calculateBtn = Array.from(document.querySelectorAll('button')).find(btn =>
|
||||||
|
btn.textContent.includes("计算估计结果")
|
||||||
|
);
|
||||||
|
if (calculateBtn) {
|
||||||
|
calculateBtn.click();
|
||||||
|
console.log("已点击'计算估计结果'按钮");
|
||||||
|
} else {
|
||||||
|
console.error("未找到'计算估计结果'按钮");
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("Case02 第二部分脚本执行完成");
|
||||||
|
} catch (error) {
|
||||||
|
console.error("执行过程中出错:", error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 执行自动化操作
|
||||||
|
automate();
|
||||||
|
}),
|
||||||
|
"id3":(function(){
|
||||||
|
function wait(ms) {
|
||||||
|
return new Promise(resolve => setTimeout(resolve, ms));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 主函数
|
||||||
|
async function automate() {
|
||||||
|
try {
|
||||||
|
console.log("开始执行 Case03 第二部分脚本...");
|
||||||
|
|
||||||
|
// 等待页面加载
|
||||||
|
await wait(2000);
|
||||||
|
|
||||||
|
// 点击"模型预测"链接
|
||||||
|
const modelPredictLink = Array.from(document.querySelectorAll('a')).find(a =>
|
||||||
|
a.textContent.includes("模型预测")
|
||||||
|
);
|
||||||
|
if (modelPredictLink) {
|
||||||
|
modelPredictLink.click();
|
||||||
|
console.log("已点击'模型预测'链接");
|
||||||
|
} else {
|
||||||
|
console.error("未找到'模型预测'链接");
|
||||||
|
}
|
||||||
|
await wait(1000);
|
||||||
|
|
||||||
|
// 点击"选择数字对象"选项卡
|
||||||
|
const numObjTab = Array.from(document.querySelectorAll('[role="tab"]')).find(tab =>
|
||||||
|
tab.textContent.includes("选择数字对象")
|
||||||
|
);
|
||||||
|
if (numObjTab) {
|
||||||
|
numObjTab.click();
|
||||||
|
console.log("已点击'选择数字对象'选项卡");
|
||||||
|
} else {
|
||||||
|
console.error("未找到'选择数字对象'选项卡");
|
||||||
|
}
|
||||||
|
await wait(1000);
|
||||||
|
|
||||||
|
// 点击"多重填补数字对象"
|
||||||
|
const multipleNumObjs = Array.from(document.querySelectorAll('div')).filter(div =>
|
||||||
|
div.textContent.trim() === "多重填补数字对象"
|
||||||
|
);
|
||||||
|
if (multipleNumObjs.length > 1) {
|
||||||
|
multipleNumObjs[1].click();
|
||||||
|
console.log("已点击'多重填补数字对象'");
|
||||||
|
} else if (multipleNumObjs.length > 0) {
|
||||||
|
multipleNumObjs[0].click();
|
||||||
|
console.log("已点击'多重填补数字对象'");
|
||||||
|
} else {
|
||||||
|
console.error("未找到'多重填补数字对象'");
|
||||||
|
}
|
||||||
|
await wait(1000);
|
||||||
|
|
||||||
|
// 选择"Clopidogrel"
|
||||||
|
const clopidogrelOption = Array.from(document.querySelectorAll('[role="option"]')).find(option =>
|
||||||
|
option.textContent.includes("Clopidogrel")
|
||||||
|
);
|
||||||
|
if (clopidogrelOption) {
|
||||||
|
clopidogrelOption.click();
|
||||||
|
console.log("已选择'Clopidogrel'");
|
||||||
|
} else {
|
||||||
|
console.error("未找到'Clopidogrel'选项");
|
||||||
|
}
|
||||||
|
await wait(1000);
|
||||||
|
|
||||||
|
// 勾选特定复选框
|
||||||
|
const checkbox = document.querySelector('#col_pro');
|
||||||
|
if (checkbox) {
|
||||||
|
checkbox.checked = true;
|
||||||
|
checkbox.dispatchEvent(new Event('change', { bubbles: true }));
|
||||||
|
console.log("已勾选复选框");
|
||||||
|
} else {
|
||||||
|
console.error("未找到指定复选框");
|
||||||
|
}
|
||||||
|
await wait(1000);
|
||||||
|
|
||||||
|
// 点击"查看上传的数据"按钮
|
||||||
|
const viewDataBtn = Array.from(document.querySelectorAll('button')).find(btn =>
|
||||||
|
btn.textContent.includes("查看上传的数据")
|
||||||
|
);
|
||||||
|
if (viewDataBtn) {
|
||||||
|
viewDataBtn.click();
|
||||||
|
console.log("已点击'查看上传的数据'按钮");
|
||||||
|
} else {
|
||||||
|
console.error("未找到'查看上传的数据'按钮");
|
||||||
|
}
|
||||||
|
await wait(2000);
|
||||||
|
|
||||||
|
// 点击"计算模型预测结果"按钮
|
||||||
|
const calculateBtn = Array.from(document.querySelectorAll('button')).find(btn =>
|
||||||
|
btn.textContent.includes("计算模型预测结果")
|
||||||
|
);
|
||||||
|
if (calculateBtn) {
|
||||||
|
calculateBtn.click();
|
||||||
|
console.log("已点击'计算模型预测结果'按钮");
|
||||||
|
} else {
|
||||||
|
console.error("未找到'计算模型预测结果'按钮");
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("Case03 第二部分脚本执行完成");
|
||||||
|
} catch (error) {
|
||||||
|
console.error("执行过程中出错:", error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 执行自动化操作
|
||||||
|
automate();
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -5,10 +5,12 @@ import { clearBadge, streamDownload } from "@/utils/pull-ollama"
|
|||||||
export default defineBackground({
|
export default defineBackground({
|
||||||
main() {
|
main() {
|
||||||
let isCopilotRunning: boolean = false
|
let isCopilotRunning: boolean = false
|
||||||
browser.runtime.onMessage.addListener(async (message) => {
|
browser.runtime.onMessage.addListener(async (message,sender,sendResponse) => {
|
||||||
if (message.type === "sidepanel") {
|
switch(message.type){
|
||||||
|
case "sidepanel":
|
||||||
await browser.sidebarAction.open()
|
await browser.sidebarAction.open()
|
||||||
} else if (message.type === "pull_model") {
|
break;
|
||||||
|
case "pull_model":
|
||||||
const ollamaURL = await getOllamaURL()
|
const ollamaURL = await getOllamaURL()
|
||||||
|
|
||||||
const isRunning = await isOllamaRunning()
|
const isRunning = await isOllamaRunning()
|
||||||
@ -21,8 +23,12 @@ export default defineBackground({
|
|||||||
clearBadge()
|
clearBadge()
|
||||||
}, 5000)
|
}, 5000)
|
||||||
}
|
}
|
||||||
|
|
||||||
await streamDownload(ollamaURL, message.modelName)
|
await streamDownload(ollamaURL, message.modelName)
|
||||||
|
break;
|
||||||
|
case "retrieveDeepScript":
|
||||||
|
return retrieveDeepScript(message);
|
||||||
|
default:
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -180,3 +186,52 @@ export default defineBackground({
|
|||||||
},
|
},
|
||||||
persistent: true
|
persistent: true
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const iodConfig = {
|
||||||
|
"gatewayUrl": "tcp://127.0.0.1:21051",
|
||||||
|
"registry":"bdware/Registry",
|
||||||
|
"localRepository":"bdtest.local/myrepo1",
|
||||||
|
"doBrowser":"http://127.0.0.1:21030/SCIDE/SCManager"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const makeDOIPParams = (doId:string, op:string, attributes:Object, requestBody: string) => ({
|
||||||
|
action: "executeContract",
|
||||||
|
contractID: "BDBrowser",
|
||||||
|
operation: "sendRequestDirectly",
|
||||||
|
arg: {
|
||||||
|
id: doId,
|
||||||
|
doipUrl: iodConfig.gatewayUrl,
|
||||||
|
op: op,
|
||||||
|
attributes: attributes,
|
||||||
|
body: requestBody
|
||||||
|
}
|
||||||
|
})
|
||||||
|
const retrieveDeepScript = async function(message) {
|
||||||
|
console.log(message);
|
||||||
|
const doId = message.doId;
|
||||||
|
console.log("retriveDoc:"+doId)
|
||||||
|
const params = makeDOIPParams(doId,"Retrieve",{
|
||||||
|
bodyBase64Encoded: false
|
||||||
|
}, "");
|
||||||
|
const abortController = new AbortController()
|
||||||
|
setTimeout(() => abortController.abort(), 10000)
|
||||||
|
return await fetch(iodConfig.doBrowser, {
|
||||||
|
method: "POST",
|
||||||
|
body: JSON.stringify(params),
|
||||||
|
signal: abortController.signal
|
||||||
|
}).then((response) => {
|
||||||
|
console.log("responseIn retrieveDoc:");
|
||||||
|
console.log(response);
|
||||||
|
return response.json()})
|
||||||
|
.then((res) => {
|
||||||
|
console.log("res:");
|
||||||
|
console.log(res.result.body);
|
||||||
|
//TODO
|
||||||
|
return {
|
||||||
|
metadata:{traceId:res.result.header.attributes?.traceId},
|
||||||
|
pageContent:res.result.body
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
@ -1,7 +1,7 @@
|
|||||||
<!doctype html>
|
<!doctype html>
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<title>Page Assist - A Web UI for Local AI Models</title>
|
<title>IoD Bot - A Web UI for Local AI Models</title>
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
<meta name="manifest.type" content="browser_action" />
|
<meta name="manifest.type" content="browser_action" />
|
||||||
<meta name="manifest.browser_style" content="false" />
|
<meta name="manifest.browser_style" content="false" />
|
||||||
|
@ -1,7 +1,13 @@
|
|||||||
import { saveHistory, saveMessage } from "@/db"
|
import { HistoryMessage, saveHistory, saveMessage } from "@/db"
|
||||||
import { setLastUsedChatModel, setLastUsedChatSystemPrompt } from "@/services/model-settings"
|
import {
|
||||||
|
setLastUsedChatModel,
|
||||||
|
setLastUsedChatSystemPrompt
|
||||||
|
} from "@/services/model-settings"
|
||||||
import { generateTitle } from "@/services/title"
|
import { generateTitle } from "@/services/title"
|
||||||
import { ChatHistory } from "@/store/option"
|
import { ChatHistory } from "@/store/option"
|
||||||
|
import { updateDialog } from "@/web/iod"
|
||||||
|
import { AllIodRegistryEntry } from "@/types/iod.ts"
|
||||||
|
import { getDefaultIodSources } from "@/libs/iod.ts"
|
||||||
|
|
||||||
export const saveMessageOnError = async ({
|
export const saveMessageOnError = async ({
|
||||||
e,
|
e,
|
||||||
@ -17,7 +23,9 @@ export const saveMessageOnError = async ({
|
|||||||
message_source = "web-ui",
|
message_source = "web-ui",
|
||||||
message_type,
|
message_type,
|
||||||
prompt_content,
|
prompt_content,
|
||||||
prompt_id
|
prompt_id,
|
||||||
|
iodSearch,
|
||||||
|
webSearch,
|
||||||
}: {
|
}: {
|
||||||
e: any
|
e: any
|
||||||
setHistory: (history: ChatHistory) => void
|
setHistory: (history: ChatHistory) => void
|
||||||
@ -32,7 +40,9 @@ export const saveMessageOnError = async ({
|
|||||||
message_source?: "copilot" | "web-ui"
|
message_source?: "copilot" | "web-ui"
|
||||||
message_type?: string
|
message_type?: string
|
||||||
prompt_id?: string
|
prompt_id?: string
|
||||||
prompt_content?: string
|
prompt_content?: string,
|
||||||
|
iodSearch?: boolean,
|
||||||
|
webSearch?: boolean,
|
||||||
}) => {
|
}) => {
|
||||||
if (
|
if (
|
||||||
e?.name === "AbortError" ||
|
e?.name === "AbortError" ||
|
||||||
@ -53,66 +63,63 @@ export const saveMessageOnError = async ({
|
|||||||
}
|
}
|
||||||
])
|
])
|
||||||
|
|
||||||
|
const defaultMessage: HistoryMessage = {
|
||||||
|
history_id: historyId,
|
||||||
|
name: selectedModel,
|
||||||
|
role: "assistant",
|
||||||
|
content: botMessage,
|
||||||
|
webSources: [],
|
||||||
|
iodSources: getDefaultIodSources(),
|
||||||
|
messageType: message_type,
|
||||||
|
iodSearch,
|
||||||
|
webSearch,
|
||||||
|
images: []
|
||||||
|
}
|
||||||
|
|
||||||
if (historyId) {
|
if (historyId) {
|
||||||
if (!isRegenerating) {
|
if (!isRegenerating) {
|
||||||
await saveMessage(
|
await saveMessage({
|
||||||
historyId,
|
...JSON.parse(JSON.stringify(defaultMessage)),
|
||||||
selectedModel,
|
role: "user",
|
||||||
"user",
|
content: userMessage,
|
||||||
userMessage,
|
images: [image]
|
||||||
[image],
|
})
|
||||||
[],
|
|
||||||
[],
|
|
||||||
1,
|
|
||||||
message_type
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
await saveMessage(
|
await saveMessage({
|
||||||
historyId,
|
...JSON.parse(JSON.stringify(defaultMessage))
|
||||||
selectedModel,
|
})
|
||||||
"assistant",
|
|
||||||
botMessage,
|
|
||||||
[],
|
|
||||||
[],
|
|
||||||
[],
|
|
||||||
2,
|
|
||||||
message_type
|
|
||||||
)
|
|
||||||
await setLastUsedChatModel(historyId, selectedModel)
|
await setLastUsedChatModel(historyId, selectedModel)
|
||||||
if (prompt_id || prompt_content) {
|
if (prompt_id || prompt_content) {
|
||||||
await setLastUsedChatSystemPrompt(historyId, { prompt_content, prompt_id })
|
await setLastUsedChatSystemPrompt(historyId, {
|
||||||
|
prompt_content,
|
||||||
|
prompt_id
|
||||||
|
})
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
const title = await generateTitle(selectedModel, userMessage, userMessage)
|
const title = await generateTitle(selectedModel, userMessage, userMessage)
|
||||||
const newHistoryId = await saveHistory(title, false, message_source)
|
const newHistoryId = await saveHistory(title, false, message_source)
|
||||||
if (!isRegenerating) {
|
if (!isRegenerating) {
|
||||||
await saveMessage(
|
await saveMessage({
|
||||||
newHistoryId.id,
|
...JSON.parse(JSON.stringify(defaultMessage)),
|
||||||
selectedModel,
|
history_id: newHistoryId.id,
|
||||||
"user",
|
content: userMessage,
|
||||||
userMessage,
|
role: "user",
|
||||||
[image],
|
images: [image]
|
||||||
[],
|
})
|
||||||
[],
|
|
||||||
1,
|
|
||||||
message_type
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
await saveMessage(
|
await saveMessage(
|
||||||
newHistoryId.id,
|
{
|
||||||
selectedModel,
|
...JSON.parse(JSON.stringify(defaultMessage)),
|
||||||
"assistant",
|
history_id: newHistoryId.id,
|
||||||
botMessage,
|
},
|
||||||
[],
|
|
||||||
[],
|
|
||||||
[],
|
|
||||||
2,
|
|
||||||
message_type
|
|
||||||
)
|
)
|
||||||
setHistoryId(newHistoryId.id)
|
setHistoryId(newHistoryId.id)
|
||||||
await setLastUsedChatModel(newHistoryId.id, selectedModel)
|
await setLastUsedChatModel(newHistoryId.id, selectedModel)
|
||||||
if (prompt_id || prompt_content) {
|
if (prompt_id || prompt_content) {
|
||||||
await setLastUsedChatSystemPrompt(newHistoryId.id, { prompt_content, prompt_id })
|
await setLastUsedChatSystemPrompt(newHistoryId.id, {
|
||||||
|
prompt_content,
|
||||||
|
prompt_id
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -130,10 +137,13 @@ export const saveMessageOnSuccess = async ({
|
|||||||
message,
|
message,
|
||||||
image,
|
image,
|
||||||
fullText,
|
fullText,
|
||||||
source,
|
iodSearch,
|
||||||
iodSource,
|
webSearch,
|
||||||
|
webSources,
|
||||||
|
iodSources,
|
||||||
message_source = "web-ui",
|
message_source = "web-ui",
|
||||||
message_type, generationInfo,
|
message_type,
|
||||||
|
generationInfo,
|
||||||
prompt_id,
|
prompt_id,
|
||||||
prompt_content,
|
prompt_content,
|
||||||
reasoning_time_taken = 0
|
reasoning_time_taken = 0
|
||||||
@ -145,81 +155,87 @@ export const saveMessageOnSuccess = async ({
|
|||||||
message: string
|
message: string
|
||||||
image: string
|
image: string
|
||||||
fullText: string
|
fullText: string
|
||||||
source: any[]
|
iodSearch?: boolean
|
||||||
iodSource: any[]
|
webSearch?: boolean
|
||||||
message_source?: "copilot" | "web-ui",
|
webSources: any[]
|
||||||
|
iodSources: AllIodRegistryEntry
|
||||||
|
message_source?: "copilot" | "web-ui"
|
||||||
message_type?: string
|
message_type?: string
|
||||||
generationInfo?: any
|
generationInfo?: any
|
||||||
prompt_id?: string
|
prompt_id?: string
|
||||||
prompt_content?: string
|
prompt_content?: string
|
||||||
reasoning_time_taken?: number
|
reasoning_time_taken?: number
|
||||||
}) => {
|
}) => {
|
||||||
|
var botMessage
|
||||||
|
|
||||||
|
const defaultMessage: HistoryMessage = {
|
||||||
|
history_id: historyId,
|
||||||
|
name: selectedModel,
|
||||||
|
role: "assistant",
|
||||||
|
content: fullText,
|
||||||
|
webSources: webSources,
|
||||||
|
iodSources: iodSources,
|
||||||
|
messageType: message_type,
|
||||||
|
images: [],
|
||||||
|
iodSearch,
|
||||||
|
webSearch,
|
||||||
|
generationInfo,
|
||||||
|
reasoning_time_taken,
|
||||||
|
}
|
||||||
if (historyId) {
|
if (historyId) {
|
||||||
if (!isRegenerate) {
|
if (!isRegenerate) {
|
||||||
await saveMessage(
|
await saveMessage(
|
||||||
historyId,
|
{
|
||||||
selectedModel,
|
...JSON.parse(JSON.stringify(defaultMessage)),
|
||||||
"user",
|
role: "user",
|
||||||
message,
|
content: message,
|
||||||
[image],
|
images: [image],
|
||||||
[],
|
webSources: [],
|
||||||
[],
|
iodSources: getDefaultIodSources(),
|
||||||
1,
|
},
|
||||||
message_type,
|
|
||||||
generationInfo,
|
|
||||||
reasoning_time_taken
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
await saveMessage(
|
botMessage = await saveMessage(
|
||||||
historyId,
|
{
|
||||||
selectedModel!,
|
...JSON.parse(JSON.stringify(defaultMessage)),
|
||||||
"assistant",
|
}
|
||||||
fullText,
|
|
||||||
[],
|
|
||||||
source,
|
|
||||||
iodSource,
|
|
||||||
2,
|
|
||||||
message_type,
|
|
||||||
generationInfo,
|
|
||||||
reasoning_time_taken
|
|
||||||
)
|
)
|
||||||
|
updateDialog(historyId, botMessage)
|
||||||
await setLastUsedChatModel(historyId, selectedModel!)
|
await setLastUsedChatModel(historyId, selectedModel!)
|
||||||
if (prompt_id || prompt_content) {
|
if (prompt_id || prompt_content) {
|
||||||
await setLastUsedChatSystemPrompt(historyId, { prompt_content, prompt_id })
|
await setLastUsedChatSystemPrompt(historyId, {
|
||||||
|
prompt_content,
|
||||||
|
prompt_id
|
||||||
|
})
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
const title = await generateTitle(selectedModel, message, message)
|
const title = await generateTitle(selectedModel, message, message)
|
||||||
const newHistoryId = await saveHistory(title, false, message_source)
|
const newHistoryId = await saveHistory(title, false, message_source)
|
||||||
await saveMessage(
|
await saveMessage(
|
||||||
newHistoryId.id,
|
{
|
||||||
selectedModel,
|
...JSON.parse(JSON.stringify(defaultMessage)),
|
||||||
"user",
|
history_id: newHistoryId.id,
|
||||||
message,
|
role: "user",
|
||||||
[image],
|
content: message,
|
||||||
[],
|
images: [image],
|
||||||
[],
|
webSources: [],
|
||||||
1,
|
iodSources: getDefaultIodSources(),
|
||||||
message_type,
|
},
|
||||||
generationInfo,
|
|
||||||
reasoning_time_taken
|
|
||||||
)
|
)
|
||||||
await saveMessage(
|
botMessage = await saveMessage(
|
||||||
newHistoryId.id,
|
{
|
||||||
selectedModel!,
|
...JSON.parse(JSON.stringify(defaultMessage)),
|
||||||
"assistant",
|
history_id: newHistoryId.id,
|
||||||
fullText,
|
}
|
||||||
[],
|
|
||||||
source,
|
|
||||||
iodSource,
|
|
||||||
2,
|
|
||||||
message_type,
|
|
||||||
generationInfo,
|
|
||||||
reasoning_time_taken
|
|
||||||
)
|
)
|
||||||
|
updateDialog(newHistoryId.id, botMessage)
|
||||||
setHistoryId(newHistoryId.id)
|
setHistoryId(newHistoryId.id)
|
||||||
await setLastUsedChatModel(newHistoryId.id, selectedModel!)
|
await setLastUsedChatModel(newHistoryId.id, selectedModel!)
|
||||||
if (prompt_id || prompt_content) {
|
if (prompt_id || prompt_content) {
|
||||||
await setLastUsedChatSystemPrompt(newHistoryId.id, { prompt_content, prompt_id })
|
await setLastUsedChatSystemPrompt(newHistoryId.id, {
|
||||||
|
prompt_content,
|
||||||
|
prompt_id
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -25,7 +25,9 @@ const useDynamicTextareaSize = (
|
|||||||
// Set max-height and adjust overflow behavior if maxHeight is provided
|
// Set max-height and adjust overflow behavior if maxHeight is provided
|
||||||
currentTextarea.style.maxHeight = `${maxHeight}px`;
|
currentTextarea.style.maxHeight = `${maxHeight}px`;
|
||||||
currentTextarea.style.overflowY = contentHeight > maxHeight ? "scroll" : "hidden";
|
currentTextarea.style.overflowY = contentHeight > maxHeight ? "scroll" : "hidden";
|
||||||
currentTextarea.style.height = `${Math.min(contentHeight, maxHeight)}px`;
|
currentTextarea.style.height = `${Math.min(contentHeight, maxHeight) < 60 ? 60 : Math.min(contentHeight, maxHeight)}px`;
|
||||||
|
currentTextarea.style.fontWeight = "normal";
|
||||||
|
currentTextarea.style.color = "#374151";
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
// Adjust height without max height constraint
|
// Adjust height without max height constraint
|
||||||
|
@ -2,16 +2,15 @@ import React from "react"
|
|||||||
import { cleanUrl } from "~/libs/clean-url"
|
import { cleanUrl } from "~/libs/clean-url"
|
||||||
import {
|
import {
|
||||||
defaultEmbeddingModelForRag,
|
defaultEmbeddingModelForRag,
|
||||||
geWebSearchFollowUpPrompt,
|
|
||||||
getOllamaURL,
|
getOllamaURL,
|
||||||
|
geWebSearchFollowUpPrompt,
|
||||||
promptForRag,
|
promptForRag,
|
||||||
systemPromptForNonRag
|
systemPromptForNonRag
|
||||||
} from "~/services/ollama"
|
} from "~/services/ollama"
|
||||||
import { useStoreMessageOption, type Message } from "~/store/option"
|
import { useStoreMessageOption } from "~/store/option"
|
||||||
import { useStoreMessage } from "~/store"
|
import { useStoreMessage } from "~/store"
|
||||||
import { SystemMessage } from "@langchain/core/messages"
|
import { SystemMessage } from "@langchain/core/messages"
|
||||||
import { getDataFromCurrentTab } from "~/libs/get-html"
|
import { getDataFromCurrentTab } from "~/libs/get-html"
|
||||||
import { MemoryVectorStore } from "langchain/vectorstores/memory"
|
|
||||||
import { memoryEmbedding } from "@/utils/memory-embeddings"
|
import { memoryEmbedding } from "@/utils/memory-embeddings"
|
||||||
import { ChatHistory } from "@/store/option"
|
import { ChatHistory } from "@/store/option"
|
||||||
import {
|
import {
|
||||||
@ -42,6 +41,9 @@ import {
|
|||||||
mergeReasoningContent,
|
mergeReasoningContent,
|
||||||
removeReasoning
|
removeReasoning
|
||||||
} from "@/libs/reasoning"
|
} from "@/libs/reasoning"
|
||||||
|
import { AllIodRegistryEntry } from "@/types/iod.ts"
|
||||||
|
import { getDefaultIodSources } from "@/libs/iod.ts"
|
||||||
|
import { Message } from "@/types/message.ts"
|
||||||
|
|
||||||
export const useMessage = () => {
|
export const useMessage = () => {
|
||||||
const {
|
const {
|
||||||
@ -59,6 +61,8 @@ export const useMessage = () => {
|
|||||||
setIsSearchingInternet,
|
setIsSearchingInternet,
|
||||||
webSearch,
|
webSearch,
|
||||||
setWebSearch,
|
setWebSearch,
|
||||||
|
iodSearch,
|
||||||
|
setIodSearch,
|
||||||
isSearchingInternet
|
isSearchingInternet
|
||||||
} = useStoreMessageOption()
|
} = useStoreMessageOption()
|
||||||
const [defaultInternetSearchOn] = useStorage("defaultInternetSearchOn", false)
|
const [defaultInternetSearchOn] = useStorage("defaultInternetSearchOn", false)
|
||||||
@ -185,16 +189,16 @@ export const useMessage = () => {
|
|||||||
isBot: false,
|
isBot: false,
|
||||||
name: "You",
|
name: "You",
|
||||||
message,
|
message,
|
||||||
sources: [],
|
webSources: [],
|
||||||
iodSources:[],
|
iodSources: getDefaultIodSources(),
|
||||||
images: []
|
images: []
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
isBot: true,
|
isBot: true,
|
||||||
name: selectedModel,
|
name: selectedModel,
|
||||||
message: "▋",
|
message: "",
|
||||||
sources: [],
|
webSources: [],
|
||||||
iodSources:[],
|
iodSources: getDefaultIodSources(),
|
||||||
id: generateMessageId
|
id: generateMessageId
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@ -205,8 +209,8 @@ export const useMessage = () => {
|
|||||||
isBot: true,
|
isBot: true,
|
||||||
name: selectedModel,
|
name: selectedModel,
|
||||||
message: "▋",
|
message: "▋",
|
||||||
sources: [],
|
webSources: [],
|
||||||
iodSources:[],
|
iodSources: getDefaultIodSources(),
|
||||||
id: generateMessageId
|
id: generateMessageId
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@ -240,6 +244,7 @@ export const useMessage = () => {
|
|||||||
}
|
}
|
||||||
isAlreadyExistEmbedding = keepTrackOfEmbedding[websiteUrl]
|
isAlreadyExistEmbedding = keepTrackOfEmbedding[websiteUrl]
|
||||||
}
|
}
|
||||||
|
|
||||||
setMessages(newMessage)
|
setMessages(newMessage)
|
||||||
const ollamaUrl = await getOllamaURL()
|
const ollamaUrl = await getOllamaURL()
|
||||||
const embeddingModle = await defaultEmbeddingModelForRag()
|
const embeddingModle = await defaultEmbeddingModelForRag()
|
||||||
@ -337,7 +342,7 @@ export const useMessage = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let context: string = ""
|
let context: string = ""
|
||||||
let source: {
|
let webSources: {
|
||||||
name: any
|
name: any
|
||||||
type: any
|
type: any
|
||||||
mode: string
|
mode: string
|
||||||
@ -345,11 +350,13 @@ export const useMessage = () => {
|
|||||||
pageContent: string
|
pageContent: string
|
||||||
metadata: Record<string, any>
|
metadata: Record<string, any>
|
||||||
}[] = []
|
}[] = []
|
||||||
|
// TODO: update type
|
||||||
|
let iodSources: AllIodRegistryEntry = getDefaultIodSources()
|
||||||
|
|
||||||
if (chatWithWebsiteEmbedding) {
|
if (chatWithWebsiteEmbedding) {
|
||||||
const docs = await vectorstore.similaritySearch(query, 4)
|
const docs = await vectorstore.similaritySearch(query, 4)
|
||||||
context = formatDocs(docs)
|
context = formatDocs(docs)
|
||||||
source = docs.map((doc) => {
|
webSources = docs.map((doc) => {
|
||||||
return {
|
return {
|
||||||
...doc,
|
...doc,
|
||||||
name: doc?.metadata?.source || "untitled",
|
name: doc?.metadata?.source || "untitled",
|
||||||
@ -368,7 +375,7 @@ export const useMessage = () => {
|
|||||||
.slice(0, maxWebsiteContext)
|
.slice(0, maxWebsiteContext)
|
||||||
}
|
}
|
||||||
|
|
||||||
source = [
|
webSources = [
|
||||||
{
|
{
|
||||||
name: embedURL,
|
name: embedURL,
|
||||||
type: type,
|
type: type,
|
||||||
@ -479,7 +486,8 @@ export const useMessage = () => {
|
|||||||
return {
|
return {
|
||||||
...message,
|
...message,
|
||||||
message: fullText,
|
message: fullText,
|
||||||
sources: source,
|
webSources,
|
||||||
|
iodSources,
|
||||||
generationInfo,
|
generationInfo,
|
||||||
reasoning_time_taken: timetaken
|
reasoning_time_taken: timetaken
|
||||||
}
|
}
|
||||||
@ -500,7 +508,7 @@ export const useMessage = () => {
|
|||||||
content: fullText
|
content: fullText
|
||||||
}
|
}
|
||||||
])
|
])
|
||||||
const iodSource = []
|
debugger
|
||||||
await saveMessageOnSuccess({
|
await saveMessageOnSuccess({
|
||||||
historyId,
|
historyId,
|
||||||
setHistoryId,
|
setHistoryId,
|
||||||
@ -509,8 +517,8 @@ export const useMessage = () => {
|
|||||||
message,
|
message,
|
||||||
image,
|
image,
|
||||||
fullText,
|
fullText,
|
||||||
source,
|
webSources,
|
||||||
iodSource,
|
iodSources,
|
||||||
message_source: "copilot",
|
message_source: "copilot",
|
||||||
generationInfo,
|
generationInfo,
|
||||||
reasoning_time_taken: timetaken
|
reasoning_time_taken: timetaken
|
||||||
@ -610,16 +618,16 @@ export const useMessage = () => {
|
|||||||
isBot: false,
|
isBot: false,
|
||||||
name: "You",
|
name: "You",
|
||||||
message,
|
message,
|
||||||
sources: [],
|
webSources: [],
|
||||||
iodSources:[],
|
iodSources: getDefaultIodSources(),
|
||||||
images: []
|
images: []
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
isBot: true,
|
isBot: true,
|
||||||
name: selectedModel,
|
name: selectedModel,
|
||||||
message: "▋",
|
message: "▋",
|
||||||
sources: [],
|
webSources: [],
|
||||||
iodSources: [],
|
iodSources: getDefaultIodSources(),
|
||||||
id: generateMessageId
|
id: generateMessageId
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@ -630,8 +638,8 @@ export const useMessage = () => {
|
|||||||
isBot: true,
|
isBot: true,
|
||||||
name: selectedModel,
|
name: selectedModel,
|
||||||
message: "▋",
|
message: "▋",
|
||||||
sources: [],
|
webSources: [],
|
||||||
iodSources: [],
|
iodSources: getDefaultIodSources(),
|
||||||
id: generateMessageId
|
id: generateMessageId
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@ -794,8 +802,8 @@ export const useMessage = () => {
|
|||||||
message,
|
message,
|
||||||
image,
|
image,
|
||||||
fullText,
|
fullText,
|
||||||
source: [],
|
webSources: [],
|
||||||
iodSource:[],
|
iodSources: getDefaultIodSources(),
|
||||||
message_source: "copilot",
|
message_source: "copilot",
|
||||||
generationInfo,
|
generationInfo,
|
||||||
reasoning_time_taken: timetaken
|
reasoning_time_taken: timetaken
|
||||||
@ -899,16 +907,16 @@ export const useMessage = () => {
|
|||||||
isBot: false,
|
isBot: false,
|
||||||
name: "You",
|
name: "You",
|
||||||
message,
|
message,
|
||||||
sources: [],
|
webSources: [],
|
||||||
iodSources: [],
|
iodSources: getDefaultIodSources(),
|
||||||
images: [image]
|
images: [image]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
isBot: true,
|
isBot: true,
|
||||||
name: selectedModel,
|
name: selectedModel,
|
||||||
message: "▋",
|
message: "▋",
|
||||||
sources: [],
|
webSources: [],
|
||||||
iodSources: [],
|
iodSources: getDefaultIodSources(),
|
||||||
id: generateMessageId
|
id: generateMessageId
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@ -919,8 +927,8 @@ export const useMessage = () => {
|
|||||||
isBot: true,
|
isBot: true,
|
||||||
name: selectedModel,
|
name: selectedModel,
|
||||||
message: "▋",
|
message: "▋",
|
||||||
sources: [],
|
webSources: [],
|
||||||
iodSources: [],
|
iodSources: getDefaultIodSources(),
|
||||||
id: generateMessageId
|
id: generateMessageId
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@ -1088,8 +1096,8 @@ export const useMessage = () => {
|
|||||||
message,
|
message,
|
||||||
image,
|
image,
|
||||||
fullText,
|
fullText,
|
||||||
source: [],
|
webSources: [],
|
||||||
iodSource:[],
|
iodSources: getDefaultIodSources(),
|
||||||
message_source: "copilot",
|
message_source: "copilot",
|
||||||
generationInfo,
|
generationInfo,
|
||||||
reasoning_time_taken: timetaken
|
reasoning_time_taken: timetaken
|
||||||
@ -1126,6 +1134,8 @@ export const useMessage = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const searchChatMode = async (
|
const searchChatMode = async (
|
||||||
|
webSearch: boolean,
|
||||||
|
iodSearch,
|
||||||
message: string,
|
message: string,
|
||||||
image: string,
|
image: string,
|
||||||
isRegenerate: boolean,
|
isRegenerate: boolean,
|
||||||
@ -1188,16 +1198,16 @@ export const useMessage = () => {
|
|||||||
isBot: false,
|
isBot: false,
|
||||||
name: "You",
|
name: "You",
|
||||||
message,
|
message,
|
||||||
sources: [],
|
webSources: [],
|
||||||
iodSources: [],
|
iodSources: getDefaultIodSources(),
|
||||||
images: [image]
|
images: [image]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
isBot: true,
|
isBot: true,
|
||||||
name: selectedModel,
|
name: selectedModel,
|
||||||
message: "▋",
|
message: "▋",
|
||||||
sources: [],
|
webSources: [],
|
||||||
iodSources: [],
|
iodSources: getDefaultIodSources(),
|
||||||
id: generateMessageId
|
id: generateMessageId
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@ -1208,8 +1218,8 @@ export const useMessage = () => {
|
|||||||
isBot: true,
|
isBot: true,
|
||||||
name: selectedModel,
|
name: selectedModel,
|
||||||
message: "▋",
|
message: "▋",
|
||||||
sources: [],
|
webSources: [],
|
||||||
iodSources: [],
|
iodSources: getDefaultIodSources(),
|
||||||
id: generateMessageId
|
id: generateMessageId
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@ -1286,10 +1296,14 @@ export const useMessage = () => {
|
|||||||
query = removeReasoning(query)
|
query = removeReasoning(query)
|
||||||
}
|
}
|
||||||
|
|
||||||
const { prompt, source, iodSource } = await getSystemPromptForWeb(query, selectedQuickPrompt)
|
const { prompt, webSources, iodSources } = await getSystemPromptForWeb(
|
||||||
|
query,
|
||||||
|
[],
|
||||||
|
webSearch,
|
||||||
|
iodSearch
|
||||||
|
)
|
||||||
setIsSearchingInternet(false)
|
setIsSearchingInternet(false)
|
||||||
console.log("iodSource:")
|
|
||||||
console.log(iodSource)
|
|
||||||
// message = message.trim().replaceAll("\n", " ")
|
// message = message.trim().replaceAll("\n", " ")
|
||||||
|
|
||||||
let humanMessage = await humanMessageFormatter({
|
let humanMessage = await humanMessageFormatter({
|
||||||
@ -1410,8 +1424,8 @@ export const useMessage = () => {
|
|||||||
return {
|
return {
|
||||||
...message,
|
...message,
|
||||||
message: fullText,
|
message: fullText,
|
||||||
sources: source,
|
webSources,
|
||||||
iodSources: iodSource,
|
iodSources,
|
||||||
generationInfo,
|
generationInfo,
|
||||||
reasoning_time_taken: timetaken
|
reasoning_time_taken: timetaken
|
||||||
}
|
}
|
||||||
@ -1441,8 +1455,8 @@ export const useMessage = () => {
|
|||||||
message,
|
message,
|
||||||
image,
|
image,
|
||||||
fullText,
|
fullText,
|
||||||
source,
|
webSources,
|
||||||
iodSource,
|
iodSources,
|
||||||
generationInfo,
|
generationInfo,
|
||||||
reasoning_time_taken: timetaken
|
reasoning_time_taken: timetaken
|
||||||
})
|
})
|
||||||
@ -1541,8 +1555,8 @@ export const useMessage = () => {
|
|||||||
isBot: false,
|
isBot: false,
|
||||||
name: "You",
|
name: "You",
|
||||||
message,
|
message,
|
||||||
sources: [],
|
webSources: [],
|
||||||
iodSources: [],
|
iodSources: getDefaultIodSources(),
|
||||||
images: [image],
|
images: [image],
|
||||||
messageType: messageType
|
messageType: messageType
|
||||||
},
|
},
|
||||||
@ -1550,8 +1564,8 @@ export const useMessage = () => {
|
|||||||
isBot: true,
|
isBot: true,
|
||||||
name: selectedModel,
|
name: selectedModel,
|
||||||
message: "▋",
|
message: "▋",
|
||||||
sources: [],
|
webSources: [],
|
||||||
iodSources: [],
|
iodSources: getDefaultIodSources(),
|
||||||
id: generateMessageId
|
id: generateMessageId
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@ -1562,8 +1576,8 @@ export const useMessage = () => {
|
|||||||
isBot: true,
|
isBot: true,
|
||||||
name: selectedModel,
|
name: selectedModel,
|
||||||
message: "▋",
|
message: "▋",
|
||||||
sources: [],
|
webSources: [],
|
||||||
iodSources: [],
|
iodSources: getDefaultIodSources(),
|
||||||
id: generateMessageId
|
id: generateMessageId
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@ -1709,8 +1723,8 @@ export const useMessage = () => {
|
|||||||
message,
|
message,
|
||||||
image,
|
image,
|
||||||
fullText,
|
fullText,
|
||||||
source: [],
|
webSources: [],
|
||||||
iodSource:[],
|
iodSources: getDefaultIodSources(),
|
||||||
message_source: "copilot",
|
message_source: "copilot",
|
||||||
message_type: messageType,
|
message_type: messageType,
|
||||||
generationInfo,
|
generationInfo,
|
||||||
@ -1788,8 +1802,10 @@ export const useMessage = () => {
|
|||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
if (chatMode === "normal") {
|
if (chatMode === "normal") {
|
||||||
if (webSearch) {
|
if (webSearch || iodSearch) {
|
||||||
await searchChatMode(
|
await searchChatMode(
|
||||||
|
webSearch,
|
||||||
|
iodSearch,
|
||||||
message,
|
message,
|
||||||
image,
|
image,
|
||||||
isRegenerate || false,
|
isRegenerate || false,
|
||||||
@ -1928,6 +1944,8 @@ export const useMessage = () => {
|
|||||||
regenerateLastMessage,
|
regenerateLastMessage,
|
||||||
webSearch,
|
webSearch,
|
||||||
setWebSearch,
|
setWebSearch,
|
||||||
|
iodSearch,
|
||||||
|
setIodSearch,
|
||||||
isSearchingInternet,
|
isSearchingInternet,
|
||||||
selectedQuickPrompt,
|
selectedQuickPrompt,
|
||||||
setSelectedQuickPrompt,
|
setSelectedQuickPrompt,
|
||||||
|
@ -2,14 +2,14 @@ import React from "react"
|
|||||||
import { cleanUrl } from "~/libs/clean-url"
|
import { cleanUrl } from "~/libs/clean-url"
|
||||||
import {
|
import {
|
||||||
defaultEmbeddingModelForRag,
|
defaultEmbeddingModelForRag,
|
||||||
geWebSearchFollowUpPrompt,
|
|
||||||
getOllamaURL,
|
getOllamaURL,
|
||||||
|
geWebSearchFollowUpPrompt,
|
||||||
promptForRag,
|
promptForRag,
|
||||||
systemPromptForNonRagOption
|
systemPromptForNonRagOption
|
||||||
} from "~/services/ollama"
|
} from "~/services/ollama"
|
||||||
import { type ChatHistory, type Message } from "~/store/option"
|
import type { ChatHistory, MeteringEntry } from "~/store/option"
|
||||||
import { SystemMessage } from "@langchain/core/messages"
|
|
||||||
import { useStoreMessageOption } from "~/store/option"
|
import { useStoreMessageOption } from "~/store/option"
|
||||||
|
import { SystemMessage } from "@langchain/core/messages"
|
||||||
import {
|
import {
|
||||||
deleteChatForEdit,
|
deleteChatForEdit,
|
||||||
generateID,
|
generateID,
|
||||||
@ -20,6 +20,8 @@ import {
|
|||||||
import { useNavigate } from "react-router-dom"
|
import { useNavigate } from "react-router-dom"
|
||||||
import { notification } from "antd"
|
import { notification } from "antd"
|
||||||
import { getSystemPromptForWeb } from "~/web/web"
|
import { getSystemPromptForWeb } from "~/web/web"
|
||||||
|
import { tokenizeInput } from "~/web/iod"
|
||||||
|
|
||||||
import { generateHistory } from "@/utils/generate-history"
|
import { generateHistory } from "@/utils/generate-history"
|
||||||
import { useTranslation } from "react-i18next"
|
import { useTranslation } from "react-i18next"
|
||||||
import {
|
import {
|
||||||
@ -37,23 +39,34 @@ import { pageAssistModel } from "@/models"
|
|||||||
import { getNoOfRetrievedDocs } from "@/services/app"
|
import { getNoOfRetrievedDocs } from "@/services/app"
|
||||||
import { humanMessageFormatter } from "@/utils/human-message"
|
import { humanMessageFormatter } from "@/utils/human-message"
|
||||||
import { pageAssistEmbeddingModel } from "@/models/embedding"
|
import { pageAssistEmbeddingModel } from "@/models/embedding"
|
||||||
|
|
||||||
import {
|
import {
|
||||||
isReasoningEnded,
|
isReasoningEnded,
|
||||||
isReasoningStarted,
|
isReasoningStarted,
|
||||||
mergeReasoningContent,
|
mergeReasoningContent,
|
||||||
removeReasoning
|
removeReasoning
|
||||||
} from "@/libs/reasoning"
|
} from "@/libs/reasoning"
|
||||||
|
import { getDefaultIodSources } from "@/libs/iod.ts"
|
||||||
|
import type { Message } from "@/types/message.ts"
|
||||||
|
|
||||||
export const useMessageOption = () => {
|
export const useMessageOption = () => {
|
||||||
const {
|
const {
|
||||||
controller: abortController,
|
controller: abortController,
|
||||||
setController: setAbortController,
|
setController: setAbortController,
|
||||||
|
iodLoading,
|
||||||
|
setIodLoading,
|
||||||
|
currentMessageId,
|
||||||
|
setCurrentMessageId,
|
||||||
messages,
|
messages,
|
||||||
setMessages
|
setMessages,
|
||||||
} = usePageAssist()
|
} = usePageAssist()
|
||||||
|
|
||||||
const {
|
const {
|
||||||
history,
|
history,
|
||||||
setHistory,
|
setHistory,
|
||||||
|
meteringEntries,
|
||||||
|
setMeteringEntries,
|
||||||
|
setCurrentMeteringEntry,
|
||||||
setStreaming,
|
setStreaming,
|
||||||
streaming,
|
streaming,
|
||||||
setIsFirstMessage,
|
setIsFirstMessage,
|
||||||
@ -67,6 +80,8 @@ export const useMessageOption = () => {
|
|||||||
setChatMode,
|
setChatMode,
|
||||||
webSearch,
|
webSearch,
|
||||||
setWebSearch,
|
setWebSearch,
|
||||||
|
iodSearch,
|
||||||
|
setIodSearch,
|
||||||
isSearchingInternet,
|
isSearchingInternet,
|
||||||
setIsSearchingInternet,
|
setIsSearchingInternet,
|
||||||
selectedQuickPrompt,
|
selectedQuickPrompt,
|
||||||
@ -104,13 +119,38 @@ export const useMessageOption = () => {
|
|||||||
setIsProcessing(false)
|
setIsProcessing(false)
|
||||||
setStreaming(false)
|
setStreaming(false)
|
||||||
currentChatModelSettings.reset()
|
currentChatModelSettings.reset()
|
||||||
|
setIodLoading(false)
|
||||||
|
setCurrentMessageId("")
|
||||||
textareaRef?.current?.focus()
|
textareaRef?.current?.focus()
|
||||||
if (defaultInternetSearchOn) {
|
if (defaultInternetSearchOn) {
|
||||||
setWebSearch(true)
|
setWebSearch(true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 从最后的结果中解析出 思维链 (Chain-of-Thought) 和 结果
|
||||||
|
const responseResolver = (msg: string) => {
|
||||||
|
const cotStart = msg.indexOf("<think>")
|
||||||
|
const cotEnd = msg.indexOf("</think>")
|
||||||
|
let cot = ""
|
||||||
|
let content = ""
|
||||||
|
if (cotStart > -1 && cotEnd > -1) {
|
||||||
|
cot = msg.substring(cotStart + 7, cotEnd)
|
||||||
|
content = msg.substring(cotEnd + 8)
|
||||||
|
} else {
|
||||||
|
content = msg
|
||||||
|
}
|
||||||
|
// 去掉换行符
|
||||||
|
cot = cot.replace(/\n/g, "")
|
||||||
|
content = content.replace(/\n/g, "")
|
||||||
|
return {
|
||||||
|
cot: cot,
|
||||||
|
content
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const searchChatMode = async (
|
const searchChatMode = async (
|
||||||
|
webSearch: boolean,
|
||||||
|
iodSearch: boolean,
|
||||||
message: string,
|
message: string,
|
||||||
image: string,
|
image: string,
|
||||||
isRegenerate: boolean,
|
isRegenerate: boolean,
|
||||||
@ -161,40 +201,53 @@ export const useMessageOption = () => {
|
|||||||
useMlock:
|
useMlock:
|
||||||
currentChatModelSettings?.useMlock ?? userDefaultModelSettings?.useMlock
|
currentChatModelSettings?.useMlock ?? userDefaultModelSettings?.useMlock
|
||||||
})
|
})
|
||||||
|
|
||||||
let newMessage: Message[] = []
|
let newMessage: Message[] = []
|
||||||
let generateMessageId = generateID()
|
let generateMessageId = generateID()
|
||||||
|
setCurrentMessageId(generateMessageId)
|
||||||
|
const meter: MeteringEntry = {
|
||||||
|
id: generateMessageId,
|
||||||
|
queryContent: message,
|
||||||
|
date: new Date().getTime()
|
||||||
|
} as MeteringEntry
|
||||||
|
|
||||||
|
setCurrentMeteringEntry({
|
||||||
|
loading: true,
|
||||||
|
data: meter
|
||||||
|
})
|
||||||
|
|
||||||
|
let defaultMessage: Message = {
|
||||||
|
isBot: true,
|
||||||
|
name: selectedModel,
|
||||||
|
message,
|
||||||
|
iodSearch,
|
||||||
|
webSearch,
|
||||||
|
webSources: [],
|
||||||
|
iodSources: getDefaultIodSources(),
|
||||||
|
images: [image]
|
||||||
|
}
|
||||||
|
|
||||||
if (!isRegenerate) {
|
if (!isRegenerate) {
|
||||||
newMessage = [
|
newMessage = [
|
||||||
...messages,
|
...messages,
|
||||||
{
|
{
|
||||||
|
...JSON.parse(JSON.stringify(defaultMessage)),
|
||||||
|
id: generateID(),
|
||||||
isBot: false,
|
isBot: false,
|
||||||
name: "You",
|
name: "You",
|
||||||
message,
|
|
||||||
sources: [],
|
|
||||||
iodSources: [],
|
|
||||||
images: [image]
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
isBot: true,
|
...JSON.parse(JSON.stringify(defaultMessage)),
|
||||||
name: selectedModel,
|
id: generateMessageId,
|
||||||
message: "▋",
|
message: "",
|
||||||
sources: [],
|
|
||||||
iodSources: [],
|
|
||||||
id: generateMessageId
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
} else {
|
} else {
|
||||||
newMessage = [
|
newMessage = [
|
||||||
...messages,
|
...messages,
|
||||||
{
|
{
|
||||||
isBot: true,
|
...JSON.parse(JSON.stringify(defaultMessage)),
|
||||||
name: selectedModel,
|
id: generateMessageId,
|
||||||
message: "▋",
|
message: " ",
|
||||||
sources: [],
|
|
||||||
iodSources: [],
|
|
||||||
id: generateMessageId
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@ -207,7 +260,8 @@ export const useMessageOption = () => {
|
|||||||
setIsSearchingInternet(true)
|
setIsSearchingInternet(true)
|
||||||
|
|
||||||
let query = message
|
let query = message
|
||||||
/*
|
let keywords: string[] = []
|
||||||
|
|
||||||
if (newMessage.length > 2) {
|
if (newMessage.length > 2) {
|
||||||
let questionPrompt = await geWebSearchFollowUpPrompt()
|
let questionPrompt = await geWebSearchFollowUpPrompt()
|
||||||
const lastTenMessages = newMessage.slice(-10)
|
const lastTenMessages = newMessage.slice(-10)
|
||||||
@ -270,17 +324,60 @@ export const useMessageOption = () => {
|
|||||||
query = response.content.toString()
|
query = response.content.toString()
|
||||||
query = removeReasoning(query)
|
query = removeReasoning(query)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Currently only IoD search use keywords
|
||||||
|
if (iodSearch) {
|
||||||
|
// Extract keywords
|
||||||
|
console.log(
|
||||||
|
"query:" + query + " --> " + JSON.stringify(tokenizeInput(query))
|
||||||
|
)
|
||||||
|
keywords = tokenizeInput(query)
|
||||||
|
/*
|
||||||
|
const questionPrompt = await geWebSearchKeywordsPrompt()
|
||||||
|
const promptForQuestion = questionPrompt.replaceAll("{query}", query)
|
||||||
|
const response = await ollama.invoke(promptForQuestion)
|
||||||
|
let res = response.content.toString()
|
||||||
|
res = removeReasoning(res)
|
||||||
|
keywords = res
|
||||||
|
.replace(/^Keywords:/i, "")
|
||||||
|
.replace(/^关键词:/i, "")
|
||||||
|
.replace(/^:/i, "")
|
||||||
|
.replace(/^:/i, "")
|
||||||
|
.replaceAll(" ", "")
|
||||||
|
.split(",")
|
||||||
|
.map((k) => k.trim())
|
||||||
*/
|
*/
|
||||||
const quickPrompt = selectedQuickPrompt;
|
}
|
||||||
console.log("quick prompt:"+quickPrompt)
|
|
||||||
const { prompt, source, iodSource } = await getSystemPromptForWeb(query, quickPrompt)
|
const {
|
||||||
|
prompt,
|
||||||
|
webSources,
|
||||||
|
iodSources,
|
||||||
|
iodSearchResults: iodData,
|
||||||
|
iodTokenCount
|
||||||
|
} = await getSystemPromptForWeb(query, keywords, webSearch, iodSearch)
|
||||||
|
setIodLoading(false)
|
||||||
|
console.log("prompt:\n" + prompt)
|
||||||
setIsSearchingInternet(false)
|
setIsSearchingInternet(false)
|
||||||
console.log("iodSource from useMessageOption:")
|
meter.prompt = prompt
|
||||||
console.log(iodSource)
|
meter.iodKeywords = keywords
|
||||||
console.log("prompt")
|
meter.iodData = Object.values(iodData).flat()
|
||||||
console.log(prompt)
|
meter.iodTokenCount = iodTokenCount
|
||||||
console.log("query")
|
|
||||||
console.log(query)
|
|
||||||
|
setMessages((prev) => {
|
||||||
|
return prev.map((message) => {
|
||||||
|
if (message.id === generateMessageId) {
|
||||||
|
return {
|
||||||
|
...message,
|
||||||
|
webSources,
|
||||||
|
iodSources,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return message
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
// message = message.trim().replaceAll("\n", " ")
|
// message = message.trim().replaceAll("\n", " ")
|
||||||
|
|
||||||
let humanMessage = await humanMessageFormatter({
|
let humanMessage = await humanMessageFormatter({
|
||||||
@ -340,6 +437,7 @@ export const useMessageOption = () => {
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
let count = 0
|
let count = 0
|
||||||
|
const chatStartTime = new Date()
|
||||||
let reasoningStartTime: Date | undefined = undefined
|
let reasoningStartTime: Date | undefined = undefined
|
||||||
let reasoningEndTime: Date | undefined = undefined
|
let reasoningEndTime: Date | undefined = undefined
|
||||||
let apiReasoning = false
|
let apiReasoning = false
|
||||||
@ -400,8 +498,8 @@ export const useMessageOption = () => {
|
|||||||
return {
|
return {
|
||||||
...message,
|
...message,
|
||||||
message: fullText,
|
message: fullText,
|
||||||
sources: source,
|
webSources,
|
||||||
iodSources:iodSource,
|
iodSources,
|
||||||
generationInfo,
|
generationInfo,
|
||||||
reasoning_time_taken: timetaken
|
reasoning_time_taken: timetaken
|
||||||
}
|
}
|
||||||
@ -431,14 +529,38 @@ export const useMessageOption = () => {
|
|||||||
message,
|
message,
|
||||||
image,
|
image,
|
||||||
fullText,
|
fullText,
|
||||||
source,
|
iodSearch,
|
||||||
iodSource,
|
webSearch,
|
||||||
|
webSources,
|
||||||
|
iodSources,
|
||||||
generationInfo,
|
generationInfo,
|
||||||
reasoning_time_taken: timetaken
|
reasoning_time_taken: timetaken
|
||||||
})
|
})
|
||||||
|
|
||||||
setIsProcessing(false)
|
setIsProcessing(false)
|
||||||
setStreaming(false)
|
setStreaming(false)
|
||||||
|
|
||||||
|
// Save metering entry
|
||||||
|
const { cot, content } = responseResolver(fullText)
|
||||||
|
const currentMeteringEntry = {
|
||||||
|
...meter,
|
||||||
|
modelInputTokenCount: prompt.length,
|
||||||
|
modelOutputTokenCount: fullText.length,
|
||||||
|
model: ollama.modelName ?? ollama.model,
|
||||||
|
relatedDataCount: Object.values(iodData).flat()?.length ?? 0,
|
||||||
|
timeTaken: new Date().getTime() - chatStartTime.getTime(),
|
||||||
|
date: chatStartTime.getTime(),
|
||||||
|
cot,
|
||||||
|
responseContent: content,
|
||||||
|
modelResponseContent: fullText
|
||||||
|
}
|
||||||
|
const _meteringEntries = [currentMeteringEntry, ...meteringEntries]
|
||||||
|
setCurrentMeteringEntry({
|
||||||
|
loading: false,
|
||||||
|
data: currentMeteringEntry
|
||||||
|
})
|
||||||
|
setMeteringEntries(_meteringEntries)
|
||||||
|
localStorage.setItem("meteringEntries", JSON.stringify(_meteringEntries))
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
const errorSave = await saveMessageOnError({
|
const errorSave = await saveMessageOnError({
|
||||||
e,
|
e,
|
||||||
@ -450,7 +572,9 @@ export const useMessageOption = () => {
|
|||||||
setHistory,
|
setHistory,
|
||||||
setHistoryId,
|
setHistoryId,
|
||||||
userMessage: message,
|
userMessage: message,
|
||||||
isRegenerating: isRegenerate
|
isRegenerating: isRegenerate,
|
||||||
|
iodSearch,
|
||||||
|
webSearch,
|
||||||
})
|
})
|
||||||
|
|
||||||
if (!errorSave) {
|
if (!errorSave) {
|
||||||
@ -556,7 +680,17 @@ export const useMessageOption = () => {
|
|||||||
|
|
||||||
let newMessage: Message[] = []
|
let newMessage: Message[] = []
|
||||||
let generateMessageId = generateID()
|
let generateMessageId = generateID()
|
||||||
|
setCurrentMessageId(generateMessageId)
|
||||||
|
const meter: MeteringEntry = {
|
||||||
|
id: generateMessageId,
|
||||||
|
queryContent: message,
|
||||||
|
date: new Date().getTime()
|
||||||
|
} as MeteringEntry
|
||||||
|
|
||||||
|
setCurrentMeteringEntry({
|
||||||
|
loading: true,
|
||||||
|
data: meter
|
||||||
|
})
|
||||||
if (!isRegenerate) {
|
if (!isRegenerate) {
|
||||||
newMessage = [
|
newMessage = [
|
||||||
...messages,
|
...messages,
|
||||||
@ -564,16 +698,17 @@ export const useMessageOption = () => {
|
|||||||
isBot: false,
|
isBot: false,
|
||||||
name: "You",
|
name: "You",
|
||||||
message,
|
message,
|
||||||
sources: [],
|
id: generateID(),
|
||||||
iodSources: [],
|
webSources: [],
|
||||||
|
iodSources: getDefaultIodSources(),
|
||||||
images: [image]
|
images: [image]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
isBot: true,
|
isBot: true,
|
||||||
name: selectedModel,
|
name: selectedModel,
|
||||||
message: "▋",
|
message: "▋",
|
||||||
sources: [],
|
webSources: [],
|
||||||
iodSources: [],
|
iodSources: getDefaultIodSources(),
|
||||||
id: generateMessageId
|
id: generateMessageId
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@ -584,8 +719,8 @@ export const useMessageOption = () => {
|
|||||||
isBot: true,
|
isBot: true,
|
||||||
name: selectedModel,
|
name: selectedModel,
|
||||||
message: "▋",
|
message: "▋",
|
||||||
sources: [],
|
webSources: [],
|
||||||
iodSources: [],
|
iodSources: getDefaultIodSources(),
|
||||||
id: generateMessageId
|
id: generateMessageId
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@ -682,6 +817,7 @@ export const useMessageOption = () => {
|
|||||||
let reasoningStartTime: Date | null = null
|
let reasoningStartTime: Date | null = null
|
||||||
let reasoningEndTime: Date | null = null
|
let reasoningEndTime: Date | null = null
|
||||||
let apiReasoning: boolean = false
|
let apiReasoning: boolean = false
|
||||||
|
const chatStartTime = new Date()
|
||||||
|
|
||||||
for await (const chunk of chunks) {
|
for await (const chunk of chunks) {
|
||||||
if (chunk?.additional_kwargs?.reasoning_content) {
|
if (chunk?.additional_kwargs?.reasoning_content) {
|
||||||
@ -762,7 +898,6 @@ export const useMessageOption = () => {
|
|||||||
content: fullText
|
content: fullText
|
||||||
}
|
}
|
||||||
])
|
])
|
||||||
|
|
||||||
await saveMessageOnSuccess({
|
await saveMessageOnSuccess({
|
||||||
historyId,
|
historyId,
|
||||||
setHistoryId,
|
setHistoryId,
|
||||||
@ -771,8 +906,9 @@ export const useMessageOption = () => {
|
|||||||
message,
|
message,
|
||||||
image,
|
image,
|
||||||
fullText,
|
fullText,
|
||||||
|
iodSearch,
|
||||||
|
webSearch,
|
||||||
source: [],
|
source: [],
|
||||||
iodSource:[],
|
|
||||||
generationInfo,
|
generationInfo,
|
||||||
prompt_content: promptContent,
|
prompt_content: promptContent,
|
||||||
prompt_id: promptId,
|
prompt_id: promptId,
|
||||||
@ -783,6 +919,27 @@ export const useMessageOption = () => {
|
|||||||
setStreaming(false)
|
setStreaming(false)
|
||||||
setIsProcessing(false)
|
setIsProcessing(false)
|
||||||
setStreaming(false)
|
setStreaming(false)
|
||||||
|
|
||||||
|
// Save metering entry
|
||||||
|
const { cot, content } = responseResolver(fullText)
|
||||||
|
const currentMeteringEntry = {
|
||||||
|
...meter,
|
||||||
|
modelInputTokenCount: prompt? prompt.length : 0,
|
||||||
|
modelOutputTokenCount: fullText? fullText.length : 0,
|
||||||
|
model: ollama.modelName ?? ollama.model,
|
||||||
|
relatedDataCount: 0,
|
||||||
|
timeTaken: new Date().getTime() - chatStartTime.getTime(),
|
||||||
|
date: chatStartTime.getTime(),
|
||||||
|
cot,
|
||||||
|
responseContent: content,
|
||||||
|
modelResponseContent: fullText
|
||||||
|
}
|
||||||
|
const _meteringEntries = [currentMeteringEntry, ...meteringEntries]
|
||||||
|
setCurrentMeteringEntry({
|
||||||
|
loading: false,
|
||||||
|
data: currentMeteringEntry
|
||||||
|
})
|
||||||
|
setMeteringEntries(_meteringEntries)
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
const errorSave = await saveMessageOnError({
|
const errorSave = await saveMessageOnError({
|
||||||
e,
|
e,
|
||||||
@ -796,7 +953,9 @@ export const useMessageOption = () => {
|
|||||||
userMessage: message,
|
userMessage: message,
|
||||||
isRegenerating: isRegenerate,
|
isRegenerating: isRegenerate,
|
||||||
prompt_content: promptContent,
|
prompt_content: promptContent,
|
||||||
prompt_id: promptId
|
prompt_id: promptId,
|
||||||
|
iodSearch,
|
||||||
|
webSearch,
|
||||||
})
|
})
|
||||||
|
|
||||||
if (!errorSave) {
|
if (!errorSave) {
|
||||||
@ -871,16 +1030,16 @@ export const useMessageOption = () => {
|
|||||||
isBot: false,
|
isBot: false,
|
||||||
name: "You",
|
name: "You",
|
||||||
message,
|
message,
|
||||||
sources: [],
|
webSources: [],
|
||||||
iodSources: [],
|
iodSources: getDefaultIodSources(),
|
||||||
images: []
|
images: []
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
isBot: true,
|
isBot: true,
|
||||||
name: selectedModel,
|
name: selectedModel,
|
||||||
message: "▋",
|
message: "▋",
|
||||||
sources: [],
|
webSources: [],
|
||||||
iodSources: [],
|
iodSources: getDefaultIodSources(),
|
||||||
id: generateMessageId
|
id: generateMessageId
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@ -891,8 +1050,8 @@ export const useMessageOption = () => {
|
|||||||
isBot: true,
|
isBot: true,
|
||||||
name: selectedModel,
|
name: selectedModel,
|
||||||
message: "▋",
|
message: "▋",
|
||||||
sources: [],
|
webSources: [],
|
||||||
iodSources: [],
|
iodSources: getDefaultIodSources(),
|
||||||
id: generateMessageId
|
id: generateMessageId
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@ -998,8 +1157,7 @@ export const useMessageOption = () => {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
// message = message.trim().replaceAll("\n", " ")
|
// message = message.trim().replaceAll("\n", " ")
|
||||||
const iodSource = []
|
|
||||||
//TODO not support iodSource in RAG
|
|
||||||
let humanMessage = await humanMessageFormatter({
|
let humanMessage = await humanMessageFormatter({
|
||||||
content: [
|
content: [
|
||||||
{
|
{
|
||||||
@ -1096,8 +1254,7 @@ export const useMessageOption = () => {
|
|||||||
return {
|
return {
|
||||||
...message,
|
...message,
|
||||||
message: fullText,
|
message: fullText,
|
||||||
sources: source,
|
webSources: source,
|
||||||
iodSources: iodSource,
|
|
||||||
generationInfo,
|
generationInfo,
|
||||||
reasoning_time_taken: timetaken
|
reasoning_time_taken: timetaken
|
||||||
}
|
}
|
||||||
@ -1128,9 +1285,10 @@ export const useMessageOption = () => {
|
|||||||
image,
|
image,
|
||||||
fullText,
|
fullText,
|
||||||
source,
|
source,
|
||||||
iodSource,
|
|
||||||
generationInfo,
|
generationInfo,
|
||||||
reasoning_time_taken: timetaken
|
reasoning_time_taken: timetaken,
|
||||||
|
iodSearch,
|
||||||
|
webSearch,
|
||||||
})
|
})
|
||||||
|
|
||||||
setIsProcessing(false)
|
setIsProcessing(false)
|
||||||
@ -1146,7 +1304,9 @@ export const useMessageOption = () => {
|
|||||||
setHistory,
|
setHistory,
|
||||||
setHistoryId,
|
setHistoryId,
|
||||||
userMessage: message,
|
userMessage: message,
|
||||||
isRegenerating: isRegenerate
|
isRegenerating: isRegenerate,
|
||||||
|
iodSearch,
|
||||||
|
webSearch,
|
||||||
})
|
})
|
||||||
|
|
||||||
if (!errorSave) {
|
if (!errorSave) {
|
||||||
@ -1197,8 +1357,11 @@ export const useMessageOption = () => {
|
|||||||
signal
|
signal
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
if (webSearch) {
|
if (webSearch || iodSearch) {
|
||||||
|
setIodLoading(iodSearch)
|
||||||
await searchChatMode(
|
await searchChatMode(
|
||||||
|
webSearch,
|
||||||
|
iodSearch,
|
||||||
message,
|
message,
|
||||||
image,
|
image,
|
||||||
isRegenerate,
|
isRegenerate,
|
||||||
@ -1312,6 +1475,10 @@ export const useMessageOption = () => {
|
|||||||
editMessage,
|
editMessage,
|
||||||
messages,
|
messages,
|
||||||
setMessages,
|
setMessages,
|
||||||
|
iodLoading,
|
||||||
|
currentMessageId,
|
||||||
|
setIodLoading,
|
||||||
|
setCurrentMessageId,
|
||||||
onSubmit,
|
onSubmit,
|
||||||
setStreaming,
|
setStreaming,
|
||||||
streaming,
|
streaming,
|
||||||
@ -1333,6 +1500,8 @@ export const useMessageOption = () => {
|
|||||||
regenerateLastMessage,
|
regenerateLastMessage,
|
||||||
webSearch,
|
webSearch,
|
||||||
setWebSearch,
|
setWebSearch,
|
||||||
|
iodSearch,
|
||||||
|
setIodSearch,
|
||||||
isSearchingInternet,
|
isSearchingInternet,
|
||||||
setIsSearchingInternet,
|
setIsSearchingInternet,
|
||||||
selectedQuickPrompt,
|
selectedQuickPrompt,
|
||||||
|
@ -6,6 +6,7 @@ import {
|
|||||||
} from "@/db"
|
} from "@/db"
|
||||||
import { exportKnowledge, importKnowledge } from "@/db/knowledge"
|
import { exportKnowledge, importKnowledge } from "@/db/knowledge"
|
||||||
import { exportVectors, importVectors } from "@/db/vector"
|
import { exportVectors, importVectors } from "@/db/vector"
|
||||||
|
import { IodDb } from "@/db/iod"
|
||||||
import { message } from "antd"
|
import { message } from "antd"
|
||||||
|
|
||||||
export const exportPageAssistData = async () => {
|
export const exportPageAssistData = async () => {
|
||||||
@ -13,12 +14,14 @@ export const exportPageAssistData = async () => {
|
|||||||
const chat = await exportChatHistory()
|
const chat = await exportChatHistory()
|
||||||
const vector = await exportVectors()
|
const vector = await exportVectors()
|
||||||
const prompts = await exportPrompts()
|
const prompts = await exportPrompts()
|
||||||
|
const iod = IodDb.getInstance().getIodConfig()
|
||||||
|
|
||||||
const data = {
|
const data = {
|
||||||
knowledge,
|
knowledge,
|
||||||
chat,
|
chat,
|
||||||
vector,
|
vector,
|
||||||
prompts
|
prompts,
|
||||||
|
iod
|
||||||
}
|
}
|
||||||
|
|
||||||
const dataStr = JSON.stringify(data)
|
const dataStr = JSON.stringify(data)
|
||||||
@ -34,6 +37,7 @@ export const exportPageAssistData = async () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const importPageAssistData = async (file: File) => {
|
export const importPageAssistData = async (file: File) => {
|
||||||
|
debugger
|
||||||
const reader = new FileReader()
|
const reader = new FileReader()
|
||||||
reader.onload = async () => {
|
reader.onload = async () => {
|
||||||
try {
|
try {
|
||||||
@ -55,6 +59,10 @@ export const importPageAssistData = async (file: File) => {
|
|||||||
await importPrompts(data.prompts)
|
await importPrompts(data.prompts)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(data?.iod) {
|
||||||
|
IodDb.getInstance().insertIodConnection(data.iod.connection)
|
||||||
|
}
|
||||||
|
|
||||||
message.success("Data imported successfully")
|
message.success("Data imported successfully")
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e)
|
console.error(e)
|
||||||
|
18
src/libs/iod.ts
Normal file
18
src/libs/iod.ts
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
import { AllIodRegistryEntry } from "@/types/iod.ts"
|
||||||
|
|
||||||
|
export const getDefaultIodSources = (): AllIodRegistryEntry => {
|
||||||
|
return {
|
||||||
|
data: {
|
||||||
|
data: [],
|
||||||
|
total: 0
|
||||||
|
},
|
||||||
|
scenario: {
|
||||||
|
data: [],
|
||||||
|
total: 0
|
||||||
|
},
|
||||||
|
organization: {
|
||||||
|
data: [],
|
||||||
|
total: 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
91
src/libs/playground.tsx
Normal file
91
src/libs/playground.tsx
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
import { Avatar } from "antd"
|
||||||
|
import { MedicineBottleFillIcon } from "@/components/Icons/MedicineBottleFill.tsx"
|
||||||
|
import { CheckIcon } from "@/components/Icons/Check.tsx"
|
||||||
|
import { NewBottleIcon } from "@/components/Icons/NewBottle.tsx"
|
||||||
|
import { BatteryIcon } from "@/components/Icons/Battery.tsx"
|
||||||
|
import { ShipIcon } from "@/components/Icons/Ship.tsx"
|
||||||
|
import { Ship1Icon } from "@/components/Icons/Ship1.tsx"
|
||||||
|
|
||||||
|
export const qaPrompt = [
|
||||||
|
// {
|
||||||
|
// title: "如何开发一个适合超大型城市的碳普惠方法学?",
|
||||||
|
// icon: <img src={RocketSvg} alt="Rocket" className="w-10 my-0" />,
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// title: "如何开发一个零碳园区的数字化评价系统?",
|
||||||
|
// icon: <img src={BulbSvg} alt="Rocket" className="w-10 my-0" />,
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// title: "如何开发一个碳定价预测系统?",
|
||||||
|
// icon: <img src={EyeSvg} alt="Rocket" className="w-10 my-0" />,
|
||||||
|
// },
|
||||||
|
{
|
||||||
|
title: "如何解决固态电池的成本和寿命难题?",
|
||||||
|
icon: (
|
||||||
|
<Avatar
|
||||||
|
className="!bg-[#3581e3b3]"
|
||||||
|
shape="square"
|
||||||
|
size={40}
|
||||||
|
icon={<BatteryIcon className="w-7" />}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "如何解决船舶制造中的材料腐蚀难题?",
|
||||||
|
icon: (
|
||||||
|
<Avatar
|
||||||
|
className="!bg-[#3581e3b3]"
|
||||||
|
shape="square"
|
||||||
|
size={40}
|
||||||
|
icon={<ShipIcon className="w-7" />}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "如何解决船舶制造中流体模拟和建模优化难题?",
|
||||||
|
icon: (
|
||||||
|
<Avatar
|
||||||
|
className="!bg-[#3581e3b3]"
|
||||||
|
shape="square"
|
||||||
|
size={40}
|
||||||
|
icon={<Ship1Icon className="w-7" />}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "新药临床研究如何提升实验安全性?",
|
||||||
|
icon: (
|
||||||
|
<Avatar
|
||||||
|
className="!bg-[#3581e3b3]"
|
||||||
|
shape="square"
|
||||||
|
size={40}
|
||||||
|
icon={<MedicineBottleFillIcon className="w-7" />}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "人工智能技术如何加速新药申报和审批?",
|
||||||
|
icon: (
|
||||||
|
<Avatar
|
||||||
|
className="!bg-[#3581e3b3]"
|
||||||
|
shape="square"
|
||||||
|
size={40}
|
||||||
|
icon={<CheckIcon className="w-7" />}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "如何研制与利妥昔单抗相似的新药?",
|
||||||
|
icon: (
|
||||||
|
<Avatar
|
||||||
|
className="!bg-[#3581e3b3]"
|
||||||
|
shape="square"
|
||||||
|
size={40}
|
||||||
|
icon={<NewBottleIcon className="w-7" />}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
].map((item, index) => ({
|
||||||
|
...item,
|
||||||
|
id: index.toString()
|
||||||
|
}))
|
@ -72,7 +72,7 @@ export const pageAssistModel = async ({
|
|||||||
configuration: {
|
configuration: {
|
||||||
apiKey: providerInfo.apiKey || "temp",
|
apiKey: providerInfo.apiKey || "temp",
|
||||||
baseURL: providerInfo.baseUrl || ""
|
baseURL: providerInfo.baseUrl || ""
|
||||||
}
|
},
|
||||||
}) as any
|
}) as any
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -85,7 +85,7 @@ export const pageAssistModel = async ({
|
|||||||
configuration: {
|
configuration: {
|
||||||
apiKey: providerInfo.apiKey || "temp",
|
apiKey: providerInfo.apiKey || "temp",
|
||||||
baseURL: providerInfo.baseUrl || ""
|
baseURL: providerInfo.baseUrl || ""
|
||||||
}
|
},
|
||||||
}) as any
|
}) as any
|
||||||
}
|
}
|
||||||
return new ChatOllama({
|
return new ChatOllama({
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"extName": {
|
"extName": {
|
||||||
"message": "Page Assist - 本地 AI 模型的 Web UI"
|
"message": "IoD Bot - 本地 AI 模型的 Web UI"
|
||||||
},
|
},
|
||||||
"extDescription": {
|
"extDescription": {
|
||||||
"message": "使用本地运行的 AI 模型来辅助您的网络浏览。"
|
"message": "使用本地运行的 AI 模型来辅助您的网络浏览。"
|
||||||
|
@ -7,11 +7,14 @@ import OptionOllamaSettings from "./options-settings-ollama"
|
|||||||
import OptionShare from "./option-settings-share"
|
import OptionShare from "./option-settings-share"
|
||||||
import OptionKnowledgeBase from "./option-settings-knowledge"
|
import OptionKnowledgeBase from "./option-settings-knowledge"
|
||||||
import OptionAbout from "./option-settings-about"
|
import OptionAbout from "./option-settings-about"
|
||||||
|
import OptionIodSettings from "./option-settings-iod"
|
||||||
import SidepanelChat from "./sidepanel-chat"
|
import SidepanelChat from "./sidepanel-chat"
|
||||||
import SidepanelSettings from "./sidepanel-settings"
|
import SidepanelSettings from "./sidepanel-settings"
|
||||||
import OptionRagSettings from "./option-rag"
|
import OptionRagSettings from "./option-rag"
|
||||||
import OptionChrome from "./option-settings-chrome"
|
import OptionChrome from "./option-settings-chrome"
|
||||||
import OptionOpenAI from "./option-settings-openai"
|
import OptionOpenAI from "./option-settings-openai"
|
||||||
|
import OptionMetering from "./option-metering"
|
||||||
|
import MeteringListDetail from "./metering-list-detail"
|
||||||
|
|
||||||
export const OptionRoutingChrome = () => {
|
export const OptionRoutingChrome = () => {
|
||||||
return (
|
return (
|
||||||
@ -26,7 +29,10 @@ export const OptionRoutingChrome = () => {
|
|||||||
<Route path="/settings/share" element={<OptionShare />} />
|
<Route path="/settings/share" element={<OptionShare />} />
|
||||||
<Route path="/settings/knowledge" element={<OptionKnowledgeBase />} />
|
<Route path="/settings/knowledge" element={<OptionKnowledgeBase />} />
|
||||||
<Route path="/settings/rag" element={<OptionRagSettings />} />
|
<Route path="/settings/rag" element={<OptionRagSettings />} />
|
||||||
|
<Route path="/settings/iod" element={<OptionIodSettings />} />
|
||||||
<Route path="/settings/about" element={<OptionAbout />} />
|
<Route path="/settings/about" element={<OptionAbout />} />
|
||||||
|
<Route path="/metering" element={<OptionMetering />} />
|
||||||
|
<Route path="/metering/list/:id" element={<MeteringListDetail />} />
|
||||||
</Routes>
|
</Routes>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user