Compare commits

..

3 Commits

Author SHA1 Message Date
zhaoweijie
224ae55d6b feat/playground: 重构 playground组件
- 更新 Data 和 History组件的样式和布局
- 添加新的功能和交互,如热门搜索和智能体选择
- 优化组件性能和可维护性
2025-08-21 14:08:07 +08:00
zhaoweijie
8a5c5f1c26 refactor(layout): 优化团队页面布局和滚动
- 在 Team组件中添加 overflow-y-auto 以启用垂直滚动
- 在 Playground组件中调整网格布局,移除不必要的导入
- 优化消息列表布局,确保内容可以滚动
2025-08-19 18:13:15 +08:00
zhaoweijie
9e379d13cb refactor(components): 重构历史记录组件和 playground 布局- 更新 History 组件样式和动画效果
- 调整 Playground 布局结构
-优化 Sidebar 聊天记录样式
2025-08-19 17:42:13 +08:00
79 changed files with 1644 additions and 3749 deletions

2
.gitignore vendored
View File

@ -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

470
bun.lock

File diff suppressed because one or more lines are too long

View File

@ -35,7 +35,6 @@
"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",
@ -48,7 +47,6 @@
"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",

1
src/assets/icons/a.svg Normal file
View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" class="styles__StyledSVGIconPathComponent-sc-i3aj97-0 bxMexi svg-icon-path-icon" viewBox="0 0 64 64" width="24" height="24"><defs></defs><g><path fill="#3e4347" d="M64 57.6c0 3.5-2.9 6.4-6.4 6.4H6.4C2.9 64 0 61.1 0 57.6V6.4C0 2.9 2.9 0 6.4 0h51.2C61.1 0 64 2.9 64 6.4z"></path><circle cx="32" cy="32" r="32" fill="#42ade2"></circle><path fill="#fff" d="M32 40.4c-4.6 0-8.4-3.8-8.4-8.4s3.8-8.4 8.4-8.4s8.4 3.8 8.4 8.4s-3.8 8.4-8.4 8.4"></path><path fill="#3e4347" d="M38.4 24c-3.5 0-6.4 2.9-6.4 6.4v3.2c0 3.5 2.9 6.4 6.4 6.4H64V24z"></path></g></svg>

After

Width:  |  Height:  |  Size: 590 B

1
src/assets/icons/b.svg Normal file
View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" class="styles__StyledSVGIconPathComponent-sc-i3aj97-0 bxMexi svg-icon-path-icon" viewBox="0 0 64 64" width="24" height="24"><defs></defs><g><g fill="#333"><path d="M0 0h4v64H0z"></path><path d="M0 60h64v4H0z"></path></g><path fill="#fb4f00" d="M38.7 60h12V6.7L38.7 20z"></path><path fill="#5c750a" d="M21.3 60h12V20l-12 13.3z"></path><path fill="#106995" d="M4 60h12V33.3L4 46.7z"></path><path fill="#9aca0a" d="M33.3 20h13.3v40H33.3z"></path><path fill="#21adf1" d="M16 33.3h13.3V60H16z"></path><path fill="#fc9100" d="M50.7 6.7H64V60H50.7z"></path></g></svg>

After

Width:  |  Height:  |  Size: 601 B

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" class="styles__StyledSVGIconPathComponent-sc-i3aj97-0 bxMexi svg-icon-path-icon" viewBox="0 0 64 64" width="24" height="24"><defs></defs><g><path fill="#616466" d="M28 58c0 2.2 1.8 4 4 4s4-1.8 4-4z"></path><path fill="#ffce31" d="M24.9 48H39c.8-4.3 3.5-8.5 6.3-12.9C48.6 30 52 24.7 52 19.6C52 9.9 43 2 32 2S12 9.9 12 19.6c0 5.1 3.4 10.4 6.6 15.5c2.8 4.4 5.5 8.6 6.3 12.9"></path><path fill="#c79127" d="M26.4 33.6c.1.6.3 1.2.4 1.8c.3 1.1.5 2.1.8 3.2c.9 3.8 1.7 7 2.4 9.5h.6c-.5-2.5-1.2-5.8-2.1-9.6c-.2-1-.5-2.1-.7-3.2c-.1-.5-.2-1.1-.4-1.6c.8-.2 2.7-.8 4.6-2.9c1.9 2.1 3.8 2.7 4.6 2.9c-.1.6-.2 1.1-.4 1.6c-.2 1.1-.5 2.2-.7 3.2c-.9 3.8-1.6 7.1-2.1 9.6h.6c.6-2.5 1.5-5.7 2.4-9.5c.2-1 .5-2.1.8-3.2c.1-.6.3-1.2.4-1.8c.8-.1 1.5-.3 2-.8c.6-.6.9-1.3.7-2.1c-.1-.4-.3-.9-.9-1c-.3-.1-.5-.1-.8 0s-.5.3-.6.4c-.5.6-.7 1.2-.9 1.8l-.3.9s-.8.1-2.7-1.2c-1-.7-1.3-1.2-1.6-1.5c.3-.4.6-.7.9-1.2c.1-.3.2-.5.3-.9c0-.3 0-.7-.2-1s-.4-.6-.9-.8c-.2-.1-.4-.1-.6-.1s-.4 0-.6.1c-.4.2-.7.5-.9.8s-.2.7-.2 1q0 .45.3.9q.3.75.9 1.2c-.3.3-.6.8-1.6 1.5c-1.8 1.2-2.7 1.2-2.7 1.2l-.3-.9c-.2-.6-.4-1.2-.9-1.8c-.1-.1-.3-.3-.6-.4s-.6-.1-.8 0c-.6.2-.8.7-.9 1c-.1.8.2 1.5.7 2.1c.6.5 1.3.7 2 .8M38 32.1c.2-.5.4-1.1.8-1.4c.1-.1.2-.1.2-.1h.2c.1 0 .2.1.3.4c.1.4-.1 1-.5 1.3c-.3.2-.7.4-1.1.5c0-.3 0-.5.1-.7m-6.6-4.8c.2-.2.4-.4.6-.4s.4.1.6.4s.1.7-.1 1.1c-.1.3-.3.6-.5.8c-.2-.3-.4-.5-.5-.8c-.2-.3-.3-.7-.1-1.1m-6.8 3.5c0-.2.1-.3.3-.4h.2s.1.1.2.1c.3.3.6.9.8 1.4c.1.2.1.4.2.6c-.4-.1-.8-.2-1.1-.5c-.4-.2-.6-.7-.6-1.2"></path><path fill="#94989b" d="M24.9 50h14.3v1.8H24.9zm1 3.6h12.3v1.8H25.9z"></path><path fill="#616466" d="M25.9 51.8h12.3v1.8H25.9z"></path><path fill="#94989b" d="m39.2 50l-13.3 3.6v1.9l13.3-3.7zm-12.3 7.3h10.3v1.8H26.9z"></path><path fill="#616466" d="M26.9 55.5h10.3v1.8H26.9z"></path><path fill="#94989b" d="m38.2 53.6l-11.3 3.7v1.9l11.3-3.7z"></path></g></svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

1
src/assets/icons/c.svg Normal file
View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" class="styles__StyledSVGIconPathComponent-sc-i3aj97-0 bxMexi svg-icon-path-icon" viewBox="0 0 64 64" width="24" height="24"><defs></defs><g><path fill="#d0d0d0" d="M23.6 36.7H2.9l10.3-26.2zm-18-1.9h15.3l-7.7-19.5zm55.5 1.9H40.4l10.3-26.2zm-18-1.9h15.3l-7.7-19.5z"></path><path fill="#b8c2c4" d="M50.3 10.2s-2.2-.8-3.4-1.3C42.6 7.1 37.3 5 32 5S21.4 7.2 17.1 9c-1.2.5-3.4 1.3-3.4 1.3l-3.3 2.5s2.7 1.7 4 1.2c1.1-.4 2.2-.9 3.5-1.4C22 10.9 27.1 8.8 32 8.8s10 2.1 14.1 3.8c1.2.5 2.4 1 3.5 1.4c1.4.5 4-1.2 4-1.2z"></path><path fill="#428bc1" d="M2 34.8C2 41 7 46 13.2 46s11.2-5 11.2-11.2zm37.5 0c0 6.2 5 11.2 11.2 11.2S62 41 62 34.8z"></path><path fill="#b8c2c4" d="M30.1 12.3h3.8v41.2h-3.8z"></path><path fill="#d0d0d0" d="M29 18.6h6.1v34.9H29z"></path><path fill="#545b60" d="M27.7 36.7h8.6v19.7h-8.6z"></path><circle cx="13.2" cy="13.2" r="3.8" fill="#dbb471"></circle><g fill="#545b60"><circle cx="50.8" cy="13.2" r="3.8"></circle><circle cx="13.2" cy="13.2" r="3.8"></circle></g><g fill="#fff"><circle cx="13.2" cy="13.2" r="1.9"></circle><circle cx="50.8" cy="13.2" r="1.9"></circle></g><g fill="#d0d0d0"><circle cx="32" cy="7.6" r="5.6"></circle><path d="M32 45.1c-8.3 0-15 4.2-15 9.4h30c0-5.2-6.7-9.4-15-9.4"></path></g><path fill="#545b60" d="M15.1 54.5h33.8v3.8H15.1z"></path><path fill="#6b767c" d="M11.4 58.2h41.2V62H11.4z"></path></g></svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

1
src/assets/icons/d.svg Normal file
View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" class="styles__StyledSVGIconPathComponent-sc-i3aj97-0 bxMexi svg-icon-path-icon" viewBox="0 0 64 64" width="24" height="24"><defs></defs><g><path fill="#979797" d="M64 31.7h-5.6c-7.3-5-11.9-13.2-12.4-22.1l2.8-5l-1.6-1l-2.8 5c-7.8 4-17 4-24.8 0l-2.8-5l-1.6 1l2.8 5c-.5 8.9-5.1 17.2-12.4 22.1H0v1.9h5.6c7.3 4.9 11.9 13.2 12.4 22.1l-2.8 5l1.6 1l2.8-5c7.8-4 17-4 24.8 0l2.8 5l1.6-1l-2.8-5c.5-8.9 5.1-17.2 12.4-22.1H64zm-8.7 0h-5.5c-4.7-3.2-7.8-8.6-8.1-14.4l2.8-4.9c1 7.6 4.9 14.5 10.8 19.3m-23.3-1l-2.3-4.1c.7.2 1.5.3 2.3.3s1.6-.1 2.3-.3zm3.9-3.1c.5 1.5 1.3 2.9 2.3 4.1h-4.6zm-5.5 4.1h-4.6c1-1.2 1.8-2.6 2.3-4.1zm0 1.9l-2.3 4.1c-.5-1.5-1.3-2.9-2.3-4.1zm1.6.9l2.3 4.1c-1.5-.3-3.1-.3-4.6 0zm1.6-.9h4.6c-1 1.2-1.8 2.6-2.3 4.1zm7.4-1.9c-2.1-1.5-3.5-3.9-3.7-6.6l2.9-5.1c.8 4.5 3.1 8.7 6.5 11.7zm-5.3-7.6c-2.3 1.2-5.1 1.2-7.4 0L25.5 19c2.1.8 4.3 1.1 6.5 1.1s4.4-.4 6.5-1.1zm-9 .9c-.2 2.7-1.5 5.1-3.7 6.6h-5.7c3.4-3 5.7-7.1 6.5-11.7zM23 33.6c2.1 1.5 3.5 3.9 3.7 6.6l-2.9 5.1c-.8-4.5-3.1-8.7-6.5-11.7zm5.3 7.5c2.3-1.2 5.1-1.2 7.4 0l2.9 5.1c-4.2-1.5-8.9-1.5-13.1 0zm9-.9c.2-2.7 1.5-5.1 3.7-6.6h5.7c-3.4 3-5.7 7.1-6.5 11.7zM32 13.5c3.7 0 7.4-.7 10.8-2.1L40 16.3c-5.1 2.6-11.1 2.6-16.1 0l-2.8-4.9c3.5 1.4 7.2 2.1 10.9 2.1m-12.4-1.1l2.8 4.9c-.4 5.8-3.4 11.2-8.1 14.4H8.7c5.9-4.8 9.8-11.7 10.9-19.3M8.8 33.6h5.5c4.7 3.2 7.7 8.5 8 14.4l-2.8 4.9c-1-7.6-4.9-14.6-10.7-19.3m12.4 20.2l2.8-4.9c5.1-2.6 11.1-2.6 16.1 0l2.8 4.9c-7-2.8-14.8-2.8-21.7 0m23.2-.9L41.7 48c.3-5.8 3.3-11.2 8.1-14.4h5.5c-5.9 4.7-9.8 11.7-10.9 19.3"></path></g></svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

1
src/assets/icons/e.svg Normal file
View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" class="styles__StyledSVGIconPathComponent-sc-i3aj97-0 bxMexi svg-icon-path-icon" viewBox="0 0 64 64" width="24" height="24"><defs></defs><g><path fill="#212528" d="M21.6 16.1c0 1.2-1 2.2-2.2 2.2H9.7c-1.2 0-2.2-1-2.2-2.2v-2.7c0-1.2 1-2.2 2.2-2.2h9.7c1.2 0 2.2 1 2.2 2.2zm39.1-.2c0 .6-.5 1.2-1.2 1.2h-5.2c-.6 0-1.2-.5-1.2-1.2v-1.4c0-.6.5-1.2 1.2-1.2h5.2c.6 0 1.2.5 1.2 1.2zM64 50.3c0 3-2.4 5.4-5.4 5.4H5.4c-3 0-5.4-2.4-5.4-5.4v-1.6h64z"></path><path fill="#51575b" d="M0 20.1c0-3 2.4-5.4 5.4-5.4h53.1c3 0 5.4 2.4 5.4 5.4v1.6H0z"></path><path fill="#3e4347" d="M0 21.5h64v28.3H0z"></path><path fill="#51575b" d="M54.7 18H22.6l3.2-10.8c.3-.6 1.6-1.6 2.4-1.8C33.1 4 44.1 4 49 5.4c.8.2 2.1 1.2 2.4 1.8z"></path><path fill="#3e4347" d="M53.1 29.6h-29L27 15c.2-.8 1.5-2.1 2.2-2.4c4.4-1.8 14.4-1.8 18.8 0c.7.3 1.9 1.6 2.2 2.4z"></path><path fill="#788287" d="M60.6 37.6c0 12.2-9.9 22-22 22s-22-9.8-22-22c0-12.1 9.9-22 22-22c12.2 0 22 9.9 22 22"></path><path fill="#212528" d="M58.2 37.6c0 10.8-8.8 19.6-19.6 19.6S19 48.4 19 37.6S27.8 18 38.6 18c10.8.1 19.6 8.8 19.6 19.6"></path><circle cx="38.6" cy="37.6" r="15.9" fill="#3e4347"></circle><circle cx="38.6" cy="37.6" r="8.6" fill="#212528"></circle><g fill="#f5f5f5"><path d="M50.3 30.9c0 2.7-2.2 4.9-4.9 4.9s-4.9-2.2-4.9-4.9s2.2-4.9 4.9-4.9c2.7-.1 4.9 2.2 4.9 4.9" opacity=".5"></path><circle cx="35.6" cy="40.7" r="3.1" opacity=".5"></circle><circle cx="30.1" cy="46.2" r="1.9" opacity=".5"></circle></g><path fill="#636c72" d="M15 45.3c0 1.2-1 2.2-2.2 2.2H3.6c-1.2 0-2.2-1-2.2-2.2V25.9c0-1.2 1-2.2 2.2-2.2h9.2c1.2 0 2.2 1 2.2 2.2z"></path><circle cx="10.1" cy="18.6" r="3.2" fill="#212528"></circle></g></svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

1
src/assets/icons/eye.svg Normal file
View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" class="styles__StyledSVGIconPathComponent-sc-i3aj97-0 bxMexi svg-icon-path-icon" viewBox="0 0 64 64" width="24" height="24"><defs></defs><g><path fill="#231f20" d="M62 32S51.9 52 32 52S2 32 2 32s10.1-20 30-20s30 20 30 20"></path><path fill="#fff" d="M57 32s-8.4 16.7-25 16.7S7 32 7 32s8.4-16.7 25-16.7S57 32 57 32"></path><path fill="#42ade2" d="M45.4 32c0 7.5-6 13.5-13.5 13.5s-13.5-6-13.5-13.5s6-13.5 13.5-13.5s13.5 6 13.5 13.5"></path><path fill="#231f20" d="M39.4 32c0 4.1-3.4 7.5-7.5 7.5s-7.5-3.4-7.5-7.5s3.4-7.5 7.5-7.5s7.5 3.4 7.5 7.5"></path></g></svg>

After

Width:  |  Height:  |  Size: 601 B

1
src/assets/icons/f.svg Normal file
View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" class="styles__StyledSVGIconPathComponent-sc-i3aj97-0 bxMexi svg-icon-path-icon" viewBox="0 0 64 64" width="24" height="24"><defs></defs><g><path fill="#0071bc" d="M57.9 32c.2-.2.3-.4.5-.6c3.9-5.6 4.7-10.7 2.3-14.5c-2.2-3.5-7-5.5-13.4-5.5c-1.1 0-2.3.1-3.5.2C40.9 5.7 36.7 2 32 2s-8.9 3.7-11.8 9.6c-1.2-.1-2.4-.2-3.5-.2c-6.4 0-11.2 1.9-13.4 5.5c-2.4 3.8-1.6 9 2.3 14.5c.2.2.4.4.5.6c-.2.2-.3.4-.5.6C1.8 38.2 1 43.4 3.4 47.2c2.2 3.5 7 5.5 13.4 5.5c1.1 0 2.3-.1 3.5-.2c2.8 5.8 7 9.5 11.7 9.5s8.9-3.7 11.8-9.6c1.2.1 2.4.2 3.5.2c6.4 0 11.2-1.9 13.4-5.5c2.4-3.8 1.6-9-2.3-14.5c-.2-.2-.4-.4-.5-.6M47.2 13.1c5.7 0 9.9 1.6 11.8 4.6c2 3.2 1.2 7.7-2.3 12.7l-.1.1c-2.5-3-5.8-6-9.7-8.6c-.6-3.2-1.4-6.1-2.5-8.7c1-.1 2-.1 2.8-.1m-7.4 31.3c-2.6 1.4-5.2 2.5-7.8 3.5c-2.6-1-5.3-2.1-7.8-3.5c-1.9-1-3.7-2.1-5.5-3.3c-.5-2.9-.8-6-.8-9.2s.3-6.3.8-9.2c1.7-1.2 3.5-2.2 5.5-3.3c2.6-1.4 5.2-2.5 7.8-3.5c2.6 1 5.3 2.1 7.8 3.5c1.9 1 3.7 2.1 5.5 3.3c.5 2.9.8 6 .8 9.2s-.3 6.3-.8 9.2c-1.7 1.2-3.6 2.3-5.5 3.3m5-.8c-.6 2.5-1.3 4.9-2.3 7c-2.6-.3-5.2-.9-7.9-1.8c2.1-.8 4.1-1.8 6.1-2.8c1.5-.8 2.8-1.6 4.1-2.4m-15.4 5.2c-2.7.8-5.4 1.4-7.9 1.8c-.9-2.1-1.7-4.4-2.3-7c1.3.8 2.6 1.6 4 2.3c2.1 1.1 4.1 2 6.2 2.9m-12.8-9.1c-3.2-2.4-6-5-8.1-7.7c2.1-2.7 4.9-5.3 8.1-7.7c-.3 2.4-.5 5-.5 7.7s.1 5.2.5 7.7m2.6-19.3c.6-2.5 1.3-4.9 2.3-7c2.6.3 5.2.9 7.9 1.8c-2.1.8-4.1 1.8-6.1 2.9c-1.5.7-2.8 1.5-4.1 2.3m15.4-5.2c2.7-.8 5.4-1.4 7.9-1.8c.9 2.1 1.7 4.4 2.3 7c-1.3-.8-2.6-1.6-4-2.3c-2.1-1.1-4.1-2-6.2-2.9m12.8 9.1c3.2 2.4 6 5 8.1 7.7c-2.1 2.7-4.9 5.3-8.1 7.7c.3-2.4.5-5 .5-7.7s-.1-5.2-.5-7.7M32 3.9c3.8 0 7.2 3 9.8 7.9c-3.1.5-6.4 1.3-9.8 2.5c-3.3-1.2-6.6-2-9.8-2.5c2.6-4.9 6-7.9 9.8-7.9M7.2 30.4c-3.5-5-4.3-9.5-2.3-12.7c1.9-3 6.1-4.6 11.8-4.6c.9 0 1.8 0 2.7.1c-1.1 2.6-1.9 5.5-2.5 8.7c-3.8 2.6-7.1 5.6-9.7 8.5m9.6 20.5c-5.7 0-9.9-1.6-11.8-4.6c-2-3.2-1.2-7.7 2.3-12.7c0 0 0-.1.1-.1c2.5 3 5.8 6 9.7 8.6c.6 3.2 1.4 6.1 2.5 8.7c-1 .1-1.9.1-2.8.1M32 60.1c-3.8 0-7.2-3-9.8-7.9c3.1-.5 6.4-1.3 9.8-2.5c3.3 1.2 6.6 2 9.8 2.5c-2.6 4.9-6 7.9-9.8 7.9m27-13.8c-1.9 3-6.1 4.6-11.8 4.6c-.9 0-1.8 0-2.7-.1c1.1-2.6 1.9-5.5 2.5-8.7c3.8-2.6 7.1-5.6 9.7-8.6c0 0 0 .1.1.1c3.5 5 4.3 9.5 2.2 12.7"></path><circle cx="32" cy="32" r="5.6" fill="#ed4c5c"></circle></g></svg>

After

Width:  |  Height:  |  Size: 2.2 KiB

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" class="styles__StyledSVGIconPathComponent-sc-i3aj97-0 bxMexi svg-icon-path-icon" viewBox="0 0 64 64" width="24" height="24"><defs></defs><g><g fill="#ff9d27"><path d="M10.9 48.7c4-4 4.4-5 6.9-2.5s1.5 2.8-2.5 6.9c-3 3-6.8 2.4-6.8 2.4s-.6-3.8 2.4-6.8"></path><path d="M18.5 52.8c1.6-4.2 2.1-4.7-.2-6s-2.3-.4-3.8 3.8c-1.2 3.1.2 5.9.2 5.9s2.7-.5 3.8-3.7"></path></g><path fill="#fdf516" d="M16.2 48.9c.9-2.3.9-2.8 2.1-2.1c1.3.7 1 1 .1 3.3c-.6 1.7-2.1 2.1-2.1 2.1s-.7-1.5-.1-3.3"></path><path fill="#ff9d27" d="M17.1 45.7c-1.3-2.3-1.8-1.8-6-.2c-3.1 1.2-3.7 3.8-3.7 3.8s2.8 1.4 5.9.2c4.2-1.6 5.1-1.6 3.8-3.8"></path><g fill="#fdf516"><path d="M15 47.8c2.3-.9 2.8-.9 2.1-2.1c-.7-1.3-1-1-3.3-.1c-1.7.6-2.1 2.1-2.1 2.1s1.6.7 3.3.1"></path><path d="M13.9 47.6c2.2-2.2 2.4-2.8 3.8-1.4s.8 1.6-1.4 3.8c-1.7 1.7-3.8 1.3-3.8 1.3s-.2-2 1.4-3.7"></path></g><path fill="#3baacf" d="M18.5 38C12.3 27.6 2 31.9 2 31.9s14.7-14.7 24.6-4.8z"></path><path fill="#428bc1" d="m23.3 30.3l3.2-3.2C16.7 17.2 2 31.9 2 31.9s12.9-9.2 21.3-1.6"></path><path fill="#3baacf" d="M26 45.5C36.4 51.7 32.1 62 32.1 62s14.7-14.7 4.8-24.6z"></path><path fill="#428bc1" d="m33.7 40.7l3.2-3.2c9.9 9.9-4.8 24.6-4.8 24.6s9.2-13 1.6-21.4"></path><path fill="#c5d0d8" d="M48.8 30.9C37.1 42.5 24.2 48.8 19.7 44.3s1.8-17.4 13.4-29.1c13.6-13.6 28.7-13 28.7-13s.5 15.1-13 28.7"></path><path fill="#dae3ea" d="M45.8 27.6C34.2 39.2 22.6 46.8 19.9 44.1s4.9-14.3 16.5-25.9C50 4.6 62 2 62 2s-2.6 12-16.2 25.6"></path><path fill="#c94747" d="M24.3 47.5c-.5.5-1.3.5-1.8 0l-6-6c-.5-.5-.5-1.4 0-1.9l1.8-1.8l7.8 7.8z"></path><path fill="#f15744" d="M22.6 45.7c-.5.5-1.1.7-1.4.4l-3.4-3.4c-.3-.3-.1-.9.4-1.4l1.8-1.8l4.4 4.4z"></path><path fill="#3e4347" d="M20.9 48.2c-.3.3-1 .3-1.3 0l-3.9-3.9c-.3-.3-.2-.9.1-1.2l1.2-1.2l5.1 5.1z"></path><path fill="#62727a" d="M20.1 47.4c-.3.3-.9.4-1.1.2l-2.7-2.7c-.2-.2-.1-.7.3-1l1.2-1.2l3.5 3.5z"></path><path fill="#c94747" d="M61.8 2.2S56.4 2 49.1 4.8l10.1 10.1C62 7.6 61.8 2.2 61.8 2.2"></path><path fill="#f15744" d="M61.8 2.2s-4.3.9-10.8 4.6l6.2 6.2c3.7-6.5 4.6-10.8 4.6-10.8"></path><circle cx="43.5" cy="20.5" r="5" fill="#edf4f9"></circle><circle cx="43.5" cy="20.5" r="3.3" fill="#3baacf"></circle><circle cx="33.5" cy="30.5" r="5" fill="#edf4f9"></circle><circle cx="33.5" cy="30.5" r="3.3" fill="#3baacf"></circle><g fill="#fff"><path d="M48.9 6.9c-.3.3-.9.3-1.2 0s-.3-.9 0-1.2s.9-.3 1.2 0s.3.9 0 1.2"></path><circle cx="50.6" cy="8.6" r=".8"></circle><circle cx="53" cy="11" r=".8"></circle><circle cx="55.3" cy="13.4" r=".8"></circle><circle cx="57.7" cy="15.7" r=".8"></circle></g></g></svg>

After

Width:  |  Height:  |  Size: 2.6 KiB

View File

@ -88,7 +88,7 @@
"system": { "system": {
"heading": "系统设置", "heading": "系统设置",
"deleteChatHistory": { "deleteChatHistory": {
"label": "清除最近对话", "label": "系统重置",
"button": "全部重置", "button": "全部重置",
"confirm": "您确定要执行系统重置吗?这将清除所有数据且无法撤消。" "confirm": "您确定要执行系统重置吗?这将清除所有数据且无法撤消。"
}, },
@ -316,10 +316,6 @@
"title": "管理知识", "title": "管理知识",
"heading": "配置知识库" "heading": "配置知识库"
}, },
"iodSettings": {
"title": "数联网 设置",
"heading": "配置数联网"
},
"rag": { "rag": {
"title": "RAG 设置", "title": "RAG 设置",
"ragSettings": { "ragSettings": {

Binary file not shown.

Before

Width:  |  Height:  |  Size: 224 KiB

View File

@ -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;
} }

View File

@ -1,22 +1,18 @@
import React from "react" import React from "react";
import { Typography } from "antd" import { Typography, Button } from "antd";
import { ChevronRightIcon } from "@heroicons/react/24/outline" import { AcademicCapIcon, ChevronRightIcon } from "@heroicons/react/24/outline";
const { Title } = Typography const { Title } = Typography;
type Props = { type Props = {
Header: React.ReactNode Header: React.ReactNode;
showButton?: boolean showButton?: boolean;
onClick?: () => void onClick?: () => void;
} };
export const DataNavigation: React.FC<Props> = ({ export const DataNavigation: React.FC<Props> = ({ Header, showButton = true, onClick }) => {
Header,
showButton = true,
onClick
}) => {
return ( return (
<div className="flex items-center justify-between bg-white dark:bg-gray-800 rounded-lg"> <div className="flex items-center justify-between bg-white dark:bg-gray-800 rounded-lg mb-3">
{/* 左侧部分 */} {/* 左侧部分 */}
<div className="flex items-center"> <div className="flex items-center">
<Title <Title
@ -29,13 +25,11 @@ export const DataNavigation: React.FC<Props> = ({
{/* 右侧部分 */} {/* 右侧部分 */}
{showButton && ( {showButton && (
<div <div className="flex items-center text-[#3a3a3a] cursor-pointer space-x-0.5 hover:text-[#00c0ef] transition" onClick={onClick}>
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> <span className="text-[12px]"></span>
<ChevronRightIcon className="w-4 h-4" /> <ChevronRightIcon className="w-4 h-4" />
</div> </div>
)} )}
</div> </div>
) )
} };

View File

@ -11,11 +11,6 @@ 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)
@ -25,12 +20,6 @@ export const PageAssistProvider = ({
messages, messages,
setMessages, setMessages,
iodLoading,
setIodLoading,
currentMessageId,
setCurrentMessageId,
controller, controller,
setController, setController,

View File

@ -1,139 +1,150 @@
import React, { useMemo } from "react" import React from "react"
import { DataNavigation } from "@/components/Common/DataNavigation.tsx" import { DataNavigation } from "@/components/Common/DataNavigation.tsx"
import { Card, Skeleton } from "antd" import { Card, Drawer, List } from "antd"
import { useMessageOption } from "@/hooks/useMessageOption.tsx" import { useCallback, useState } from "react"
import { IodRegistryEntry } from "@/types/iod.ts"
import { useIodPlaygroundContext } from "@/components/Option/Playground/PlaygroundIod.tsx"
// import { Drawer } from './Drawer.tsx' export const PlaygroundData = () => {
// 模拟数据
const defaultData: IodRegistryEntry[] = [ const data: {
{ title: string
name: "固态电池固体电解质材料数据集", description: string
doId: "CSTR:16666.11.nbsdc.9bjqrscd", time: string
description: "国家基础学科公共科学数据中心" metadata?: string
}, }[] = [
{ {
name: "固体颗粒物与流体耦合", title: "2019-2024年黄海清浅海域中河湖代数生物物种数据集",
doId: "CSTR:16666.11.nbsdc.xyzbycl7", description:
description: "清华大学" "数字对象标识: CSTR:13452.11.01.11.2021.242 国家海洋科学数据中心",
} time: "包括2019年8月2021年8月和2024年6月",
] metadata: "热 榜 第2"
},
type HeaderProps = { {
title: string title: "祁连山老虎沟大本营10米气象每日值数据集V1.02018-2023",
showButton?: boolean description:
onClick?: () => void "中国科学院西北生态环境资源研究院2021年8月3日发布2021年8月3日20:48更新",
} time: "包括2019年8月2021年8月和2024年6月",
const Header: React.FC<HeaderProps> = ({ metadata: "热 榜 第2"
title, },
showButton = true, {
onClick title: "李嘉图为研究老虎沟大本营2014-2018年...",
}) => ( description:
<DataNavigation "中国科学院西北生态环境资源研究院2021年8月3日发布2021年8月3日20:48更新",
Header={ time: "包括2019年8月2021年8月和2024年6月",
<div className="flex items-center gap-0.5 text-[#3581e3]"> metadata: "热 榜 第2"
<svg },
className="icon" {
viewBox="0 0 1024 1024" title: "青海玉树B1区俄日矿勘探数据2017-2023",
version="1.1" description:
xmlns="http://www.w3.org/2000/svg" "数字中国集团CSTR:3260.11.1528414774204895456DT2023年地质勘探补充调查",
p-id="3572" time: "包括2019年8月2021年8月和2024年6月",
width="18" metadata: "热 榜 第2"
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 = { for (let i = 0; i < 10; i++) {
loading: boolean data.push({
data: IodRegistryEntry[] title: "中国资源环境网",
truncate?: boolean description: "中国资源环境网2021年8月3日发布2021年8月3日20:48更新",
} time: "包括2019年8月2021年8月和2024年6月"
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 = { const [open, setOpen] = useState(false)
className?: string
}
export const PlaygroundData: React.FC<Props> = ({ className }) => {
const { iodLoading } = useMessageOption()
const { const showDrawer = () => {
setShowPlayground, setOpen(true)
setDetailHeader, }
setDetailMain,
currentIodMessage
} = useIodPlaygroundContext()
const data = useMemo<IodRegistryEntry[]>(() => { const onClose = () => {
return currentIodMessage ? currentIodMessage.data?.data ?? [] : defaultData setOpen(false)
}, [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 ( return (
<Card className={`${className}`} hoverable> <Card
<div className="h-full flex flex-col gap-2 relative"> className="h-full [&_.ant-card-body]:h-full [&_.ant-card-body]:!p-[20px] overflow-y-hidden"
hoverable>
<div className="h-full flex flex-col relative">
{/* 数据导航 */} {/* 数据导航 */}
<Header title={title} onClick={showMore} /> <DataNavigation
Header={
<div className="flex items-center gap-0.5 text-[#3480e3]">
<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="#3480e3"></path>
</svg>
</div>
}
onClick={showDrawer}
/>
{/* 数据列表 */} {/* 数据列表 */}
<Main loading={iodLoading && Boolean(currentIodMessage)} data={data.slice(0, 3)} /> <div className="space-y-1.5 flex-1 overflow-y-auto">
{data.slice(0, 3).map((item, index) => (
<Card
key={index}
className="[&_.ant-card-body]:!p-2 !bg-[gb(248, 248, 248)] border !border-[#e9e9e9]">
<div className="flex flex-col gap-0.5">
<h3 className="text-base font-medium mb-1 text-[#222222] line-clamp-2">
{item.title}
</h3>
<p className="text-sm text-[#383838] line-clamp-2">
{item.description}
</p>
<p className="text-[#828282] text-xs truncate">{item.time}</p>
{item.metadata && (
<div>
<span className="inline-block text-[#D90000] bg-[#eb1c1c30] h-5 leading-5 mt-1 text-xs rounded-full px-2.5">
{item.metadata}
</span>
</div>
)}
</div>
</Card>
))}
</div>
</div> </div>
{/* 抽屉 */}
<Drawer
title="相关数据"
closable={{ "aria-label": "Close Button" }}
onClose={onClose}
open={open}
width="33.33%">
<div className="grid grid-cols-1 gap-3 overflow-y-auto">
{data.slice(0, 3).map((item, index) => (
<Card
key={index}
hoverable
className="[&_.ant-card-body]:!p-2 !bg-[gb(248, 248, 248)] border !border-[#e9e9e9]">
<div className="flex flex-col gap-0.5">
<h3 className="text-base font-medium mb-1 text-[#222222]">
{item.title}
</h3>
<p className="text-sm text-[#383838]">{item.description}</p>
<p className="text-[#828282] text-xs">{item.time}</p>
{item.metadata && (
<div>
<span className="inline-block text-[#D90000] bg-[#eb1c1c30] h-5 leading-5 mt-1 text-xs rounded-full px-2.5">
{item.metadata}
</span>
</div>
)}
</div>
</Card>
))}
</div>
</Drawer>
</Card> </Card>
) )
} }

View File

@ -1,91 +0,0 @@
// 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>
</>
)
}

View File

@ -1,8 +1,7 @@
import { useForm } from "@mantine/form" import { useForm } from "@mantine/form"
import React, { useEffect, useState } from "react" import React 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
@ -15,14 +14,6 @@ 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: {
@ -38,27 +29,46 @@ 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(value, true) props.onSumbit(data.message, true)
})} })}
className="flex flex-col gap-2 w-96 ml-auto"> className="flex flex-col gap-2">
<TextArea <textarea
{...form.getInputProps("message")}
onCompositionStart={() => setIsComposing(true)}
onCompositionEnd={() => setIsComposing(false)}
required required
rows={2} rows={1}
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-end" : "justify-end" !props.isBot ? "justify-between" : "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}
@ -66,12 +76,6 @@ 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>{" "}

View File

@ -1,11 +1,12 @@
import { Sidebar } from "@/components/Option/Sidebar.tsx" import { Sidebar } from "@/components/Option/Sidebar.tsx"
import React, { useMemo } from "react" import React, { useContext, useMemo, useState } from "react"
import { useMessageOption } from "@/hooks/useMessageOption.tsx" import { useMessageOption } from "@/hooks/useMessageOption.tsx"
import { useStoreChatModelSettings } from "@/store/model.tsx" import { useStoreChatModelSettings } from "@/store/model.tsx"
import { import {
Button, Button,
Card, Card,
Divider, Divider,
List,
Menu, Menu,
MenuProps, MenuProps,
Popover, Popover,
@ -16,12 +17,11 @@ import { PageAssitDatabase } from "@/db"
import { EraserIcon, PanelLeftIcon } from "lucide-react" import { EraserIcon, PanelLeftIcon } from "lucide-react"
import { useTranslation } from "react-i18next" import { useTranslation } from "react-i18next"
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query" import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"
import { useOptionLayoutContext } from "@/components/Layouts/Layout.tsx" import { HistoryContext } from "@/components/Layouts/Layout.tsx"
import { PlusOutlined, RightOutlined } from "@ant-design/icons" import { PlusOutlined, RightOutlined } from "@ant-design/icons"
import { qaPrompt } from "@/libs/playground.tsx" import { qaPrompt } from "@/libs/playground.tsx"
import { ProviderIcons } from "@/components/Common/ProviderIcon.tsx" import { ProviderIcons } from "@/components/Common/ProviderIcon.tsx"
import { fetchChatModels } from "@/services/ollama.ts" import { fetchChatModels } from "@/services/ollama.ts"
import logo from "@/assets/logo.png"
const ModelIcon = () => { const ModelIcon = () => {
return ( return (
@ -41,10 +41,10 @@ const ModelIcon = () => {
) )
} }
export const PlaygroundSidebar = () => { export const PlaygroundHistory = () => {
const { setSystemPrompt } = useStoreChatModelSettings() const { setSystemPrompt } = useStoreChatModelSettings()
const { showOptionSidebar, setShowOptionSidebar, setShowVideo } = useOptionLayoutContext() const { show, setShow } = useContext(HistoryContext)
const { const {
setMessages, setMessages,
@ -55,8 +55,7 @@ export const PlaygroundSidebar = () => {
selectedModel, selectedModel,
setSelectedModel, setSelectedModel,
temporaryChat, temporaryChat,
setSelectedSystemPrompt, setSelectedSystemPrompt
stopStreamingRequest
} = useMessageOption() } = useMessageOption()
const { t } = useTranslation(["option", "common", "settings"]) const { t } = useTranslation(["option", "common", "settings"])
@ -68,21 +67,13 @@ export const PlaygroundSidebar = () => {
return [ return [
{ {
key: "qaPrompt", key: "qaPrompt",
label: "热点问题", label: "热门搜索",
type: "group" as const, type: "group" as const,
children: qaPrompt.map((item) => { children: qaPrompt.map((item) => {
return { return {
key: item.id, key: item.id,
label: ( label: <span title={item.title}>{item.title}</span>,
<div className="flex items-center gap-2 truncate w-full"> icon: <p className="w-3.5">{item.icon}</p>
<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>
)
} }
}) })
} }
@ -106,36 +97,32 @@ export const PlaygroundSidebar = () => {
} }
// 大模型 // 大模型
const { data: models, isLoading: isModelsLoading } = useQuery({ const {
data: models,
isLoading: isModelsLoading,
refetch
} = useQuery({
queryKey: ["fetchModel"], queryKey: ["fetchModel"],
queryFn: () => fetchChatModels({ returnEmpty: true }), queryFn: () => fetchChatModels({ returnEmpty: true }),
refetchIntervalInBackground: false, refetchIntervalInBackground: false,
placeholderData: (prev) => prev placeholderData: (prev) => prev
}) })
// 是否隐藏logo
const hideLogo = useMemo(() => {
return localStorage.getItem("hideLogo") === "true"
}, [])
return ( return (
<Card <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]`} className={`flex flex-col [&_.ant-card-body]:h-full w-[300px] overflow-hidden h-full pb-5 transition-all duration-300 ease-in-out !bg-[#f3f4f6]`}
style={{ width: showOptionSidebar ? "300px" : "0" }}> style={{ width: show ? "300px" : "0" }}>
{/*Header*/} {/*Header*/}
<div className="flex flex-col overflow-y-hidden h-full"> <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 justify-between transition-all duration-300 ease-in-out w-[250px]">
<div className="flex items-center gap-2 cursor-pointer" onClick={() => setShowVideo(true)}> <h2 className="text-xl font-bold text-zinc-700 dark:text-zinc-300 mr-3">
{!hideLogo && <img src={logo} alt="logo" className="w-8" />} <span className="text-[#d30100]"></span>
<h2 className="text-xl font-bold text-zinc-700 dark:text-zinc-300 mr-3"> </h2>
<span className="text-[#d30100]"></span>
</h2>
</div>
<button <button
className="text-gray-500 dark:text-gray-400" className="text-gray-500 dark:text-gray-400"
onClick={() => { onClick={() => {
setShowOptionSidebar(!showOptionSidebar) setShow(!show)
}}> }}>
<PanelLeftIcon className="w-6 h-6" /> <PanelLeftIcon className="w-6 h-6" />
</button> </button>
@ -257,7 +244,7 @@ export const PlaygroundSidebar = () => {
</div> </div>
<div className="overflow-y-auto flex-1 pl-7"> <div className="overflow-y-auto flex-1 pl-7">
<Sidebar <Sidebar
onClose={() => setShowOptionSidebar(true)} onClose={() => setShow(true)}
setMessages={setMessages} setMessages={setMessages}
setHistory={setHistory} setHistory={setHistory}
setHistoryId={setHistoryId} setHistoryId={setHistoryId}
@ -267,7 +254,6 @@ export const PlaygroundSidebar = () => {
historyId={historyId} historyId={historyId}
setSystemPrompt={setSystemPrompt} setSystemPrompt={setSystemPrompt}
temporaryChat={temporaryChat} temporaryChat={temporaryChat}
stopStreamingRequest={stopStreamingRequest}
history={history} history={history}
/> />
</div> </div>

View File

@ -1,17 +1,8 @@
import React, { useEffect, useMemo, useState } from "react" import React from "react"
import { Avatar, Card } from "antd" import { Button, Card } from "antd"
import { AnimatePresence, motion } from "framer-motion" // 使用 CSS-in-JS 方式
import styled, { keyframes } from "styled-components" // 使用 CSS-in-JS 方式
import CountUp from "react-countup" import styled, { keyframes } from 'styled-components'
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` const rotate = keyframes`
0% { 0% {
@ -34,44 +25,27 @@ const breathe = keyframes`
} }
` `
// 花瓣 /* ${(props) => (props.playing ? "running" : "paused")}; */
const CircleElement = styled.div<{ delay: number }>` const CircleElement = styled.div<{ delay: number }>`
position: absolute; position: absolute;
width: 300px; width: 300px;
height: 160px; height: 160px;
background: #3b82f6; // blue-500 background: #3b82f6; // blue-500
opacity: 0.2; opacity: 0.2;
border-radius: 50%; border-radius: 50%;
top: 55%; top: 55%;
left: 50%; left: 50%;
animation: animation: ${rotate} 6s linear infinite, ${breathe} 2s infinite alternate;
${rotate} 6s linear infinite, animation-delay: ${props => props.delay}s;
${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< const SuccessIcon = () => {
SVGSVGElement,
React.SVGProps<SVGSVGElement>
>((props, ref) => {
return ( return (
<svg <svg
viewBox="0 0 24 24" viewBox="0 0 24 24"
fill="none" fill="none"
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
className="text-green-500" className="w-full h-full text-green-500">
ref={ref}
{...props}>
<path <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" 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" stroke="currentColor"
@ -81,28 +55,26 @@ const SuccessIcon = React.forwardRef<
/> />
</svg> </svg>
) )
}) }
const LoadingIcon = React.forwardRef< const LoadingIcon = () => {
SVGSVGElement,
React.SVGProps<SVGSVGElement>
>((props, ref) => {
return ( return (
<svg <svg
className="icon animate-spin" className="icon animate-spin"
viewBox="0 0 1024 1024" viewBox="0 0 1024 1024"
version="1.1" version="1.1"
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
p-id="29588" p-id="8408"
ref={ref} width="18"
{...props}> height="18">
<path <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" d="M512 128C299.936 128 128 296.672 128 504.736c0 130.784 67.904 245.984 170.976 313.536l35.52-52.256C248.576 709.696 192 613.696 192 504.736c0-173.376 143.264-313.92 320-313.92s320 140.544 320 313.92c0 98.112-45.856 185.696-117.696 243.296l-73.792-72.416V864h192l-72.768-71.36C843.072 723.52 896 620.16 896 504.704 896 296.672 724.064 128 512 128z"
p-id="29589" fill="#52c41a"
fill="#4284f6"></path> p-id="8409"></path>
</svg> </svg>
) )
}) }
const SearchIcon = () => { const SearchIcon = () => {
return ( return (
<svg <svg
@ -121,274 +93,42 @@ const SearchIcon = () => {
) )
} }
// 自定义统计卡片组件 export const PlaygroundIodRelevant: React.FC = () => {
const StatCard: React.FC<{ const data = [
number: number {
unit?: string title: <p><span className="text-[#9d0000]">29</span><span className="text-[#9d0000]">50</span></p>,
label: string description: <p><span className="text-green-700"> 4 </span></p>,
decimals?: number status: "success"
icon: React.ReactNode },
}> = ({ number, unit, label, decimals, icon }) => { {
return ( title: <p><span className="text-[#9d0000]">100</span><span className="text-[#9d0000]">2800</span></p>,
<div description: "已发现4个数据集",
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)] status: "success"
"> },
<Avatar size={40} className="!bg-[#3581e3b3]" icon={icon} /> {
title: <p><span className="text-[#9d0000]">1000</span><span className="text-[#9d0000]">12</span></p>,
status: "loading"
}
]
<div className="text-lg font-bold text-[#f00000]"> for (let i = 0; i < 10; i++) {
<CountUp data.push({
end={number} title: <p><span className="text-[#9d0000]">1000</span><span className="text-[#9d0000]">12</span>{i}</p>,
duration={2.5} description: "已发现4个数据集",
separator="," status: "success"
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 ( return (
<Card <Card
hoverable hoverable
variant="outlined" variant="outlined"
className={`${className} translate-y-[-2px] !bg-[#d0e6ff] shadow-md`}> className="flex flex-col h-full [&_.ant-card-body]:h-full [&_.ant-card-body]:!p-[20px] translate-y-[-2px] !bg-[#f0f9ff]"
>
<div className="h-full flex flex-col relative"> <div className="h-full flex flex-col relative">
{/* 花瓣效果 */} {/* 花瓣效果 */}
<div <div className="absolute inset-0 pointer-events-none z-0 overflow-hidden">
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"> <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={0} />
<CircleElement delay={1} /> <CircleElement delay={1} />
@ -398,83 +138,46 @@ export const PlaygroundIodRelevant: React.FC<Props> = ({ className }) => {
{/* Header */} {/* Header */}
<div className="p-3"> <div className="p-3">
<h2 className="text-xl font-semibold text-[#1a3c87] flex justify-center items-center"> <h2 className="text-xl font-semibold text-[#08307f] flex justify-between items-center">
<div className="flex items-center gap-2"> <div className='flex items-center gap-2'>
<SearchIcon /> <SearchIcon />
{currentIodMessage ? "科创数联网深度搜索" : "科创数联网连接资源"}
</div> </div>
{/*<button className="bg-[#2563eb1a] text-[#08307f] font-medium py-1 px-3 rounded-full text-sm hover:bg-[#2563eb1a] transition-colors float-right">*/} <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}个结果*/} {data.length}
{/*</button>*/} </button>
</h2> </h2>
<p className="text-sm text-[#1a3c87] mt-1 text-center"> <p className="text-sm text-[#08307f] mt-1 align-middle">
{currentIodMessage
? "下面是在科创数联网上进行深度搜索得到的相关数据、场景和团队"
: "下面是科创数联网连接的数据、场景和团队"}
</p> </p>
</div> </div>
{/* Content */} {/* Content */}
<div className="space-y-2 flex-1 overflow-y-auto"> <div className="space-y-2 flex-1 overflow-y-auto">
{currentIodMessage ? ( {data.map((item, index) => (
<AnimatePresence mode="wait"> <Card
<motion.div className="[&>*:first-child]:!p-3 shadow-md"
key="search-results" key={index}
initial={{ opacity: 0, y: 10 }} >
animate={{ opacity: 1, y: 0 }} <div className="flex items-start gap-2">
exit={{ opacity: 0, y: -10 }} <div className="w-5 h-5 mt-1 flex-shrink-0">
transition={{ duration: 0.3 }} {item.status === "success" ? (
className="space-y-2 flex-1 overflow-y-auto"> <SuccessIcon />
{data.map((item, index) => ( ) : (
<FrostedGlassCard <LoadingIcon />
className="[&_.ant-card-body]:!p-3 [&_.ant-card-body]:h-full shadow-md min-h-[88px]" )}
key={index}> </div>
<div <div className="flex-1">
className={`flex flex-col gap-2 h-full items-start ${showSearchData ? "justify-start" : "justify-center"}`}> <p className="text-sm text-gray-700">{item.title}</p>
<div className="flex items-center gap-2"> {item.description && (
<div> <p className="text-xs text-gray-500 mt-1">
{iodSearch && iodLoading ? ( {item.description}
<LoadingIcon </p>
width={showSearchData ? 16 : 22} )}
height={showSearchData ? 16 : 22} </div>
/> </div>
) : ( </Card>
<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>
</div> </div>
</Card> </Card>

View File

@ -1,34 +1,31 @@
import Markdown from "../../Common/Markdown" import Markdown from "../../Common/Markdown"
import React from "react" import React from "react"
import { Collapse, Image, Popover, Tag, Tooltip } from "antd" import { Tag, Image, Tooltip, Collapse, Popover } 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, Star,
ThumbsUp,
ThumbsDown, ThumbsDown,
ThumbsUp MessageSquareShare,
ArrowUpSquare
} 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
@ -45,18 +42,16 @@ type Props = {
webSearch?: {} webSearch?: {}
isSearchingInternet?: boolean isSearchingInternet?: boolean
webSources?: any[] webSources?: any[]
iodSources?: AllIodRegistryEntry iodSources?: any[]
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: React.FC<Props> = (props) => { export const PlaygroundMessage = (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)
@ -65,10 +60,25 @@ export const PlaygroundMessage: React.FC<Props> = (props) => {
return ( return (
<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="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 <div className={`flex flex-row gap-1 md:gap-1 my-2 m-auto w-full ${props.isBot ? "" : "flex-row-reverse"}`}>
className={`flex flex-row gap-1 md:gap-1 my-2 m-auto w-full ${props.isBot ? "" : "flex-row-reverse"}`}> <div className="w-8 flex flex-col relative items-center justify-center bottom-[8px]">
<div className="flex flex-col gap-2"> <div className="relative h-7 w-7 p-1 rounded-sm text-white flex items-center justify-center text-opacity-100r">
<span className="text-xs font-bold text-gray-800 dark:text-white"></span> {props.isBot ? (
!props.botAvatar ? (
<div className="absolute h-8 w-8 rounded-full bg-gradient-to-r from-green-300 to-purple-400 hidden"></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">
</span>
{props.isBot && {props.isBot &&
props.isSearchingInternet && props.isSearchingInternet &&
@ -82,7 +92,7 @@ export const PlaygroundMessage: React.FC<Props> = (props) => {
</Tag> </Tag>
)} )}
</div> </div>
<div className={`flex flex-grow flex-col w-full`}> <div className={`flex flex-grow flex-col`}>
{!editMode ? ( {!editMode ? (
props.isBot ? ( props.isBot ? (
<> <>
@ -91,7 +101,6 @@ export const PlaygroundMessage: React.FC<Props> = (props) => {
return ( return (
<Collapse <Collapse
key={i} key={i}
defaultActiveKey={["reasoning"]}
className="border-none !mb-3" className="border-none !mb-3"
items={[ items={[
{ {
@ -121,18 +130,13 @@ export const PlaygroundMessage: React.FC<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={`bg-[#2563eb] font-normal rounded-tr-none className={`prose-lg dark:prose-invert whitespace-pre-line prose-p:leading-relaxed prose-pre:p-0 dark:prose-dark ${
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 ${ props.message_type &&
props.message_type && "italic dark:text-gray-400" "italic dark:text-gray-400"
} flex flex-row-reverse`}> } flex flex-row-reverse`}>
{props.message} {props.message}
{/*<span className="bg-[#0000000a] inline-block py-[9px] text-[#000000d9] rounded-xl px-4 font-light text-sm">{props.message}</span>*/}
</p> </p>
) )
) : ( ) : (
@ -166,10 +170,9 @@ export const PlaygroundMessage: React.FC<Props> = (props) => {
<Collapse <Collapse
className="mt-6" className="mt-6"
ghost ghost
// defaultActiveKey={['webSources']}
items={[ items={[
{ {
key: "webSources", key: "1",
label: ( label: (
<div className="italic text-gray-500 dark:text-gray-400"> <div className="italic text-gray-500 dark:text-gray-400">
{t("webCitations")} {t("webCitations")}
@ -191,42 +194,34 @@ export const PlaygroundMessage: React.FC<Props> = (props) => {
]} ]}
/> />
)} )}
{props.isBot && {props.isBot && props?.iodSources && props?.iodSources.length > 0 && (
props?.iodSources && <Collapse
Object.values(props?.iodSources) className="mt-6"
.map((item) => item.data) ghost
.flat().length > 0 && ( items={[
<Collapse {
className="mt-6" key: "1",
ghost label: (
// defaultActiveKey={['iod']} <div className="italic text-gray-500 dark:text-gray-400">
items={[ {t("iodCitations")}
{ </div>
key: "iod", ),
label: ( children: (
<div className="italic text-gray-500 dark:text-gray-400"> <div className="mb-3 flex flex-wrap gap-2">
{t("iodCitations")} {props?.iodSources?.map((source, index) => (
</div> <MessageSource
), onSourceClick={props.onSourceClick}
children: ( key={index}
<div className="mb-3 flex flex-wrap gap-2"> index={index}
{Object.values(props?.iodSources) source={source}
.map((item) => item.data) />
.flat() ))}
?.map((source, index) => ( </div>
<MessageSource )
onSourceClick={props.onSourceClick} }
key={index} ]}
index={index} />
source={source} )}
/>
))}
</div>
)
}
]}
/>
)}
{!props.isProcessing && !editMode ? ( {!props.isProcessing && !editMode ? (
<div <div
className={`space-x-2 gap-2 flex ${ className={`space-x-2 gap-2 flex ${
@ -239,7 +234,7 @@ export const PlaygroundMessage: React.FC<Props> = (props) => {
// : "flex" // : "flex"
}`}> }`}>
{props.isTTSEnabled && ( {props.isTTSEnabled && (
<Tooltip title={t("tts")} className="hidden"> <Tooltip title={t("tts")}>
<button <button
aria-label={t("tts")} aria-label={t("tts")}
onClick={() => { onClick={() => {
@ -262,18 +257,6 @@ export const PlaygroundMessage: React.FC<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
@ -297,7 +280,6 @@ export const PlaygroundMessage: React.FC<Props> = (props) => {
{props.generationInfo && ( {props.generationInfo && (
<Popover <Popover
className="hidden"
content={ content={
<GenerationInfo generationInfo={props.generationInfo} /> <GenerationInfo generationInfo={props.generationInfo} />
} }
@ -323,8 +305,8 @@ export const PlaygroundMessage: React.FC<Props> = (props) => {
)} )}
</> </>
)} )}
{(!props.hideEditAndRegenerate && !props.isBot) && ( {!props.hideEditAndRegenerate && (
<Tooltip title={t("edit")} className="hidden"> <Tooltip title={t("edit")}>
<button <button
onClick={() => setEditMode(true)} onClick={() => setEditMode(true)}
aria-label={t("edit")} aria-label={t("edit")}
@ -333,51 +315,51 @@ export const PlaygroundMessage: React.FC<Props> = (props) => {
</button> </button>
</Tooltip> </Tooltip>
)} )}
{ { (
<Tooltip title="收藏" className="hidden"> <Tooltip title="收藏">
<button <button
aria-label="收藏" 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"> 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" /> <Star className="w-3 h-3 text-gray-400 group-hover:text-gray-500" />
</button> </button>
</Tooltip> </Tooltip>
} )}
{ { (
<Tooltip title="发布语用" className="hidden"> <Tooltip title="发布语用">
<button <button
aria-label="发布语用" 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"> 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" /> <ArrowUpSquare className="w-3 h-3 text-gray-400 group-hover:text-gray-500" />
</button> </button>
</Tooltip> </Tooltip>
} )}
{ { (
<Tooltip title="发布对话" className="hidden"> <Tooltip title="发布对话">
<button <button
aria-label="发布对话" 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"> 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" /> <MessageSquareShare className="w-3 h-3 text-gray-400 group-hover:text-gray-500" />
</button> </button>
</Tooltip> </Tooltip>
} )}
{ { (
<Tooltip title="点赞" className="hidden"> <Tooltip title="点赞">
<button <button
aria-label="点赞" 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"> 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" /> <ThumbsUp className="w-3 h-3 text-gray-400 group-hover:text-gray-500" />
</button> </button>
</Tooltip> </Tooltip>
} )}
{ { (
<Tooltip title="点踩" className="hidden"> <Tooltip title="点踩">
<button <button
aria-label="点踩" 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"> 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" /> <ThumbsDown className="w-3 h-3 text-gray-400 group-hover:text-gray-500" />
</button> </button>
</Tooltip> </Tooltip>
} )}
</div> </div>
) : ( ) : (
// add invisible div to prevent layout shift // add invisible div to prevent layout shift

View File

@ -1,150 +1,143 @@
import React, { useMemo } from "react" import React, { useState } from "react"
import { DataNavigation } from "@/components/Common/DataNavigation.tsx" import { DataNavigation } from "@/components/Common/DataNavigation.tsx"
import { Card, Skeleton } from "antd" import { Card, Drawer, List } from "antd"
import { IodRegistryEntry } from "@/types/iod.ts"
import { useIodPlaygroundContext } from "@/components/Option/Playground/PlaygroundIod.tsx"
const defaultData: IodRegistryEntry[] = [ export const PlaygroundScene = () => {
{ // 模拟数据
name: "绿色化工工艺项目", const data = [
description: {
"基于生物基原料采用repeal2.0可降解材料技术,开发新型环保材料。", title: "绿色化工工艺项目",
doId: "CSTR:13552.11.01.61.2021.742" description:
}, "基于生物基原料采用repeal2.0可降解材料技术,开发新型环保材料。",
{ demander: "奥赛康药业 供方美国Propella公司"
name: "智能农业解决方案", },
description: "利用物联网技术,实现精准农业管理,提高农作物产量。", {
doId: "CSTR:14542.11.01.61.2031.528" title: "智能农业解决方案",
}, description: "利用物联网技术,实现精准农业管理,提高农作物产量。",
{ demander: "奥赛康药业 供方美国Propella公司"
name: "新能源汽车电池技术", },
description: "研发高能量密度、长寿命的新型电池材料,推动电动汽车发展。", {
doId: "CSTR:147842.11.04.91.2031.680" title: "新能源汽车电池技术",
}, description: "研发高能量密度、长寿命的新型电池材料,推动电动汽车发展。",
{ demander: "奥赛康药业 供方美国Propella公司"
name: "碳捕集与封存技术", },
description: "开发高效的碳捕集技术,减少工业排放,助力碳中和目标。", {
doId: "CSTR:14242.19.11.61.2131.428" title: "碳捕集与封存技术",
} description: "开发高效的碳捕集技术,减少工业排放,助力碳中和目标。",
] demander: "奥赛康药业 供方美国Propella公司"
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 = { for (let i = 0; i < 10; i++) {
loading: boolean data.push({
data: IodRegistryEntry[] title: "开发新型电池材料",
truncate?: boolean description: "研发高能量密度、长寿命的新型电池材料,推动电动汽车发展。",
} demander: "奥赛康药业 供方美国Propella公司"
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 = { const [open, setOpen] = useState(false)
className?: string
}
export const PlaygroundScene: React.FC<Props> = ({ className }) => {
const { iodLoading } = useMessageOption()
const { const showDrawer = () => {
setShowPlayground, setOpen(true)
setDetailHeader, }
setDetailMain,
currentIodMessage
} = useIodPlaygroundContext()
const data = useMemo<IodRegistryEntry[]>(() => { const onClose = () => {
return currentIodMessage setOpen(false)
? 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 ( return (
<Card className={`${className}`} hoverable> <Card
<div className="h-full flex flex-col gap-2 relative"> className="h-full [&_.ant-card-body]:h-full [&_.ant-card-body]:!p-[20px] overflow-y-hidden"
hoverable>
<div className="h-full flex flex-col relative">
{/* 数据导航 */} {/* 数据导航 */}
<Header title={title} onClick={showMore} /> <DataNavigation
Header={
<div className="flex items-center text-[#52c41a] 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="#52c41a"
p-id="6236"></path>
</svg>
</div>
}
onClick={showDrawer}
/>
{/* 数据列表 */} {/* 场景列表 */}
<Main loading={iodLoading && Boolean(currentIodMessage)} data={data.slice(0, 3)} /> <div className="space-y-1.5 flex-1 overflow-y-auto">
{data.slice(0, 3).map((item, index) => (
<Card
key={index}
className="[&_.ant-card-body]:!p-2 !bg-[gb(248, 248, 248)] border !border-[#e9e9e9]">
<div className="flex flex-col gap-0.5">
<h3 className="text-base font-medium mb-1 text-[#222222] line-clamp-2">
{item.title}
</h3>
<span className="text-sm text-[#383838] line-clamp-2">
{item.description}
</span>
<p className="text-[#828282] text-xs truncate">
{item.demander}
</p>
<p className="flex items-center gap-1.5">
<span className="inline-block text-[#003AD4] bg-[#003ad430] h-5 leading-5 mt-1 text-xs rounded-full px-2.5">
</span>
<span className="inline-block text-[#00BF68] bg-[#00bf6830] h-5 leading-5 mt-1 text-xs rounded-full px-2.5">
</span>
</p>
</div>
</Card>
))}
</div>
</div> </div>
{/* 抽屉 */}
<Drawer
title="相关场景"
closable={{ "aria-label": "Close Button" }}
onClose={onClose}
open={open}
width="33.33%">
<div className="grid grid-cols-1 gap-3 overflow-y-auto">
{data.slice(0, 3).map((item, index) => (
<Card
key={index}
hoverable
className="[&_.ant-card-body]:!p-2 !bg-[gb(248, 248, 248)] border !border-[#e9e9e9]">
<div className="flex flex-col gap-0.5">
<h3 className="text-base font-medium mb-1 text-[#222222]">
{item.title}
</h3>
<span className="text-sm text-[#383838]">
{item.description}
</span>
<p className="text-[#828282] text-xs">{item.demander}</p>
<p className="flex items-center gap-1.5">
<span className="inline-block text-[#003AD4] bg-[#003ad430] h-5 leading-5 mt-1 text-xs rounded-full px-2.5">
</span>
<span className="inline-block text-[#00BF68] bg-[#00bf6830] h-5 leading-5 mt-1 text-xs rounded-full px-2.5">
</span>
</p>
</div>
</Card>
))}
</div>
</Drawer>
</Card> </Card>
) )
} }

View File

@ -1,158 +1,141 @@
import React, { useMemo } from "react" import React, { useState } from "react"
import { DataNavigation } from "@/components/Common/DataNavigation.tsx" import { DataNavigation } from "@/components/Common/DataNavigation.tsx"
import { Card, Skeleton } from "antd" import { Card, Drawer, List } from "antd"
import { IodRegistryEntry } from "@/types/iod.ts"
import { useIodPlaygroundContext } from "@/components/Option/Playground/PlaygroundIod.tsx"
const defaultData: IodRegistryEntry[] = [ export const PlaygroundTeam = () => {
{ // 模拟数据
name: "北京大学", const data = [
description: {
"北大是常为新的,改进的运动的先锋,要使中国向着好的,往上的道路走。", title: "绿色化工工艺项目",
doId: "12100000400002259P" description:
}, "基于生物基原料采用repeal2.0可降解材料技术,开发新型环保材料。",
{ demander: "奥赛康药业 供方美国Propella公司"
name: "长三角先进材料研究院", },
description: "由江苏省人民政府联合中国科学院、中国钢研科技集团和中国", {
doId: "91320507MAEKWL5Y2L" title: "智能农业解决方案",
}, description: "利用物联网技术,实现精准农业管理,提高农作物产量。",
{ demander: "奥赛康药业 供方美国Propella公司"
name: "伊利诺伊大学香槟分校UIUC", },
description: "创建于1867年坐落于伊利诺伊州双子城厄巴纳香槟市", {
doId: "bdware.org/uiuc" title: "新能源汽车电池技术",
} description: "研发高能量密度、长寿命的新型电池材料,推动电动汽车发展。",
] demander: "奥赛康药业 供方美国Propella公司"
},
type HeaderProps = { {
title: string title: "碳捕集与封存技术",
showButton?: boolean description: "开发高效的碳捕集技术,减少工业排放,助力碳中和目标。",
onClick?: () => void demander: "奥赛康药业 供方美国Propella公司"
}
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 = { for (let i = 0; i < 10; i++) {
loading: boolean data.push({
data: IodRegistryEntry[] title: "开发新型电池材料",
truncate?: boolean description: "研发高能量密度、长寿命的新型电池材料,推动电动汽车发展。",
// 水平展示三个还是按列展示(页面和详情展示不一样) demander: "奥赛康药业 供方美国Propella公司"
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 = { const [open, setOpen] = useState(false)
className?: string
}
export const PlaygroundTeam: React.FC<Props> = ({ className }) => {
const { iodLoading } = useMessageOption()
const { const showDrawer = () => {
setShowPlayground, setOpen(true)
setDetailHeader, }
setDetailMain,
currentIodMessage
} = useIodPlaygroundContext()
const data = useMemo<IodRegistryEntry[]>(() => { const onClose = () => {
return currentIodMessage setOpen(false)
? 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 ( return (
<Card className={`${className}`} hoverable> <Card
<div className="h-full flex flex-col gap-2 relative"> className="h-full [&_.ant-card-body]:h-full [&_.ant-card-body]:!p-[20px] overflow-y-hidden"
hoverable>
<div className="h-full flex flex-col relative">
{/* 数据导航 */} {/* 数据导航 */}
<Header title={title} onClick={showMore} /> <DataNavigation
{/* 数据列表 */} Header={
<Main loading={iodLoading && Boolean(currentIodMessage)} data={data.slice(0, 3)} /> <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>
</div>
}
onClick={showDrawer}
/>
{/* 场景列表 */}
<div className="grid grid-cols-3 gap-3 flex-1 overflow-y-auto">
{data.slice(0, 5).map((item, index) => (
<Card
key={index}
className="[&_.ant-card-body]:!p-2 !bg-[gb(248, 248, 248)] border !border-[#e9e9e9]">
<div className="flex flex-col gap-0.5">
<h3 className="text-base font-medium mb-1 text-[#222222] line-clamp-2">
{item.title}
</h3>
<p className="text-[#828282] text-xs truncate">
{item.description}
</p>
<p className="flex items-center gap-1.5">
<span className="inline-block text-[#BE0BAC] bg-[#be0bac30] h-5 leading-5 mt-1 text-xs rounded-full px-2.5">
</span>
<span className="inline-block text-[#EB1C1C] bg-[#eb1c1c30] h-5 leading-5 mt-1 text-xs rounded-full px-2.5">
</span>
</p>
</div>
</Card>
))}
</div>
</div> </div>
{/* 抽屉 */}
<Drawer
title="相关团队"
closable={{ "aria-label": "Close Button" }}
onClose={onClose}
open={open}
width="33.33%">
<div className="grid grid-cols-1 gap-3 overflow-y-auto">
{data.slice(0, 5).map((item, index) => (
<Card
key={index}
hoverable
className="[&_.ant-card-body]:!p-2 !bg-[gb(248, 248, 248)] border !border-[#e9e9e9]">
<div className="flex flex-col gap-0.5">
<h3 className="text-base font-medium mb-1 text-[#222222]">
{item.title}
</h3>
<p className="text-[#828282] text-xs">{item.description}</p>
<p className="flex items-center gap-1.5">
<span className="inline-block text-[#BE0BAC] bg-[#be0bac30] h-5 leading-5 mt-1 text-xs rounded-full px-2.5">
</span>
<span className="inline-block text-[#EB1C1C] bg-[#eb1c1c30] h-5 leading-5 mt-1 text-xs rounded-full px-2.5">
</span>
</p>
</div>
</Card>
))}
</div>
</Drawer>
</Card> </Card>
) )
} }

View File

@ -1,13 +1,15 @@
import { Form, Image, Input, message, Modal } from "antd" import { Form, Image, Input, Modal, Tooltip, message } from "antd"
import React from "react" import { Share } from "lucide-react"
import { useState } from "react"
import type { Message } from "~/store/option"
import Markdown from "./Markdown" import Markdown from "./Markdown"
import React from "react"
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[]

View File

@ -1,21 +0,0 @@
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>
)
})

View File

@ -1,26 +0,0 @@
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>
)
})

View File

@ -1,26 +0,0 @@
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>
)
})

View File

@ -1,24 +0,0 @@
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>
)
})

View File

@ -1,24 +0,0 @@
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>
)
})

View File

@ -1,27 +0,0 @@
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>
)
})

View File

@ -1,33 +0,0 @@
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>
)
})

View File

@ -1,22 +0,0 @@
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>
)
})

View File

@ -1,26 +0,0 @@
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>
)
})

View File

@ -1,23 +0,0 @@
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>
)
})

View File

@ -1,23 +0,0 @@
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>
)
})

View File

@ -1,24 +0,0 @@
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>
)
})

View File

@ -1,21 +0,0 @@
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>
)
})

View File

@ -1,22 +0,0 @@
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>
)
})

View File

@ -1,23 +0,0 @@
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>
)
})

File diff suppressed because one or more lines are too long

View File

@ -1,23 +0,0 @@
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>
)
})

View File

@ -1,24 +0,0 @@
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>
)
})

View File

@ -1,22 +0,0 @@
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>
)
})

View File

@ -1,53 +1,27 @@
import React, { useMemo, useState } from "react" import React, { useContext } from "react"
import { useOptionLayoutContext } from "@/components/Layouts/Layout.tsx" import { HistoryContext } from "@/components/Layouts/Layout.tsx"
import { PanelLeftIcon } from "lucide-react" import { PanelLeftIcon } from "lucide-react"
import { Button, Tooltip } from "antd" import { Button } from "antd"
import { PlusOutlined } from "@ant-design/icons" import { PlusOutlined } from "@ant-design/icons"
import { useMessageOption } from "@/hooks/useMessageOption.tsx" import { useMessageOption } from "@/hooks/useMessageOption.tsx"
import { useTranslation } from "react-i18next" import { useTranslation } from "react-i18next"
import { NavLink, useLocation } from "react-router-dom"
import logo from "@/assets/logo.png"
import { BellIcon } from "@/components/Icons/Bell.tsx"
import { ShareIcon } from "@/components/Icons/Share.tsx"
import { NotCollectIcon } from "@/components/Icons/NotCollect.tsx"
import { CollectIcon } from "@/components/Icons/Collect.tsx"
import { SettingIcon } from "@/components/Icons/Setting.tsx"
type Props = {} export const Header = () => {
const { show, setShow } = useContext(HistoryContext)
export const Header: React.FC<Props> = ({}) => {
const location = useLocation()
const { showOptionSidebar, setShowOptionSidebar } = useOptionLayoutContext()
const showLeft = useMemo<boolean>(() => {
console.log(location.pathname)
if (location.pathname.includes("/settings")) {
return true
}
return showOptionSidebar
}, [location.pathname, showOptionSidebar])
const { t } = useTranslation(["option", "common", "settings"]) const { t } = useTranslation(["option", "common", "settings"])
const { clearChat } = useMessageOption() const { clearChat } = useMessageOption()
// 是否隐藏logo
const hideLogo = useMemo(() => {
return localStorage.getItem("hideLogo") === "true"
}, [])
const [collect, setCollect] = useState<boolean>(false)
return ( return (
<div <div
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]" : ""}`}> className={`w-full h-[60px] absolute inset-0 pl-5 z-10 flex items-center transition-all duration-300 ease-in-out ${show ? "left-[300px]" : ""}`}>
{/*控制侧边栏显示隐藏与新建对话*/} {/*控制侧边栏显示隐藏与新建对话*/}
{!showLeft && ( {!show && (
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
<button <button
className="text-gray-500 dark:text-gray-400" className="text-gray-500 dark:text-gray-400"
onClick={() => { onClick={() => {
setShowOptionSidebar(!showOptionSidebar) setShow(!show)
}}> }}>
<PanelLeftIcon className="w-6 h-6" /> <PanelLeftIcon className="w-6 h-6" />
</button> </button>
@ -73,85 +47,21 @@ export const Header: React.FC<Props> = ({}) => {
</Button> </Button>
</div> </div>
)} )}
{location.pathname.includes("/settings") && (
<h2 className="text-xl font-bold text-zinc-700 dark:text-zinc-300 mr-3">
<NavLink
to="/"
className="!text-gray-500 dark:text-gray-400 flex items-center gap-2 hover:text-gray-600 dark:hover:text-gray-300 transition-colors">
{!hideLogo && <img src={logo} alt="logo" className="w-8" />}
<p>
<span className="text-[#d30100]"></span>
</p>
</NavLink>
</h2>
)}
{/* 项目标题 */} {/* 项目标题 */}
<div <div
className={` className={`
absolute left-1/2 transform -translate-x-1/2 absolute left-1/2 transform -translate-x-1/2
w-[600px] h-[60px] dark:bg-black w-[600px] h-[55px] bg-white dark:bg-black
flex items-center justify-center flex items-center justify-center
transition-[top] drop-shadow shadow-[0px_0px_5px_rgba(0,0,0,0.2)]
${showOptionSidebar ? "-top-[60px]" : "-top-[2px] delay-200"} rounded-b-[15px]
transition-[top]
${show ? "-top-[56px]" : "-top-[1px] delay-200"}
`}> `}>
<svg <h2 className="text-xl font-bold text-zinc-700 dark:text-zinc-300 mr-3">
className="icon" <span className="text-[#d30100]"></span>
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> </h2>
</div> </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>
</Tooltip>
</div>
</div> </div>
) )
} }

View File

@ -1,86 +1,47 @@
import React, { useContext, useState } from "react" import React, { useCallback, useEffect, useState } from "react"
import { CurrentChatModelSettings } from "../Common/Settings/CurrentChatModelSettings" import { CurrentChatModelSettings } from "../Common/Settings/CurrentChatModelSettings"
import { Header } from "./Header.tsx" import { Header } from "./Header.tsx"
import IodVideo from "@/components/Option/VideoPlayer"
interface OptionLayoutContextType { interface History {
showOptionSidebar: boolean show: boolean
setShowOptionSidebar: (show: boolean) => void setShow: (show: boolean) => void
showVideo: boolean
setShowVideo: (show: boolean) => void
} }
const OptionLayoutContext = React.createContext<OptionLayoutContextType>({ export const HistoryContext = React.createContext<History>({
showOptionSidebar: true, show: true,
setShowOptionSidebar: () => {}, setShow: () => {}
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 [showHistory, setShowHistory] = useState(true)
const [openModelSettings, setOpenModelSettings] = useState(false) const [openModelSettings, setOpenModelSettings] = useState(false)
const historyContextValue = {
show: showHistory,
setShow: setShowHistory
}
const useToggle = useCallback(() => {
setShowHistory(!showHistory)
}, [showHistory])
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">*/}
{/*</div>*/} {/*</div>*/}
{/* <div className="relative flex h-full flex-col items-center"> */} {/* <div className="relative flex h-full flex-col items-center"> */}
<OptionLayoutProvider> <HistoryContext.Provider value={historyContextValue}>
<OptionLayoutMain>{children}</OptionLayoutMain> <Header />
</OptionLayoutProvider> {children}
</HistoryContext.Provider>
{/* </div> */} {/* </div> */}
<CurrentChatModelSettings <CurrentChatModelSettings
open={openModelSettings} open={openModelSettings}
setOpen={setOpenModelSettings} setOpen={setOpenModelSettings}

View File

@ -1,18 +1,17 @@
import { import {
BlocksIcon,
BookIcon, BookIcon,
BrainCircuitIcon, BrainCircuitIcon,
ChromeIcon,
CombineIcon,
CpuIcon,
InfoIcon,
OrbitIcon, OrbitIcon,
ShareIcon ShareIcon,
BlocksIcon,
InfoIcon,
CombineIcon,
ChromeIcon,
CpuIcon
} 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[]) {
@ -83,12 +82,6 @@ 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"

View File

@ -1,10 +1,13 @@
import React from "react" import React, { useContext } from "react"
import { Card } from "antd"
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 { PlaygroundData } from '@/components/Common/Playground/Data.tsx'
import { PlaygroundScene } from "@/components/Common/Playground/Scene.tsx"
import { import {
formatToChatHistory, formatToChatHistory,
@ -16,7 +19,10 @@ 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" import { PlaygroundTeam } from "@/components/Common/Playground/Team.tsx"
import { PlaygroundHistory } from "@/components/Common/Playground/History.tsx"
import { PlaygroundIodRelevant } from "@/components/Common/Playground/IodRelevant.tsx"
export const Playground = () => { export const Playground = () => {
const drop = React.useRef<HTMLDivElement>(null) const drop = React.useRef<HTMLDivElement>(null)
@ -139,15 +145,14 @@ export const Playground = () => {
className={`relative flex gap-3 h-full 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 /> <PlaygroundHistory />
<div className="h-full flex-1 overflow-x-hidden prose-lg flex flex-col items-center [&>*]:max-w-[848px] pt-[60px]"> <div className="relative h-full flex-1 prose-lg flex flex-col items-center [&>*]:max-w-[848px] pt-[60px]">
<div <div
ref={containerRef} ref={containerRef}
className="custom-scrollbar flex h-auto w-full flex-col items-center px-5"> className="custom-scrollbar flex h-auto w-full flex-col items-center overflow-x-hidden overflow-y-auto px-5">
<PlaygroundChat /> <PlaygroundChat />
</div> </div>
<div <div className="relative bottom-0 w-full">
className={`${messages.length ? "absolute" : "relative"} bottom-0 w-full`}>
{!isAtBottom && ( {!isAtBottom && (
<div className="absolute 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
@ -160,7 +165,23 @@ export const Playground = () => {
<PlaygroundForm dropedFile={dropedFile} /> <PlaygroundForm dropedFile={dropedFile} />
</div> </div>
</div> </div>
<PlaygroundIod /> {/*auto_530px_165px*/}
{messages.length && (
<div
className="w-4/12 h-full grid grid-rows-10 gap-3 pt-16 pr-5 pb-0"
style={{ paddingTop: "4rem" }}>
<div className="w-full row-span-4">
<PlaygroundIodRelevant />
</div>
<div className="w-full row-span-4 grid grid-cols-2 gap-3 custom-scrollbar">
<PlaygroundData />
<PlaygroundScene />
</div>
<div className="w-full row-span-2 pb-3">
<PlaygroundTeam />
</div>
</div>
)}
</div> </div>
) )
} }

View File

@ -11,8 +11,7 @@ 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)
@ -28,7 +27,6 @@ export const PlaygroundChat = () => {
{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}
@ -51,12 +49,10 @@ 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>
{messages.length !== 0 && <div className="w-full pb-[157px]"></div>} {/*<div className="w-full pb-[0px]"></div>*/}
<MessageSourcePopup <MessageSourcePopup
open={isSourceOpen} open={isSourceOpen}
setOpen={setIsSourceOpen} setOpen={setIsSourceOpen}

View File

@ -1,3 +1,5 @@
import { Card, Col, Row } from "antd"
import { useMessageOption } from "@/hooks/useMessageOption.tsx" import { useMessageOption } from "@/hooks/useMessageOption.tsx"
import { useMutation, useQueryClient } from "@tanstack/react-query" import { useMutation, useQueryClient } from "@tanstack/react-query"
import { qaPrompt } from "@/libs/playground.tsx" import { qaPrompt } from "@/libs/playground.tsx"
@ -21,20 +23,36 @@ export const PlaygroundEmpty = () => {
} }
return ( return (
<div className="w-full pb-4 pt-[20%] grid grid-cols-3 gap-3"> <div className="w-full p-4">
{qaPrompt.map((item, index) => ( {/* 标题区域 */}
<div <div className="mb-4">
key={item.id} <h2
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" className="text-xl font-bold text-gray-800"
onClick={() => handleQuestion(item.title)}> style={{ lineHeight: "0" }}>
<div className="flex items-center">
<div className="text-blue-500 mr-2 w-10">{item.icon}</div> </h2>
<div className="text-sm text-gray-800"> <p className="text-sm text-gray-500"></p>
{item.title} </div>
</div>
</div> {/* 卡片网格布局 */}
</div> <Row gutter={[16, 16]} className="w-full">
))} {qaPrompt.map((item, index) => (
<Col key={index} xs={24} sm={12} md={8}>
<Card
hoverable
style={{ backgroundColor: "#f3f4f6" }}
className="border border-gray-200 rounded-lg shadow-sm hover:shadow-md transition-shadow duration-200 cursor-pointer"
onClick={() => handleQuestion(item.title)}>
<div className="flex items-center">
<div className="text-blue-500 mr-2 w-10">{item.icon}</div>
<div className="font-medium text-sm text-gray-800">
{item.title}
</div>
</div>
</Card>
</Col>
))}
</Row>
</div> </div>
) )
} }

View File

@ -1,24 +1,17 @@
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, { useMemo } from "react" import React 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 { import { Checkbox, Dropdown, Switch, Tooltip } from "antd"
Button, import { Image } from "antd"
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, PiNetwork } from "react-icons/pi" import { PiGlobe, PiNetwork } from "react-icons/pi"
import { handleChatInputKeyDown } from "@/utils/key-down" import { handleChatInputKeyDown } from "@/utils/key-down"
@ -213,47 +206,14 @@ export const PlaygroundForm = ({ dropedFile }: Props) => {
} }
} }
const iodSearchItems = useMemo<MenuProps["items"]>(() => {
return [
{
key: 0,
label: (
<div
onClick={() => {
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 ( return (
<div className="flex w-full flex-col items-center pt-1 px-5 pb-4"> <div className="flex w-full flex-col items-center p-2 px-5 pt-1 pb-4">
<div className="relative z-10 flex w-full flex-col items-center justify-center gap-2 text-base"> <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="relative flex w-full flex-row justify-center gap-2 lg:w-5/5">
<div <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`}> className={` bg-neutral-50 dark:bg-[#262626] relative w-full max-w-[65rem] p-1 backdrop-blur-lg duration-100 border border-gray-300 rounded-xl dark:border-gray-600
${temporaryChat ? "!bg-gray-200 dark:!bg-black " : ""}
`}>
<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"
@ -274,7 +234,8 @@ export const PlaygroundForm = ({ dropedFile }: Props) => {
/> />
</div> </div>
<div> <div>
<div className={`flex bg-transparent `}> <div
className={`flex bg-transparent `}>
<form <form
onSubmit={form.onSubmit(async (value) => { onSubmit={form.onSubmit(async (value) => {
stopListening() stopListening()
@ -343,74 +304,60 @@ export const PlaygroundForm = ({ dropedFile }: Props) => {
<div className="flex"> <div className="flex">
{!selectedKnowledge && ( {!selectedKnowledge && (
<div> <div>
{/* 展示隐藏深度搜索*/} <Tooltip title={t("tooltip.searchInternet")}>
<Tooltip <div className="inline-flex items-center gap-2">
title={t("tooltip.searchInternet")} <PiGlobe
className="hidden"> className={`h-5 w-5 dark:text-gray-300 `}
<div className="inline-flex items-center gap-2"> />
<PiGlobe <Switch
className={`h-5 w-5 dark:text-gray-300 `} value={webSearch}
/> onChange={(e) => setWebSearch(e)}
<Switch checkedChildren={t("form.webSearch.on")}
value={webSearch} unCheckedChildren={t("form.webSearch.off")}
onChange={(e) => setWebSearch(e)} />
checkedChildren={t("form.webSearch.on")} </div>
unCheckedChildren={t("form.webSearch.off")} </Tooltip>
/> <Tooltip title={t("tooltip.searchIod")} className="ml-3">
</div> <div className="inline-flex items-center gap-2">
</Tooltip> <PiNetwork
<Dropdown className={`h-5 w-5 dark:text-gray-300 `}
menu={{ items: iodSearchItems }} />
placement="bottom" <Switch
trigger={["click"]} value={iodSearch}
arrow> onChange={(e) => setIodSearch(e)}
<Button checkedChildren={t("form.webSearch.on")}
color="default" unCheckedChildren={t("form.webSearch.off")}
variant="filled" />
size="large" </div>
className="w-full mt-4 hover:!bg-[#0057ff1a]" </Tooltip>
style={ </div>
iodSearch
? {
color: "#0057ff",
background: "#0057ff0f",
border: "1px solid #0066ff26"
}
: {}
}>
<PiNetwork className="h-5 w-5" />
{iodSearch ? ":开" : ""}
</Button>
</Dropdown>
</div>
)} )}
</div> </div>
<div className="flex !justify-end gap-1"> <div className="flex !justify-end gap-3">
{!selectedKnowledge && ( {!selectedKnowledge && (
<Tooltip title={t("tooltip.uploadImage")}> <Tooltip title={t("tooltip.uploadImage")}>
<Button <button
color="default" type="button"
variant="text"
onClick={() => { onClick={() => {
inputRef.current?.click() inputRef.current?.click()
}} }}
className={`!px-[5px] flex items-center justify-center dark:text-gray-300 ${ className={`flex items-center justify-center dark:text-gray-300 ${
chatMode === "rag" ? "hidden" : "block" chatMode === "rag" ? "hidden" : "block"
}`}> }`}>
<ImageIcon strokeWidth={1} className="h-5 w-5" /> <ImageIcon className="h-5 w-5" />
</Button> </button>
</Tooltip> </Tooltip>
)} )}
{browserSupportsSpeechRecognition && ( {browserSupportsSpeechRecognition && (
<Tooltip title={t("tooltip.speechToText")}> <Tooltip title={t("tooltip.speechToText")}>
<Button <button
color="default" type="button"
variant="text"
onClick={async () => { onClick={async () => {
if (isListening) { if (isListening) {
stopSpeechRecognition() stopSpeechRecognition()
} else { } else {
console.log("开始语音识别,语言:", speechToTextLanguage);
resetTranscript() resetTranscript()
startListening({ startListening({
continuous: true, continuous: true,
@ -418,43 +365,40 @@ export const PlaygroundForm = ({ dropedFile }: Props) => {
}) })
} }
}} }}
className={`flex items-center justify-center dark:text-gray-300 !px-[5px]`}> className={`flex items-center justify-center dark:text-gray-300`}>
{!isListening ? ( {!isListening ? (
<MicIcon strokeWidth={1} className="h-5 w-5" /> <MicIcon 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 <MicIcon className="h-5 w-5" />
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}
// icon={ className="!justify-end !w-auto"
// <svg icon={
// xmlns="http://www.w3.org/2000/svg" <svg
// fill="none" xmlns="http://www.w3.org/2000/svg"
// viewBox="0 0 24 24" fill="none"
// strokeWidth={1.5} viewBox="0 0 24 24"
// stroke="currentColor" strokeWidth={1.5}
// className="w-5 h-5"> stroke="currentColor"
// <path className="w-5 h-5">
// strokeLinecap="round" <path
// strokeLinejoin="round" strokeLinecap="round"
// d="m19.5 8.25-7.5 7.5-7.5-7.5" strokeLinejoin="round"
// /> d="m19.5 8.25-7.5 7.5-7.5-7.5"
// </svg> />
// } </svg>
}
menu={{ menu={{
items: [ items: [
{ {
@ -484,6 +428,20 @@ 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>

View File

@ -1,160 +0,0 @@
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>
)
}

View File

@ -0,0 +1,25 @@
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>
)
}

View File

@ -1,33 +0,0 @@
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>
)
}

View File

@ -1,21 +1,21 @@
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query" import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"
import { import {
deleteByHistoryId, PageAssitDatabase,
formatToChatHistory, formatToChatHistory,
formatToMessage, formatToMessage,
getPromptById, deleteByHistoryId,
PageAssitDatabase, updateHistory,
pinHistory, pinHistory,
updateHistory getPromptById
} from "@/db" } from "@/db"
import { Dropdown, Empty, Menu, Skeleton, Tooltip } from "antd" import { Empty, Skeleton, Dropdown, Menu, Tooltip } from "antd"
import { import {
BotIcon,
MoreVertical,
PencilIcon, PencilIcon,
Trash2,
MoreVertical,
PinIcon, PinIcon,
PinOffIcon, PinOffIcon,
Trash2 BotIcon
} 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,7 +24,7 @@ import {
getLastUsedChatSystemPrompt, getLastUsedChatSystemPrompt,
lastUsedChatModelEnabled lastUsedChatModelEnabled
} from "@/services/model-settings" } from "@/services/model-settings"
import { useMessageOption } from "@/hooks/useMessageOption.tsx" import { useState } from "react"
type Props = { type Props = {
onClose: () => void onClose: () => void
@ -34,7 +34,6 @@ type Props = {
setSelectedModel: (model: string) => void setSelectedModel: (model: string) => void
setSelectedSystemPrompt: (prompt: string) => void setSelectedSystemPrompt: (prompt: string) => void
setSystemPrompt: (prompt: string) => void setSystemPrompt: (prompt: string) => void
stopStreamingRequest: () => void
clearChat: () => void clearChat: () => void
temporaryChat: boolean temporaryChat: boolean
historyId: string historyId: string
@ -48,7 +47,6 @@ export const Sidebar = ({
setHistoryId, setHistoryId,
setSelectedModel, setSelectedModel,
setSelectedSystemPrompt, setSelectedSystemPrompt,
stopStreamingRequest,
clearChat, clearChat,
historyId, historyId,
setSystemPrompt, setSystemPrompt,
@ -58,8 +56,6 @@ 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 () => {
@ -145,41 +141,6 @@ 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" : ""}`}>
@ -210,9 +171,10 @@ export const Sidebar = ({
<div <div
key={index} key={index}
className={` className={`
flex py-2 px-2 items-center gap-3 relative rounded-md truncate group ease-in-out flex py-2 px-2 items-center gap-3 relative rounded-md truncate hover:pr-4 group transition-opacity duration-300 ease-in-out border
dark:hover:bg-[#2d2d2d] transition-colors duration-300 hover:text-[#000000d9] hover:bg-[#f3f2ff] dark:hover:bg-[#2d2d2d] dark:border-gray-800
${historyId === chat.id ? "text-white bg-[#2563eb] hover:bg-[#1d4ed8]" : "dark:text-gray-100 text-gray-800 hover:text-white hover:bg-[#2563eb]"} hover:[&_.more-vertical]:text-[#000000d9]
${historyId === chat.id ? 'text-[#000000d9] bg-[#f3f2ff] border-[#000000d9]' : 'dark:text-gray-100 text-gray-800'}
`}> `}>
{chat?.message_source === "copilot" && ( {chat?.message_source === "copilot" && (
<Tooltip title={t("common:sidebarChat")} placement="top"> <Tooltip title={t("common:sidebarChat")} placement="top">
@ -221,7 +183,38 @@ 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={() => handleHistoryClick(chat)}> onClick={async () => {
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">
@ -278,9 +271,7 @@ 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 <MoreVertical className={`group-hover:text-[#000000d9] w-4 h-4 more-vertical ${historyId === chat.id ? 'text-[#000000d9]' : ''}`} />
className={`w-4 h-4 group-hover:text-white ${historyId === chat.id ? "text-white" : ""}`}
/>
</button> </button>
</Dropdown> </Dropdown>
</div> </div>

View File

@ -1,419 +0,0 @@
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

View File

@ -5,12 +5,6 @@ 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>>
@ -22,12 +16,6 @@ export const PageAssistContext = createContext<PageAssistContext>({
messages: [], messages: [],
setMessages: () => {}, setMessages: () => {},
currentMessageId: "",
setCurrentMessageId: () => {},
iodLoading: false,
setIodLoading: () => {},
controller: null, controller: null,
setController: () => {}, setController: () => {},

View File

@ -1,7 +1,7 @@
import { type ChatHistory as ChatHistoryType } from "~/store/option" import {
import { AllIodRegistryEntry } from "@/types/iod.ts" type ChatHistory as ChatHistoryType,
import { type Message as MessageType } from "@/types/message.ts" type Message as MessageType
import { getDefaultIodSources } from "@/libs/iod.ts" } from "~/store/option"
type HistoryInfo = { type HistoryInfo = {
id: string id: string
@ -30,7 +30,7 @@ type Message = {
content: string content: string
images?: string[] images?: string[]
webSources?: string[] webSources?: string[]
iodSources?: AllIodRegistryEntry iodSources?: string[]
search?: WebSearch search?: WebSearch
createdAt: number createdAt: number
reasoning_time_taken?: number reasoning_time_taken?: number
@ -248,31 +248,38 @@ export const saveHistory = async (
await db.addChatHistory(history) await db.addChatHistory(history)
return history return history
} }
export type HistoryMessage = {
history_id: string export const saveMessage = async (
name: string history_id: string,
role: string name: string,
content: string role: string,
images: string[] content: string,
iodSearch?: boolean images: string[],
webSearch?: boolean webSources?: any[],
webSources?: any[] iodSources?: any[],
iodSources?: AllIodRegistryEntry time?: number,
createdAt?: number message_type?: string,
messageType?: string generationInfo?: any,
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 (msg.createdAt) { if (time) {
createdAt += msg.createdAt createdAt += time
} }
const message = { const message = {
...msg,
id, id,
history_id,
name,
role,
content,
images,
createdAt, createdAt,
webSources,
iodSources,
messageType: message_type,
generationInfo: generationInfo,
reasoning_time_taken
} }
const db = new PageAssitDatabase() const db = new PageAssitDatabase()
await db.addMessage(message) await db.addMessage(message)
@ -296,12 +303,11 @@ 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,
webSources: message?.webSources || [], webSources: message?.webSources || [],
iodSources: message?.iodSources || getDefaultIodSources(), iodSources: message?.iodSources || [],
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

View File

@ -1,75 +0,0 @@
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(),
}
}
}

View File

@ -1,14 +1,8 @@
import { HistoryMessage, saveHistory, saveMessage } from "@/db" import { saveHistory, saveMessage } from "@/db"
import { import { setLastUsedChatModel, setLastUsedChatSystemPrompt } from "@/services/model-settings"
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 { 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,
history, history,
@ -23,9 +17,7 @@ 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
@ -40,9 +32,7 @@ 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" ||
@ -63,63 +53,66 @@ 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(
...JSON.parse(JSON.stringify(defaultMessage)), historyId,
role: "user", selectedModel,
content: userMessage, "user",
images: [image] userMessage,
}) [image],
[],
[],
1,
message_type
)
} }
await saveMessage({ await saveMessage(
...JSON.parse(JSON.stringify(defaultMessage)) historyId,
}) 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, { await setLastUsedChatSystemPrompt(historyId, { prompt_content, prompt_id })
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(
...JSON.parse(JSON.stringify(defaultMessage)), newHistoryId.id,
history_id: newHistoryId.id, selectedModel,
content: userMessage, "user",
role: "user", userMessage,
images: [image] [image],
}) [],
[],
1,
message_type
)
} }
await saveMessage( await saveMessage(
{ newHistoryId.id,
...JSON.parse(JSON.stringify(defaultMessage)), selectedModel,
history_id: newHistoryId.id, "assistant",
}, 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, { await setLastUsedChatSystemPrompt(newHistoryId.id, { prompt_content, prompt_id })
prompt_content,
prompt_id
})
} }
} }
@ -137,13 +130,10 @@ export const saveMessageOnSuccess = async ({
message, message,
image, image,
fullText, fullText,
iodSearch,
webSearch,
webSources, webSources,
iodSources, iodSources,
message_source = "web-ui", message_source = "web-ui",
message_type, message_type, generationInfo,
generationInfo,
prompt_id, prompt_id,
prompt_content, prompt_content,
reasoning_time_taken = 0 reasoning_time_taken = 0
@ -155,87 +145,84 @@ export const saveMessageOnSuccess = async ({
message: string message: string
image: string image: string
fullText: string fullText: string
iodSearch?: boolean
webSearch?: boolean
webSources: any[] webSources: any[]
iodSources: AllIodRegistryEntry iodSources: any[]
message_source?: "copilot" | "web-ui" 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 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,
...JSON.parse(JSON.stringify(defaultMessage)), selectedModel,
role: "user", "user",
content: message, message,
images: [image], [image],
webSources: [], [],
iodSources: getDefaultIodSources(), [],
}, 1,
message_type,
generationInfo,
reasoning_time_taken
) )
} }
botMessage = await saveMessage( botMessage = await saveMessage(
{ historyId,
...JSON.parse(JSON.stringify(defaultMessage)), selectedModel!,
} "assistant",
fullText,
[],
webSources,
iodSources,
2,
message_type,
generationInfo,
reasoning_time_taken
) )
updateDialog(historyId, botMessage) updateDialog(historyId, botMessage)
await setLastUsedChatModel(historyId, selectedModel!) await setLastUsedChatModel(historyId, selectedModel!)
if (prompt_id || prompt_content) { if (prompt_id || prompt_content) {
await setLastUsedChatSystemPrompt(historyId, { await setLastUsedChatSystemPrompt(historyId, { prompt_content, prompt_id })
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,
...JSON.parse(JSON.stringify(defaultMessage)), selectedModel,
history_id: newHistoryId.id, "user",
role: "user", message,
content: message, [image],
images: [image], [],
webSources: [], [],
iodSources: getDefaultIodSources(), 1,
}, message_type,
generationInfo,
reasoning_time_taken
) )
botMessage = await saveMessage( botMessage = await saveMessage(
{ newHistoryId.id,
...JSON.parse(JSON.stringify(defaultMessage)), selectedModel!,
history_id: newHistoryId.id, "assistant",
} fullText,
[],
webSources,
iodSources,
2,
message_type,
generationInfo,
reasoning_time_taken
) )
updateDialog(newHistoryId.id, botMessage) 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, { await setLastUsedChatSystemPrompt(newHistoryId.id, { prompt_content, prompt_id })
prompt_content,
prompt_id
})
} }
} }
} }

View File

@ -21,15 +21,13 @@ const useDynamicTextareaSize = (
const contentHeight = currentTextarea.scrollHeight; const contentHeight = currentTextarea.scrollHeight;
if (maxHeight) { if (maxHeight) {
// 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) < 60 ? 60 : Math.min(contentHeight, maxHeight)}px`; currentTextarea.style.height = `${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
currentTextarea.style.height = `${contentHeight}px`; currentTextarea.style.height = `${contentHeight}px`;
} }
@ -37,4 +35,4 @@ const useDynamicTextareaSize = (
}, [textareaRef, textContent, maxHeight]); }, [textareaRef, textContent, maxHeight]);
}; };
export default useDynamicTextareaSize; export default useDynamicTextareaSize;

View File

@ -2,15 +2,16 @@ import React from "react"
import { cleanUrl } from "~/libs/clean-url" import { cleanUrl } from "~/libs/clean-url"
import { import {
defaultEmbeddingModelForRag, defaultEmbeddingModelForRag,
getOllamaURL,
geWebSearchFollowUpPrompt, geWebSearchFollowUpPrompt,
getOllamaURL,
promptForRag, promptForRag,
systemPromptForNonRag systemPromptForNonRag
} from "~/services/ollama" } from "~/services/ollama"
import { useStoreMessageOption } from "~/store/option" import { useStoreMessageOption, type Message } 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 {
@ -41,9 +42,6 @@ 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 {
@ -190,15 +188,15 @@ export const useMessage = () => {
name: "You", name: "You",
message, message,
webSources: [], webSources: [],
iodSources: getDefaultIodSources(), iodSources: [],
images: [] images: []
}, },
{ {
isBot: true, isBot: true,
name: selectedModel, name: selectedModel,
message: "", message: "",
webSources: [], webSources: [],
iodSources: getDefaultIodSources(), iodSources: [],
id: generateMessageId id: generateMessageId
} }
] ]
@ -210,7 +208,7 @@ export const useMessage = () => {
name: selectedModel, name: selectedModel,
message: "▋", message: "▋",
webSources: [], webSources: [],
iodSources: getDefaultIodSources(), iodSources: [],
id: generateMessageId id: generateMessageId
} }
] ]
@ -244,7 +242,6 @@ 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()
@ -351,7 +348,14 @@ export const useMessage = () => {
metadata: Record<string, any> metadata: Record<string, any>
}[] = [] }[] = []
// TODO: update type // TODO: update type
let iodSources: AllIodRegistryEntry = getDefaultIodSources() let iodSources: {
name: any
type: any
mode: string
url: string
pageContent: string
metadata: Record<string, any>
}[] = []
if (chatWithWebsiteEmbedding) { if (chatWithWebsiteEmbedding) {
const docs = await vectorstore.similaritySearch(query, 4) const docs = await vectorstore.similaritySearch(query, 4)
@ -508,7 +512,7 @@ export const useMessage = () => {
content: fullText content: fullText
} }
]) ])
debugger
await saveMessageOnSuccess({ await saveMessageOnSuccess({
historyId, historyId,
setHistoryId, setHistoryId,
@ -619,7 +623,7 @@ export const useMessage = () => {
name: "You", name: "You",
message, message,
webSources: [], webSources: [],
iodSources: getDefaultIodSources(), iodSources: [],
images: [] images: []
}, },
{ {
@ -627,7 +631,7 @@ export const useMessage = () => {
name: selectedModel, name: selectedModel,
message: "▋", message: "▋",
webSources: [], webSources: [],
iodSources: getDefaultIodSources(), iodSources: [],
id: generateMessageId id: generateMessageId
} }
] ]
@ -639,7 +643,7 @@ export const useMessage = () => {
name: selectedModel, name: selectedModel,
message: "▋", message: "▋",
webSources: [], webSources: [],
iodSources: getDefaultIodSources(), iodSources: [],
id: generateMessageId id: generateMessageId
} }
] ]
@ -803,7 +807,7 @@ export const useMessage = () => {
image, image,
fullText, fullText,
webSources: [], webSources: [],
iodSources: getDefaultIodSources(), iodSources: [],
message_source: "copilot", message_source: "copilot",
generationInfo, generationInfo,
reasoning_time_taken: timetaken reasoning_time_taken: timetaken
@ -908,7 +912,7 @@ export const useMessage = () => {
name: "You", name: "You",
message, message,
webSources: [], webSources: [],
iodSources: getDefaultIodSources(), iodSources: [],
images: [image] images: [image]
}, },
{ {
@ -916,7 +920,7 @@ export const useMessage = () => {
name: selectedModel, name: selectedModel,
message: "▋", message: "▋",
webSources: [], webSources: [],
iodSources: getDefaultIodSources(), iodSources: [],
id: generateMessageId id: generateMessageId
} }
] ]
@ -928,7 +932,7 @@ export const useMessage = () => {
name: selectedModel, name: selectedModel,
message: "▋", message: "▋",
webSources: [], webSources: [],
iodSources: getDefaultIodSources(), iodSources: [],
id: generateMessageId id: generateMessageId
} }
] ]
@ -1097,7 +1101,7 @@ export const useMessage = () => {
image, image,
fullText, fullText,
webSources: [], webSources: [],
iodSources: getDefaultIodSources(), iodSources: [],
message_source: "copilot", message_source: "copilot",
generationInfo, generationInfo,
reasoning_time_taken: timetaken reasoning_time_taken: timetaken
@ -1141,7 +1145,7 @@ export const useMessage = () => {
isRegenerate: boolean, isRegenerate: boolean,
messages: Message[], messages: Message[],
history: ChatHistory, history: ChatHistory,
signal: AbortSignal signal: AbortSignal,
) => { ) => {
const url = await getOllamaURL() const url = await getOllamaURL()
setStreaming(true) setStreaming(true)
@ -1199,7 +1203,7 @@ export const useMessage = () => {
name: "You", name: "You",
message, message,
webSources: [], webSources: [],
iodSources: getDefaultIodSources(), iodSources: [],
images: [image] images: [image]
}, },
{ {
@ -1207,7 +1211,7 @@ export const useMessage = () => {
name: selectedModel, name: selectedModel,
message: "▋", message: "▋",
webSources: [], webSources: [],
iodSources: getDefaultIodSources(), iodSources: [],
id: generateMessageId id: generateMessageId
} }
] ]
@ -1219,7 +1223,7 @@ export const useMessage = () => {
name: selectedModel, name: selectedModel,
message: "▋", message: "▋",
webSources: [], webSources: [],
iodSources: getDefaultIodSources(), iodSources: [],
id: generateMessageId id: generateMessageId
} }
] ]
@ -1296,12 +1300,8 @@ export const useMessage = () => {
query = removeReasoning(query) query = removeReasoning(query)
} }
const { prompt, webSources, iodSources } = await getSystemPromptForWeb( const { prompt, webSources, iodSources } =
query, await getSystemPromptForWeb(query, [], webSearch, iodSearch)
[],
webSearch,
iodSearch
)
setIsSearchingInternet(false) setIsSearchingInternet(false)
// message = message.trim().replaceAll("\n", " ") // message = message.trim().replaceAll("\n", " ")
@ -1556,7 +1556,7 @@ export const useMessage = () => {
name: "You", name: "You",
message, message,
webSources: [], webSources: [],
iodSources: getDefaultIodSources(), iodSources: [],
images: [image], images: [image],
messageType: messageType messageType: messageType
}, },
@ -1565,7 +1565,7 @@ export const useMessage = () => {
name: selectedModel, name: selectedModel,
message: "▋", message: "▋",
webSources: [], webSources: [],
iodSources: getDefaultIodSources(), iodSources: [],
id: generateMessageId id: generateMessageId
} }
] ]
@ -1577,7 +1577,7 @@ export const useMessage = () => {
name: selectedModel, name: selectedModel,
message: "▋", message: "▋",
webSources: [], webSources: [],
iodSources: getDefaultIodSources(), iodSources: [],
id: generateMessageId id: generateMessageId
} }
] ]
@ -1724,7 +1724,7 @@ export const useMessage = () => {
image, image,
fullText, fullText,
webSources: [], webSources: [],
iodSources: getDefaultIodSources(), iodSources: [],
message_source: "copilot", message_source: "copilot",
message_type: messageType, message_type: messageType,
generationInfo, generationInfo,
@ -1811,7 +1811,7 @@ export const useMessage = () => {
isRegenerate || false, isRegenerate || false,
messages, messages,
memory || history, memory || history,
signal signal,
) )
} else { } else {
await normalChatMode( await normalChatMode(

View File

@ -2,14 +2,15 @@ import React from "react"
import { cleanUrl } from "~/libs/clean-url" import { cleanUrl } from "~/libs/clean-url"
import { import {
defaultEmbeddingModelForRag, defaultEmbeddingModelForRag,
getOllamaURL,
geWebSearchFollowUpPrompt, geWebSearchFollowUpPrompt,
geWebSearchKeywordsPrompt,
getOllamaURL,
promptForRag, promptForRag,
systemPromptForNonRagOption systemPromptForNonRagOption
} from "~/services/ollama" } from "~/services/ollama"
import type { ChatHistory, MeteringEntry } from "~/store/option" import type { ChatHistory, Message, MeteringEntry } from "~/store/option"
import { useStoreMessageOption } from "~/store/option"
import { SystemMessage } from "@langchain/core/messages" import { SystemMessage } from "@langchain/core/messages"
import { useStoreMessageOption } from "~/store/option"
import { import {
deleteChatForEdit, deleteChatForEdit,
generateID, generateID,
@ -46,21 +47,14 @@ import {
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,
@ -119,8 +113,6 @@ 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)
@ -203,7 +195,6 @@ export const useMessageOption = () => {
}) })
let newMessage: Message[] = [] let newMessage: Message[] = []
let generateMessageId = generateID() let generateMessageId = generateID()
setCurrentMessageId(generateMessageId)
const meter: MeteringEntry = { const meter: MeteringEntry = {
id: generateMessageId, id: generateMessageId,
queryContent: message, queryContent: message,
@ -215,39 +206,36 @@ export const useMessageOption = () => {
data: meter 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,
webSources: [],
iodSources: [],
images: [image]
}, },
{ {
...JSON.parse(JSON.stringify(defaultMessage)), isBot: true,
id: generateMessageId, name: selectedModel,
message: "", message: "▋",
webSources: [],
iodSources: [],
id: generateMessageId
} }
] ]
} else { } else {
newMessage = [ newMessage = [
...messages, ...messages,
{ {
...JSON.parse(JSON.stringify(defaultMessage)), isBot: true,
id: generateMessageId, name: selectedModel,
message: " ", message: "▋",
webSources: [],
iodSources: [],
id: generateMessageId
} }
] ]
} }
@ -328,9 +316,7 @@ export const useMessageOption = () => {
// Currently only IoD search use keywords // Currently only IoD search use keywords
if (iodSearch) { if (iodSearch) {
// Extract keywords // Extract keywords
console.log( console.log("query:"+query+" --> "+JSON.stringify(tokenizeInput(query)));
"query:" + query + " --> " + JSON.stringify(tokenizeInput(query))
)
keywords = tokenizeInput(query) keywords = tokenizeInput(query)
/* /*
const questionPrompt = await geWebSearchKeywordsPrompt() const questionPrompt = await geWebSearchKeywordsPrompt()
@ -349,35 +335,15 @@ export const useMessageOption = () => {
*/ */
} }
const { const { prompt, webSources, iodSources, iodSearchResults: iodData, iodTokenCount } =
prompt, await getSystemPromptForWeb(query, keywords, webSearch, iodSearch)
webSources,
iodSources,
iodSearchResults: iodData,
iodTokenCount
} = await getSystemPromptForWeb(query, keywords, webSearch, iodSearch)
setIodLoading(false)
console.log("prompt:\n" + prompt) console.log("prompt:\n" + prompt)
setIsSearchingInternet(false) setIsSearchingInternet(false)
meter.prompt = prompt meter.prompt = prompt
meter.iodKeywords = keywords meter.iodKeywords = keywords
meter.iodData = Object.values(iodData).flat() meter.iodData = iodData
meter.iodTokenCount = iodTokenCount meter.iodTokenCount = iodTokenCount
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({
@ -529,8 +495,6 @@ export const useMessageOption = () => {
message, message,
image, image,
fullText, fullText,
iodSearch,
webSearch,
webSources, webSources,
iodSources, iodSources,
generationInfo, generationInfo,
@ -547,17 +511,20 @@ export const useMessageOption = () => {
modelInputTokenCount: prompt.length, modelInputTokenCount: prompt.length,
modelOutputTokenCount: fullText.length, modelOutputTokenCount: fullText.length,
model: ollama.modelName ?? ollama.model, model: ollama.modelName ?? ollama.model,
relatedDataCount: Object.values(iodData).flat()?.length ?? 0, relatedDataCount: iodData?.length ?? 0,
timeTaken: new Date().getTime() - chatStartTime.getTime(), timeTaken: new Date().getTime() - chatStartTime.getTime(),
date: chatStartTime.getTime(), date: chatStartTime.getTime(),
cot, cot,
responseContent: content, responseContent: content,
modelResponseContent: fullText modelResponseContent: fullText,
} }
const _meteringEntries = [currentMeteringEntry, ...meteringEntries] const _meteringEntries = [
currentMeteringEntry,
...meteringEntries,
]
setCurrentMeteringEntry({ setCurrentMeteringEntry({
loading: false, loading: false,
data: currentMeteringEntry data: currentMeteringEntry,
}) })
setMeteringEntries(_meteringEntries) setMeteringEntries(_meteringEntries)
localStorage.setItem("meteringEntries", JSON.stringify(_meteringEntries)) localStorage.setItem("meteringEntries", JSON.stringify(_meteringEntries))
@ -572,9 +539,7 @@ export const useMessageOption = () => {
setHistory, setHistory,
setHistoryId, setHistoryId,
userMessage: message, userMessage: message,
isRegenerating: isRegenerate, isRegenerating: isRegenerate
iodSearch,
webSearch,
}) })
if (!errorSave) { if (!errorSave) {
@ -680,7 +645,6 @@ export const useMessageOption = () => {
let newMessage: Message[] = [] let newMessage: Message[] = []
let generateMessageId = generateID() let generateMessageId = generateID()
setCurrentMessageId(generateMessageId)
const meter: MeteringEntry = { const meter: MeteringEntry = {
id: generateMessageId, id: generateMessageId,
queryContent: message, queryContent: message,
@ -689,8 +653,9 @@ export const useMessageOption = () => {
setCurrentMeteringEntry({ setCurrentMeteringEntry({
loading: true, loading: true,
data: meter data: meter,
}) })
if (!isRegenerate) { if (!isRegenerate) {
newMessage = [ newMessage = [
...messages, ...messages,
@ -698,9 +663,8 @@ export const useMessageOption = () => {
isBot: false, isBot: false,
name: "You", name: "You",
message, message,
id: generateID(),
webSources: [], webSources: [],
iodSources: getDefaultIodSources(), iodSources: [],
images: [image] images: [image]
}, },
{ {
@ -708,7 +672,7 @@ export const useMessageOption = () => {
name: selectedModel, name: selectedModel,
message: "▋", message: "▋",
webSources: [], webSources: [],
iodSources: getDefaultIodSources(), iodSources: [],
id: generateMessageId id: generateMessageId
} }
] ]
@ -720,7 +684,7 @@ export const useMessageOption = () => {
name: selectedModel, name: selectedModel,
message: "▋", message: "▋",
webSources: [], webSources: [],
iodSources: getDefaultIodSources(), iodSources: [],
id: generateMessageId id: generateMessageId
} }
] ]
@ -898,6 +862,7 @@ export const useMessageOption = () => {
content: fullText content: fullText
} }
]) ])
await saveMessageOnSuccess({ await saveMessageOnSuccess({
historyId, historyId,
setHistoryId, setHistoryId,
@ -906,8 +871,6 @@ export const useMessageOption = () => {
message, message,
image, image,
fullText, fullText,
iodSearch,
webSearch,
source: [], source: [],
generationInfo, generationInfo,
prompt_content: promptContent, prompt_content: promptContent,
@ -920,24 +883,28 @@ export const useMessageOption = () => {
setIsProcessing(false) setIsProcessing(false)
setStreaming(false) setStreaming(false)
// Save metering entry // Save metering entry
const { cot, content } = responseResolver(fullText) const { cot, content } = responseResolver(fullText)
const currentMeteringEntry = { const currentMeteringEntry = {
...meter, ...meter,
modelInputTokenCount: prompt? prompt.length : 0, modelInputTokenCount: prompt.length,
modelOutputTokenCount: fullText? fullText.length : 0, modelOutputTokenCount: fullText.length,
model: ollama.modelName ?? ollama.model, model: ollama.modelName ?? ollama.model,
relatedDataCount: 0, relatedDataCount: 0,
timeTaken: new Date().getTime() - chatStartTime.getTime(), timeTaken: new Date().getTime() - chatStartTime.getTime(),
date: chatStartTime.getTime(), date: chatStartTime.getTime(),
cot, cot,
responseContent: content, responseContent: content,
modelResponseContent: fullText modelResponseContent: fullText,
} }
const _meteringEntries = [currentMeteringEntry, ...meteringEntries] const _meteringEntries = [
currentMeteringEntry,
...meteringEntries,
]
setCurrentMeteringEntry({ setCurrentMeteringEntry({
loading: false, loading: false,
data: currentMeteringEntry data: currentMeteringEntry,
}) })
setMeteringEntries(_meteringEntries) setMeteringEntries(_meteringEntries)
} catch (e) { } catch (e) {
@ -953,9 +920,7 @@ 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) {
@ -1031,7 +996,7 @@ export const useMessageOption = () => {
name: "You", name: "You",
message, message,
webSources: [], webSources: [],
iodSources: getDefaultIodSources(), iodSources: [],
images: [] images: []
}, },
{ {
@ -1039,7 +1004,7 @@ export const useMessageOption = () => {
name: selectedModel, name: selectedModel,
message: "▋", message: "▋",
webSources: [], webSources: [],
iodSources: getDefaultIodSources(), iodSources: [],
id: generateMessageId id: generateMessageId
} }
] ]
@ -1051,7 +1016,7 @@ export const useMessageOption = () => {
name: selectedModel, name: selectedModel,
message: "▋", message: "▋",
webSources: [], webSources: [],
iodSources: getDefaultIodSources(), iodSources: [],
id: generateMessageId id: generateMessageId
} }
] ]
@ -1286,9 +1251,7 @@ export const useMessageOption = () => {
fullText, fullText,
source, source,
generationInfo, generationInfo,
reasoning_time_taken: timetaken, reasoning_time_taken: timetaken
iodSearch,
webSearch,
}) })
setIsProcessing(false) setIsProcessing(false)
@ -1304,9 +1267,7 @@ export const useMessageOption = () => {
setHistory, setHistory,
setHistoryId, setHistoryId,
userMessage: message, userMessage: message,
isRegenerating: isRegenerate, isRegenerating: isRegenerate
iodSearch,
webSearch,
}) })
if (!errorSave) { if (!errorSave) {
@ -1358,7 +1319,6 @@ export const useMessageOption = () => {
) )
} else { } else {
if (webSearch || iodSearch) { if (webSearch || iodSearch) {
setIodLoading(iodSearch)
await searchChatMode( await searchChatMode(
webSearch, webSearch,
iodSearch, iodSearch,
@ -1475,10 +1435,6 @@ export const useMessageOption = () => {
editMessage, editMessage,
messages, messages,
setMessages, setMessages,
iodLoading,
currentMessageId,
setIodLoading,
setCurrentMessageId,
onSubmit, onSubmit,
setStreaming, setStreaming,
streaming, streaming,

View File

@ -6,7 +6,6 @@ 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 () => {
@ -14,14 +13,12 @@ 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)
@ -37,7 +34,6 @@ 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 {
@ -59,10 +55,6 @@ 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)

View File

@ -1,18 +0,0 @@
import { AllIodRegistryEntry } from "@/types/iod.ts"
export const getDefaultIodSources = (): AllIodRegistryEntry => {
return {
data: {
data: [],
total: 0
},
scenario: {
data: [],
total: 0
},
organization: {
data: [],
total: 0
}
}
}

View File

@ -1,91 +1,51 @@
import { Avatar } from "antd" import RocketSvg from "@/assets/icons/rocket.svg"
import { MedicineBottleFillIcon } from "@/components/Icons/MedicineBottleFill.tsx" import BulbSvg from "@/assets/icons/bulb.svg"
import { CheckIcon } from "@/components/Icons/Check.tsx" import EyeSvg from "@/assets/icons/eye.svg"
import { NewBottleIcon } from "@/components/Icons/NewBottle.tsx" import ASvg from "@/assets/icons/a.svg"
import { BatteryIcon } from "@/components/Icons/Battery.tsx" import BSvg from "@/assets/icons/b.svg"
import { ShipIcon } from "@/components/Icons/Ship.tsx" import CSvg from "@/assets/icons/c.svg"
import { Ship1Icon } from "@/components/Icons/Ship1.tsx" import DSvg from "@/assets/icons/d.svg"
import ESvg from "@/assets/icons/e.svg"
import FSvg from "@/assets/icons/f.svg"
export const qaPrompt = [ 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: "如何解决固态电池的成本和寿命难题?", title: "最近一年大型语言模型的技术进展有哪些?",
icon: ( icon: <img src={RocketSvg} alt="Rocket" className="w-full my-0" />,
<Avatar
className="!bg-[#3581e3b3]"
shape="square"
size={40}
icon={<BatteryIcon className="w-7" />}
/>
)
}, },
{ {
title: "如何解决船舶制造中的材料腐蚀难题?", title: "生成式AI在企业中有哪些具体应用场景",
icon: ( icon: <img src={BulbSvg} alt="Rocket" className="w-full my-0" />,
<Avatar
className="!bg-[#3581e3b3]"
shape="square"
size={40}
icon={<ShipIcon className="w-7" />}
/>
)
}, },
{ {
title: "如何解决船舶制造中流体模拟和建模优化难题?", title: "多模态学习技术的最新研究方向是什么?",
icon: ( icon: <img src={EyeSvg} alt="Rocket" className="w-full my-0" />,
<Avatar
className="!bg-[#3581e3b3]"
shape="square"
size={40}
icon={<Ship1Icon className="w-7" />}
/>
)
}, },
{ {
title: "新药临床研究如何提升实验安全性?", title: "当前AI芯片市场格局和未来三年发展趋势如何",
icon: ( icon: <img src={ASvg} alt="Rocket" className="w-full my-0" />,
<Avatar
className="!bg-[#3581e3b3]"
shape="square"
size={40}
icon={<MedicineBottleFillIcon className="w-7" />}
/>
)
}, },
{ {
title: "人工智能技术如何加速新药申报和审批?", title: "主流深度学习框架性能与易用性对比分析?",
icon: ( icon: <img src={BSvg} alt="Rocket" className="w-full my-0" />,
<Avatar
className="!bg-[#3581e3b3]"
shape="square"
size={40}
icon={<CheckIcon className="w-7" />}
/>
)
}, },
{ {
title: "如何研制与利妥昔单抗相似的新药?", title: "国内外AI伦理治理框架有哪些最佳实践",
icon: ( icon: <img src={CSvg} alt="Rocket" className="w-full my-0" />,
<Avatar },
className="!bg-[#3581e3b3]" {
shape="square" title: "大规模知识图谱构建与应用最新进展?",
size={40} icon: <img src={DSvg} alt="Rocket" className="w-full my-0" />,
icon={<NewBottleIcon className="w-7" />} },
/> {
) title: "计算机视觉领域近期突破性技术有哪些?",
} icon: <img src={ESvg} alt="Rocket" className="w-full my-0" />,
},
{
title: "量子计算对AI算法的影响与应用前景",
icon: <img src={FSvg} alt="Rocket" className="w-full my-0" />,
},
].map((item, index) => ({ ].map((item, index) => ({
...item, ...item,
id: index.toString() id: index.toString(),
})) }))

View File

@ -7,7 +7,6 @@ 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"
@ -29,7 +28,6 @@ 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" element={<OptionMetering />} />
<Route path="/metering/list/:id" element={<MeteringListDetail />} /> <Route path="/metering/list/:id" element={<MeteringListDetail />} />

View File

@ -15,7 +15,6 @@ const MeteringListDetail = lazy(() => import("./metering-list-detail"))
const OptionShare = lazy(() => import("./option-settings-share")) const OptionShare = lazy(() => import("./option-settings-share"))
const OptionKnowledgeBase = lazy(() => import("./option-settings-knowledge")) const OptionKnowledgeBase = lazy(() => import("./option-settings-knowledge"))
const OptionAbout = lazy(() => import("./option-settings-about")) const OptionAbout = lazy(() => import("./option-settings-about"))
const OptionIodSettings = lazy(() => import("./option-settings-iod"))
const OptionRagSettings = lazy(() => import("./option-rag")) const OptionRagSettings = lazy(() => import("./option-rag"))
const OptionOpenAI = lazy(() => import("./option-settings-openai")) const OptionOpenAI = lazy(() => import("./option-settings-openai"))
@ -32,7 +31,6 @@ export const OptionRoutingFirefox = () => {
<Route path="/settings/knowledge" element={<OptionKnowledgeBase />} /> <Route path="/settings/knowledge" element={<OptionKnowledgeBase />} />
<Route path="/settings/about" element={<OptionAbout />} /> <Route path="/settings/about" element={<OptionAbout />} />
<Route path="/settings/rag" element={<OptionRagSettings />} /> <Route path="/settings/rag" element={<OptionRagSettings />} />
<Route path="/settings/iod" element={<OptionIodSettings />} />
<Route path="/metering" element={<OptionMetering />} /> <Route path="/metering" element={<OptionMetering />} />
<Route path="/metering/list/:id" element={<MeteringListDetail />} /> <Route path="/metering/list/:id" element={<MeteringListDetail />} />
</Routes> </Routes>

View File

@ -1,11 +1,10 @@
import OptionLayout from "~/components/Layouts/Layout" import OptionLayout from "~/components/Layouts/Layout"
import IodVideo from "@/components/Option/VideoPlayer/index.tsx"
import { Playground } from "~/components/Option/Playground/Playground" import { Playground } from "~/components/Option/Playground/Playground"
const OptionIndex = () => { const OptionIndex = () => {
return ( return (
<OptionLayout> <OptionLayout>
<Playground /> <Playground />
</OptionLayout> </OptionLayout>
) )
} }

View File

@ -1,15 +0,0 @@
import { SettingsLayout } from "~/components/Layouts/SettingsOptionLayout"
import OptionLayout from "~/components/Layouts/Layout"
import { IodApp } from "@/components/Option/Settings/iod"
const OptionAbout = () => {
return (
<OptionLayout>
<SettingsLayout>
<IodApp />
</SettingsLayout>
</OptionLayout>
)
}
export default OptionAbout

View File

@ -21,26 +21,8 @@ const DEFAULT_RAG_QUESTION_PROMPT =
const DEFAUTL_RAG_SYSTEM_PROMPT = `You are a helpful AI assistant. Use the following pieces of context to answer the question at the end. If you don't know the answer, just say you don't know. DO NOT try to make up an answer. If the question is not related to the context, politely respond that you are tuned to only answer questions that are related to the context. {context} Question: {question} Helpful answer:` const DEFAUTL_RAG_SYSTEM_PROMPT = `You are a helpful AI assistant. Use the following pieces of context to answer the question at the end. If you don't know the answer, just say you don't know. DO NOT try to make up an answer. If the question is not related to the context, politely respond that you are tuned to only answer questions that are related to the context. {context} Question: {question} Helpful answer:`
const DEFAULT_WEBSEARCH_PROMPT = `你是一个中文AI助手当前日期和时间是 {current_date_time}。在<数联网搜索结果> 中提供了来自数联网Internet of Data)的搜索结果。
\`<result doId="{doId}" name="{title}" authors="{authors}" dataType="{paper,dataset or algorithm}" year="{year}" url="{url}" id="{id}">{abstract}</result>\`
\`doId\`\`name\` ,如果没有url则空着 :
\`[数联网引用[id] doId: {doId} "{name}"]({url})\`
:
\`[数联网引用[1] doId: 10.48550/arXiv.1803.05591v2 "On the insufficiency of existing momentum schemes for Stochastic Optimization"](http://arxiv.org/pdf/1803.05591v2.pdf)\`
<数联网搜索结果> const DEFAULT_WEBSEARCH_PROMPT = `You are an AI assistant specialized in retrieving and analyzing academic papers from Neo4j graph database.
{iod_search_results}
</数联网搜索结果>
<数联网搜索结果><数联网搜索结果>
<数联网搜索结果>
<think>使
4;
4
`
const DEFAULT_WEBSEARCH_PROMPT2 = `You are an AI assistant specialized in retrieving and analyzing academic papers from Neo4j graph database.
Generate a response that how can user achieve his request based on provided search results. The current date and time are {current_date_time}. Generate a response that how can user achieve his request based on provided search results. The current date and time are {current_date_time}.
@ -66,6 +48,7 @@ Use this information to generate a meaningful response that includes:
<iod-search-results> <iod-search-results>
{iod_search_results} {iod_search_results}
</iod-search-results> </iod-search-results>
` `
const DEFAULT_WEBSEARCH2_PROMPT = `You are an AI model who is expert at searching the web and answering user's queries. const DEFAULT_WEBSEARCH2_PROMPT = `You are an AI model who is expert at searching the web and answering user's queries.

View File

@ -18,10 +18,6 @@ export type ChatHistory = {
type State = { type State = {
messages: Message[] messages: Message[]
setMessages: (messages: Message[]) => void setMessages: (messages: Message[]) => void
currentMessageId: string
setCurrentMessageId: (messageId: string) => void
iodLoading: boolean
setIodLoading: (iodLoading: boolean) => void
history: ChatHistory history: ChatHistory
setHistory: (history: ChatHistory) => void setHistory: (history: ChatHistory) => void
streaming: boolean streaming: boolean
@ -57,10 +53,6 @@ type State = {
export const useStoreMessage = create<State>((set) => ({ export const useStoreMessage = create<State>((set) => ({
messages: [], messages: [],
setMessages: (messages) => set({ messages }), setMessages: (messages) => set({ messages }),
currentMessageId: "",
setCurrentMessageId: (currentMessageId) => set({ currentMessageId }),
iodLoading: false,
setIodLoading: (iodLoading) => set({ iodLoading }),
history: [], history: [],
setHistory: (history) => set({ history }), setHistory: (history) => set({ history }),
streaming: false, streaming: false,

View File

@ -1,6 +1,27 @@
import { Knowledge } from "@/db/knowledge" import { Knowledge } from "@/db/knowledge"
import { create } from "zustand" import { create } from "zustand"
import { Message } from "esbuild"
type WebSearch = {
search_engine: string
search_url: string
search_query: string
search_results: {
title: string
link: string
}[]
}
export type Message = {
isBot: boolean
name: string
message: string
webSources: any[]
iodSources: any[]
images?: string[]
search?: WebSearch
reasoning_time_taken?: number
id?: string
messageType?: string
}
export type ChatHistory = { export type ChatHistory = {
role: "user" | "assistant" | "system" role: "user" | "assistant" | "system"
@ -14,11 +35,8 @@ type State = {
setMessages: (messages: Message[]) => void setMessages: (messages: Message[]) => void
history: ChatHistory history: ChatHistory
setHistory: (history: ChatHistory) => void setHistory: (history: ChatHistory) => void
currentMeteringEntry: { data: MeteringEntry; loading: boolean } currentMeteringEntry: {data: MeteringEntry, loading: boolean}
setCurrentMeteringEntry: (meteringEntry: { setCurrentMeteringEntry: (meteringEntry: {data: MeteringEntry, loading: boolean}) => void
data: MeteringEntry
loading: boolean
}) => void
meteringEntries: MeteringEntry[] meteringEntries: MeteringEntry[]
setMeteringEntries: (meteringEntries: MeteringEntry[]) => void setMeteringEntries: (meteringEntries: MeteringEntry[]) => void
streaming: boolean streaming: boolean
@ -104,12 +122,9 @@ export const useStoreMessageOption = create<State>((set) => ({
setMessages: (messages) => set({ messages }), setMessages: (messages) => set({ messages }),
history: [], history: [],
setHistory: (history) => set({ history }), setHistory: (history) => set({ history }),
currentMeteringEntry: { data: {} as MeteringEntry, loading: false }, currentMeteringEntry: {data: {} as MeteringEntry, loading: false},
setCurrentMeteringEntry: (currentMeteringEntry) => setCurrentMeteringEntry: (currentMeteringEntry) => set({ currentMeteringEntry }),
set({ currentMeteringEntry }), meteringEntries: JSON.parse(localStorage.getItem("meteringEntries") || JSON.stringify([])),
meteringEntries: JSON.parse(
localStorage.getItem("meteringEntries") || JSON.stringify([])
),
setMeteringEntries: (meteringEntries) => set({ meteringEntries }), setMeteringEntries: (meteringEntries) => set({ meteringEntries }),
streaming: false, streaming: false,
setStreaming: (streaming) => set({ streaming }), setStreaming: (streaming) => set({ streaming }),
@ -132,7 +147,7 @@ export const useStoreMessageOption = create<State>((set) => ({
setIsEmbedding: (isEmbedding) => set({ isEmbedding }), setIsEmbedding: (isEmbedding) => set({ isEmbedding }),
webSearch: false, webSearch: false,
setWebSearch: (webSearch) => set({ webSearch }), setWebSearch: (webSearch) => set({ webSearch }),
iodSearch: true, iodSearch: false,
setIodSearch: (iodSearch) => set({ iodSearch }), setIodSearch: (iodSearch) => set({ iodSearch }),
isSearchingInternet: false, isSearchingInternet: false,
setIsSearchingInternet: (isSearchingInternet) => set({ isSearchingInternet }), setIsSearchingInternet: (isSearchingInternet) => set({ isSearchingInternet }),

View File

@ -8,20 +8,4 @@ export type IodRegistryEntry = {
data_space?: string data_space?: string
data_type?:string data_type?:string
traceId?:string traceId?:string
fromRepo?: string
}
export type AllIodRegistryEntry = {
data: {
data: IodRegistryEntry[]
total: number
}
scenario: {
data: IodRegistryEntry[]
total: number
}
organization: {
data: IodRegistryEntry[]
total: number
}
} }

View File

@ -1,5 +1,3 @@
import { AllIodRegistryEntry } from "@/types/iod.ts"
type WebSearch = { type WebSearch = {
search_engine: string search_engine: string
search_url: string search_url: string
@ -13,16 +11,12 @@ export type Message = {
isBot: boolean isBot: boolean
name: string name: string
message: string message: string
webSearch?: boolean
webSources: any[] webSources: any[]
iodSearch?: boolean iodSources: any[]
iodSources: AllIodRegistryEntry
images?: string[] images?: string[]
search?: WebSearch search?: WebSearch
messageType?: string messageType?: string
id?: string id?: string
generationInfo?: any generationInfo?: any
reasoning_time_taken?: number reasoning_time_taken?: number
} }
export type Messages = Message[]

View File

@ -1,386 +1,275 @@
import { cleanUrl } from "@/libs/clean-url"
import { PageAssistHtmlLoader } from "@/loader/html" import { PageAssistHtmlLoader } from "@/loader/html"
import { PageAssistPDFUrlLoader } from "@/loader/pdf-url" import { PageAssistPDFUrlLoader } from "@/loader/pdf-url"
import { getOllamaURL } from "@/services/ollama" import { pageAssistEmbeddingModel } from "@/models/embedding"
import { defaultEmbeddingModelForRag, getOllamaURL } from "@/services/ollama"
import { import {
getIsSimpleInternetSearch, getIsSimpleInternetSearch,
totalSearchResults totalSearchResults
} from "@/services/search" } from "@/services/search"
import { Document } from "@langchain/core/documents" import { getPageAssistTextSplitter } from "@/utils/text-splitter"
import { AllIodRegistryEntry, IodRegistryEntry } from "~/types/iod" import { Document } from "@langchain/core/documents"
import { MemoryVectorStore } from "langchain/vectorstores/memory"
import type { IodRegistryEntry } from "~/types/iod"
import { PageAssitDatabase } from "@/db" import { PageAssitDatabase } from "@/db"
import exp from "constants"
import { enPOSTag, Segment, useDefault } from "segmentit" import { Segment, useDefault, cnPOSTag, enPOSTag} from 'segmentit';
import { getDefaultIodSources } from "@/libs/iod.ts" const segment = useDefault(new Segment());
import { IodDb } from "@/db/iod.ts"
const segment = useDefault(new Segment())
export const tokenizeInput = function (input: string): string[] { export const tokenizeInput = function (input: string): string[] {
const words = segment.doSegment(input, { simple: false }) const words = segment.doSegment(input, { simple: false });
console.log( console.log(words.map(function(word){return {w:word.w, p:enPOSTag(word.p)}}) );
words.map(function (word) { return words.filter(word =>( word.w.length > 1)).map(word=>word.w);
return { w: word.w, p: enPOSTag(word.p) }
})
)
return words.filter((word) => word.w.length > 1).map((word) => word.w)
} }
//doipUrl = tcp://reg01.public.internetofdata.cn:21037
function getIodConfig() { export const iodConfig = {
return IodDb.getInstance().getIodConnection() "gatewayUrl": "tcp://021.node.internetapi.cn:21052",
} "registry":"data/Registry",
export const iodConfigLocal = { "localRepository":"data/Repository",
gatewayUrl: "tcp://127.0.0.1:21036", "doBrowser":"http://021.node.internetapi.cn:21030/SCIDE/SCManager"
registry: "bdware/Registry",
localRepository: "bdtest.local/myrepo1",
doBrowser: "http://127.0.0.1:21030/SCIDE/SCManager"
} }
function inGrepList(str: string){ function inGrepList(str: string){
return "什么|问题|需要|合适|设计|考虑|合作|精度|传感器|最新|研究|药物|如何|解决|中的|难题|成本|提升".indexOf( return "什么|问题|需要|合适|设计|考虑|合作|精度|传感器|最新|研究|药物".indexOf(str)!=-1;
str
) != -1
} }
export const makeRegSearchParams = function(count: number, keyword: string| string[]){
export const makeSearchParamsWithDataType = function ( const searchMode = [];
count: number, if (typeof keyword === 'string') {
keyword: string | string[],
dataType: string
) {
const iodConfig = getIodConfig()
const searchMode = []
searchMode.push({ key: "data_type", type: "MUST", value: dataType })
if (typeof keyword === "string") {
// 如果 keyword 是字符串,则直接添加一个 searchMode 条目 // 如果 keyword 是字符串,则直接添加一个 searchMode 条目
searchMode.push({ searchMode.push({
key: "description", key: "description",
type: "MUST", type: "MUST",
value: keyword value: keyword
}) });
} else if (Array.isArray(keyword)) { } else if (Array.isArray(keyword)) {
// 如果 keyword 是数组,则为每个元素添加一个 searchMode 条目 // 如果 keyword 是数组,则为每个元素添加一个 searchMode 条目
keyword.forEach((str) => { keyword.forEach(str => {
if (!inGrepList(str)) if (!inGrepList(str))
searchMode.push({ searchMode.push({
key: "description", key: "description",
type: "SHOULD", type: "SHOULD",
value: str value: str
}) });
}) });
} }
return { return {
action: "executeContract", action: "executeContract",
contractID: "BDBrowser", contractID: "BDBrowser",
operation: "sendRequestDirectly", operation: "sendRequestDirectly",
arg: { arg: {
id: iodConfig.registry, id: iodConfig.registry,
//doipUrl:"tcp://127.0.0.1:21039", //doipUrl:"tcp://127.0.0.1:21039",
doipUrl: iodConfig.gatewayUrl, doipUrl: iodConfig.gatewayUrl,
op: "Search", op: "Search",
vars: { vars:{
timeout: 15000 timeout:15000
}, },
attributes: { attributes: {
offset: 0, offset: 0,
count, count,
bodyBase64Encoded: false, bodyBase64Encoded: false,
searchMode: searchMode searchMode:searchMode
}, },
body: "" body: ""
} }
} }
} }
export const makeRegSearchParams = function ( export const makeDOIPParams = (doId:string, op:string, attributes:Object, requestBody: string) => ({
count: number, action: "executeContract",
keyword: string | string[] contractID: "BDBrowser",
) { operation: "sendRequestDirectly",
const searchMode = [] arg: {
const iodConfig = getIodConfig() id: doId,
if (typeof keyword === "string") { doipUrl: iodConfig.gatewayUrl,
// 如果 keyword 是字符串,则直接添加一个 searchMode 条目 op: op,
searchMode.push({ attributes: attributes,
key: "description", body: requestBody
type: "MUST",
value: keyword
})
} else if (Array.isArray(keyword)) {
// 如果 keyword 是数组,则为每个元素添加一个 searchMode 条目
keyword.forEach((str) => {
if (!inGrepList(str))
searchMode.push({
key: "description",
type: "SHOULD",
value: str
})
})
} }
return { })
action: "executeContract",
contractID: "BDBrowser",
operation: "sendRequestDirectly",
arg: {
id: iodConfig.registry,
//doipUrl:"tcp://127.0.0.1:21039",
doipUrl: iodConfig.gatewayUrl,
op: "Search",
vars: {
timeout: 15000
},
attributes: {
offset: 0,
count,
bodyBase64Encoded: false,
searchMode: searchMode
},
body: ""
}
}
}
export const makeDOIPParams = ( export const retrieveDoc = function(doId: string) : Promise<Document> {
doId: string, console.log("retriveDoc:"+doId)
op: string, const params = makeDOIPParams(doId,"Retrieve",{
attributes: Object, bodyBase64Encoded: false
requestBody: string }, "");
) => {
const iodConfig = getIodConfig()
return {
action: "executeContract",
contractID: "BDBrowser",
operation: "sendRequestDirectly",
arg: {
id: doId,
doipUrl: iodConfig.gatewayUrl,
op: op,
attributes: attributes,
body: requestBody
}
}
}
export const retrieveDoc = function (doId: string): Promise<Document> {
const iodConfig = getIodConfig()
console.log("retriveDoc:" + doId)
const params = makeDOIPParams(
doId,
"Retrieve",
{
bodyBase64Encoded: false
},
""
)
const abortController = new AbortController() const abortController = new AbortController()
setTimeout(() => abortController.abort(), 10000) setTimeout(() => abortController.abort(), 10000)
return fetch(iodConfig.doBrowser, { return fetch(iodConfig.doBrowser, {
method: "POST", method: "POST",
body: JSON.stringify(params), body: JSON.stringify(params),
signal: abortController.signal signal: abortController.signal
}) }).then((response) => {
.then((response) => { console.log("responseIn retrieveDoc:");
console.log("responseIn retrieveDoc:") console.log(response);
console.log(response) return response.json()})
return response.json() .then((res) => {
}) console.log("res:");
.then((res) => { console.log(res.result.body);
console.log("res:") //TODO
console.log(res.result.body)
//TODO
return { return {
metadata: { traceId: res.result.header.attributes?.traceId }, metadata:{traceId:res.result.header.attributes?.traceId},
pageContent: res.result.body pageContent:res.result.body
} }
}) })
} }
export const updateInLocalRepo = function ( export const updateInLocalRepo = function(historyId: string, requestBody: Object) : Promise<string> {
historyId: string, const params = makeDOIPParams(iodConfig.localRepository,"Update",{
requestBody: Object "aiDialogID": historyId,
): Promise<string> { bodyBase64Encoded: false
const iodConfig = getIodConfig() }, JSON.stringify(requestBody));
const params = makeDOIPParams(
iodConfig.localRepository,
"Update",
{
aiDialogID: historyId,
bodyBase64Encoded: false
},
JSON.stringify(requestBody)
)
const abortController = new AbortController() const abortController = new AbortController()
setTimeout(() => abortController.abort(), 10000) setTimeout(() => abortController.abort(), 10000)
return fetch(iodConfig.doBrowser, { return fetch(iodConfig.doBrowser, {
method: "POST", method: "POST",
body: JSON.stringify(params), body: JSON.stringify(params),
signal: abortController.signal signal: abortController.signal
}).then((response) => response.json())
.then((res) => {
console.log("update dialog:"+JSON.stringify(res))
return res.body;
}) })
.then((response) => response.json())
.then((res) => {
console.log("update dialog:" + JSON.stringify(res))
return res.body
})
} }
export const updateDialog = async function ( export const updateDialog = async function(histroyId : string, botMessage: any): Promise<string> {
histroyId: string,
botMessage: any
): Promise<string> {
//TODO @Nex confused by Message/MessageType in ./db/index.ts! //TODO @Nex confused by Message/MessageType in ./db/index.ts!
const db = new PageAssitDatabase() const db = new PageAssitDatabase()
const chatHistory = await db.getChatHistory(histroyId) const chatHistory = await db.getChatHistory(histroyId)
var userMessage = null var userMessage = null;
for (var i = 0; i < chatHistory.length; i++) { for (var i=0;i<chatHistory.length;i++){
userMessage = chatHistory[i] userMessage = chatHistory[i];
if (userMessage.role == "user") break if (userMessage.role=='user') break;
} }
let updateBody: any = {} let updateBody:any = {};
// !!!IMPORTANT!!! traceId = histroyId+"/"+userMessage.id; // !!!IMPORTANT!!! traceId = histroyId+"/"+userMessage.id;
// Update traceId in retrieveDoc! // Update traceId in retrieveDoc!
updateBody.traceId = histroyId + "/" + userMessage.id updateBody.traceId = histroyId+"/"+userMessage.id;
updateBody.question = { updateBody.question = {
id: histroyId + "/" + userMessage.id, "id": histroyId+"/"+userMessage.id,
content: userMessage.content, "content": userMessage.content,
tokenCount: userMessage.content.length "tokenCount": userMessage.content.length
} }
updateBody.answer = { updateBody.answer = {
id: histroyId + "/" + botMessage.id, "id": histroyId+"/"+botMessage.id,
content: botMessage.content, "content": botMessage.content,
tokenCount: botMessage.content.length "tokenCount": botMessage.content.length
} }
//TODO set a correct model ID //TODO set a correct model ID
updateBody.model = { id: "bdware.ollama/" + userMessage.name } updateBody.model = {"id":"bdware.ollama/" + userMessage.name}
//TODO incorrect tokenCount calculated!! //TODO incorrect tokenCount calculated!!
updateBody.webSources = updateBody.webSources = botMessage.webSources?.map((r) => ({
botMessage.webSources?.map((r) => ({ url: r.url,
url: r.url, tokenCount: r.url.length,
tokenCount: r.url.length, content: r.url,
content: r.url, traceId: r?.traceId
traceId: r?.traceId })) ?? [];
})) ?? [] updateBody.IoDSources = botMessage.iodSources?.map((r) => ({
id: r.doId,
updateBody.IoDSources = tokenCount: (r.content || r.description)?calculateTokenCount((r.content || r.description)):0,
Object.values((botMessage?.iodSources ?? {}) as AllIodRegistryEntry).flatMap(iod => iod.data)?.map((r) => ({ content: r.content || r.description,
id: r.doId, traceId: r?.traceId
tokenCount: })) ?? [];
r.content || r.description console.log("updateBody:");
? calculateTokenCount(r.content || r.description)
: 0,
content: r.content || r.description,
traceId: r?.traceId
})) ?? []
console.log("updateBody:")
console.log(updateBody) console.log(updateBody)
return updateInLocalRepo(histroyId, updateBody) return updateInLocalRepo(histroyId,updateBody)
} }
export async function localIodSearch( export async function localIodSearch(
query: string, query: string,
keywords: string[] keywords: string[]
): Promise<AllIodRegistryEntry> { ): Promise<IodRegistryEntry[]> {
const iodConfig = getIodConfig()
const TOTAL_SEARCH_RESULTS = await totalSearchResults() const TOTAL_SEARCH_RESULTS = await totalSearchResults()
const abortController = new AbortController() const abortController = new AbortController();
setTimeout(() => abortController.abort(), 10000) setTimeout(() => abortController.abort(), 10000);
const params = makeRegSearchParams(TOTAL_SEARCH_RESULTS, keywords) const params = makeRegSearchParams(TOTAL_SEARCH_RESULTS, keywords);
const dataParams = makeSearchParamsWithDataType( console.log('params------->',params)
TOTAL_SEARCH_RESULTS,
keywords,
"data"
)
const scenarioParams = makeSearchParamsWithDataType(
TOTAL_SEARCH_RESULTS,
keywords,
"scenario"
)
const orgParams = makeSearchParamsWithDataType(
TOTAL_SEARCH_RESULTS,
keywords,
"organization"
)
try { try {
console.log("params------->", params) const response = await fetch(iodConfig.doBrowser, {
const requests = [ method: "POST",
fetch(iodConfig.doBrowser, { body: JSON.stringify(params),
method: "POST", signal: abortController.signal
body: JSON.stringify(dataParams), });
signal: abortController.signal
}),
fetch(iodConfig.doBrowser, {
method: "POST",
body: JSON.stringify(scenarioParams),
signal: abortController.signal
}),
fetch(iodConfig.doBrowser, {
method: "POST",
body: JSON.stringify(orgParams),
signal: abortController.signal
})
]
//TODO @Zhaoweijie 这三类分别是数据、场景、团队的搜索请求。
const responses = await Promise.all(requests)
const results = await Promise.all(responses.map((res) => res.json()))
const allResults: AllIodRegistryEntry = getDefaultIodSources() const res = await response.json();
let i = 0 if (res.status !== "Success") {
for (const res of results) { return [];
// 检查顶层状态
if (res.status !== "Success") {
continue // 跳过失败的请求
}
let body
try {
body = JSON.parse(res.result.body)
} catch (e) {
console.warn("Failed to parse result.body as JSON", e)
continue
}
if (body.code !== 0) {
continue
}
const entries: IodRegistryEntry[] = body.data?.results || []
const prunedEntries: IodRegistryEntry[] = []
const seenDoIds = new Set<string>()
// 数据清洗:补全 url 和 doId
for (const r of entries) {
r.url = r.url || r.pdf_url
// @ts-ignore
r.doId = r.doId || r.doid
if (seenDoIds.has(r.doId)) {
continue
}
prunedEntries.push(r)
}
// 数据
if (i === 0) {
allResults.data = {
data: prunedEntries,
total: body.data?.total ?? 0
}
}
// 场景
if (i === 1) {
allResults.scenario = {
data: prunedEntries,
total: body.data?.total ?? 0
}
}
// 团队
if (i === 2) {
allResults.organization = {
data: prunedEntries,
total: body.data?.total ?? 0
}
}
i++
} }
return allResults const body = JSON.parse(res.result.body);
if (body.code !== 0) {
return [];
}
let results: IodRegistryEntry[] = body.data?.results || [];
for (const r of results) {
r.url = r.url || r.pdf_url;
}
for (const r of results) {
r.doId = r.doId || r.doid;
}
// results 根据 doId 去重
const map = new Map<string, IodRegistryEntry>();
for (const r of results) {
map.set(r.doId, r);
}
return Array.from(map.values());
} catch (e) { } catch (e) {
console.log(e) console.log(e);
return getDefaultIodSources() return [];
} }
/*
const results = (
await Promise.all(
keywords.map(async (keyword) => {
const abortController = new AbortController()
setTimeout(() => abortController.abort(), 10000)
//http://47.93.156.31:21033/SCIDE/SCManager
const params = makeRegSearchParams(TOTAL_SEARCH_RESULTS, keyword)
return fetch(iodConfig.doBrowser, {
method: "POST",
body: JSON.stringify(params),
signal: abortController.signal
})
.then((response) => response.json())
.then((res) => {
if (res.status !== "Success") {
console.log(res)
return []
}
const body = JSON.parse(res.result.body)
if (body.code !== 0) {
console.log(body)
return []
}
const results: IodRegistryEntry[] = body.data?.results || []
for (const r of results) {
r.url = r.url || r.pdf_url
}
for (const r of results) {
r.doId = r.doId || r.doid
}
return results
})
.catch((e) => {
console.log(e)
return []
})
})
)
).flat()
// results 根据 doId 去重
const map = new Map<string, IodRegistryEntry>()
for (const r of results) {
map.set(r.doId, r)
}
return Array.from(map.values())
*/
} }
const ARXIV_URL_PATTERN = /^https?:\/\/arxiv\.org\// const ARXIV_URL_PATTERN = /^https?:\/\/arxiv\.org\//
@ -390,41 +279,27 @@ export const searchIod = async (query: string, keywords: string[]) => {
const searchResults = await localIodSearch(query, keywords) const searchResults = await localIodSearch(query, keywords)
const isSimpleMode = await getIsSimpleInternetSearch() const isSimpleMode = await getIsSimpleInternetSearch()
console.log( console.log("searchMode:"+isSimpleMode+"\n kw:"+JSON.stringify(keywords)+"\n"+" ->searchResult:\n"+JSON.stringify(searchResults))
"searchMode:" +
isSimpleMode +
"\n kw:" +
JSON.stringify(keywords) +
"\n" +
" ->searchResult:\n" +
JSON.stringify(searchResults)
)
if (isSimpleMode) { if (isSimpleMode) {
await getOllamaURL() await getOllamaURL()
return searchResults return searchResults
} }
const docs: Document<Record<string, any>>[] = [] const docs: Document<Record<string, any>>[] = []
for (const result of Object.values(searchResults) const resMap = new Map<string, IodRegistryEntry>()
.map((item) => item.data) for (const result of searchResults) {
.flat()) {
const url = result.url const url = result.url
if (result.doId) { if (result.doId){
//TODO !!!!@Nex traceId should be the id of history/question! //TODO !!!!@Nex traceId should be the id of history/question!
let docFromRetrieve = await retrieveDoc(result.doId) let docFromRetrieve = await retrieveDoc(result.doId);
console.log( console.log("doc from Retrieve:"+result.doId+" -->"+JSON.stringify(docFromRetrieve))
"doc from Retrieve:" + docs.push(docFromRetrieve)
result.doId + result.description = docFromRetrieve.pageContent;
" -->" + result.traceId = docFromRetrieve.metadata?.traceId;
JSON.stringify(docFromRetrieve) continue;
)
docs.push(docFromRetrieve)
result.description = docFromRetrieve.pageContent
result.traceId = docFromRetrieve.metadata?.traceId
continue
} }
if (!url) { if (!url) {
continue continue;
} }
let htmlUrl = "" let htmlUrl = ""
@ -522,7 +397,8 @@ export const searchIod = async (query: string, keywords: string[]) => {
*/ */
} }
export const calculateTokenCount = function (str: string) { export const calculateTokenCount = function(str:string){
const byteArray = new TextEncoder().encode(str) const byteArray = new TextEncoder().encode(str);
return byteArray.length return byteArray.length;
} }

File diff suppressed because one or more lines are too long

View File

@ -1,5 +1,4 @@
import { defineConfig } from "wxt" import { defineConfig } from "wxt"
import { defineRunnerConfig } from "wxt"
import react from "@vitejs/plugin-react" import react from "@vitejs/plugin-react"
import topLevelAwait from "vite-plugin-top-level-await" import topLevelAwait from "vite-plugin-top-level-await"
@ -9,6 +8,7 @@ const chromeMV3Permissions = [
"activeTab", "activeTab",
"scripting", "scripting",
"declarativeNetRequest", "declarativeNetRequest",
"action",
"unlimitedStorage", "unlimitedStorage",
"contextMenus", "contextMenus",
"tts", "tts",
@ -49,6 +49,7 @@ export default defineConfig({
process.env.TARGET === "firefox" ? "entries-firefox" : "entries", process.env.TARGET === "firefox" ? "entries-firefox" : "entries",
srcDir: "src", srcDir: "src",
outDir: "build", outDir: "build",
manifest: { manifest: {
version: "1.5.0", version: "1.5.0",
name: name: