1 Commits

Author SHA1 Message Date
zhaoweijie
90228512f7 feat: change token get 2025-02-24 10:10:21 +08:00
58 changed files with 705 additions and 5214 deletions

View File

@@ -49,9 +49,7 @@ cd page-assist
2. Install the dependencies 2. Install the dependencies
```bash ```bash
export PATH="/Users/huaqiancai/.bun/bin/:$PATH"
bun install bun install
``` ```
3. Build the extension (by default it will build for Chrome) 3. Build the extension (by default it will build for Chrome)

2500
bun.lock

File diff suppressed because one or more lines are too long

BIN
bun.lockb Executable file

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 659 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 763 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 941 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 610 KiB

View File

@@ -1,6 +1,6 @@
{ {
"name": "pageassist", "name": "pageassist",
"displayName": "IoD Bot - A Web UI for Local AI Models", "displayName": "Page Assist - A Web UI for Local AI Models",
"version": "1.0.9", "version": "1.0.9",
"description": "Use your locally running AI models to assist you in your web browsing.", "description": "Use your locally running AI models to assist you in your web browsing.",
"author": "n4ze3m", "author": "n4ze3m",
@@ -58,8 +58,6 @@
"rehype-mathjax": "4.0.3", "rehype-mathjax": "4.0.3",
"remark-gfm": "3.0.1", "remark-gfm": "3.0.1",
"remark-math": "5.1.1", "remark-math": "5.1.1",
"segmentit": "^2.0.3",
"styled-components": "^6.1.19",
"tesseract.js": "^5.1.1", "tesseract.js": "^5.1.1",
"turndown": "^7.1.3", "turndown": "^7.1.3",
"unist-util-visit": "^5.0.0", "unist-util-visit": "^5.0.0",
@@ -77,7 +75,6 @@
"@types/react-dom": "18.2.18", "@types/react-dom": "18.2.18",
"@types/react-speech-recognition": "^3.9.5", "@types/react-speech-recognition": "^3.9.5",
"@types/react-syntax-highlighter": "^15.5.11", "@types/react-syntax-highlighter": "^15.5.11",
"@types/styled-components": "^5.1.34",
"@types/turndown": "^5.0.4", "@types/turndown": "^5.0.4",
"autoprefixer": "^10.4.17", "autoprefixer": "^10.4.17",
"cross-env": "^7.0.3", "cross-env": "^7.0.3",

View File

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

Before

Width:  |  Height:  |  Size: 590 B

View File

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

Before

Width:  |  Height:  |  Size: 601 B

View File

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

Before

Width:  |  Height:  |  Size: 1.8 KiB

View File

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

Before

Width:  |  Height:  |  Size: 1.4 KiB

View File

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

Before

Width:  |  Height:  |  Size: 1.5 KiB

View File

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

Before

Width:  |  Height:  |  Size: 1.7 KiB

View File

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

Before

Width:  |  Height:  |  Size: 601 B

View File

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

Before

Width:  |  Height:  |  Size: 2.2 KiB

View File

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

Before

Width:  |  Height:  |  Size: 2.6 KiB

View File

@@ -1,5 +1,5 @@
{ {
"pageAssist": "IoD Bot", "pageAssist": "Page Assist",
"selectAModel": "选择一个模型", "selectAModel": "选择一个模型",
"save": "保存", "save": "保存",
"saved": "已保存", "saved": "已保存",
@@ -38,7 +38,7 @@
} }
}, },
"copyToClipboard": "复制到剪贴板", "copyToClipboard": "复制到剪贴板",
"webSearch": "搜索中...", "webSearch": "搜索万维网",
"iodSearch": "搜索数联网", "iodSearch": "搜索数联网",
"regenerate": "重新生成", "regenerate": "重新生成",
"edit": "编辑", "edit": "编辑",

View File

@@ -1,17 +1,16 @@
{ {
"projectTitle": "数联网科创智能体", "newChat": "新聊天",
"newChat": "新对话",
"selectAPrompt": "选择一个提示词", "selectAPrompt": "选择一个提示词",
"githubRepository": "GitHub 仓库", "githubRepository": "GitHub 仓库",
"settings": "设置", "settings": "设置",
"metering": "计量", "metering": "计量",
"sidebarTitle": "对话历史", "sidebarTitle": "聊天历史",
"error": "错误", "error": "错误",
"somethingWentWrong": "出现了错误", "somethingWentWrong": "出现了错误",
"validationSelectModel": "请选择一个模型以继续", "validationSelectModel": "请选择一个模型以继续",
"deleteHistoryConfirmation": "你确定要删除这个历史记录吗?", "deleteHistoryConfirmation": "你确定要删除这个历史记录吗?",
"editHistoryTitle": "输入一个新的标题", "editHistoryTitle": "输入一个新的标题",
"temporaryChat": "临时对话", "temporaryChat": "临时聊天",
"more": { "more": {
"copy": { "copy": {
"group": "复制", "group": "复制",

View File

@@ -19,7 +19,7 @@
} }
}, },
"tooltip": { "tooltip": {
"searchInternet": "深度搜索", "searchInternet": "搜索万维网",
"searchIod": "搜索数联网", "searchIod": "搜索数联网",
"speechToText": "语音到文本", "speechToText": "语音到文本",
"uploadImage": "上传图片", "uploadImage": "上传图片",

View File

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

View File

@@ -12,7 +12,7 @@ import { preprocessLaTeX } from "@/utils/latex"
function Markdown({ function Markdown({
message, message,
className = "prose-lg break-words dark:prose-invert prose-p:leading-relaxed prose-pre:p-0 dark:prose-dark" className = "prose break-words dark:prose-invert prose-p:leading-relaxed prose-pre:p-0 dark:prose-dark"
}: { }: {
message: string message: string
className?: string className?: string

View File

@@ -1,150 +0,0 @@
import React from "react"
import { DataNavigation } from "@/components/Common/DataNavigation.tsx"
import { Card, Drawer, List } from "antd"
import { useCallback, useState } from "react"
export const PlaygroundData = () => {
// 模拟数据
const data: {
title: string
description: string
time: string
metadata?: string
}[] = [
{
title: "2019-2024年黄海清浅海域中河湖代数生物物种数据集",
description:
"数字对象标识: CSTR:13452.11.01.11.2021.242 国家海洋科学数据中心",
time: "包括2019年8月2021年8月和2024年6月",
metadata: "热 榜 第2"
},
{
title: "祁连山老虎沟大本营10米气象每日值数据集V1.02018-2023",
description:
"中国科学院西北生态环境资源研究院2021年8月3日发布2021年8月3日20:48更新",
time: "包括2019年8月2021年8月和2024年6月",
metadata: "热 榜 第2"
},
{
title: "李嘉图为研究老虎沟大本营2014-2018年...",
description:
"中国科学院西北生态环境资源研究院2021年8月3日发布2021年8月3日20:48更新",
time: "包括2019年8月2021年8月和2024年6月",
metadata: "热 榜 第2"
},
{
title: "青海玉树B1区俄日矿勘探数据2017-2023",
description:
"数字中国集团CSTR:3260.11.1528414774204895456DT2023年地质勘探补充调查",
time: "包括2019年8月2021年8月和2024年6月",
metadata: "热 榜 第2"
}
]
for (let i = 0; i < 10; i++) {
data.push({
title: "中国资源环境网",
description: "中国资源环境网2021年8月3日发布2021年8月3日20:48更新",
time: "包括2019年8月2021年8月和2024年6月"
})
}
const [open, setOpen] = useState(false)
const showDrawer = () => {
setOpen(true)
}
const onClose = () => {
setOpen(false)
}
return (
<Card
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">
{/* 数据导航 */}
<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}
/>
{/* 数据列表 */}
<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>
{/* 抽屉 */}
<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>
)
}

View File

@@ -1,263 +0,0 @@
import { Sidebar } from "@/components/Option/Sidebar.tsx"
import React, { useContext, useMemo, useState } from "react"
import { useMessageOption } from "@/hooks/useMessageOption.tsx"
import { useStoreChatModelSettings } from "@/store/model.tsx"
import {
Button,
Card,
Divider,
List,
Menu,
MenuProps,
Popover,
Select,
Tooltip
} from "antd"
import { PageAssitDatabase } from "@/db"
import { EraserIcon, PanelLeftIcon } from "lucide-react"
import { useTranslation } from "react-i18next"
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"
import { HistoryContext } from "@/components/Layouts/Layout.tsx"
import { PlusOutlined, RightOutlined } from "@ant-design/icons"
import { qaPrompt } from "@/libs/playground.tsx"
import { ProviderIcons } from "@/components/Common/ProviderIcon.tsx"
import { fetchChatModels } from "@/services/ollama.ts"
const ModelIcon = () => {
return (
<svg
className="icon"
viewBox="0 0 1024 1024"
version="1.1"
xmlns="http://www.w3.org/2000/svg"
p-id="9426"
width="16"
height="16">
<path
d="M509.952 161.512727c148.945455-82.850909 300.730182-91.229091 371.479273-20.945454s62.324364 221.509818-20.526546 370.501818h-0.465454a429.335273 429.335273 0 0 1 65.163636 284.392727 168.727273 168.727273 0 0 1-44.683636 85.643637 173.754182 173.754182 0 0 1-86.109091 44.683636 435.665455 435.665455 0 0 1-285.277091-65.675636 430.731636 430.731636 0 0 1-282.530909 63.813818 172.218182 172.218182 0 0 1-86.109091-44.683637c-70.283636-69.818182-62.370909-220.206545 19.502545-368.174545-81.966545-148.48-89.786182-298.309818-19.502545-368.686546s220.625455-62.324364 369.058909 19.130182z m291.886545 440.785455a901.818182 901.818182 0 0 1-92.16 106.589091 934.027636 934.027636 0 0 1-108.916363 93.602909 586.891636 586.891636 0 0 0 58.600727 21.410909c74.938182 22.341818 127.069091 19.502545 155.508364-8.843636l-0.465455 0.884363c28.811636-28.392727 31.697455-80.523636 8.843637-155.508363a546.443636 546.443636 0 0 0-21.41091-58.135273z m-582.74909-0.465455a539.927273 539.927273 0 0 0-20.433455 55.854546c-22.295273 75.357091-19.549091 127.022545 8.797091 155.368727s80.151273 31.697455 155.508364 8.936727h-0.558546a539.927273 539.927273 0 0 0 55.854546-20.526545 967.400727 967.400727 0 0 1-199.214546-199.726546z m290.90909-332.753454a851.781818 851.781818 0 0 0-131.258181 108.404363 823.296 823.296 0 0 0-109.847273 133.12 823.854545 823.854545 0 0 0 109.847273 133.12v-0.884363a852.293818 852.293818 0 0 0 131.211636 108.357818 846.754909 846.754909 0 0 0 133.538909-109.800727 856.436364 856.436364 0 0 0 108.962909-131.258182 852.852364 852.852364 0 0 0-108.962909-131.211637 829.998545 829.998545 0 0 0-133.538909-109.847272zM503.994182 418.909091a94.347636 94.347636 0 1 1-35.84 10.705454 92.811636 92.811636 0 0 1 35.84-10.705454z m310.877091-212.340364c-28.253091-28.299636-80.151273-31.557818-155.508364-8.750545a591.592727 591.592727 0 0 0-58.600727 21.876363 933.794909 933.794909 0 0 1 108.869818 93.556364 947.060364 947.060364 0 0 1 92.718545 107.054546 545.326545 545.326545 0 0 0 21.41091-58.181819q33.559273-113.058909-8.843637-155.508363zM363.054545 199.68c-74.938182-22.295273-127.069091-19.549091-155.508363 8.843636v-0.465454c-28.997818 28.392727-31.744 80.523636-8.936727 155.508363a507.345455 507.345455 0 0 0 20.433454 56.273455A976.663273 976.663273 0 0 1 418.909091 220.206545a541.230545 541.230545 0 0 0-55.854546-20.526545z m0 0"
fill="#696F85"
p-id="9427"></path>
</svg>
)
}
export const PlaygroundHistory = () => {
const { setSystemPrompt } = useStoreChatModelSettings()
const { show, setShow } = useContext(HistoryContext)
const {
setMessages,
setHistory,
setHistoryId,
historyId,
clearChat,
selectedModel,
setSelectedModel,
temporaryChat,
setSelectedSystemPrompt
} = useMessageOption()
const { t } = useTranslation(["option", "common", "settings"])
const queryClient = useQueryClient()
type MenuItem = Required<MenuProps>["items"][number]
const qaPromptItems = useMemo<MenuItem[]>(() => {
return [
{
key: "qaPrompt",
label: "热门搜索",
type: "group" as const,
children: qaPrompt.map((item) => {
return {
key: item.id,
label: <span title={item.title}>{item.title}</span>,
icon: <p className="w-3.5">{item.icon}</p>
}
})
}
]
}, [])
const { onSubmit } = useMessageOption()
const { mutateAsync: sendMessage } = useMutation({
mutationFn: onSubmit,
onSuccess: () => {
queryClient.invalidateQueries({
queryKey: ["fetchChatHistory"]
})
}
})
const onClickQaPromptItem: MenuProps["onClick"] = (e) => {
const record = qaPrompt.find((item) => item.id === e.key)
void sendMessage({ message: record.title, image: "" })
}
// 大模型
const {
data: models,
isLoading: isModelsLoading,
refetch
} = useQuery({
queryKey: ["fetchModel"],
queryFn: () => fetchChatModels({ returnEmpty: true }),
refetchIntervalInBackground: false,
placeholderData: (prev) => prev
})
return (
<Card
className={`flex flex-col [&_.ant-card-body]:h-full w-[300px] overflow-hidden h-full pb-5 transition-all duration-300 ease-in-out !bg-[#f3f4f6]`}
style={{ width: show ? "300px" : "0" }}>
{/*Header*/}
<div className="flex flex-col overflow-y-hidden h-full">
<div className="flex items-center justify-between transition-all duration-300 ease-in-out w-[250px]">
<h2 className="text-xl font-bold text-zinc-700 dark:text-zinc-300 mr-3">
<span className="text-[#d30100]"></span>
</h2>
<button
className="text-gray-500 dark:text-gray-400"
onClick={() => {
setShow(!show)
}}>
<PanelLeftIcon className="w-6 h-6" />
</button>
</div>
<div className="flex flex-col gap-1">
{/*新建对话*/}
<Button
color="purple"
variant="filled"
size="large"
className="w-full mt-4 hover:!bg-[#0057ff1a]"
style={{
color: "#0057ff",
background: "#0057ff0f",
border: "1px solid #0066ff26"
}}
onClick={clearChat}>
<div className="flex items-center justify-between w-full">
<div className="flex items-center">
<PlusOutlined
className="text-sm"
style={{ fontSize: "16px", fontWeight: 500 }}
/>
<span className="font-medium ml-2.5">{t("newChat")}</span>
</div>
</div>
</Button>
{/*选择智能体*/}
<Popover
placement="right"
content={
<Select
className="w-80"
placeholder={t("common:selectAModel")}
// loadingText={t("common:selectAModel")}
value={selectedModel}
onChange={(e) => {
setSelectedModel(e)
localStorage.setItem("selectedModel", e)
}}
filterOption={(input, option) => {
//@ts-ignore
return (
option?.label?.props["data-title"]
?.toLowerCase()
?.indexOf(input.toLowerCase()) >= 0
)
}}
showSearch
loading={isModelsLoading}
options={models?.map((model) => ({
label: (
<span
key={model.model}
data-title={model.name}
className="flex flex-row gap-3 items-center ">
<ProviderIcons
provider={model?.provider}
className="w-5 h-5"
/>
<span className="line-clamp-2">{model.name}</span>
</span>
),
value: model.model
}))}
size="large"
// onRefresh={() => {
// refetch()
// }}
/>
}>
<Button
size="large"
color="default"
variant="text"
className="w-full !justify-between !text-[#000000d9] font-normal">
<div className="flex items-center gap-2.5">
<ModelIcon />
<span className="!text-[#000000d9] font-normal text-sm">
</span>
</div>
<RightOutlined style={{ color: "#0000004d" }} />
</Button>
</Popover>
<Divider size="small" />
{/*热门搜索*/}
<Menu
items={qaPromptItems}
onClick={onClickQaPromptItem}
className="!bg-[#f3f4f6] !border-r-0"
/>
</div>
<Divider size="small" />
<div className="pb-1.5 pl-4 text-sm text-[#00000073] flex items-center justify-between pr-2">
<span></span>
<Tooltip
title={t("settings:generalSettings.system.deleteChatHistory.label")}
placement="right">
<button
onClick={async () => {
const confirm = window.confirm(
t("settings:generalSettings.system.deleteChatHistory.confirm")
)
if (confirm) {
const db = new PageAssitDatabase()
await db.deleteAllChatHistory()
await queryClient.invalidateQueries({
queryKey: ["fetchChatHistory"]
})
clearChat()
}
}}
className="text-gray-600 hover:text-gray-800 dark:text-gray-300 dark:hover:text-gray-100">
<EraserIcon className="size-5" />
</button>
</Tooltip>
</div>
<div className="overflow-y-auto flex-1 pl-7">
<Sidebar
onClose={() => setShow(true)}
setMessages={setMessages}
setHistory={setHistory}
setHistoryId={setHistoryId}
setSelectedModel={setSelectedModel}
setSelectedSystemPrompt={setSelectedSystemPrompt}
clearChat={clearChat}
historyId={historyId}
setSystemPrompt={setSystemPrompt}
temporaryChat={temporaryChat}
history={history}
/>
</div>
</div>
</Card>
)
}

View File

@@ -1,185 +0,0 @@
import React from "react"
import { Button, Card } from "antd"
// 使用 CSS-in-JS 方式
import styled, { keyframes } from 'styled-components'
const rotate = keyframes`
0% {
transform: translate(-50%, -50%) rotate(0deg);
}
100% {
transform: translate(-50%, -50%) rotate(360deg);
}
`
const breathe = keyframes`
0% {
box-shadow: 0 0 5px rgba(37, 231, 232, 0.3);
}
50% {
box-shadow: 0 0 20px rgba(37, 231, 232, 0.8);
}
100% {
box-shadow: 0 0 5px rgba(37, 231, 232, 0.3);
}
`
const CircleElement = styled.div<{ delay: number }>`
position: absolute;
width: 300px;
height: 160px;
background: #3b82f6; // blue-500
opacity: 0.2;
border-radius: 50%;
top: 55%;
left: 50%;
animation: ${rotate} 6s linear infinite, ${breathe} 2s infinite alternate;
animation-delay: ${props => props.delay}s;
`
const SuccessIcon = () => {
return (
<svg
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
className="w-full h-full text-green-500">
<path
d="M9 12L11 14L15 10M21 12C21 16.9706 16.9706 21 12 21C7.02944 21 3 16.9706 3 12C3 7.02944 7.02944 3 12 3C16.9706 3 21 7.02944 21 12Z"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
)
}
const LoadingIcon = () => {
return (
<svg
className="icon animate-spin"
viewBox="0 0 1024 1024"
version="1.1"
xmlns="http://www.w3.org/2000/svg"
p-id="8408"
width="18"
height="18">
<path
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"
fill="#52c41a"
p-id="8409"></path>
</svg>
)
}
const SearchIcon = () => {
return (
<svg
className="icon"
viewBox="0 0 1024 1024"
version="1.1"
xmlns="http://www.w3.org/2000/svg"
p-id="2585"
width="22px"
height="22px">
<path
d="M913.365333 842.794667l-188.16-188.16a347.648 347.648 0 0 0 69.674667-209.194667c0-192.682667-156.757333-349.44-349.44-349.44s-349.44 156.757333-349.44 349.44 156.757333 349.44 349.44 349.44a347.648 347.648 0 0 0 209.152-69.674667l188.16 188.16a49.962667 49.962667 0 0 0 70.613333-70.570666zM195.84 445.44a249.6 249.6 0 1 1 249.6 249.6 249.898667 249.898667 0 0 1-249.6-249.6z"
fill="#08307f"
p-id="2586"></path>
</svg>
)
}
export const PlaygroundIodRelevant: React.FC = () => {
const data = [
{
title: <p><span className="text-[#9d0000]">29</span><span className="text-[#9d0000]">50</span></p>,
description: <p><span className="text-green-700"> 4 </span></p>,
status: "success"
},
{
title: <p><span className="text-[#9d0000]">100</span><span className="text-[#9d0000]">2800</span></p>,
description: "已发现4个数据集",
status: "success"
},
{
title: <p><span className="text-[#9d0000]">1000</span><span className="text-[#9d0000]">12</span></p>,
status: "loading"
}
]
for (let i = 0; i < 10; i++) {
data.push({
title: <p><span className="text-[#9d0000]">1000</span><span className="text-[#9d0000]">12</span>{i}</p>,
description: "已发现4个数据集",
status: "success"
})
}
return (
<Card
hoverable
variant="outlined"
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="absolute inset-0 pointer-events-none z-0 overflow-hidden">
<div className="absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2 w-64 h-64">
<CircleElement delay={0} />
<CircleElement delay={1} />
<CircleElement delay={2} />
</div>
</div>
{/* Header */}
<div className="p-3">
<h2 className="text-xl font-semibold text-[#08307f] flex justify-between items-center">
<div className='flex items-center gap-2'>
<SearchIcon />
</div>
<button className="bg-[#2563eb1a] text-[#08307f] font-medium py-1 px-3 rounded-full text-sm hover:bg-[#2563eb1a] transition-colors float-right">
{data.length}
</button>
</h2>
<p className="text-sm text-[#08307f] mt-1 align-middle">
</p>
</div>
{/* Content */}
<div className="space-y-2 flex-1 overflow-y-auto">
{data.map((item, index) => (
<Card
className="[&>*:first-child]:!p-3 shadow-md"
key={index}
>
<div className="flex items-start gap-2">
<div className="w-5 h-5 mt-1 flex-shrink-0">
{item.status === "success" ? (
<SuccessIcon />
) : (
<LoadingIcon />
)}
</div>
<div className="flex-1">
<p className="text-sm text-gray-700">{item.title}</p>
{item.description && (
<p className="text-xs text-gray-500 mt-1">
{item.description}
</p>
)}
</div>
</div>
</Card>
))}
</div>
</div>
</Card>
)
}

View File

@@ -9,12 +9,7 @@ import {
Pen, Pen,
PlayIcon, PlayIcon,
RotateCcw, RotateCcw,
Square, Square
Star,
ThumbsUp,
ThumbsDown,
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"
@@ -58,14 +53,14 @@ export const PlaygroundMessage = (props: Props) => {
const { t } = useTranslation("common") const { t } = useTranslation("common")
const { cancel, isSpeaking, speak } = useTTS() const { cancel, isSpeaking, speak } = useTTS()
return ( return (
<div className="group relative flex w-full 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 max-w-3xl flex-col items-end justify-center pb-2 md:px-4 lg:w-4/5 text-gray-800 dark:text-gray-100">
{/* <div className="text-base md:max-w-2xl lg:max-w-xl xl:max-w-3xl flex lg:px-0 m-auto w-full"> */} {/* <div className="text-base md:max-w-2xl lg:max-w-xl xl:max-w-3xl flex lg:px-0 m-auto w-full"> */}
<div className={`flex flex-row gap-1 md:gap-1 my-2 m-auto w-full ${props.isBot ? "" : "flex-row-reverse"}`}> <div className="flex flex-row gap-4 md:gap-6 my-2 m-auto w-full">
<div className="w-8 flex flex-col relative items-center justify-center bottom-[8px]"> <div className="w-8 flex flex-col relative items-end">
<div className="relative h-7 w-7 p-1 rounded-sm text-white flex items-center justify-center text-opacity-100r"> <div className="relative h-7 w-7 p-1 rounded-sm text-white flex items-center justify-center text-opacity-100r">
{props.isBot ? ( {props.isBot ? (
!props.botAvatar ? ( !props.botAvatar ? (
<div className="absolute h-8 w-8 rounded-full bg-gradient-to-r from-green-300 to-purple-400 hidden"></div> <div className="absolute h-8 w-8 rounded-full bg-gradient-to-r from-green-300 to-purple-400"></div>
) : ( ) : (
props.botAvatar props.botAvatar
) )
@@ -78,6 +73,13 @@ export const PlaygroundMessage = (props: Props) => {
</div> </div>
<div className="flex w-[calc(100%-50px)] flex-col gap-2 lg:w-[calc(100%-115px)]"> <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 className="text-xs font-bold text-gray-800 dark:text-white">
{props.isBot
? props.name === "chrome::gemini-nano::page-assist"
? "Gemini Nano"
: removeModelSuffix(
props.name?.replaceAll(/accounts\/[^\/]+\/models\//g, "")
)
: "You"}
</span> </span>
{props.isBot && {props.isBot &&
@@ -92,7 +94,7 @@ export const PlaygroundMessage = (props: Props) => {
</Tag> </Tag>
)} )}
</div> </div>
<div className={`flex flex-grow flex-col`}> <div className="flex flex-grow flex-col">
{!editMode ? ( {!editMode ? (
props.isBot ? ( props.isBot ? (
<> <>
@@ -131,12 +133,11 @@ export const PlaygroundMessage = (props: Props) => {
</> </>
) : ( ) : (
<p <p
className={`prose-lg dark:prose-invert whitespace-pre-line prose-p:leading-relaxed prose-pre:p-0 dark:prose-dark ${ className={`prose 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 text-gray-500 dark:text-gray-400 text-sm"
} 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>
) )
) : ( ) : (
@@ -315,51 +316,6 @@ export const PlaygroundMessage = (props: Props) => {
</button> </button>
</Tooltip> </Tooltip>
)} )}
{ (
<Tooltip title="收藏">
<button
aria-label="收藏"
className="flex items-center justify-center w-6 h-6 rounded-full bg-gray-100 dark:bg-gray-800 hover:bg-gray-200 dark:hover:bg-gray-700 transition-colors duration-200 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-gray-500">
<Star className="w-3 h-3 text-gray-400 group-hover:text-gray-500" />
</button>
</Tooltip>
)}
{ (
<Tooltip title="发布语用">
<button
aria-label="发布语用"
className="flex items-center justify-center w-6 h-6 rounded-full bg-gray-100 dark:bg-gray-800 hover:bg-gray-200 dark:hover:bg-gray-700 transition-colors duration-200 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-gray-500">
<ArrowUpSquare className="w-3 h-3 text-gray-400 group-hover:text-gray-500" />
</button>
</Tooltip>
)}
{ (
<Tooltip title="发布对话">
<button
aria-label="发布对话"
className="flex items-center justify-center w-6 h-6 rounded-full bg-gray-100 dark:bg-gray-800 hover:bg-gray-200 dark:hover:bg-gray-700 transition-colors duration-200 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-gray-500">
<MessageSquareShare className="w-3 h-3 text-gray-400 group-hover:text-gray-500" />
</button>
</Tooltip>
)}
{ (
<Tooltip title="点赞">
<button
aria-label="点赞"
className="flex items-center justify-center w-6 h-6 rounded-full bg-gray-100 dark:bg-gray-800 hover:bg-gray-200 dark:hover:bg-gray-700 transition-colors duration-200 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-gray-500">
<ThumbsUp className="w-3 h-3 text-gray-400 group-hover:text-gray-500" />
</button>
</Tooltip>
)}
{ (
<Tooltip title="点踩">
<button
aria-label="点踩"
className="flex items-center justify-center w-6 h-6 rounded-full bg-gray-100 dark:bg-gray-800 hover:bg-gray-200 dark:hover:bg-gray-700 transition-colors duration-200 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-gray-500">
<ThumbsDown className="w-3 h-3 text-gray-400 group-hover:text-gray-500" />
</button>
</Tooltip>
)}
</div> </div>
) : ( ) : (
// add invisible div to prevent layout shift // add invisible div to prevent layout shift

View File

@@ -1,143 +0,0 @@
import React, { useState } from "react"
import { DataNavigation } from "@/components/Common/DataNavigation.tsx"
import { Card, Drawer, List } from "antd"
export const PlaygroundScene = () => {
// 模拟数据
const data = [
{
title: "绿色化工工艺项目",
description:
"基于生物基原料采用repeal2.0可降解材料技术,开发新型环保材料。",
demander: "奥赛康药业 供方美国Propella公司"
},
{
title: "智能农业解决方案",
description: "利用物联网技术,实现精准农业管理,提高农作物产量。",
demander: "奥赛康药业 供方美国Propella公司"
},
{
title: "新能源汽车电池技术",
description: "研发高能量密度、长寿命的新型电池材料,推动电动汽车发展。",
demander: "奥赛康药业 供方美国Propella公司"
},
{
title: "碳捕集与封存技术",
description: "开发高效的碳捕集技术,减少工业排放,助力碳中和目标。",
demander: "奥赛康药业 供方美国Propella公司"
}
]
for (let i = 0; i < 10; i++) {
data.push({
title: "开发新型电池材料",
description: "研发高能量密度、长寿命的新型电池材料,推动电动汽车发展。",
demander: "奥赛康药业 供方美国Propella公司"
})
}
const [open, setOpen] = useState(false)
const showDrawer = () => {
setOpen(true)
}
const onClose = () => {
setOpen(false)
}
return (
<Card
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">
{/* 数据导航 */}
<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}
/>
{/* 场景列表 */}
<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>
{/* 抽屉 */}
<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>
)
}

View File

@@ -1,141 +0,0 @@
import React, { useState } from "react"
import { DataNavigation } from "@/components/Common/DataNavigation.tsx"
import { Card, Drawer, List } from "antd"
export const PlaygroundTeam = () => {
// 模拟数据
const data = [
{
title: "绿色化工工艺项目",
description:
"基于生物基原料采用repeal2.0可降解材料技术,开发新型环保材料。",
demander: "奥赛康药业 供方美国Propella公司"
},
{
title: "智能农业解决方案",
description: "利用物联网技术,实现精准农业管理,提高农作物产量。",
demander: "奥赛康药业 供方美国Propella公司"
},
{
title: "新能源汽车电池技术",
description: "研发高能量密度、长寿命的新型电池材料,推动电动汽车发展。",
demander: "奥赛康药业 供方美国Propella公司"
},
{
title: "碳捕集与封存技术",
description: "开发高效的碳捕集技术,减少工业排放,助力碳中和目标。",
demander: "奥赛康药业 供方美国Propella公司"
}
]
for (let i = 0; i < 10; i++) {
data.push({
title: "开发新型电池材料",
description: "研发高能量密度、长寿命的新型电池材料,推动电动汽车发展。",
demander: "奥赛康药业 供方美国Propella公司"
})
}
const [open, setOpen] = useState(false)
const showDrawer = () => {
setOpen(true)
}
const onClose = () => {
setOpen(false)
}
return (
<Card
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">
{/* 数据导航 */}
<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>
</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>
{/* 抽屉 */}
<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>
)
}

View File

@@ -1,46 +0,0 @@
import { DataNavigation } from "@/components/Common/DataNavigation.tsx"
import { Card, Descriptions, DescriptionsProps, Drawer, List, Spin } from "antd"
import { useCallback, useMemo, useState } from "react"
import { useMessageOption } from "@/hooks/useMessageOption.tsx"
import { useStoreMessageOption } from "@/store/option.tsx"
export const PlaygroundTokenStatistics = () => {
const { currentMeteringEntry } = useStoreMessageOption()
const items = useMemo<DescriptionsProps["items"]>(() => {
const { data } = currentMeteringEntry
return [
// {
// key: "relatedDataCount",
// label: "关联数据个数",
// children: data.relatedDataCount
// },
{
key: "iodTokenCount",
label: "数联网引用token总数",
children: data.iodTokenCount ?? 0
},
{
key: "modelInputTokenCount",
label: "大模型输入token数量",
children: data.modelInputTokenCount ?? 0
},
{
key: "modelOutputTokenCount",
label: "大模型输出token数量",
children: data.modelOutputTokenCount ?? 0
}
]
}, [currentMeteringEntry])
return (
<Card
style={{ marginBottom: "1rem" }}
className="h-full"
title={<DataNavigation title="Token统计" showButton={false} />}>
<Spin spinning={currentMeteringEntry.loading}>
<Descriptions layout="horizontal" items={items} column={2} />
</Spin>
</Card>
)
}

View File

@@ -1,66 +1,255 @@
import React, { useContext } from "react" import { useStorage } from "@plasmohq/storage/hook"
import { HistoryContext } from "@/components/Layouts/Layout.tsx" import {
import { PanelLeftIcon } from "lucide-react" BrainCog,
import { Button } from "antd" ChevronLeft,
import { PlusOutlined } from "@ant-design/icons" ChevronRight,
import { useMessageOption } from "@/hooks/useMessageOption.tsx" CogIcon,
ComputerIcon,
Slice,
GithubIcon,
PanelLeftIcon,
ZapIcon
} from "lucide-react"
import { useTranslation } from "react-i18next" import { useTranslation } from "react-i18next"
import { useLocation, NavLink } from "react-router-dom"
import { SelectedKnowledge } from "../Option/Knowledge/SelectedKnowledge"
import { ModelSelect } from "../Common/ModelSelect"
import { PromptSelect } from "../Common/PromptSelect"
import { useQuery } from "@tanstack/react-query"
import { fetchChatModels } from "~/services/ollama"
import { useMessageOption } from "~/hooks/useMessageOption"
import { Select, Tooltip } from "antd"
import { getAllPrompts } from "@/db"
import { ProviderIcons } from "../Common/ProviderIcon"
import { NewChat } from "./NewChat"
import { PageAssistSelect } from "../Select"
import { MoreOptions } from "./MoreOptions"
type Props = {
setSidebarOpen: (open: boolean) => void
setOpenModelSettings: (open: boolean) => void
}
export const Header = () => { export const Header: React.FC<Props> = ({
const { show, setShow } = useContext(HistoryContext) setOpenModelSettings,
setSidebarOpen
}) => {
const { t, i18n } = useTranslation(["option", "common"])
const isRTL = i18n?.dir() === "rtl"
const { t } = useTranslation(["option", "common", "settings"]) const [shareModeEnabled] = useStorage("shareMode", false)
const [hideCurrentChatModelSettings] = useStorage(
"hideCurrentChatModelSettings",
false
)
const {
selectedModel,
setSelectedModel,
clearChat,
selectedSystemPrompt,
setSelectedQuickPrompt,
setSelectedSystemPrompt,
messages,
streaming,
historyId,
temporaryChat
} = useMessageOption()
const {
data: models,
isLoading: isModelsLoading,
refetch
} = useQuery({
queryKey: ["fetchModel"],
queryFn: () => fetchChatModels({ returnEmpty: true }),
refetchIntervalInBackground: false,
placeholderData: (prev) => prev
})
const { data: prompts, isLoading: isPromptLoading } = useQuery({
queryKey: ["fetchAllPromptsLayout"],
queryFn: getAllPrompts
})
const { pathname } = useLocation()
const getPromptInfoById = (id: string) => {
return prompts?.find((prompt) => prompt.id === id)
}
const handlePromptChange = (value?: string) => {
if (!value) {
setSelectedSystemPrompt(undefined)
setSelectedQuickPrompt(undefined)
return
}
const prompt = getPromptInfoById(value)
if (prompt?.is_system) {
setSelectedSystemPrompt(prompt.id)
} else {
setSelectedSystemPrompt(undefined)
setSelectedQuickPrompt(prompt!.content)
}
}
const { clearChat } = useMessageOption()
return ( return (
<div <div
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]" : ""}`}> className={`absolute top-0 z-10 flex h-14 w-full flex-row items-center justify-center p-3 overflow-x-auto lg:overflow-x-visible bg-gray-50 border-b dark:bg-[#171717] dark:border-gray-600 ${
{/*控制侧边栏显示隐藏与新建对话*/} temporaryChat && "!bg-gray-200 dark:!bg-black"
{!show && ( }`}>
<div className="flex items-center gap-3"> <div className="flex gap-2 items-center">
<button {pathname !== "/" && (
className="text-gray-500 dark:text-gray-400" <div>
onClick={() => { <NavLink
setShow(!show) to="/"
}}> className="text-gray-500 items-center dark:text-gray-400 hover:text-gray-600 dark:hover:text-gray-300 transition-colors">
<PanelLeftIcon className="w-6 h-6" /> {isRTL ? (
</button> <ChevronRight className={`w-8 h-8`} />
<Button ) : (
color="cyan" <ChevronLeft className={`w-8 h-8`} />
variant="filled" )}
shape="round" </NavLink>
style={{
color: "#0057ff",
background: "#0057ff0f",
border: "1px solid #0066ff26"
}}
onClick={clearChat}>
<div className="flex items-center justify-between w-full">
<div className="flex items-center">
<PlusOutlined
className="text-sm"
style={{ fontSize: "16px", fontWeight: 500 }}
/>
<span>{t("newChat")}</span>
</div>
</div>
</Button>
</div> </div>
)} )}
{/* 项目标题 */} <div>
<div <button
className={` className="text-gray-500 dark:text-gray-400"
absolute left-1/2 transform -translate-x-1/2 onClick={() => setSidebarOpen(true)}>
w-[600px] h-[55px] bg-white dark:bg-black <PanelLeftIcon className="w-6 h-6" />
flex items-center justify-center </button>
shadow-[0px_0px_5px_rgba(0,0,0,0.2)] </div>
rounded-b-[15px] <NewChat clearChat={clearChat} />
transition-[top] <span className="text-lg font-thin text-zinc-300 dark:text-zinc-600">
${show ? "-top-[56px]" : "-top-[1px] delay-200"} {"/"}
`}> </span>
<h2 className="text-xl font-bold text-zinc-700 dark:text-zinc-300 mr-3"> <div className="hidden lg:block">
<span className="text-[#d30100]"></span> <Select
</h2> className="w-80"
placeholder={t("common:selectAModel")}
// loadingText={t("common:selectAModel")}
value={selectedModel}
onChange={(e) => {
setSelectedModel(e)
localStorage.setItem("selectedModel", e)
}}
filterOption={(input, option) => {
//@ts-ignore
return (
option?.label?.props["data-title"]
?.toLowerCase()
?.indexOf(input.toLowerCase()) >= 0
)
}}
showSearch
loading={isModelsLoading}
options={models?.map((model) => ({
label: (
<span
key={model.model}
data-title={model.name}
className="flex flex-row gap-3 items-center ">
<ProviderIcons
provider={model?.provider}
className="w-5 h-5"
/>
<span className="line-clamp-2">{model.name}</span>
</span>
),
value: model.model
}))}
size="large"
// onRefresh={() => {
// refetch()
// }}
/>
</div>
<div className="lg:hidden">
<ModelSelect />
</div>
<span className="text-lg font-thin text-zinc-300 dark:text-zinc-600">
{"/"}
</span>
<div className="hidden lg:block">
<Select
size="large"
loading={isPromptLoading}
showSearch
placeholder={t("selectAPrompt")}
className="w-60"
allowClear
onChange={handlePromptChange}
value={selectedSystemPrompt}
filterOption={(input, option) =>
//@ts-ignore
option.label.key.toLowerCase().indexOf(input.toLowerCase()) >= 0
}
options={prompts?.map((prompt) => ({
label: (
<span
key={prompt.title}
className="flex flex-row gap-3 items-center">
{prompt.is_system ? (
<ComputerIcon className="w-4 h-4" />
) : (
<ZapIcon className="w-4 h-4" />
)}
{prompt.title}
</span>
),
value: prompt.id
}))}
/>
</div>
<div className="lg:hidden">
<PromptSelect
selectedSystemPrompt={selectedSystemPrompt}
setSelectedSystemPrompt={setSelectedSystemPrompt}
setSelectedQuickPrompt={setSelectedQuickPrompt}
/>
</div>
<SelectedKnowledge />
</div>
<div className="flex flex-1 justify-end px-4">
<div className="ml-4 flex items-center md:ml-6">
<div className="flex gap-4 items-center">
{messages.length > 0 && !streaming && (
<MoreOptions
shareModeEnabled={shareModeEnabled}
historyId={historyId}
messages={messages}
/>
)}
{!hideCurrentChatModelSettings && (
<Tooltip title={t("common:currentChatModelSettings")}>
<button
onClick={() => setOpenModelSettings(true)}
className="!text-gray-500 dark:text-gray-300 hover:text-gray-600 dark:hover:text-gray-300 transition-colors">
<BrainCog className="w-6 h-6" />
</button>
</Tooltip>
)}
<Tooltip title={t("githubRepository")}>
<a
href="https://github.com/n4ze3m/page-assist"
target="_blank"
className="!text-gray-500 hidden lg:block dark:text-gray-400 hover:text-gray-600 dark:hover:text-gray-300 transition-colors">
<GithubIcon className="w-6 h-6" />
</a>
</Tooltip>
<Tooltip title={t("settings")}>
<NavLink
to="/settings"
className="!text-gray-500 dark:text-gray-400 hover:text-gray-600 dark:hover:text-gray-300 transition-colors">
<CogIcon className="w-6 h-6" />
</NavLink>
</Tooltip>
<Tooltip title={t("metering")}>
<NavLink
to="/metering"
className="!text-gray-500 dark:text-gray-400 hover:text-gray-600 dark:hover:text-gray-300 transition-colors">
<Slice className="w-6 h-6" />
</NavLink>
</Tooltip>
</div>
</div>
</div> </div>
</div> </div>
) )

View File

@@ -1,46 +1,103 @@
import React, { useCallback, useEffect, useState } from "react" import React, { useState } from "react"
import { Sidebar } from "../Option/Sidebar"
import { Drawer, Tooltip } from "antd"
import { useTranslation } from "react-i18next"
import { CurrentChatModelSettings } from "../Common/Settings/CurrentChatModelSettings" import { CurrentChatModelSettings } from "../Common/Settings/CurrentChatModelSettings"
import { Header } from "./Header.tsx" import { Header } from "./Header"
import { EraserIcon } from "lucide-react"
interface History { import { PageAssitDatabase } from "@/db"
show: boolean import { useMessageOption } from "@/hooks/useMessageOption"
setShow: (show: boolean) => void import { useQueryClient } from "@tanstack/react-query"
} import { useStoreChatModelSettings } from "@/store/model"
export const HistoryContext = React.createContext<History>({
show: true,
setShow: () => {}
})
export default function OptionLayout({ export default function OptionLayout({
children children
}: { }: {
children: React.ReactNode children: React.ReactNode
}) { }) {
const [showHistory, setShowHistory] = useState(true) const [sidebarOpen, setSidebarOpen] = useState(false)
const { t } = useTranslation(["option", "common", "settings"])
const [openModelSettings, setOpenModelSettings] = useState(false) const [openModelSettings, setOpenModelSettings] = useState(false)
const {
setMessages,
setHistory,
setHistoryId,
historyId,
clearChat,
setSelectedModel,
temporaryChat,
setSelectedSystemPrompt
} = useMessageOption()
const historyContextValue = { const queryClient = useQueryClient()
show: showHistory, const { setSystemPrompt } = useStoreChatModelSettings()
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>*/} <Header
setSidebarOpen={setSidebarOpen}
setOpenModelSettings={setOpenModelSettings}
/>
</div>
{/* <div className="relative flex h-full flex-col items-center"> */} {/* <div className="relative flex h-full flex-col items-center"> */}
<HistoryContext.Provider value={historyContextValue}>
<Header />
{children} {children}
</HistoryContext.Provider>
{/* </div> */} {/* </div> */}
<Drawer
title={
<div className="flex items-center justify-between">
{t("sidebarTitle")}
<Tooltip
title={t(
"settings:generalSettings.system.deleteChatHistory.label"
)}
placement="right">
<button
onClick={async () => {
const confirm = window.confirm(
t(
"settings:generalSettings.system.deleteChatHistory.confirm"
)
)
if (confirm) {
const db = new PageAssitDatabase()
await db.deleteAllChatHistory()
await queryClient.invalidateQueries({
queryKey: ["fetchChatHistory"]
})
clearChat()
}
}}
className="text-gray-600 hover:text-gray-800 dark:text-gray-300 dark:hover:text-gray-100">
<EraserIcon className="size-5" />
</button>
</Tooltip>
</div>
}
placement="left"
closeIcon={null}
onClose={() => setSidebarOpen(false)}
open={sidebarOpen}>
<Sidebar
onClose={() => setSidebarOpen(false)}
setMessages={setMessages}
setHistory={setHistory}
setHistoryId={setHistoryId}
setSelectedModel={setSelectedModel}
setSelectedSystemPrompt={setSelectedSystemPrompt}
clearChat={clearChat}
historyId={historyId}
setSystemPrompt={setSystemPrompt}
temporaryChat={temporaryChat}
history={history}
/>
</Drawer>
<CurrentChatModelSettings <CurrentChatModelSettings
open={openModelSettings} open={openModelSettings}

View File

@@ -1,264 +0,0 @@
import { useStorage } from "@plasmohq/storage/hook"
import {
BrainCog,
ChevronLeft,
ChevronRight,
CogIcon,
ComputerIcon,
GaugeCircle,
GithubIcon,
PanelLeftIcon,
ZapIcon
} from "lucide-react"
import { useTranslation } from "react-i18next"
import { useLocation, NavLink } from "react-router-dom"
import { SelectedKnowledge } from "../Option/Knowledge/SelectedKnowledge"
import { ModelSelect } from "../Common/ModelSelect"
import { PromptSelect } from "../Common/PromptSelect"
import { useQuery } from "@tanstack/react-query"
import { fetchChatModels } from "~/services/ollama"
import { useMessageOption } from "~/hooks/useMessageOption"
import { Select, Tooltip } from "antd"
import { getAllPrompts } from "@/db"
import { ProviderIcons } from "../Common/ProviderIcon"
import { NewChat } from "./NewChat"
import { MoreOptions } from "./MoreOptions"
type Props = {
sidebarOpen: boolean
setSidebarOpen: () => void
setOpenModelSettings: (open: boolean) => void
}
export const Header: React.FC<Props> = ({
setOpenModelSettings,
setSidebarOpen,
sidebarOpen
}) => {
const { t, i18n } = useTranslation(["option", "common"])
const isRTL = i18n?.dir() === "rtl"
const [shareModeEnabled] = useStorage("shareMode", false)
const [hideCurrentChatModelSettings] = useStorage(
"hideCurrentChatModelSettings",
false
)
const {
selectedModel,
setSelectedModel,
clearChat,
selectedSystemPrompt,
setSelectedQuickPrompt,
setSelectedSystemPrompt,
messages,
streaming,
historyId,
temporaryChat
} = useMessageOption()
const {
data: models,
isLoading: isModelsLoading,
} = useQuery({
queryKey: ["fetchModel"],
queryFn: () => fetchChatModels({ returnEmpty: true }),
refetchIntervalInBackground: false,
placeholderData: (prev) => prev
})
const { data: prompts, isLoading: isPromptLoading } = useQuery({
queryKey: ["fetchAllPromptsLayout"],
queryFn: getAllPrompts
})
const { pathname } = useLocation()
const getPromptInfoById = (id: string) => {
return prompts?.find((prompt) => prompt.id === id)
}
const handlePromptChange = (value?: string) => {
if (!value) {
setSelectedSystemPrompt(undefined)
setSelectedQuickPrompt(undefined)
return
}
const prompt = getPromptInfoById(value)
if (prompt?.is_system) {
setSelectedSystemPrompt(prompt.id)
} else {
setSelectedSystemPrompt(undefined)
setSelectedQuickPrompt(prompt!.content)
}
}
return (
<div
className={`absolute top-0 z-10 flex h-14 w-full flex-row items-center justify-center p-3 overflow-x-auto lg:overflow-x-visible bg-gray-50 border-b dark:bg-[#171717] dark:border-gray-600 ${
temporaryChat && "!bg-gray-200 dark:!bg-black"
}`}>
<div className="flex gap-2 items-center">
{pathname !== "/" && (
<div>
<NavLink
to="/"
className="text-gray-500 items-center dark:text-gray-400 hover:text-gray-600 dark:hover:text-gray-300 transition-colors">
{isRTL ? (
<ChevronRight className={`w-8 h-8`} />
) : (
<ChevronLeft className={`w-8 h-8`} />
)}
</NavLink>
</div>
)}
<div style={{width: sidebarOpen ? "288px" : "205px"}} className="flex items-center justify-between transition-all duration-300 ease-in-out">
<h2
className="text-xl font-bold text-zinc-700 dark:text-zinc-300 mr-3"
style={{ lineHeight: "0" }}>
<span className="text-[#d30100]"></span>
</h2>
<button
className="text-gray-500 dark:text-gray-400"
onClick={() => setSidebarOpen()}>
<PanelLeftIcon className="w-6 h-6" />
</button>
</div>
<NewChat clearChat={clearChat} />
<span className="text-lg font-thin text-zinc-300 dark:text-zinc-600">
{"/"}
</span>
<div className="hidden lg:block">
<Select
className="w-80"
placeholder={t("common:selectAModel")}
// loadingText={t("common:selectAModel")}
value={selectedModel}
onChange={(e) => {
setSelectedModel(e)
localStorage.setItem("selectedModel", e)
}}
filterOption={(input, option) => {
//@ts-ignore
return (
option?.label?.props["data-title"]
?.toLowerCase()
?.indexOf(input.toLowerCase()) >= 0
)
}}
showSearch
loading={isModelsLoading}
options={models?.map((model) => ({
label: (
<span
key={model.model}
data-title={model.name}
className="flex flex-row gap-3 items-center ">
<ProviderIcons
provider={model?.provider}
className="w-5 h-5"
/>
<span className="line-clamp-2">{model.name}</span>
</span>
),
value: model.model
}))}
size="large"
// onRefresh={() => {
// refetch()
// }}
/>
</div>
<div className="lg:hidden">
<ModelSelect />
</div>
<span className="text-lg font-thin text-zinc-300 dark:text-zinc-600">
{"/"}
</span>
<div className="hidden lg:block">
<Select
size="large"
loading={isPromptLoading}
showSearch
placeholder={t("selectAPrompt")}
className="w-60"
allowClear
onChange={handlePromptChange}
value={selectedSystemPrompt}
filterOption={(input, option) =>
//@ts-ignore
option.label.key.toLowerCase().indexOf(input.toLowerCase()) >= 0
}
options={prompts?.map((prompt) => ({
label: (
<span
key={prompt.title}
className="flex flex-row gap-3 items-center">
{prompt.is_system ? (
<ComputerIcon className="w-4 h-4" />
) : (
<ZapIcon className="w-4 h-4" />
)}
{prompt.title}
</span>
),
value: prompt.id
}))}
/>
</div>
<div className="lg:hidden">
<PromptSelect
selectedSystemPrompt={selectedSystemPrompt}
setSelectedSystemPrompt={setSelectedSystemPrompt}
setSelectedQuickPrompt={setSelectedQuickPrompt}
/>
</div>
<SelectedKnowledge />
</div>
<div className="flex flex-1 justify-end px-4">
<div className="ml-4 flex items-center md:ml-6">
<div className="flex gap-4 items-center">
{messages.length > 0 && !streaming && (
<MoreOptions
shareModeEnabled={shareModeEnabled}
historyId={historyId}
messages={messages}
/>
)}
{!hideCurrentChatModelSettings && (
<Tooltip title={t("common:currentChatModelSettings")}>
<button
onClick={() => setOpenModelSettings(true)}
className="!text-gray-500 dark:text-gray-300 hover:text-gray-600 dark:hover:text-gray-300 transition-colors">
<BrainCog className="w-6 h-6" />
</button>
</Tooltip>
)}
<Tooltip title={t("githubRepository")}>
<a
href="https://github.com/n4ze3m/page-assist"
target="_blank"
className="!text-gray-500 hidden lg:block dark:text-gray-400 hover:text-gray-600 dark:hover:text-gray-300 transition-colors">
<GithubIcon className="w-6 h-6" />
</a>
</Tooltip>
<Tooltip title={t("settings")}>
<NavLink
to="/settings"
className="!text-gray-500 dark:text-gray-400 hover:text-gray-600 dark:hover:text-gray-300 transition-colors">
<CogIcon className="w-6 h-6" />
</NavLink>
</Tooltip>
<Tooltip title={t("metering")}>
<NavLink
to="/metering"
className="!text-gray-500 dark:text-gray-400 hover:text-gray-600 dark:hover:text-gray-300 transition-colors">
<GaugeCircle className="w-6 h-6" />
</NavLink>
</Tooltip>
</div>
</div>
</div>
</div>
)
}

View File

@@ -2,13 +2,14 @@ import {
Card, Card,
List, List,
Table, Table,
Tag,
Space, Space,
TableProps, TableProps,
Divider, Divider,
Typography, Typography,
Tooltip Tooltip
} from "antd" } from "antd"
import { useParams } from "react-router-dom" import { NavLink, useParams } from "react-router-dom"
import { useStoreMessageOption } from "@/store/option.tsx" import { useStoreMessageOption } from "@/store/option.tsx"
import { useMemo } from "react" import { useMemo } from "react"
@@ -23,28 +24,26 @@ interface DataType {
const columns: TableProps<DataType>["columns"] = [ const columns: TableProps<DataType>["columns"] = [
{ {
title: "序号", title: '序号',
key: "index", key: 'index',
width: 100, width: 100,
render: (_text, _record, index) => index + 1 // 索引从0开始+1后从1显示 render: (_text, _record, index) => index + 1, // 索引从0开始+1后从1显示
}, },
{ {
title: "标识", title: "标识",
dataIndex: "doId", dataIndex: "doId",
key: "doId", key: "doId"
width: 350
}, },
{ {
title: "提供方", title: "提供方",
dataIndex: "data_space", dataIndex: "data_space",
key: "data_space", key: "data_space"
width: 250
}, },
{ {
title: "token数", title: "token数",
key: "tokenCount", key: "tokenCount",
dataIndex: "tokenCount", dataIndex: "tokenCount",
width: 120 width: 100
}, },
{ {
title: "内容", title: "内容",
@@ -62,19 +61,15 @@ const columns: TableProps<DataType>["columns"] = [
] ]
export const ListDetail = () => { export const ListDetail = () => {
const { meteringEntries } = useStoreMessageOption() const { chatMessages } = useStoreMessageOption()
const { id } = useParams() const { id } = useParams()
const record = useMemo( const record = useMemo(
() => meteringEntries.find((item) => item.id === id), () => chatMessages.find((item) => item.id === id),
[meteringEntries] [chatMessages]
) )
const modelData = useMemo( const modelData = useMemo(
() => [ () => [
{
key: "数联网引用token总数",
value: record.iodTokenCount
},
{ {
key: "大模型输入token数", key: "大模型输入token数",
value: record.modelInputTokenCount value: record.modelInputTokenCount
@@ -134,7 +129,7 @@ export const ListDetail = () => {
return ( return (
<div className="p-[1rem] pt-[4rem]"> <div className="p-[1rem] pt-[4rem]">
<List <List
grid={{ gutter: 16, column: 4 }} grid={{ gutter: 16, column: 3 }}
dataSource={modelData} dataSource={modelData}
renderItem={(item) => ( renderItem={(item) => (
<List.Item> <List.Item>
@@ -144,12 +139,7 @@ export const ListDetail = () => {
style={{ marginBottom: "2rem" }} style={{ marginBottom: "2rem" }}
/> />
<Space direction="vertical" className="w-full" size={10}> <Space direction="vertical" size={10}>
<Divider orientation="left"></Divider>
<Table<DataType> columns={columns} dataSource={record.iodData} />
</Space>
<Space direction="vertical" className="w-full" size={10}>
<Divider orientation="left">token详情</Divider> <Divider orientation="left">token详情</Divider>
<List <List
bordered bordered
@@ -157,24 +147,22 @@ export const ListDetail = () => {
dataSource={inputTokenData} dataSource={inputTokenData}
renderItem={(item) => ( renderItem={(item) => (
<List.Item style={{ justifyContent: "flex-start" }}> <List.Item style={{ justifyContent: "flex-start" }}>
<Typography.Paragraph <Typography.Paragraph style={{ marginBottom: 0 }} className="mr-1">
style={{ marginBottom: 0 }}
className="mr-1">
{item.key} {item.key}
</Typography.Paragraph> </Typography.Paragraph>
<Tooltip <Tooltip placement="topLeft" style={{ marginBottom: 0 }} title={item.value}>
placement="topLeft"
style={{ marginBottom: 0 }}
title={item.value}>
{item.value} {item.value}
</Tooltip> </Tooltip>
</List.Item> </List.Item>
)} )}
style={{ marginBottom: "1rem" }} style={{ marginBottom: "1rem" }}
/> />
<Card title="数联网引用数据">
<Table<DataType> columns={columns} dataSource={record.iodData} />
</Card>
</Space> </Space>
<Space direction="vertical" className="w-full" size={10}> <Space direction="vertical" size={10}>
<Divider orientation="left">token详情</Divider> <Divider orientation="left">token详情</Divider>
<List <List
bordered bordered
@@ -182,13 +170,8 @@ export const ListDetail = () => {
header={<div></div>} header={<div></div>}
renderItem={(item) => ( renderItem={(item) => (
<List.Item style={{ justifyContent: "flex-start" }}> <List.Item style={{ justifyContent: "flex-start" }}>
<Typography.Text className="mr-1" style={{ marginBottom: 0 }}> <Typography.Text className="mr-1" style={{ marginBottom: 0 }}>{item.key}</Typography.Text>
{item.key} <Tooltip style={{ marginBottom: 0 }} placement="topLeft" title={item.value}>
</Typography.Text>
<Tooltip
style={{ marginBottom: 0 }}
placement="topLeft"
title={item.value}>
{item.value} {item.value}
</Tooltip> </Tooltip>
</List.Item> </List.Item>

View File

@@ -1,10 +1,10 @@
import React, { useMemo } from "react" import React, { useMemo } from "react"
import { MeteringEntry, useStoreMessageOption } from "@/store/option" import { ChatMessage, useStoreMessageOption } from "@/store/option"
import { Card, List, Table, Tag, Space, TableProps, Tooltip } from "antd" import { Card, List, Table, Tag, Space, TableProps, Tooltip } from "antd"
import { NavLink } from "react-router-dom" import { NavLink } from "react-router-dom"
import { formatDate } from "@/utils/date" import { formatDate } from "@/utils/date"
const columns: TableProps<MeteringEntry>["columns"] = [ const columns: TableProps<ChatMessage>["columns"] = [
{ {
title: '序号', title: '序号',
key: 'index', key: 'index',
@@ -32,8 +32,8 @@ const columns: TableProps<MeteringEntry>["columns"] = [
}, },
{ {
title: "思维链", title: "思维链",
key: "cot", key: "thinkingChain",
dataIndex: "cot", dataIndex: "thinkingChain",
ellipsis: { ellipsis: {
showTitle: false showTitle: false
}, },
@@ -66,8 +66,8 @@ const columns: TableProps<MeteringEntry>["columns"] = [
}, },
{ {
title: "数联网token", title: "数联网token",
dataIndex: "iodTokenCount", dataIndex: "iodDataTokenCount",
key: "iodTokenCount" key: "iodDataTokenCount"
}, },
{ {
title: "大模型token", title: "大模型token",
@@ -84,7 +84,7 @@ const columns: TableProps<MeteringEntry>["columns"] = [
dataIndex: "date", dataIndex: "date",
key: "date", key: "date",
render: (date) => { render: (date) => {
return <div>{formatDate(new Date(date))}</div> return <div>{formatDate(date ?? new Date())}</div>
} }
}, },
{ {
@@ -108,17 +108,17 @@ const columns: TableProps<MeteringEntry>["columns"] = [
] ]
export const MeteringDetail = () => { export const MeteringDetail = () => {
const { meteringEntries } = useStoreMessageOption() const { chatMessages } = useStoreMessageOption()
const data = useMemo( const data = useMemo(
() => [ () => [
{ {
key: "对话数量", key: "对话数量",
value: meteringEntries.length value: chatMessages.length
}, },
{ {
key: "数联网输入token数", key: "数联网输入token数",
value: meteringEntries.reduce((acc, cur) => { value: chatMessages.reduce((acc, cur) => {
for (const item of cur.iodKeywords) { for (const item of cur.iodKeywords) {
acc += item.length acc += item.length
} }
@@ -127,24 +127,24 @@ export const MeteringDetail = () => {
}, },
{ {
key: "数联网输出token数", key: "数联网输出token数",
value: meteringEntries.reduce((acc, cur) => acc + cur.iodTokenCount, 0) value: chatMessages.reduce((acc, cur) => acc + cur.iodDataTokenCount, 0)
}, },
{ {
key: "大模型输入token数", key: "大模型输入token数",
value: meteringEntries.reduce( value: chatMessages.reduce(
(acc, cur) => acc + cur.modelInputTokenCount, (acc, cur) => acc + cur.modelInputTokenCount,
0 0
) )
}, },
{ {
key: "大模型输出token数", key: "大模型输出token数",
value: meteringEntries.reduce( value: chatMessages.reduce(
(acc, cur) => acc + cur.modelOutputTokenCount, (acc, cur) => acc + cur.modelOutputTokenCount,
0 0
) )
} }
], ],
[meteringEntries] [chatMessages]
) )
return ( return (
<div className="p-4 pt-[4rem]"> <div className="p-4 pt-[4rem]">
@@ -159,7 +159,7 @@ export const MeteringDetail = () => {
)} )}
/> />
<Table<MeteringEntry> columns={columns} dataSource={meteringEntries} /> <Table<ChatMessage> columns={columns} dataSource={chatMessages} />
</div> </div>
) )
} }

View File

@@ -1,14 +1,8 @@
import React, { useContext } from "react" import React from "react"
import { Card } from "antd"
import { PlaygroundForm } from "./PlaygroundForm" import { PlaygroundForm } from "./PlaygroundForm"
import { PlaygroundChat } from "./PlaygroundChat" import { PlaygroundChat } from "./PlaygroundChat"
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,
formatToMessage, formatToMessage,
@@ -19,10 +13,6 @@ 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 { 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)
@@ -142,19 +132,17 @@ export const Playground = () => {
return ( return (
<div <div
ref={drop} ref={drop}
className={`relative flex gap-3 h-full items-center ${ className={`relative flex h-full flex-col 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]`}>
<PlaygroundHistory />
<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 overflow-x-hidden overflow-y-auto px-5"> className="custom-scrollbar bg-bottom-mask-light dark:bg-bottom-mask-dark mask-bottom-fade will-change-mask flex h-full w-full flex-col items-center overflow-x-hidden overflow-y-auto px-5">
<PlaygroundChat /> <PlaygroundChat />
</div> </div>
<div className="relative bottom-0 w-full"> <div className="absolute bottom-0 w-full">
{!isAtBottom && ( {!isAtBottom && (
<div className="absolute bottom-36 z-20 left-0 right-0 flex justify-center"> <div className="fixed bottom-36 z-20 left-0 right-0 flex justify-center">
<button <button
onClick={scrollToBottom} onClick={scrollToBottom}
className="bg-gray-50 shadow border border-gray-200 dark:border-none dark:bg-white/20 p-1.5 rounded-full pointer-events-auto"> className="bg-gray-50 shadow border border-gray-200 dark:border-none dark:bg-white/20 p-1.5 rounded-full pointer-events-auto">
@@ -165,23 +153,5 @@ export const Playground = () => {
<PlaygroundForm dropedFile={dropedFile} /> <PlaygroundForm dropedFile={dropedFile} />
</div> </div>
</div> </div>
{/*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>
) )
} }

View File

@@ -18,9 +18,9 @@ export const PlaygroundChat = () => {
return ( return (
<> <>
<div className="relative flex w-full flex-col items-center pb-4"> <div className="relative flex w-full flex-col items-center pt-16 pb-4">
{messages.length === 0 && ( {messages.length === 0 && (
<div className="mt-3 w-full"> <div className="mt-32 w-full">
<PlaygroundEmpty /> <PlaygroundEmpty />
</div> </div>
)} )}
@@ -52,7 +52,8 @@ export const PlaygroundChat = () => {
/> />
))} ))}
</div> </div>
{/*<div className="w-full pb-[0px]"></div>*/} <div className="w-full pb-[157px]"></div>
<MessageSourcePopup <MessageSourcePopup
open={isSourceOpen} open={isSourceOpen}
setOpen={setIsSourceOpen} setOpen={setIsSourceOpen}

View File

@@ -1,58 +1,130 @@
import { Card, Col, Row } from "antd" import { cleanUrl } from "@/libs/clean-url"
import { useStorage } from "@plasmohq/storage/hook"
import { useMessageOption } from "@/hooks/useMessageOption.tsx" import { useQuery } from "@tanstack/react-query"
import { useMutation, useQueryClient } from "@tanstack/react-query" import { RotateCcw } from "lucide-react"
import { qaPrompt } from "@/libs/playground.tsx" import { useEffect, useState } from "react"
import { Trans, useTranslation } from "react-i18next"
import {
getOllamaURL,
isOllamaRunning,
setOllamaURL as saveOllamaURL
} from "~/services/ollama"
export const PlaygroundEmpty = () => { export const PlaygroundEmpty = () => {
const { onSubmit } = useMessageOption() const [ollamaURL, setOllamaURL] = useState<string>("")
const { t } = useTranslation(["playground", "common"])
const queryClient = useQueryClient() const [checkOllamaStatus] = useStorage("checkOllamaStatus", true)
const { mutateAsync: sendMessage } = useMutation({ const {
mutationFn: onSubmit, data: ollamaInfo,
onSuccess: () => { status: ollamaStatus,
queryClient.invalidateQueries({ refetch,
queryKey: ["fetchChatHistory"] isRefetching
}) } = useQuery({
} queryKey: ["ollamaStatus"],
}) queryFn: async () => {
const ollamaURL = await getOllamaURL()
const isOk = await isOllamaRunning()
function handleQuestion(message: string) { if (ollamaURL) {
void sendMessage({ message, image: "" }) saveOllamaURL(ollamaURL)
} }
return {
isOk,
ollamaURL
}
},
enabled: checkOllamaStatus
})
useEffect(() => {
if (ollamaInfo?.ollamaURL) {
setOllamaURL(ollamaInfo.ollamaURL)
}
}, [ollamaInfo])
if (!checkOllamaStatus) {
return ( return (
<div className="w-full p-4"> <div className="mx-auto sm:max-w-xl px-4 mt-10">
{/* 标题区域 */} <div className="rounded-lg justify-center items-center flex flex-col border p-8 bg-gray-50 dark:bg-[#262626] dark:border-gray-600">
<div className="mb-4"> <h1 className="text-sm font-medium text-center text-gray-500 dark:text-gray-400 flex gap-3 items-center justify-center">
<h2 <span >👋</span>
className="text-xl font-bold text-gray-800" <span className="text-gray-700 dark:text-gray-300">
style={{ lineHeight: "0" }}> {t("welcome")}
</span>
</h2> </h1>
<p className="text-sm text-gray-500"></p> </div>
</div>
)
}
return (
<div className="mx-auto sm:max-w-xl px-4 mt-10">
<div className="rounded-lg justify-center items-center flex flex-col border p-8 bg-gray-50 dark:bg-[#262626] dark:border-gray-600">
{(ollamaStatus === "pending" || isRefetching) && (
<div className="inline-flex items-center space-x-2">
<div className="w-3 h-3 bg-blue-500 rounded-full animate-pulse"></div>
<p className="dark:text-gray-400 text-gray-900">
{t("ollamaState.searching")}
</p>
</div>
)}
{!isRefetching && ollamaStatus === "success" ? (
ollamaInfo.isOk ? (
<div className="inline-flex items-center space-x-2">
<div className="w-3 h-3 bg-green-500 rounded-full animate-pulse"></div>
<p className="dark:text-gray-400 text-gray-900">
{t("ollamaState.running")}
</p>
</div>
) : (
<div className="flex flex-col space-y-2 justify-center items-center">
<div className="inline-flex space-x-2">
<div className="w-3 h-3 bg-red-500 rounded-full animate-pulse"></div>
<p className="dark:text-gray-400 text-gray-900">
{t("ollamaState.notRunning")}
</p>
</div>
<input
className="bg-gray-100 dark:bg-[#262626] dark:text-gray-100 rounded-md px-4 py-2 mt-2 w-full"
type="url"
value={ollamaURL}
onChange={(e) => setOllamaURL(e.target.value)}
/>
<button
onClick={() => {
saveOllamaURL(ollamaURL)
refetch()
}}
className="inline-flex mt-4 items-center rounded-md border border-transparent bg-black px-2 py-2 text-sm font-medium leading-4 text-white shadow-sm hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 dark:bg-white dark:text-gray-800 dark:hover:bg-gray-100 dark:focus:ring-gray-500 dark:focus:ring-offset-gray-100 disabled:opacity-50 ">
<RotateCcw className="h-4 w-4 mr-3" />
{t("common:retry")}
</button>
{ollamaURL &&
cleanUrl(ollamaURL) !== "http://127.0.0.1:11434" && (
<p className="text-xs text-gray-500 dark:text-gray-400 mb-4 text-center">
<Trans
i18nKey="playground:ollamaState.connectionError"
components={{
anchor: (
<a
href="https://github.com/n4ze3m/page-assist/blob/main/docs/connection-issue.md"
target="__blank"
className="text-blue-600 dark:text-blue-400"></a>
)
}}
/>
</p>
)}
</div>
)
) : null}
</div> </div>
{/* 卡片网格布局 */}
<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

@@ -207,11 +207,11 @@ export const PlaygroundForm = ({ dropedFile }: Props) => {
} }
return ( return (
<div className="flex w-full flex-col items-center p-2 px-5 pt-1 pb-4"> <div className="flex w-full flex-col items-center p-2 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-4/5">
<div <div
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 className={` bg-neutral-50 dark:bg-[#262626] relative w-full max-w-[48rem] p-1 backdrop-blur-lg duration-100 border border-gray-300 rounded-xl dark:border-gray-600
${temporaryChat ? "!bg-gray-200 dark:!bg-black " : ""} ${temporaryChat ? "!bg-gray-200 dark:!bg-black " : ""}
`}> `}>
<div <div
@@ -357,7 +357,6 @@ export const PlaygroundForm = ({ dropedFile }: Props) => {
if (isListening) { if (isListening) {
stopSpeechRecognition() stopSpeechRecognition()
} else { } else {
console.log("开始语音识别,语言:", speechToTextLanguage);
resetTranscript() resetTranscript()
startListening({ startListening({
continuous: true, continuous: true,

View File

@@ -24,7 +24,6 @@ import {
getLastUsedChatSystemPrompt, getLastUsedChatSystemPrompt,
lastUsedChatModelEnabled lastUsedChatModelEnabled
} from "@/services/model-settings" } from "@/services/model-settings"
import { useState } from "react"
type Props = { type Props = {
onClose: () => void onClose: () => void
@@ -170,12 +169,7 @@ export const Sidebar = ({
{group.items.map((chat, index) => ( {group.items.map((chat, index) => (
<div <div
key={index} key={index}
className={` className="flex py-2 px-2 items-center gap-3 relative rounded-md truncate hover:pr-4 group transition-opacity duration-300 ease-in-out bg-gray-100 dark:bg-[#232222] dark:text-gray-100 text-gray-800 border hover:bg-gray-200 dark:hover:bg-[#2d2d2d] dark:border-gray-800">
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
hover:text-[#000000d9] hover:bg-[#f3f2ff] dark:hover:bg-[#2d2d2d] dark:border-gray-800
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">
<BotIcon className="size-3 text-green-500" /> <BotIcon className="size-3 text-green-500" />
@@ -271,7 +265,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 className={`group-hover:text-[#000000d9] w-4 h-4 more-vertical ${historyId === chat.id ? 'text-[#000000d9]' : ''}`} /> <MoreVertical className="w-4 h-4" />
</button> </button>
</Dropdown> </Dropdown>
</div> </div>

View File

View File

@@ -1,507 +0,0 @@
export default defineContentScript({
matches: ['<all_urls>'],
main(ctx) {
setTimeout(getDeepScript,1000)
},
});
async function getDeepScript(){
console.log("getDeepScript!!")
const href = document.location.href;
let id = "unknown";
if (href.startsWith("http://39.105.188.3:3838/topic3/missing/?autoexecute="))
id = "id1";
if (href.startsWith("http://39.105.188.3:3838/topic3/PKUCausalEfficacy/?autoexecute="))
id = "id2";
if (href.startsWith("http://39.105.188.3:3838/topic3/ADR23/?autoexecute="))
id = "id3";
if (idToScript[id]!=undefined){
idToScript[id]();
}
//sendMessageToServiceWorker({});
}
async function sendMessageToServiceWorker(message) {
chrome.runtime.sendMessage({ type: 'retrieveDeepScript', doId:"10.1002/2014JA019817" }, response => {
console.log(response);
});
return;
}
const idToScript = {
"id1":(function() {
// 等待函数
function wait(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
// 主函数
async function automate() {
try {
console.log("开始执行第二部分脚本...");
// 等待页面加载
await wait(2000);
// 点击"缺失数据填补"链接
const dataFillLink = Array.from(document.querySelectorAll('a')).find(a =>
a.textContent.includes("缺失数据填补")
);
if (dataFillLink) {
dataFillLink.click();
console.log("已点击'缺失数据填补'链接");
} else {
console.error("未找到'缺失数据填补'链接");
}
await wait(1000);
// 点击"选择数字对象"选项卡
const numObjTab = Array.from(document.querySelectorAll('[role="tab"]')).find(tab =>
tab.textContent.includes("选择数字对象")
);
if (numObjTab) {
numObjTab.click();
console.log("已点击'选择数字对象'选项卡");
} else {
console.error("未找到'选择数字对象'选项卡");
}
await wait(1000);
// 点击"多重填补数字对象"
const multipleNumObjs = Array.from(document.querySelectorAll('div')).filter(div =>
div.textContent.trim() === "多重填补数字对象"
);
if (multipleNumObjs.length > 1) {
multipleNumObjs[1].click();
console.log("已点击'多重填补数字对象'");
} else if (multipleNumObjs.length > 0) {
multipleNumObjs[0].click();
console.log("已点击'多重填补数字对象'");
} else {
console.error("未找到'多重填补数字对象'");
}
await wait(1000);
// 选择"围术期处理后"
const periOption = Array.from(document.querySelectorAll('[role="option"]')).find(option =>
option.textContent.includes("围术期处理后")
);
if (periOption) {
periOption.click();
console.log("已选择'围术期处理后'");
} else {
console.error("未找到'围术期处理后'选项");
}
await wait(1000);
// 勾选特定复选框
const checkbox = document.querySelector('#col_pro');
if (checkbox) {
checkbox.checked = true;
checkbox.dispatchEvent(new Event('change', { bubbles: true }));
console.log("已勾选复选框");
} else {
console.error("未找到指定复选框");
}
await wait(1000);
// 点击"开始填补"按钮
const startFillBtn = Array.from(document.querySelectorAll('button')).find(btn =>
btn.textContent.includes("开始填补")
);
if (startFillBtn) {
startFillBtn.click();
console.log("已点击'开始填补'按钮");
} else {
console.error("未找到'开始填补'按钮");
}
await wait(2000);
// 点击"进入数据分析"按钮
const enterAnalysisBtn = Array.from(document.querySelectorAll('button')).find(btn =>
btn.textContent.includes("进入数据分析")
);
if (enterAnalysisBtn) {
enterAnalysisBtn.click();
console.log("已点击'进入数据分析'按钮");
} else {
console.error("未找到'进入数据分析'按钮");
}
await wait(2000);
// 点击"中心序号"
const centerNumDivs = Array.from(document.querySelectorAll('[id^="tab-"][id$="-3"] div')).filter(div =>
div.textContent.includes("中心序号")
);
if (centerNumDivs.length > 1) {
centerNumDivs[1].click();
console.log("已点击'中心序号'");
} else if (centerNumDivs.length > 0) {
centerNumDivs[0].click();
console.log("已点击'中心序号'");
} else {
console.error("未找到'中心序号'");
}
await wait(1000);
// 选择"术后血红蛋白HB"
const hbOption = Array.from(document.querySelectorAll('[role="option"]')).find(option =>
option.textContent.includes("术后血红蛋白HB")
);
if (hbOption) {
hbOption.click();
console.log("已选择'术后血红蛋白HB'");
} else {
console.error("未找到'术后血红蛋白HB'选项");
}
await wait(1000);
// 点击X变量选择框
const xSelector = document.querySelector('[id="X"] > .form-group > div > .selectize-control > .selectize-input');
if (xSelector) {
xSelector.click();
console.log("已点击X变量选择框");
} else {
console.error("未找到X变量选择框");
}
await wait(1000);
// 选择"性别"
const genderOption = Array.from(document.querySelectorAll('[role="option"]')).find(option =>
option.textContent.includes("性别")
);
if (genderOption) {
genderOption.click();
console.log("已选择'性别'");
} else {
console.error("未找到'性别'选项");
}
await wait(1000);
// 点击变量选择区域
const varSelectors = Array.from(document.querySelectorAll('div')).filter(div =>
div.textContent.includes("1. 根据预览数据选择变量 *选择处理变量Z")
);
if (varSelectors.length > 3) {
varSelectors[3].click();
console.log("已点击变量选择区域");
} else if (varSelectors.length > 0) {
varSelectors[varSelectors.length - 1].click();
console.log("已点击变量选择区域");
} else {
console.error("未找到变量选择区域");
}
await wait(1000);
// 点击"开始分析"按钮
const startAnalysisBtn = Array.from(document.querySelectorAll('button')).find(btn =>
btn.textContent.includes("开始分析")
);
if (startAnalysisBtn) {
startAnalysisBtn.click();
console.log("已点击'开始分析'按钮");
} else {
console.error("未找到'开始分析'按钮");
}
console.log("第二部分脚本执行完成");
} catch (error) {
console.error("执行过程中出错:", error);
}
}
// 执行自动化操作
automate();
}),
"id2":(function() {
function wait(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
// 主函数
async function automate() {
try {
console.log("开始执行 Case02 第二部分脚本...");
// 等待页面加载
await wait(2000);
// 点击"准备数据"链接
const prepDataLink = Array.from(document.querySelectorAll('a')).find(a =>
a.textContent.includes("准备数据")
);
if (prepDataLink) {
prepDataLink.click();
console.log("已点击'准备数据'链接");
} else {
console.error("未找到'准备数据'链接");
}
await wait(1000);
// 点击"选择数字对象"选项卡
const numObjTab = Array.from(document.querySelectorAll('[role="tab"]')).find(tab =>
tab.textContent.includes("选择数字对象")
);
if (numObjTab) {
numObjTab.click();
console.log("已点击'选择数字对象'选项卡");
} else {
console.error("未找到'选择数字对象'选项卡");
}
await wait(1000);
// 点击"RS"
const rsDivs = Array.from(document.querySelectorAll('div')).filter(div =>
div.textContent.trim() === "RS"
);
if (rsDivs.length > 1) {
rsDivs[1].click();
console.log("已点击'RS'");
} else if (rsDivs.length > 0) {
rsDivs[0].click();
console.log("已点击'RS'");
} else {
console.error("未找到'RS'");
}
await wait(1000);
// 选择"TQ-B2303-III-01_merged"
const mergedOption = Array.from(document.querySelectorAll('[role="option"]')).find(option =>
option.textContent.includes("TQ-B2303-III-01_merged")
);
if (mergedOption) {
mergedOption.click();
console.log("已选择'TQ-B2303-III-01_merged'");
} else {
console.error("未找到'TQ-B2303-III-01_merged'选项");
}
await wait(1000);
// 点击特定元素
const noncomplianceElement = document.querySelector('[id="data\\.goto\\.noncompliance1"]');
if (noncomplianceElement) {
noncomplianceElement.click();
console.log("已点击特定元素");
} else {
console.error("未找到特定元素");
}
await wait(1000);
// 点击"AGEGR1"下拉框
const agegr1Combobox = Array.from(document.querySelectorAll('[role="combobox"]')).find(box =>
box.getAttribute('name') === "AGEGR1"
);
if (agegr1Combobox) {
agegr1Combobox.click();
console.log("已点击'AGEGR1'下拉框");
await wait(500);
// 选择特定选项
const agegr1Option = document.querySelector('#bs-select-9-29');
if (agegr1Option) {
agegr1Option.click();
console.log("已选择AGEGR1选项");
} else {
console.error("未找到AGEGR1选项");
}
} else {
console.error("未找到'AGEGR1'下拉框");
}
await wait(1000);
// 点击"SUBJID"下拉框
const subjidCombobox = Array.from(document.querySelectorAll('[role="combobox"]')).find(box =>
box.getAttribute('name') === "SUBJID"
);
if (subjidCombobox) {
subjidCombobox.click();
console.log("已点击'SUBJID'下拉框");
await wait(500);
// 选择特定选项
const subjidOption = document.querySelector('#bs-select-14-42');
if (subjidOption) {
subjidOption.click();
console.log("已选择SUBJID选项");
} else {
console.error("未找到SUBJID选项");
}
} else {
console.error("未找到'SUBJID'下拉框");
}
await wait(1000);
// 点击"Nothing selected"下拉框
const nothingSelectedCombobox = Array.from(document.querySelectorAll('[role="combobox"]')).find(box =>
box.getAttribute('name') === "Nothing selected"
);
if (nothingSelectedCombobox) {
nothingSelectedCombobox.click();
console.log("已点击'Nothing selected'下拉框");
await wait(500);
// 选择特定选项
const nothingSelectedOption = document.querySelector('#bs-select-16-6');
if (nothingSelectedOption) {
nothingSelectedOption.click();
console.log("已选择选项");
} else {
console.error("未找到选项");
}
} else {
console.error("未找到'Nothing selected'下拉框");
}
await wait(1000);
// 点击"IPI"下拉框
const ipiCombobox = Array.from(document.querySelectorAll('[role="combobox"]')).find(box =>
box.getAttribute('name') === "IPI"
);
if (ipiCombobox) {
ipiCombobox.click();
console.log("已点击'IPI'下拉框");
await wait(500);
// 取消勾选第四个复选框
const checkboxes = document.querySelectorAll('[role="checkbox"]');
if (checkboxes.length > 3) {
checkboxes[3].click(); // 取消勾选
console.log("已取消勾选复选框");
} else {
console.error("未找到足够的复选框");
}
} else {
console.error("未找到'IPI'下拉框");
}
await wait(1000);
// 点击"计算估计结果"按钮
const calculateBtn = Array.from(document.querySelectorAll('button')).find(btn =>
btn.textContent.includes("计算估计结果")
);
if (calculateBtn) {
calculateBtn.click();
console.log("已点击'计算估计结果'按钮");
} else {
console.error("未找到'计算估计结果'按钮");
}
console.log("Case02 第二部分脚本执行完成");
} catch (error) {
console.error("执行过程中出错:", error);
}
}
// 执行自动化操作
automate();
}),
"id3":(function(){
function wait(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
// 主函数
async function automate() {
try {
console.log("开始执行 Case03 第二部分脚本...");
// 等待页面加载
await wait(2000);
// 点击"模型预测"链接
const modelPredictLink = Array.from(document.querySelectorAll('a')).find(a =>
a.textContent.includes("模型预测")
);
if (modelPredictLink) {
modelPredictLink.click();
console.log("已点击'模型预测'链接");
} else {
console.error("未找到'模型预测'链接");
}
await wait(1000);
// 点击"选择数字对象"选项卡
const numObjTab = Array.from(document.querySelectorAll('[role="tab"]')).find(tab =>
tab.textContent.includes("选择数字对象")
);
if (numObjTab) {
numObjTab.click();
console.log("已点击'选择数字对象'选项卡");
} else {
console.error("未找到'选择数字对象'选项卡");
}
await wait(1000);
// 点击"多重填补数字对象"
const multipleNumObjs = Array.from(document.querySelectorAll('div')).filter(div =>
div.textContent.trim() === "多重填补数字对象"
);
if (multipleNumObjs.length > 1) {
multipleNumObjs[1].click();
console.log("已点击'多重填补数字对象'");
} else if (multipleNumObjs.length > 0) {
multipleNumObjs[0].click();
console.log("已点击'多重填补数字对象'");
} else {
console.error("未找到'多重填补数字对象'");
}
await wait(1000);
// 选择"Clopidogrel"
const clopidogrelOption = Array.from(document.querySelectorAll('[role="option"]')).find(option =>
option.textContent.includes("Clopidogrel")
);
if (clopidogrelOption) {
clopidogrelOption.click();
console.log("已选择'Clopidogrel'");
} else {
console.error("未找到'Clopidogrel'选项");
}
await wait(1000);
// 勾选特定复选框
const checkbox = document.querySelector('#col_pro');
if (checkbox) {
checkbox.checked = true;
checkbox.dispatchEvent(new Event('change', { bubbles: true }));
console.log("已勾选复选框");
} else {
console.error("未找到指定复选框");
}
await wait(1000);
// 点击"查看上传的数据"按钮
const viewDataBtn = Array.from(document.querySelectorAll('button')).find(btn =>
btn.textContent.includes("查看上传的数据")
);
if (viewDataBtn) {
viewDataBtn.click();
console.log("已点击'查看上传的数据'按钮");
} else {
console.error("未找到'查看上传的数据'按钮");
}
await wait(2000);
// 点击"计算模型预测结果"按钮
const calculateBtn = Array.from(document.querySelectorAll('button')).find(btn =>
btn.textContent.includes("计算模型预测结果")
);
if (calculateBtn) {
calculateBtn.click();
console.log("已点击'计算模型预测结果'按钮");
} else {
console.error("未找到'计算模型预测结果'按钮");
}
console.log("Case03 第二部分脚本执行完成");
} catch (error) {
console.error("执行过程中出错:", error);
}
}
// 执行自动化操作
automate();
})
}

View File

@@ -5,12 +5,10 @@ import { clearBadge, streamDownload } from "@/utils/pull-ollama"
export default defineBackground({ export default defineBackground({
main() { main() {
let isCopilotRunning: boolean = false let isCopilotRunning: boolean = false
browser.runtime.onMessage.addListener(async (message,sender,sendResponse) => { browser.runtime.onMessage.addListener(async (message) => {
switch(message.type){ if (message.type === "sidepanel") {
case "sidepanel":
await browser.sidebarAction.open() await browser.sidebarAction.open()
break; } else if (message.type === "pull_model") {
case "pull_model":
const ollamaURL = await getOllamaURL() const ollamaURL = await getOllamaURL()
const isRunning = await isOllamaRunning() const isRunning = await isOllamaRunning()
@@ -23,12 +21,8 @@ export default defineBackground({
clearBadge() clearBadge()
}, 5000) }, 5000)
} }
await streamDownload(ollamaURL, message.modelName) await streamDownload(ollamaURL, message.modelName)
break;
case "retrieveDeepScript":
return retrieveDeepScript(message);
default:
break;
} }
}) })
@@ -186,52 +180,3 @@ export default defineBackground({
}, },
persistent: true persistent: true
}) })
const iodConfig = {
"gatewayUrl": "tcp://127.0.0.1:21051",
"registry":"bdware/Registry",
"localRepository":"bdtest.local/myrepo1",
"doBrowser":"http://127.0.0.1:21030/SCIDE/SCManager"
}
const makeDOIPParams = (doId:string, op:string, attributes:Object, requestBody: string) => ({
action: "executeContract",
contractID: "BDBrowser",
operation: "sendRequestDirectly",
arg: {
id: doId,
doipUrl: iodConfig.gatewayUrl,
op: op,
attributes: attributes,
body: requestBody
}
})
const retrieveDeepScript = async function(message) {
console.log(message);
const doId = message.doId;
console.log("retriveDoc:"+doId)
const params = makeDOIPParams(doId,"Retrieve",{
bodyBase64Encoded: false
}, "");
const abortController = new AbortController()
setTimeout(() => abortController.abort(), 10000)
return await fetch(iodConfig.doBrowser, {
method: "POST",
body: JSON.stringify(params),
signal: abortController.signal
}).then((response) => {
console.log("responseIn retrieveDoc:");
console.log(response);
return response.json()})
.then((res) => {
console.log("res:");
console.log(res.result.body);
//TODO
return {
metadata:{traceId:res.result.header.attributes?.traceId},
pageContent:res.result.body
}
})
}

View File

@@ -1,7 +1,7 @@
<!doctype html> <!doctype html>
<html> <html>
<head> <head>
<title>IoD Bot - A Web UI for Local AI Models</title> <title>Page Assist - A Web UI for Local AI Models</title>
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="manifest.type" content="browser_action" /> <meta name="manifest.type" content="browser_action" />
<meta name="manifest.browser_style" content="false" /> <meta name="manifest.browser_style" content="false" />

View File

@@ -2,7 +2,7 @@ import { saveHistory, saveMessage } from "@/db"
import { setLastUsedChatModel, setLastUsedChatSystemPrompt } from "@/services/model-settings" import { setLastUsedChatModel, setLastUsedChatSystemPrompt } from "@/services/model-settings"
import { generateTitle } from "@/services/title" import { generateTitle } from "@/services/title"
import { ChatHistory } from "@/store/option" import { ChatHistory } from "@/store/option"
import { updateDialog } from "@/web/iod"
export const saveMessageOnError = async ({ export const saveMessageOnError = async ({
e, e,
history, history,
@@ -154,7 +154,6 @@ export const saveMessageOnSuccess = async ({
prompt_content?: string prompt_content?: string
reasoning_time_taken?: number reasoning_time_taken?: number
}) => { }) => {
var botMessage;
if (historyId) { if (historyId) {
if (!isRegenerate) { if (!isRegenerate) {
await saveMessage( await saveMessage(
@@ -171,7 +170,7 @@ export const saveMessageOnSuccess = async ({
reasoning_time_taken reasoning_time_taken
) )
} }
botMessage = await saveMessage( await saveMessage(
historyId, historyId,
selectedModel!, selectedModel!,
"assistant", "assistant",
@@ -184,7 +183,6 @@ export const saveMessageOnSuccess = async ({
generationInfo, generationInfo,
reasoning_time_taken reasoning_time_taken
) )
updateDialog(historyId, botMessage)
await setLastUsedChatModel(historyId, selectedModel!) await setLastUsedChatModel(historyId, selectedModel!)
if (prompt_id || prompt_content) { if (prompt_id || prompt_content) {
await setLastUsedChatSystemPrompt(historyId, { prompt_content, prompt_id }) await setLastUsedChatSystemPrompt(historyId, { prompt_content, prompt_id })
@@ -205,7 +203,7 @@ export const saveMessageOnSuccess = async ({
generationInfo, generationInfo,
reasoning_time_taken reasoning_time_taken
) )
botMessage = await saveMessage( await saveMessage(
newHistoryId.id, newHistoryId.id,
selectedModel!, selectedModel!,
"assistant", "assistant",
@@ -218,7 +216,6 @@ export const saveMessageOnSuccess = async ({
generationInfo, generationInfo,
reasoning_time_taken reasoning_time_taken
) )
updateDialog(newHistoryId.id, botMessage)
setHistoryId(newHistoryId.id) setHistoryId(newHistoryId.id)
await setLastUsedChatModel(newHistoryId.id, selectedModel!) await setLastUsedChatModel(newHistoryId.id, selectedModel!)
if (prompt_id || prompt_content) { if (prompt_id || prompt_content) {

View File

@@ -8,7 +8,7 @@ import {
promptForRag, promptForRag,
systemPromptForNonRagOption systemPromptForNonRagOption
} from "~/services/ollama" } from "~/services/ollama"
import type { ChatHistory, Message, MeteringEntry } from "~/store/option" import { type ChatHistory, ChatMessage, type Message } from "~/store/option"
import { SystemMessage } from "@langchain/core/messages" import { SystemMessage } from "@langchain/core/messages"
import { useStoreMessageOption } from "~/store/option" import { useStoreMessageOption } from "~/store/option"
import { import {
@@ -21,8 +21,6 @@ import {
import { useNavigate } from "react-router-dom" import { useNavigate } from "react-router-dom"
import { notification } from "antd" import { notification } from "antd"
import { getSystemPromptForWeb } from "~/web/web" import { getSystemPromptForWeb } from "~/web/web"
import { tokenizeInput } from "~/web/iod"
import { generateHistory } from "@/utils/generate-history" import { generateHistory } from "@/utils/generate-history"
import { useTranslation } from "react-i18next" import { useTranslation } from "react-i18next"
import { import {
@@ -40,7 +38,6 @@ import { pageAssistModel } from "@/models"
import { getNoOfRetrievedDocs } from "@/services/app" import { getNoOfRetrievedDocs } from "@/services/app"
import { humanMessageFormatter } from "@/utils/human-message" import { humanMessageFormatter } from "@/utils/human-message"
import { pageAssistEmbeddingModel } from "@/models/embedding" import { pageAssistEmbeddingModel } from "@/models/embedding"
import { import {
isReasoningEnded, isReasoningEnded,
isReasoningStarted, isReasoningStarted,
@@ -58,9 +55,8 @@ export const useMessageOption = () => {
const { const {
history, history,
setHistory, setHistory,
meteringEntries, chatMessages,
setMeteringEntries, setChatMessages,
setCurrentMeteringEntry,
setStreaming, setStreaming,
streaming, streaming,
setIsFirstMessage, setIsFirstMessage,
@@ -118,24 +114,23 @@ export const useMessageOption = () => {
setWebSearch(true) setWebSearch(true)
} }
} }
// 从最后的结果中解析出 思维链 和 结果
// 从最后的结果中解析出 思维链 (Chain-of-Thought) 和 结果
const responseResolver = (msg: string) => { const responseResolver = (msg: string) => {
const cotStart = msg.indexOf("<think>") const thinkStart = msg.indexOf("<think>")
const cotEnd = msg.indexOf("</think>") const thinkEnd = msg.indexOf("</think>")
let cot = "" let think = ""
let content = "" let content = ""
if (cotStart > -1 && cotEnd > -1) { if (thinkStart > -1 && thinkEnd > -1) {
cot = msg.substring(cotStart + 7, cotEnd) think = msg.substring(thinkStart + 7, thinkEnd)
content = msg.substring(cotEnd + 8) content = msg.substring(thinkEnd + 8)
} else { } else {
content = msg content = msg
} }
// 去掉换行符 // 去掉换行符
cot = cot.replace(/\n/g, "") think = think.replace(/\n/g, "")
content = content.replace(/\n/g, "") content = content.replace(/\n/g, "")
return { return {
cot: cot, think,
content content
} }
} }
@@ -195,16 +190,11 @@ export const useMessageOption = () => {
}) })
let newMessage: Message[] = [] let newMessage: Message[] = []
let generateMessageId = generateID() let generateMessageId = generateID()
const meter: MeteringEntry = { const chatMessage: ChatMessage = {
id: generateMessageId, id: generateMessageId,
queryContent: message, queryContent: message,
date: new Date().getTime() date: new Date()
} as MeteringEntry } as ChatMessage
setCurrentMeteringEntry({
loading: true,
data: meter
})
if (!isRegenerate) { if (!isRegenerate) {
newMessage = [ newMessage = [
@@ -316,9 +306,6 @@ 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("query:"+query+" --> "+JSON.stringify(tokenizeInput(query)));
keywords = tokenizeInput(query)
/*
const questionPrompt = await geWebSearchKeywordsPrompt() const questionPrompt = await geWebSearchKeywordsPrompt()
const promptForQuestion = questionPrompt.replaceAll("{query}", query) const promptForQuestion = questionPrompt.replaceAll("{query}", query)
const response = await ollama.invoke(promptForQuestion) const response = await ollama.invoke(promptForQuestion)
@@ -326,23 +313,18 @@ export const useMessageOption = () => {
res = removeReasoning(res) res = removeReasoning(res)
keywords = res keywords = res
.replace(/^Keywords:/i, "") .replace(/^Keywords:/i, "")
.replace(/^关键词:/i, "")
.replace(/^/i, "")
.replace(/^:/i, "")
.replaceAll(" ", "")
.split(", ") .split(", ")
.map((k) => k.trim()) .map((k) => k.trim())
*/
} }
const { prompt, webSources, iodSources, iodSearchResults: iodData, iodTokenCount } = const { prompt, webSources, iodSources, iodData, iodDataTokenCount } =
await getSystemPromptForWeb(query, keywords, webSearch, iodSearch) await getSystemPromptForWeb(query, keywords, webSearch, iodSearch)
console.log("prompt:\n" + prompt) console.log("prompt:\n" + prompt)
setIsSearchingInternet(false) setIsSearchingInternet(false)
meter.prompt = prompt chatMessage.prompt = prompt
meter.iodKeywords = keywords chatMessage.iodKeywords = keywords
meter.iodData = iodData chatMessage.iodData = iodData
meter.iodTokenCount = iodTokenCount chatMessage.iodDataTokenCount = iodDataTokenCount
// message = message.trim().replaceAll("\n", " ") // message = message.trim().replaceAll("\n", " ")
@@ -403,7 +385,6 @@ export const useMessageOption = () => {
} }
) )
let count = 0 let count = 0
const chatStartTime = new Date()
let reasoningStartTime: Date | undefined = undefined let reasoningStartTime: Date | undefined = undefined
let reasoningEndTime: Date | undefined = undefined let reasoningEndTime: Date | undefined = undefined
let apiReasoning = false let apiReasoning = false
@@ -504,30 +485,16 @@ export const useMessageOption = () => {
setIsProcessing(false) setIsProcessing(false)
setStreaming(false) setStreaming(false)
// Save metering entry chatMessage.modelInputTokenCount = prompt.length
const { cot, content } = responseResolver(fullText) chatMessage.modelOutputTokenCount = fullText.length
const currentMeteringEntry = { chatMessage.model = ollama.modelName
...meter, chatMessage.relatedDataCount = iodData?.length ?? 0
modelInputTokenCount: prompt.length, chatMessage.timeTaken = new Date().getTime() - chatMessage.date.getTime()
modelOutputTokenCount: fullText.length, const { think, content } = responseResolver(fullText)
model: ollama.modelName ?? ollama.model, chatMessage.thinkingChain = think
relatedDataCount: iodData?.length ?? 0, chatMessage.responseContent = content
timeTaken: new Date().getTime() - chatStartTime.getTime(), chatMessage.modelResponseContent = fullText
date: chatStartTime.getTime(), setChatMessages([chatMessage, ...chatMessages])
cot,
responseContent: content,
modelResponseContent: fullText,
}
const _meteringEntries = [
currentMeteringEntry,
...meteringEntries,
]
setCurrentMeteringEntry({
loading: false,
data: currentMeteringEntry,
})
setMeteringEntries(_meteringEntries)
localStorage.setItem("meteringEntries", JSON.stringify(_meteringEntries))
} catch (e) { } catch (e) {
const errorSave = await saveMessageOnError({ const errorSave = await saveMessageOnError({
e, e,
@@ -645,16 +612,6 @@ export const useMessageOption = () => {
let newMessage: Message[] = [] let newMessage: Message[] = []
let generateMessageId = generateID() let generateMessageId = generateID()
const meter: MeteringEntry = {
id: generateMessageId,
queryContent: message,
date: new Date().getTime()
} as MeteringEntry
setCurrentMeteringEntry({
loading: true,
data: meter,
})
if (!isRegenerate) { if (!isRegenerate) {
newMessage = [ newMessage = [
@@ -781,7 +738,6 @@ export const useMessageOption = () => {
let reasoningStartTime: Date | null = null let reasoningStartTime: Date | null = null
let reasoningEndTime: Date | null = null let reasoningEndTime: Date | null = null
let apiReasoning: boolean = false let apiReasoning: boolean = false
const chatStartTime = new Date()
for await (const chunk of chunks) { for await (const chunk of chunks) {
if (chunk?.additional_kwargs?.reasoning_content) { if (chunk?.additional_kwargs?.reasoning_content) {
@@ -882,31 +838,6 @@ export const useMessageOption = () => {
setStreaming(false) setStreaming(false)
setIsProcessing(false) setIsProcessing(false)
setStreaming(false) setStreaming(false)
// Save metering entry
const { cot, content } = responseResolver(fullText)
const currentMeteringEntry = {
...meter,
modelInputTokenCount: prompt.length,
modelOutputTokenCount: fullText.length,
model: ollama.modelName ?? ollama.model,
relatedDataCount: 0,
timeTaken: new Date().getTime() - chatStartTime.getTime(),
date: chatStartTime.getTime(),
cot,
responseContent: content,
modelResponseContent: fullText,
}
const _meteringEntries = [
currentMeteringEntry,
...meteringEntries,
]
setCurrentMeteringEntry({
loading: false,
data: currentMeteringEntry,
})
setMeteringEntries(_meteringEntries)
} catch (e) { } catch (e) {
const errorSave = await saveMessageOnError({ const errorSave = await saveMessageOnError({
e, e,

View File

@@ -1,51 +0,0 @@
import RocketSvg from "@/assets/icons/rocket.svg"
import BulbSvg from "@/assets/icons/bulb.svg"
import EyeSvg from "@/assets/icons/eye.svg"
import ASvg from "@/assets/icons/a.svg"
import BSvg from "@/assets/icons/b.svg"
import CSvg from "@/assets/icons/c.svg"
import DSvg from "@/assets/icons/d.svg"
import ESvg from "@/assets/icons/e.svg"
import FSvg from "@/assets/icons/f.svg"
export const qaPrompt = [
{
title: "最近一年大型语言模型的技术进展有哪些?",
icon: <img src={RocketSvg} alt="Rocket" className="w-full my-0" />,
},
{
title: "生成式AI在企业中有哪些具体应用场景",
icon: <img src={BulbSvg} alt="Rocket" className="w-full my-0" />,
},
{
title: "多模态学习技术的最新研究方向是什么?",
icon: <img src={EyeSvg} alt="Rocket" className="w-full my-0" />,
},
{
title: "当前AI芯片市场格局和未来三年发展趋势如何",
icon: <img src={ASvg} alt="Rocket" className="w-full my-0" />,
},
{
title: "主流深度学习框架性能与易用性对比分析?",
icon: <img src={BSvg} alt="Rocket" className="w-full my-0" />,
},
{
title: "国内外AI伦理治理框架有哪些最佳实践",
icon: <img src={CSvg} alt="Rocket" className="w-full my-0" />,
},
{
title: "大规模知识图谱构建与应用最新进展?",
icon: <img src={DSvg} alt="Rocket" className="w-full my-0" />,
},
{
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) => ({
...item,
id: index.toString(),
}))

View File

@@ -1,6 +1,6 @@
{ {
"extName": { "extName": {
"message": "IoD Bot - 本地 AI 模型的 Web UI" "message": "Page Assist - 本地 AI 模型的 Web UI"
}, },
"extDescription": { "extDescription": {
"message": "使用本地运行的 AI 模型来辅助您的网络浏览。" "message": "使用本地运行的 AI 模型来辅助您的网络浏览。"

View File

@@ -21,37 +21,7 @@ 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 = `You are an AI model who is expert at searching the web and answering user's queries.
const DEFAULT_WEBSEARCH_PROMPT = `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}.
The \`iod-search-results\` block provides information retrieved from Internet of Data. Each search result has a format of:
\`<result doId="{doId}" name="{title}" authors="{authors}" dataType="{paper,dataset or algorithm}" year="{year}" url="{url}" id="{id}">{abstract}</result>\`
Please show the \`doId\` and \`name\` of the search result when you refer to search result in your response and chain of thought, in the following format, in English:
\`[IoD source [id] doId: {doId} "{name}"]({url})\`
Or in Chinese:
\`[数联网引用[id] doId: {doId} "{name}"]({url})\`
For example, in English:
\`[IoD source [1] doId: 10.48550/arXiv.1803.05591v2 "On the insufficiency of existing momentum schemes for Stochastic Optimization"](http://arxiv.org/pdf/1803.05591v2.pdf)\`
Or in Chinese:
\`[数联网引用[1] doId: 10.48550/arXiv.1803.05591v2 "On the insufficiency of existing momentum schemes for Stochastic Optimization"](http://arxiv.org/pdf/1803.05591v2.pdf)\`
Use this information to generate a meaningful response that includes:
0. 如果搜索结果看着和用户想做的事儿无关,那么直接忽略它,不需要在思维链和回答中体现。
1. 从搜索结果中,用户可以参考哪些论文来实现他的目标。
2. 从搜索结果中,用户可以使用哪些数据集(dataset)
3. 用户想干的这个事儿,如何结合这些数据来实现。
4. 请用中文回答这个问题。
<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.
Generate a response that is informative and relevant to the user's query based on provided search results. the current date and time are {current_date_time}. Generate a response that is informative and relevant to the user's query based on provided search results. the current date and time are {current_date_time}.
@@ -116,25 +86,24 @@ const DEFAULT_WEBSEARCH_KEYWORDS_PROMPT = `Extract the most important keywords f
The result format should be: keyword_1, keyword_2, ..., keyword_n The result format should be: keyword_1, keyword_2, ..., keyword_n
注意,以下关键词请不要输出:"research", "研究", "data analysis", "data", "数据" 。
注意英文单词的输出首字母应该小写仅需输出Keywords部分Query部分不用输出。以下是一些例子。
Example: Example:
Query: What are the symptoms of a heart attack? Query: What are the symptoms of a heart attack?
你的输出: symptoms, 症状, heart attack, 心臟病 Keywords: symptoms, 症状, heart attack, 心臟病
Query: 什么是物联网? Query: 什么是物联网?
你的输出: Internet of Things, IoT, 物联网 Keywords: Internet of Things, IoT, 物联网
Query: 人工智能的发展趋势? Query: 人工智能的发展趋势?
你的输出: Artificial Intelligence, AI, 人工智能, trend, 趋势 Keywords: Artificial Intelligence, AI, 人工智能, trend, 趋势
接下来,开始你的关键词提取吧。
Query: {query} Query: {query}
Keywords:
` `
export const getOllamaURL = async () => { export const getOllamaURL = async () => {

View File

@@ -30,15 +30,49 @@ export type ChatHistory = {
messageType?: string messageType?: string
}[] }[]
export type ChatMessage = {
id: string
// 问题
queryContent: string
// 提示词全文
prompt: string
// 思维链(只有深度思考时有)
thinkingChain?: string
// 回答
responseContent: string
// 关联数据个数
relatedDataCount: number
// 数联网输入token
iodInputToken: string
// 数联网输出token
iodOutputToken: string
// 大模型输入token数量
modelInputTokenCount: number
// 大模型输出token数量
modelOutputTokenCount: number
// 日期
date: Date
// 耗时
timeTaken: number
// 大模型回答的全部内容
modelResponseContent: string
// iod的全部内容的token数量
iodDataTokenCount: number
// iod返回的数据
iodData: any[]
// iod keywords
iodKeywords: string[]
// 模型
model: string
}
type State = { type State = {
messages: Message[] messages: Message[]
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} chatMessages: ChatMessage[]
setCurrentMeteringEntry: (meteringEntry: {data: MeteringEntry, loading: boolean}) => void setChatMessages: (chatMessages: ChatMessage[]) => void
meteringEntries: MeteringEntry[]
setMeteringEntries: (meteringEntries: MeteringEntry[]) => void
streaming: boolean streaming: boolean
setStreaming: (streaming: boolean) => void setStreaming: (streaming: boolean) => void
isFirstMessage: boolean isFirstMessage: boolean
@@ -81,51 +115,13 @@ type State = {
setUseOCR: (useOCR: boolean) => void setUseOCR: (useOCR: boolean) => void
} }
export type MeteringEntry = {
id: string
// 问题
queryContent: string
// 提示词全文
prompt: string
// 思维链(只有深度思考时有)
cot?: string
// 回答
responseContent: string
// 关联数据个数
relatedDataCount: number
// 数联网输入token
iodInputToken: string
// 数联网输出token
iodOutputToken: string
// 大模型输入token数量
modelInputTokenCount: number
// 大模型输出token数量
modelOutputTokenCount: number
// 日期
date: number
// 耗时
timeTaken: number
// 大模型回答的全部内容
modelResponseContent: string
// iod的全部内容的token数量
iodTokenCount: number
// iod返回的数据
iodData: any[]
// iod keywords
iodKeywords: string[]
// 模型
model: string
}
export const useStoreMessageOption = create<State>((set) => ({ export const useStoreMessageOption = create<State>((set) => ({
messages: [], messages: [],
setMessages: (messages) => set({ messages }), setMessages: (messages) => set({ messages }),
history: [], history: [],
setHistory: (history) => set({ history }), setHistory: (history) => set({ history }),
currentMeteringEntry: {data: {} as MeteringEntry, loading: false}, chatMessages: [],
setCurrentMeteringEntry: (currentMeteringEntry) => set({ currentMeteringEntry }), setChatMessages: (chatMessages) => set({ chatMessages }),
meteringEntries: JSON.parse(localStorage.getItem("meteringEntries") || JSON.stringify([])),
setMeteringEntries: (meteringEntries) => set({ meteringEntries }),
streaming: false, streaming: false,
setStreaming: (streaming) => set({ streaming }), setStreaming: (streaming) => set({ streaming }),
isFirstMessage: true, isFirstMessage: true,

View File

@@ -6,6 +6,4 @@ export type IodRegistryEntry = {
description: string description: string
content?: string content?: string
data_space?: string data_space?: string
data_type?:string
traceId?:string
} }

View File

@@ -1,8 +0,0 @@
import { Segment, useDefault, cnPOSTag, enPOSTag } from 'segmentit';
declare module 'segmentit' {
export = Segment;
export = useDefault;
export = cnPOSTag;
export = enPOSTag;
}

View File

@@ -1,21 +0,0 @@
{
"action": "executeContract",
"contractID": "BDBrowser",
"operation": "sendRequestDirectly",
"arg": {
"id": "670E241C9937B3537047C87053E3AA36",
"doipUrl": "tcp://reg01.public.internetofdata.cn:21037",
"op": "Search",
"attributes": {
"offset": 2100,
"count": 5,
"bodyBase64Encoded": false,
"searchMode": [
{ "key": "data_type", "type": "MUST", "value": "paper" },
{ "key": "title", "type": "MUST", "value": "Number_1" },
{ "key": "description", "type": "MUST", "value": "Number_1" }
]
},
"body": ""
}
}

View File

@@ -1,33 +0,0 @@
const ollama = await pageAssistModel({
model: selectedModel!,
baseUrl: cleanUrl(url),
keepAlive:
currentChatModelSettings?.keepAlive ?? userDefaultModelSettings?.keepAlive,
temperature:
currentChatModelSettings?.temperature ??
userDefaultModelSettings?.temperature,
topK: currentChatModelSettings?.topK ?? userDefaultModelSettings?.topK,
topP: currentChatModelSettings?.topP ?? userDefaultModelSettings?.topP,
numCtx: currentChatModelSettings?.numCtx ?? userDefaultModelSettings?.numCtx,
seed: currentChatModelSettings?.seed,
numGpu: currentChatModelSettings?.numGpu ?? userDefaultModelSettings?.numGpu,
numPredict:
currentChatModelSettings?.numPredict ??
userDefaultModelSettings?.numPredict,
useMMap:
currentChatModelSettings?.useMMap ?? userDefaultModelSettings?.useMMap,
minP: currentChatModelSettings?.minP ?? userDefaultModelSettings?.minP,
repeatLastN:
currentChatModelSettings?.repeatLastN ??
userDefaultModelSettings?.repeatLastN,
repeatPenalty:
currentChatModelSettings?.repeatPenalty ??
userDefaultModelSettings?.repeatPenalty,
tfsZ: currentChatModelSettings?.tfsZ ?? userDefaultModelSettings?.tfsZ,
numKeep:
currentChatModelSettings?.numKeep ?? userDefaultModelSettings?.numKeep,
numThread:
currentChatModelSettings?.numThread ?? userDefaultModelSettings?.numThread,
useMlock:
currentChatModelSettings?.useMlock ?? userDefaultModelSettings?.useMlock
})

View File

@@ -3,234 +3,64 @@ import { PageAssistHtmlLoader } from "@/loader/html"
import { PageAssistPDFUrlLoader } from "@/loader/pdf-url" import { PageAssistPDFUrlLoader } from "@/loader/pdf-url"
import { pageAssistEmbeddingModel } from "@/models/embedding" import { pageAssistEmbeddingModel } from "@/models/embedding"
import { defaultEmbeddingModelForRag, getOllamaURL } from "@/services/ollama" import { defaultEmbeddingModelForRag, getOllamaURL } from "@/services/ollama"
import { import {
getIsSimpleInternetSearch, getIsSimpleInternetSearch,
totalSearchResults totalSearchResults
} from "@/services/search" } from "@/services/search"
import { getPageAssistTextSplitter } from "@/utils/text-splitter" import { getPageAssistTextSplitter } from "@/utils/text-splitter"
import { Document } from "@langchain/core/documents" import type { Document } from "@langchain/core/documents"
import { MemoryVectorStore } from "langchain/vectorstores/memory" import { MemoryVectorStore } from "langchain/vectorstores/memory"
import type { IodRegistryEntry } from "~/types/iod" import type { IodRegistryEntry } from "~/types/iod"
const makeRegSearchParams = (count: number, keyword: string) => ({
import { PageAssitDatabase } from "@/db"
import exp from "constants"
import { Segment, useDefault, cnPOSTag, enPOSTag} from 'segmentit';
const segment = useDefault(new Segment());
export const tokenizeInput = function (input: string): string[] {
const words = segment.doSegment(input, { simple: false });
console.log(words.map(function(word){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
export const iodConfig = {
"gatewayUrl": "tcp://021.node.internetapi.cn:21052",
"registry":"data/Registry",
"localRepository":"data/Repository",
"doBrowser":"http://021.node.internetapi.cn:21030/SCIDE/SCManager"
}
function inGrepList(str: string){
return "什么|问题|需要|合适|设计|考虑|合作|精度|传感器|最新|研究|药物".indexOf(str)!=-1;
}
export const makeRegSearchParams = function(count: number, keyword: string| string[]){
const searchMode = [];
if (typeof keyword === 'string') {
// 如果 keyword 是字符串,则直接添加一个 searchMode 条目
searchMode.push({
key: "description",
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", action: "executeContract",
contractID: "BDBrowser", contractID: "BDBrowser",
operation: "sendRequestDirectly", operation: "sendRequestDirectly",
arg: { arg: {
id: iodConfig.registry, id: "670E241C9937B3537047C87053E3AA36",
//doipUrl:"tcp://127.0.0.1:21039", doipUrl: "tcp://reg01.public.internetofdata.cn:21037",
doipUrl: iodConfig.gatewayUrl,
op: "Search", op: "Search",
vars:{
timeout:15000
},
attributes: { attributes: {
offset: 0, offset: 0,
count, count,
bodyBase64Encoded: false, bodyBase64Encoded: false,
searchMode:searchMode searchMode: [
{
key: "data_type",
type: "MUST",
value: "paper"
},
// {
// key: "title",
// type: "MUST",
// value: keyword,
// },
{
key: "description",
type: "MUST",
value: keyword
}
]
}, },
body: "" body: ""
} }
}
}
export const makeDOIPParams = (doId:string, op:string, attributes:Object, requestBody: string) => ({
action: "executeContract",
contractID: "BDBrowser",
operation: "sendRequestDirectly",
arg: {
id: doId,
doipUrl: iodConfig.gatewayUrl,
op: op,
attributes: attributes,
body: requestBody
}
}) })
export const retrieveDoc = function(doId: string) : Promise<Document> {
console.log("retriveDoc:"+doId)
const params = makeDOIPParams(doId,"Retrieve",{
bodyBase64Encoded: false
}, "");
const abortController = new AbortController()
setTimeout(() => abortController.abort(), 10000)
return fetch(iodConfig.doBrowser, {
method: "POST",
body: JSON.stringify(params),
signal: abortController.signal
}).then((response) => {
console.log("responseIn retrieveDoc:");
console.log(response);
return response.json()})
.then((res) => {
console.log("res:");
console.log(res.result.body);
//TODO
return {
metadata:{traceId:res.result.header.attributes?.traceId},
pageContent:res.result.body
}
})
}
export const updateInLocalRepo = function(historyId: string, requestBody: Object) : Promise<string> {
const params = makeDOIPParams(iodConfig.localRepository,"Update",{
"aiDialogID": historyId,
bodyBase64Encoded: false
}, JSON.stringify(requestBody));
const abortController = new AbortController()
setTimeout(() => abortController.abort(), 10000)
return fetch(iodConfig.doBrowser, {
method: "POST",
body: JSON.stringify(params),
signal: abortController.signal
}).then((response) => response.json())
.then((res) => {
console.log("update dialog:"+JSON.stringify(res))
return res.body;
})
}
export const updateDialog = async function(histroyId : string, botMessage: any): Promise<string> {
//TODO @Nex confused by Message/MessageType in ./db/index.ts!
const db = new PageAssitDatabase()
const chatHistory = await db.getChatHistory(histroyId)
var userMessage = null;
for (var i=0;i<chatHistory.length;i++){
userMessage = chatHistory[i];
if (userMessage.role=='user') break;
}
let updateBody:any = {};
// !!!IMPORTANT!!! traceId = histroyId+"/"+userMessage.id;
// Update traceId in retrieveDoc!
updateBody.traceId = histroyId+"/"+userMessage.id;
updateBody.question = {
"id": histroyId+"/"+userMessage.id,
"content": userMessage.content,
"tokenCount": userMessage.content.length
}
updateBody.answer = {
"id": histroyId+"/"+botMessage.id,
"content": botMessage.content,
"tokenCount": botMessage.content.length
}
//TODO set a correct model ID
updateBody.model = {"id":"bdware.ollama/" + userMessage.name}
//TODO incorrect tokenCount calculated!!
updateBody.webSources = botMessage.webSources?.map((r) => ({
url: r.url,
tokenCount: r.url.length,
content: r.url,
traceId: r?.traceId
})) ?? [];
updateBody.IoDSources = botMessage.iodSources?.map((r) => ({
id: r.doId,
tokenCount: (r.content || r.description)?calculateTokenCount((r.content || r.description)):0,
content: r.content || r.description,
traceId: r?.traceId
})) ?? [];
console.log("updateBody:");
console.log(updateBody)
return updateInLocalRepo(histroyId,updateBody)
}
export async function localIodSearch( export async function localIodSearch(
query: string, query: string,
keywords: string[] keywords: string[]
): Promise<IodRegistryEntry[]> { ): Promise<IodRegistryEntry[]> {
const TOTAL_SEARCH_RESULTS = await totalSearchResults() const TOTAL_SEARCH_RESULTS = await totalSearchResults()
const abortController = new AbortController();
setTimeout(() => abortController.abort(), 10000);
const params = makeRegSearchParams(TOTAL_SEARCH_RESULTS, keywords);
console.log('params------->',params)
try {
const response = await fetch(iodConfig.doBrowser, {
method: "POST",
body: JSON.stringify(params),
signal: abortController.signal
});
const res = await response.json();
if (res.status !== "Success") {
return [];
}
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) {
console.log(e);
return [];
}
/*
const results = ( const results = (
await Promise.all( await Promise.all(
keywords.map(async (keyword) => { keywords.map(async (keyword) => {
const abortController = new AbortController() const abortController = new AbortController()
setTimeout(() => abortController.abort(), 10000) setTimeout(() => abortController.abort(), 10000)
//http://47.93.156.31:21033/SCIDE/SCManager
const params = makeRegSearchParams(TOTAL_SEARCH_RESULTS, keyword) const params = makeRegSearchParams(TOTAL_SEARCH_RESULTS, keyword)
return fetch(iodConfig.doBrowser, {
return fetch("http://47.93.156.31:21033/SCIDE/SCManager", {
method: "POST", method: "POST",
body: JSON.stringify(params), body: JSON.stringify(params),
signal: abortController.signal signal: abortController.signal
@@ -246,13 +76,11 @@ export async function localIodSearch(
console.log(body) console.log(body)
return [] return []
} }
const results: IodRegistryEntry[] = body.data?.results || [] const results: IodRegistryEntry[] =
body.data?.results?.filter((r) => r.url || r.pdf_url) || []
for (const r of results) { for (const r of results) {
r.url = r.url || r.pdf_url r.url = r.url || r.pdf_url
} }
for (const r of results) {
r.doId = r.doId || r.doid
}
return results return results
}) })
.catch((e) => { .catch((e) => {
@@ -268,8 +96,8 @@ export async function localIodSearch(
for (const r of results) { for (const r of results) {
map.set(r.doId, r) map.set(r.doId, r)
} }
return Array.from(map.values()) return Array.from(map.values())
*/
} }
const ARXIV_URL_PATTERN = /^https?:\/\/arxiv\.org\// const ARXIV_URL_PATTERN = /^https?:\/\/arxiv\.org\//
@@ -279,7 +107,7 @@ 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("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
@@ -289,18 +117,7 @@ export const searchIod = async (query: string, keywords: string[]) => {
const resMap = new Map<string, IodRegistryEntry>() const resMap = new Map<string, IodRegistryEntry>()
for (const result of searchResults) { for (const result of searchResults) {
const url = result.url const url = result.url
if (result.doId){ if (!url) continue
//TODO !!!!@Nex traceId should be the id of history/question!
let docFromRetrieve = await retrieveDoc(result.doId);
console.log("doc from Retrieve:"+result.doId+" -->"+JSON.stringify(docFromRetrieve))
docs.push(docFromRetrieve)
result.description = docFromRetrieve.pageContent;
result.traceId = docFromRetrieve.metadata?.traceId;
continue;
}
if (!url) {
continue;
}
let htmlUrl = "" let htmlUrl = ""
if (ARXIV_URL_PATTERN.test(url)) { if (ARXIV_URL_PATTERN.test(url)) {
@@ -361,9 +178,6 @@ export const searchIod = async (query: string, keywords: string[]) => {
} }
} }
} }
return searchResults
/*
const ollamaUrl = await getOllamaURL() const ollamaUrl = await getOllamaURL()
const embeddingModle = await defaultEmbeddingModelForRag() const embeddingModle = await defaultEmbeddingModelForRag()
@@ -394,11 +208,4 @@ export const searchIod = async (query: string, keywords: string[]) => {
}).filter((r) => r) }).filter((r) => r)
return searchResult return searchResult
*/
} }
export const calculateTokenCount = function(str:string){
const byteArray = new TextEncoder().encode(str);
return byteArray.length;
}

View File

@@ -11,7 +11,6 @@ import { webBaiduSearch } from "./search-engines/baidu"
import { searchIod } from "./iod" import { searchIod } from "./iod"
import type { WebSearchResult } from "~/types/web" import type { WebSearchResult } from "~/types/web"
import type { IodRegistryEntry } from "~/types/iod" import type { IodRegistryEntry } from "~/types/iod"
import {calculateTokenCount} from "./iod"
const getHostName = (url: string) => { const getHostName = (url: string) => {
try { try {
@@ -101,24 +100,18 @@ export const getSystemPromptForWeb = async (
doId: res.doId, doId: res.doId,
name: res.name, name: res.name,
url: res.url, url: res.url,
data_type: res.data_type,
data_space: res.data_space, data_space: res.data_space,
content: res.content || res.description, tokenCount: (res.content || res.description)?.length ?? 0,
tokenCount: (res.content || res.description)?calculateTokenCount((res.content || res.description)):0, content: res.content || res.description
traceId:res?.traceId
})) }))
const iod_search_results = _iodSearchResults const iod_search_results = _iodSearchResults
.map( .map(
(result, idx) =>{ (result, idx) =>
const nameAttr = result.name ? ` name="${result.name}"` : ''; `<result doId="${result.doId}" name="${result.name}" source="${result.url}" id="${idx + 1}">${result.content}</result>`
const sourceAttr = result.url ? ` source="${result.url}"` : '';
const dataTypeAttr = result.data_type ? ` dataType="${result.data_type}"` : '';
const dataSourceAttr = result.data_space ?` 数据来源="${result.data_space}"`:''
return `<result doId="${result.doId}"${nameAttr}${sourceAttr}${dataTypeAttr}${dataSourceAttr}" >${result.content}</result>`
}
) )
.join("\n") .join("\n")
console.log("iod_search_result: " + iod_search_results)
const web_search_results = webSearchResults const web_search_results = webSearchResults
.map( .map(
@@ -126,6 +119,7 @@ export const getSystemPromptForWeb = async (
`<result source="${result.url}" name="${result.name}" id="${idx + 1}">${result.content}</result>` `<result source="${result.url}" name="${result.name}" id="${idx + 1}">${result.content}</result>`
) )
.join("\n") .join("\n")
console.log("web_search_result: " + web_search_results)
const current_date_time = new Date().toLocaleString() const current_date_time = new Date().toLocaleString()
@@ -146,17 +140,15 @@ export const getSystemPromptForWeb = async (
} }
}), }),
iodSources: iodSearchResults, iodSources: iodSearchResults,
iodSearchResults: _iodSearchResults, iodData: _iodSearchResults,
iodTokenCount: _iodSearchResults.reduce((acc, cur) => (acc + cur.content.length), 0) iodDataTokenCount: _iodSearchResults.reduce((acc, cur) => (acc + cur.content.length), 0)
} }
} catch (e) { } catch (e) {
console.error(e) console.error(e)
return { return {
prompt: "", prompt: "",
webSources: [], webSources: [],
iodSources: [], iodSources: []
iodSearchResults: [],
iodTokenCount: 0,
} }
} }
} }

View File

@@ -5,9 +5,6 @@ module.exports = {
content: ["./src/**/*.tsx"], content: ["./src/**/*.tsx"],
theme: { theme: {
extend: { extend: {
width: {
'1/10': '10%',
},
backgroundImage: { backgroundImage: {
'bottom-mask-light': 'linear-gradient(0deg, transparent 0, #ffffff 160px)', 'bottom-mask-light': 'linear-gradient(0deg, transparent 0, #ffffff 160px)',
'bottom-mask-dark': 'linear-gradient(0deg, transparent 0, #171717 160px)', 'bottom-mask-dark': 'linear-gradient(0deg, transparent 0, #171717 160px)',

View File

@@ -54,7 +54,7 @@ export default defineConfig({
version: "1.5.0", version: "1.5.0",
name: name:
process.env.TARGET === "firefox" process.env.TARGET === "firefox"
? "IoD Bot - A Web UI for Local AI Models" ? "Page Assist - A Web UI for Local AI Models"
: "__MSG_extName__", : "__MSG_extName__",
description: "__MSG_extDescription__", description: "__MSG_extDescription__",
default_locale: "en", default_locale: "en",