Browse Source

fix: 更新

Gaokun Wang 2 months ago
parent
commit
beba9d8204
59 changed files with 2145 additions and 991 deletions
  1. 2 2
      README.md
  2. 8 0
      SPONSOR.md
  3. 55 0
      docker-compose/gpts-mj-file/docker-compose.yml
  4. 27 0
      docker-compose/gpts-mj-file/nginx/nginx.conf
  5. 10 0
      docker-compose/gpts-mj-file/readme.md
  6. 6 0
      docker-compose/gpts-mj-file/start.sh
  7. 5 0
      docker-compose/gpts-mj-file/start_h.sh
  8. BIN
      docs/alipay.jpg
  9. BIN
      docs/c1-2.8.0.png
  10. BIN
      docs/c1-2.9.0.png
  11. BIN
      docs/c1.png
  12. BIN
      docs/c2-2.8.0.png
  13. BIN
      docs/c2-2.9.0.png
  14. BIN
      docs/c2.png
  15. BIN
      docs/check_error.jpg
  16. BIN
      docs/docker.png
  17. BIN
      docs/gptbase.jpg
  18. BIN
      docs/gpts.jpg
  19. BIN
      docs/gpts1.jpg
  20. BIN
      docs/mj1.jpg
  21. BIN
      docs/mj2.jpg
  22. BIN
      docs/mj2a1.jpg
  23. BIN
      docs/mj2a2.jpg
  24. BIN
      docs/mj2a3.jpg
  25. BIN
      docs/mj3a2.jpg
  26. BIN
      docs/mj4a1.png
  27. BIN
      docs/mjs1.jpg
  28. BIN
      docs/mjs2.jpg
  29. BIN
      docs/mjs3.jpg
  30. BIN
      docs/tts-whisper.png
  31. BIN
      docs/tts.jpg
  32. BIN
      docs/wxpay.jpg
  33. 35 0
      nginx.conf
  34. 642 72
      package-lock.json
  35. 1 0
      package.json
  36. 254 0
      pnpm-lock.yaml
  37. 4 0
      pnpm-workspace.yaml
  38. 31 0
      public/gpts.json
  39. 2 0
      src/api/mjapi.ts
  40. 4 1
      src/api/model.ts
  41. 6 2
      src/api/openapi.ts
  42. BIN
      src/assets/01.png
  43. 5 4
      src/components/common/PromptStore/index.vue
  44. 3 1
      src/router/index.ts
  45. 1 1
      src/store/modules/app/helper.ts
  46. 1 1
      src/utils/request/req.ts
  47. 31 11
      src/views/chat/components/Message/Text.vue
  48. 19 19
      src/views/chat/components/Message/index.vue
  49. 4 1
      src/views/chat/components/Message/style.less
  50. 648 650
      src/views/chat/index.vue
  51. 1 1
      src/views/fanyi/components/textComponent.vue
  52. 16 5
      src/views/knowledge/index.vue
  53. 2 2
      src/views/login/index.vue
  54. 11 5
      src/views/mj/aiGpt.vue
  55. 47 11
      src/views/mj/aiGptInput.vue
  56. 197 197
      src/views/mj/aiModel.vue
  57. 62 0
      src/views/wxbot/layout.vue
  58. 1 1
      vercel.json
  59. 4 4
      vite.config.ts

+ 2 - 2
README.md

@@ -1,2 +1,2 @@
-# ruoyi-web-pro
-基于ruoyi-plus实现AI聊天和绘画功能
+# ruoyi-web
+ruoyi-ai 用户端页面

+ 8 - 0
SPONSOR.md

@@ -0,0 +1,8 @@
+# Sponsor My Open Source Works
+
+如果我的开源项目对你有帮助,请考虑通过以下任意一种方式赞助:
+
+## 微信赞助
+![微信](./docs/wxpay.jpg)
+## 支付宝赞助
+![支付宝](./docs/alipay.jpg)

+ 55 - 0
docker-compose/gpts-mj-file/docker-compose.yml

@@ -0,0 +1,55 @@
+version: '3'
+
+services:
+  gptweb:
+    container_name: chatgpt-web-midjourney-proxy
+    image: ydlhero/chatgpt-web-midjourney-proxy # 总是使用latest,更新时重新pull该tag镜像即可
+    ports:
+      - 6050:3002
+    environment:
+      TZ: Asia/Shanghai # 指定时区
+      # 必选
+      OPENAI_API_KEY: sk-xxxx
+      # API接口地址,可选,设置 OPENAI_API_KEY 时可用
+      OPENAI_API_BASE_URL:
+      # 访问权限密钥,可选 注意修改
+      AUTH_SECRET_KEY: mygod
+      # midjourney 服务器地址,可选 可用下面的 http://midjourney-proxy:8080
+      MJ_SERVER: http://midjourney-proxy:8080
+      # midjourney API密钥,可选
+      MJ_API_SECRET: mygod
+      #API_UPLOADER 是否可以上传 1 可以其他都不可以,可选
+      API_UPLOADER: 1
+      #FILE_SERVER 文件服务器,可选 可以用下面的 http://fileserver:3012
+      FILE_SERVER: http://fileserver:3012
+      #爆破:验证次数 注意: vercel 不支持 nginx 请设置  proxy_set_header   X-Forwarded-For  $remote_addr;
+      AUTH_SECRET_ERROR_COUNT: 3
+      #爆破:验证停留时间 单位分钟 注意: vercel 不支持
+      AUTH_SECRET_ERROR_TIME: 10
+
+  # midjourney服务 可选
+  midjourney-proxy:
+    image: novicezk/midjourney-proxy:2.5.5
+    restart: always
+    # ports:
+    #   - 6013:8080 #映射端口
+    environment:
+      TZ: Asia/Shanghai # 指定时区
+      mj.discord.guild-id: xxx # xxx 如何获取
+      mj.discord.channel-id: xxx
+      mj.discord.user-token: xxx
+      mj.api-secret: mygod # MJ_API_SECRET
+  
+  #文件服务 可选
+  fileserver:
+    image: ydlhero/file-server:latest
+    restart: always
+    environment:
+      TZ: Asia/Shanghai # 指定时区
+      #对外显示的域名
+      SERVER_NAME: http://myip:3102
+    ports:
+      - "3102:3102"
+    volumes:
+      - /data/uploads:/app/uploads
+

+ 27 - 0
docker-compose/gpts-mj-file/nginx/nginx.conf

@@ -0,0 +1,27 @@
+server {
+	listen 80;
+	server_name  localhost;
+	charset utf-8;
+	error_page   500 502 503 504  /50x.html;
+	
+	# 防止爬虫抓取
+	if ($http_user_agent ~* "360Spider|JikeSpider|Spider|spider|bot|Bot|2345Explorer|curl|wget|webZIP|qihoobot|Baiduspider|Googlebot|Googlebot-Mobile|Googlebot-Image|Mediapartners-Google|Adsbot-Google|Feedfetcher-Google|Yahoo! Slurp|Yahoo! Slurp China|YoudaoBot|Sosospider|Sogou spider|Sogou web spider|MSNBot|ia_archiver|Tomato Bot|NSPlayer|bingbot")
+	{
+		return 403;
+	}
+	
+	location / {
+			root /usr/share/nginx/html;
+   		try_files $uri /index.html;
+	}
+
+	location /api {
+			proxy_set_header   X-Real-IP $remote_addr; #转发用户IP
+			proxy_pass http://app:3002;
+	}
+
+	proxy_set_header Host $host;
+	proxy_set_header X-Real-IP $remote_addr;
+	proxy_set_header REMOTE-HOST $remote_addr;
+	proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
+}

+ 10 - 0
docker-compose/gpts-mj-file/readme.md

@@ -0,0 +1,10 @@
+### docker-compose 部署教程 
+
+```shell
+    git clone https://github.com/Dooy/chatgpt-web-midjourney-proxy.git
+    cd chatgpt-web-midjourney-proxy/docker-compose/gpts-mj-file
+    #修改 docker-compose.yml 文件配置问文件
+    
+    #如果执行有问题 请执行 start_h.sh
+    start.sh 
+  ``` 

+ 6 - 0
docker-compose/gpts-mj-file/start.sh

@@ -0,0 +1,6 @@
+#!/bin/bash
+set -e
+# docker compose pull
+# docker compose up -d --remove-orphans
+docker-compose pull
+docker-compose up -d --remove-orphans

+ 5 - 0
docker-compose/gpts-mj-file/start_h.sh

@@ -0,0 +1,5 @@
+#!/bin/bash
+set -e
+docker compose pull
+docker compose up -d --remove-orphans
+ 

BIN
docs/alipay.jpg


BIN
docs/c1-2.8.0.png


BIN
docs/c1-2.9.0.png


BIN
docs/c1.png


BIN
docs/c2-2.8.0.png


BIN
docs/c2-2.9.0.png


BIN
docs/c2.png


BIN
docs/check_error.jpg


BIN
docs/docker.png


BIN
docs/gptbase.jpg


BIN
docs/gpts.jpg


BIN
docs/gpts1.jpg


BIN
docs/mj1.jpg


BIN
docs/mj2.jpg


BIN
docs/mj2a1.jpg


BIN
docs/mj2a2.jpg


BIN
docs/mj2a3.jpg


BIN
docs/mj3a2.jpg


BIN
docs/mj4a1.png


BIN
docs/mjs1.jpg


BIN
docs/mjs2.jpg


BIN
docs/mjs3.jpg


BIN
docs/tts-whisper.png


BIN
docs/tts.jpg


BIN
docs/wxpay.jpg


+ 35 - 0
nginx.conf

@@ -0,0 +1,35 @@
+worker_processes 1;
+
+events {
+    worker_connections 1024;
+}
+
+http {
+    include       mime.types;
+    default_type  application/octet-stream;
+
+    sendfile        on;
+    keepalive_timeout  65;
+
+    server {
+        listen       8081;
+        server_name  localhost;
+
+        location / {
+            root   /usr/share/nginx/html/web;
+            index  index.html index.htm;
+            try_files $uri $uri/ /index.html;
+        }
+
+        location /api/{
+            proxy_pass http://ruoyi-server:6039/; 
+            # 避免出现反代https域名出现502错误
+            proxy_ssl_server_name on;
+        }
+
+        error_page   500 502 503 504  /50x.html;
+        location = /50x.html {
+            root   /usr/share/nginx/html;
+        }
+    }
+}

+ 642 - 72
package-lock.json

@@ -8,6 +8,7 @@
       "name": "ruoyi-web",
       "version": "2.0.0",
       "dependencies": {
+        "@tinyflow-ai/vue": "^0.0.9",
         "@traptitech/markdown-it-katex": "^3.6.0",
         "@vicons/ionicons5": "^0.13.0",
         "@vue-office/pdf": "^2.0.10",
@@ -317,13 +318,13 @@
       }
     },
     "node_modules/@ampproject/remapping": {
-      "version": "2.2.1",
-      "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.1.tgz",
-      "integrity": "sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg==",
-      "dev": true,
+      "version": "2.3.0",
+      "resolved": "https://registry.npmmirror.com/@ampproject/remapping/-/remapping-2.3.0.tgz",
+      "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==",
+      "license": "Apache-2.0",
       "dependencies": {
-        "@jridgewell/gen-mapping": "^0.3.0",
-        "@jridgewell/trace-mapping": "^0.3.9"
+        "@jridgewell/gen-mapping": "^0.3.5",
+        "@jridgewell/trace-mapping": "^0.3.24"
       },
       "engines": {
         "node": ">=6.0.0"
@@ -3123,14 +3124,14 @@
       }
     },
     "node_modules/@jridgewell/gen-mapping": {
-      "version": "0.3.3",
-      "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz",
-      "integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==",
-      "dev": true,
+      "version": "0.3.8",
+      "resolved": "https://registry.npmmirror.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz",
+      "integrity": "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==",
+      "license": "MIT",
       "dependencies": {
-        "@jridgewell/set-array": "^1.0.1",
+        "@jridgewell/set-array": "^1.2.1",
         "@jridgewell/sourcemap-codec": "^1.4.10",
-        "@jridgewell/trace-mapping": "^0.3.9"
+        "@jridgewell/trace-mapping": "^0.3.24"
       },
       "engines": {
         "node": ">=6.0.0"
@@ -3140,16 +3141,15 @@
       "version": "3.1.0",
       "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz",
       "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==",
-      "dev": true,
       "engines": {
         "node": ">=6.0.0"
       }
     },
     "node_modules/@jridgewell/set-array": {
-      "version": "1.1.2",
-      "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz",
-      "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==",
-      "dev": true,
+      "version": "1.2.1",
+      "resolved": "https://registry.npmmirror.com/@jridgewell/set-array/-/set-array-1.2.1.tgz",
+      "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==",
+      "license": "MIT",
       "engines": {
         "node": ">=6.0.0"
       }
@@ -3171,21 +3171,15 @@
       "license": "MIT"
     },
     "node_modules/@jridgewell/trace-mapping": {
-      "version": "0.3.18",
-      "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.18.tgz",
-      "integrity": "sha512-w+niJYzMHdd7USdiH2U6869nqhD2nbfZXND5Yp93qIbEmnDNk7PD48o+YchRVpzMU7M6jVCbenTR7PA1FLQ9pA==",
-      "dev": true,
+      "version": "0.3.25",
+      "resolved": "https://registry.npmmirror.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz",
+      "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==",
+      "license": "MIT",
       "dependencies": {
-        "@jridgewell/resolve-uri": "3.1.0",
-        "@jridgewell/sourcemap-codec": "1.4.14"
+        "@jridgewell/resolve-uri": "^3.1.0",
+        "@jridgewell/sourcemap-codec": "^1.4.14"
       }
     },
-    "node_modules/@jridgewell/trace-mapping/node_modules/@jridgewell/sourcemap-codec": {
-      "version": "1.4.14",
-      "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz",
-      "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==",
-      "dev": true
-    },
     "node_modules/@juggle/resize-observer": {
       "version": "3.4.0",
       "resolved": "https://registry.npmmirror.com/@juggle/resize-observer/-/resize-observer-3.4.0.tgz",
@@ -3660,6 +3654,47 @@
         "sourcemap-codec": "^1.4.8"
       }
     },
+    "node_modules/@svelte-put/shortcut": {
+      "version": "3.1.1",
+      "resolved": "https://registry.npmmirror.com/@svelte-put/shortcut/-/shortcut-3.1.1.tgz",
+      "integrity": "sha512-2L5EYTZXiaKvbEelVkg5znxqvfZGZai3m97+cAiUBhLZwXnGtviTDpHxOoZBsqz41szlfRMcamW/8o0+fbW3ZQ==",
+      "license": "MIT",
+      "peerDependencies": {
+        "svelte": "^3.55.0 || ^4.0.0 || ^5.0.0"
+      }
+    },
+    "node_modules/@sveltejs/acorn-typescript": {
+      "version": "1.0.5",
+      "resolved": "https://registry.npmmirror.com/@sveltejs/acorn-typescript/-/acorn-typescript-1.0.5.tgz",
+      "integrity": "sha512-IwQk4yfwLdibDlrXVE04jTZYlLnwsTT2PIOQQGNLWfjavGifnk1JD1LcZjZaBTRcxZu2FfPfNLOE04DSu9lqtQ==",
+      "license": "MIT",
+      "peer": true,
+      "peerDependencies": {
+        "acorn": "^8.9.0"
+      }
+    },
+    "node_modules/@tinyflow-ai/ui": {
+      "version": "0.0.9",
+      "resolved": "https://registry.npmmirror.com/@tinyflow-ai/ui/-/ui-0.0.9.tgz",
+      "integrity": "sha512-dEm3/LGsqA8YOiQYyGu9XdzVvskmNsjrHhRGsT4CEA/jCzAN6ef82qH5AGrSUWljTvBghZOT3OLzjwycVYKaYQ==",
+      "license": "LGPL-3.0-or-later",
+      "dependencies": {
+        "@floating-ui/dom": "^1.6.13",
+        "@xyflow/svelte": "^0.1.31"
+      }
+    },
+    "node_modules/@tinyflow-ai/vue": {
+      "version": "0.0.9",
+      "resolved": "https://registry.npmmirror.com/@tinyflow-ai/vue/-/vue-0.0.9.tgz",
+      "integrity": "sha512-+/N8zpKUV85b5yLPprn0Ux7qMgzGWChHrHqR23+mMa6l9xznpL2B+tUP6UyXjF47snf7h4oypJSkyeMDl7fVFg==",
+      "license": "LGPL-3.0-or-later",
+      "dependencies": {
+        "@tinyflow-ai/ui": "0.0.9"
+      },
+      "peerDependencies": {
+        "vue": "^3.5.13"
+      }
+    },
     "node_modules/@traptitech/markdown-it-katex": {
       "version": "3.6.0",
       "resolved": "https://registry.npmjs.org/@traptitech/markdown-it-katex/-/markdown-it-katex-3.6.0.tgz",
@@ -3707,11 +3742,59 @@
       "integrity": "sha512-BG7fQKZ689HIoc5h+6D2Dgq1fABRa0RbBWKBd9SP/MVRVXROflpm5fhwyATX5duFmbStzyzyycPB8qUYKDH3NA==",
       "dev": true
     },
+    "node_modules/@types/d3-color": {
+      "version": "3.1.3",
+      "resolved": "https://registry.npmmirror.com/@types/d3-color/-/d3-color-3.1.3.tgz",
+      "integrity": "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==",
+      "license": "MIT"
+    },
+    "node_modules/@types/d3-drag": {
+      "version": "3.0.7",
+      "resolved": "https://registry.npmmirror.com/@types/d3-drag/-/d3-drag-3.0.7.tgz",
+      "integrity": "sha512-HE3jVKlzU9AaMazNufooRJ5ZpWmLIoc90A37WU2JMmeq28w1FQqCZswHZ3xR+SuxYftzHq6WU6KJHvqxKzTxxQ==",
+      "license": "MIT",
+      "dependencies": {
+        "@types/d3-selection": "*"
+      }
+    },
+    "node_modules/@types/d3-interpolate": {
+      "version": "3.0.4",
+      "resolved": "https://registry.npmmirror.com/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz",
+      "integrity": "sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==",
+      "license": "MIT",
+      "dependencies": {
+        "@types/d3-color": "*"
+      }
+    },
+    "node_modules/@types/d3-selection": {
+      "version": "3.0.11",
+      "resolved": "https://registry.npmmirror.com/@types/d3-selection/-/d3-selection-3.0.11.tgz",
+      "integrity": "sha512-bhAXu23DJWsrI45xafYpkQ4NtcKMwWnAC/vKrd2l+nxMFuvOT3XMYTIj2opv8vq8AO5Yh7Qac/nSeP/3zjTK0w==",
+      "license": "MIT"
+    },
+    "node_modules/@types/d3-transition": {
+      "version": "3.0.9",
+      "resolved": "https://registry.npmmirror.com/@types/d3-transition/-/d3-transition-3.0.9.tgz",
+      "integrity": "sha512-uZS5shfxzO3rGlu0cC3bjmMFKsXv+SmZZcgp0KD22ts4uGXp5EVYGzu/0YdwZeKmddhcAccYtREJKkPfXkZuCg==",
+      "license": "MIT",
+      "dependencies": {
+        "@types/d3-selection": "*"
+      }
+    },
+    "node_modules/@types/d3-zoom": {
+      "version": "3.0.8",
+      "resolved": "https://registry.npmmirror.com/@types/d3-zoom/-/d3-zoom-3.0.8.tgz",
+      "integrity": "sha512-iqMC4/YlFCSlO8+2Ii1GGGliCAY4XdeG748w5vQUbevlbDu0zSjH/+jojorQVBK/se0j6DUFNPBGSqD3YWYnDw==",
+      "license": "MIT",
+      "dependencies": {
+        "@types/d3-interpolate": "*",
+        "@types/d3-selection": "*"
+      }
+    },
     "node_modules/@types/estree": {
       "version": "1.0.6",
       "resolved": "https://registry.npmmirror.com/@types/estree/-/estree-1.0.6.tgz",
       "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==",
-      "dev": true,
       "license": "MIT"
     },
     "node_modules/@types/hast": {
@@ -4478,11 +4561,40 @@
         "url": "https://github.com/sponsors/antfu"
       }
     },
+    "node_modules/@xyflow/svelte": {
+      "version": "0.1.32",
+      "resolved": "https://registry.npmmirror.com/@xyflow/svelte/-/svelte-0.1.32.tgz",
+      "integrity": "sha512-IS4aXbLC4SYdPuolMBzD2cMpyYOnK+S6i8ot+gQXdPpZJhrRpDiTEr8gFJgeLK2dHnW0n4p8uOALhtePyOPULw==",
+      "license": "MIT",
+      "dependencies": {
+        "@svelte-put/shortcut": "3.1.1",
+        "@xyflow/system": "0.0.53",
+        "classcat": "^5.0.4"
+      },
+      "peerDependencies": {
+        "svelte": "^3.0.0 || ^4.0.0 || ^5.0.0"
+      }
+    },
+    "node_modules/@xyflow/system": {
+      "version": "0.0.53",
+      "resolved": "https://registry.npmmirror.com/@xyflow/system/-/system-0.0.53.tgz",
+      "integrity": "sha512-QTWieiTtvNYyQAz1fxpzgtUGXNpnhfh6vvZa7dFWpWS2KOz6bEHODo/DTK3s07lDu0Bq0Db5lx/5M5mNjb9VDQ==",
+      "license": "MIT",
+      "dependencies": {
+        "@types/d3-drag": "^3.0.7",
+        "@types/d3-selection": "^3.0.10",
+        "@types/d3-transition": "^3.0.8",
+        "@types/d3-zoom": "^3.0.8",
+        "d3-drag": "^3.0.0",
+        "d3-selection": "^3.0.0",
+        "d3-zoom": "^3.0.0"
+      }
+    },
     "node_modules/acorn": {
-      "version": "8.11.3",
-      "resolved": "https://registry.npmmirror.com/acorn/-/acorn-8.11.3.tgz",
-      "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==",
-      "dev": true,
+      "version": "8.14.1",
+      "resolved": "https://registry.npmmirror.com/acorn/-/acorn-8.14.1.tgz",
+      "integrity": "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==",
+      "license": "MIT",
       "bin": {
         "acorn": "bin/acorn"
       },
@@ -4648,6 +4760,16 @@
       "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
       "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="
     },
+    "node_modules/aria-query": {
+      "version": "5.3.2",
+      "resolved": "https://registry.npmmirror.com/aria-query/-/aria-query-5.3.2.tgz",
+      "integrity": "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==",
+      "license": "Apache-2.0",
+      "peer": true,
+      "engines": {
+        "node": ">= 0.4"
+      }
+    },
     "node_modules/arr-diff": {
       "version": "4.0.0",
       "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz",
@@ -4923,6 +5045,16 @@
         "proxy-from-env": "^1.1.0"
       }
     },
+    "node_modules/axobject-query": {
+      "version": "4.1.0",
+      "resolved": "https://registry.npmmirror.com/axobject-query/-/axobject-query-4.1.0.tgz",
+      "integrity": "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==",
+      "license": "Apache-2.0",
+      "peer": true,
+      "engines": {
+        "node": ">= 0.4"
+      }
+    },
     "node_modules/babel-plugin-polyfill-corejs2": {
       "version": "0.3.3",
       "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.3.3.tgz",
@@ -5412,6 +5544,12 @@
         "node": ">=0.10.0"
       }
     },
+    "node_modules/classcat": {
+      "version": "5.0.5",
+      "resolved": "https://registry.npmmirror.com/classcat/-/classcat-5.0.5.tgz",
+      "integrity": "sha512-JhZUT7JFcQy/EzW605k/ktHtncoo9vnyW/2GspNYwFlN1C/WmjuV/xtS04e9SOkL2sTdw0VAZ2UGCcQ9lR6p6w==",
+      "license": "MIT"
+    },
     "node_modules/clean-regexp": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/clean-regexp/-/clean-regexp-1.0.0.tgz",
@@ -5522,6 +5660,16 @@
         "node": ">=0.8"
       }
     },
+    "node_modules/clsx": {
+      "version": "2.1.1",
+      "resolved": "https://registry.npmmirror.com/clsx/-/clsx-2.1.1.tgz",
+      "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==",
+      "license": "MIT",
+      "peer": true,
+      "engines": {
+        "node": ">=6"
+      }
+    },
     "node_modules/collection-visit": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz",
@@ -5900,6 +6048,111 @@
       "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.0.11.tgz",
       "integrity": "sha512-sa6P2wJ+CAbgyy4KFssIb/JNMLxFvKF1pCYCSXS8ZMuqZnMsrxqI2E5sPyoTpxoPU/gVZMzr2zjOfg8GIZOMsw=="
     },
+    "node_modules/d3-color": {
+      "version": "3.1.0",
+      "resolved": "https://registry.npmmirror.com/d3-color/-/d3-color-3.1.0.tgz",
+      "integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==",
+      "license": "ISC",
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/d3-dispatch": {
+      "version": "3.0.1",
+      "resolved": "https://registry.npmmirror.com/d3-dispatch/-/d3-dispatch-3.0.1.tgz",
+      "integrity": "sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==",
+      "license": "ISC",
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/d3-drag": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmmirror.com/d3-drag/-/d3-drag-3.0.0.tgz",
+      "integrity": "sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==",
+      "license": "ISC",
+      "dependencies": {
+        "d3-dispatch": "1 - 3",
+        "d3-selection": "3"
+      },
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/d3-ease": {
+      "version": "3.0.1",
+      "resolved": "https://registry.npmmirror.com/d3-ease/-/d3-ease-3.0.1.tgz",
+      "integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==",
+      "license": "BSD-3-Clause",
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/d3-interpolate": {
+      "version": "3.0.1",
+      "resolved": "https://registry.npmmirror.com/d3-interpolate/-/d3-interpolate-3.0.1.tgz",
+      "integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==",
+      "license": "ISC",
+      "dependencies": {
+        "d3-color": "1 - 3"
+      },
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/d3-selection": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmmirror.com/d3-selection/-/d3-selection-3.0.0.tgz",
+      "integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==",
+      "license": "ISC",
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/d3-timer": {
+      "version": "3.0.1",
+      "resolved": "https://registry.npmmirror.com/d3-timer/-/d3-timer-3.0.1.tgz",
+      "integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==",
+      "license": "ISC",
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/d3-transition": {
+      "version": "3.0.1",
+      "resolved": "https://registry.npmmirror.com/d3-transition/-/d3-transition-3.0.1.tgz",
+      "integrity": "sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==",
+      "license": "ISC",
+      "dependencies": {
+        "d3-color": "1 - 3",
+        "d3-dispatch": "1 - 3",
+        "d3-ease": "1 - 3",
+        "d3-interpolate": "1 - 3",
+        "d3-timer": "1 - 3"
+      },
+      "engines": {
+        "node": ">=12"
+      },
+      "peerDependencies": {
+        "d3-selection": "2 - 3"
+      }
+    },
+    "node_modules/d3-zoom": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmmirror.com/d3-zoom/-/d3-zoom-3.0.0.tgz",
+      "integrity": "sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==",
+      "license": "ISC",
+      "dependencies": {
+        "d3-dispatch": "1 - 3",
+        "d3-drag": "2 - 3",
+        "d3-interpolate": "1 - 3",
+        "d3-selection": "2 - 3",
+        "d3-transition": "2 - 3"
+      },
+      "engines": {
+        "node": ">=12"
+      }
+    },
     "node_modules/dargs": {
       "version": "7.0.0",
       "resolved": "https://registry.npmjs.org/dargs/-/dargs-7.0.0.tgz",
@@ -7130,6 +7383,13 @@
       "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
       "dev": true
     },
+    "node_modules/esm-env": {
+      "version": "1.2.2",
+      "resolved": "https://registry.npmmirror.com/esm-env/-/esm-env-1.2.2.tgz",
+      "integrity": "sha512-Epxrv+Nr/CaL4ZcFGPJIYLWFom+YeV1DqMLHJoEd9SYRxNbaFruBwfEX/kkHUJf55j2+TUbmDcmuilbP1TmXHA==",
+      "license": "MIT",
+      "peer": true
+    },
     "node_modules/espree": {
       "version": "9.5.1",
       "resolved": "https://registry.npmjs.org/espree/-/espree-9.5.1.tgz",
@@ -7168,6 +7428,16 @@
         "node": ">=4.0"
       }
     },
+    "node_modules/esrap": {
+      "version": "1.4.5",
+      "resolved": "https://registry.npmmirror.com/esrap/-/esrap-1.4.5.tgz",
+      "integrity": "sha512-CjNMjkBWWZeHn+VX+gS8YvFwJ5+NDhg8aWZBSFJPR8qQduDNjbJodA2WcwCm7uQa5Rjqj+nZvVmceg1RbHFB9g==",
+      "license": "MIT",
+      "peer": true,
+      "dependencies": {
+        "@jridgewell/sourcemap-codec": "^1.4.15"
+      }
+    },
     "node_modules/esrecurse": {
       "version": "4.3.0",
       "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz",
@@ -8793,6 +9063,16 @@
         "node": ">=0.10.0"
       }
     },
+    "node_modules/is-reference": {
+      "version": "3.0.3",
+      "resolved": "https://registry.npmmirror.com/is-reference/-/is-reference-3.0.3.tgz",
+      "integrity": "sha512-ixkJoqQvAP88E6wLydLGGqCJsrFUnqoH6HnaczB8XmDH1oaWU+xxdptvikTgaEhtZ53Ky6YXiBuUI2WXLMCwjw==",
+      "license": "MIT",
+      "peer": true,
+      "dependencies": {
+        "@types/estree": "^1.0.6"
+      }
+    },
     "node_modules/is-regex": {
       "version": "1.1.4",
       "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz",
@@ -9572,6 +9852,13 @@
         "lie": "3.1.1"
       }
     },
+    "node_modules/locate-character": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmmirror.com/locate-character/-/locate-character-3.0.0.tgz",
+      "integrity": "sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA==",
+      "license": "MIT",
+      "peer": true
+    },
     "node_modules/locate-path": {
       "version": "6.0.0",
       "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
@@ -13252,6 +13539,42 @@
         "url": "https://github.com/sponsors/ljharb"
       }
     },
+    "node_modules/svelte": {
+      "version": "5.25.3",
+      "resolved": "https://registry.npmmirror.com/svelte/-/svelte-5.25.3.tgz",
+      "integrity": "sha512-J9rcZ/xVJonAoESqVGHHZhrNdVbrCfkdB41BP6eiwHMoFShD9it3yZXApVYMHdGfCshBsZCKsajwJeBbS/M1zg==",
+      "license": "MIT",
+      "peer": true,
+      "dependencies": {
+        "@ampproject/remapping": "^2.3.0",
+        "@jridgewell/sourcemap-codec": "^1.5.0",
+        "@sveltejs/acorn-typescript": "^1.0.5",
+        "@types/estree": "^1.0.5",
+        "acorn": "^8.12.1",
+        "aria-query": "^5.3.1",
+        "axobject-query": "^4.1.0",
+        "clsx": "^2.1.1",
+        "esm-env": "^1.2.1",
+        "esrap": "^1.4.3",
+        "is-reference": "^3.0.3",
+        "locate-character": "^3.0.0",
+        "magic-string": "^0.30.11",
+        "zimmerframe": "^1.1.2"
+      },
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/svelte/node_modules/magic-string": {
+      "version": "0.30.17",
+      "resolved": "https://registry.npmmirror.com/magic-string/-/magic-string-0.30.17.tgz",
+      "integrity": "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==",
+      "license": "MIT",
+      "peer": true,
+      "dependencies": {
+        "@jridgewell/sourcemap-codec": "^1.5.0"
+      }
+    },
     "node_modules/svg-baker": {
       "version": "1.7.0",
       "resolved": "https://registry.npmjs.org/svg-baker/-/svg-baker-1.7.0.tgz",
@@ -16377,6 +16700,13 @@
         "url": "https://github.com/sponsors/sindresorhus"
       }
     },
+    "node_modules/zimmerframe": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmmirror.com/zimmerframe/-/zimmerframe-1.1.2.tgz",
+      "integrity": "sha512-rAbqEGa8ovJy4pyBxZM70hg4pE6gDgaQ0Sl9M3enG3I0d6H4XSAM3GeNGLKnsBpuijUow064sf7ww1nutC5/3w==",
+      "license": "MIT",
+      "peer": true
+    },
     "node_modules/zwitch": {
       "version": "2.0.4",
       "resolved": "https://registry.npmmirror.com/zwitch/-/zwitch-2.0.4.tgz",
@@ -16573,13 +16903,12 @@
       "dev": true
     },
     "@ampproject/remapping": {
-      "version": "2.2.1",
-      "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.1.tgz",
-      "integrity": "sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg==",
-      "dev": true,
+      "version": "2.3.0",
+      "resolved": "https://registry.npmmirror.com/@ampproject/remapping/-/remapping-2.3.0.tgz",
+      "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==",
       "requires": {
-        "@jridgewell/gen-mapping": "^0.3.0",
-        "@jridgewell/trace-mapping": "^0.3.9"
+        "@jridgewell/gen-mapping": "^0.3.5",
+        "@jridgewell/trace-mapping": "^0.3.24"
       }
     },
     "@antfu/eslint-config": {
@@ -18489,27 +18818,24 @@
       }
     },
     "@jridgewell/gen-mapping": {
-      "version": "0.3.3",
-      "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz",
-      "integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==",
-      "dev": true,
+      "version": "0.3.8",
+      "resolved": "https://registry.npmmirror.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz",
+      "integrity": "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==",
       "requires": {
-        "@jridgewell/set-array": "^1.0.1",
+        "@jridgewell/set-array": "^1.2.1",
         "@jridgewell/sourcemap-codec": "^1.4.10",
-        "@jridgewell/trace-mapping": "^0.3.9"
+        "@jridgewell/trace-mapping": "^0.3.24"
       }
     },
     "@jridgewell/resolve-uri": {
       "version": "3.1.0",
       "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz",
-      "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==",
-      "dev": true
+      "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w=="
     },
     "@jridgewell/set-array": {
-      "version": "1.1.2",
-      "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz",
-      "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==",
-      "dev": true
+      "version": "1.2.1",
+      "resolved": "https://registry.npmmirror.com/@jridgewell/set-array/-/set-array-1.2.1.tgz",
+      "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A=="
     },
     "@jridgewell/source-map": {
       "version": "0.3.3",
@@ -18527,21 +18853,12 @@
       "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ=="
     },
     "@jridgewell/trace-mapping": {
-      "version": "0.3.18",
-      "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.18.tgz",
-      "integrity": "sha512-w+niJYzMHdd7USdiH2U6869nqhD2nbfZXND5Yp93qIbEmnDNk7PD48o+YchRVpzMU7M6jVCbenTR7PA1FLQ9pA==",
-      "dev": true,
+      "version": "0.3.25",
+      "resolved": "https://registry.npmmirror.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz",
+      "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==",
       "requires": {
-        "@jridgewell/resolve-uri": "3.1.0",
-        "@jridgewell/sourcemap-codec": "1.4.14"
-      },
-      "dependencies": {
-        "@jridgewell/sourcemap-codec": {
-          "version": "1.4.14",
-          "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz",
-          "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==",
-          "dev": true
-        }
+        "@jridgewell/resolve-uri": "^3.1.0",
+        "@jridgewell/sourcemap-codec": "^1.4.14"
       }
     },
     "@juggle/resize-observer": {
@@ -18842,6 +19159,36 @@
         }
       }
     },
+    "@svelte-put/shortcut": {
+      "version": "3.1.1",
+      "resolved": "https://registry.npmmirror.com/@svelte-put/shortcut/-/shortcut-3.1.1.tgz",
+      "integrity": "sha512-2L5EYTZXiaKvbEelVkg5znxqvfZGZai3m97+cAiUBhLZwXnGtviTDpHxOoZBsqz41szlfRMcamW/8o0+fbW3ZQ==",
+      "requires": {}
+    },
+    "@sveltejs/acorn-typescript": {
+      "version": "1.0.5",
+      "resolved": "https://registry.npmmirror.com/@sveltejs/acorn-typescript/-/acorn-typescript-1.0.5.tgz",
+      "integrity": "sha512-IwQk4yfwLdibDlrXVE04jTZYlLnwsTT2PIOQQGNLWfjavGifnk1JD1LcZjZaBTRcxZu2FfPfNLOE04DSu9lqtQ==",
+      "peer": true,
+      "requires": {}
+    },
+    "@tinyflow-ai/ui": {
+      "version": "0.0.9",
+      "resolved": "https://registry.npmmirror.com/@tinyflow-ai/ui/-/ui-0.0.9.tgz",
+      "integrity": "sha512-dEm3/LGsqA8YOiQYyGu9XdzVvskmNsjrHhRGsT4CEA/jCzAN6ef82qH5AGrSUWljTvBghZOT3OLzjwycVYKaYQ==",
+      "requires": {
+        "@floating-ui/dom": "^1.6.13",
+        "@xyflow/svelte": "^0.1.31"
+      }
+    },
+    "@tinyflow-ai/vue": {
+      "version": "0.0.9",
+      "resolved": "https://registry.npmmirror.com/@tinyflow-ai/vue/-/vue-0.0.9.tgz",
+      "integrity": "sha512-+/N8zpKUV85b5yLPprn0Ux7qMgzGWChHrHqR23+mMa6l9xznpL2B+tUP6UyXjF47snf7h4oypJSkyeMDl7fVFg==",
+      "requires": {
+        "@tinyflow-ai/ui": "0.0.9"
+      }
+    },
     "@traptitech/markdown-it-katex": {
       "version": "3.6.0",
       "resolved": "https://registry.npmjs.org/@traptitech/markdown-it-katex/-/markdown-it-katex-3.6.0.tgz",
@@ -18886,11 +19233,53 @@
       "integrity": "sha512-BG7fQKZ689HIoc5h+6D2Dgq1fABRa0RbBWKBd9SP/MVRVXROflpm5fhwyATX5duFmbStzyzyycPB8qUYKDH3NA==",
       "dev": true
     },
+    "@types/d3-color": {
+      "version": "3.1.3",
+      "resolved": "https://registry.npmmirror.com/@types/d3-color/-/d3-color-3.1.3.tgz",
+      "integrity": "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A=="
+    },
+    "@types/d3-drag": {
+      "version": "3.0.7",
+      "resolved": "https://registry.npmmirror.com/@types/d3-drag/-/d3-drag-3.0.7.tgz",
+      "integrity": "sha512-HE3jVKlzU9AaMazNufooRJ5ZpWmLIoc90A37WU2JMmeq28w1FQqCZswHZ3xR+SuxYftzHq6WU6KJHvqxKzTxxQ==",
+      "requires": {
+        "@types/d3-selection": "*"
+      }
+    },
+    "@types/d3-interpolate": {
+      "version": "3.0.4",
+      "resolved": "https://registry.npmmirror.com/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz",
+      "integrity": "sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==",
+      "requires": {
+        "@types/d3-color": "*"
+      }
+    },
+    "@types/d3-selection": {
+      "version": "3.0.11",
+      "resolved": "https://registry.npmmirror.com/@types/d3-selection/-/d3-selection-3.0.11.tgz",
+      "integrity": "sha512-bhAXu23DJWsrI45xafYpkQ4NtcKMwWnAC/vKrd2l+nxMFuvOT3XMYTIj2opv8vq8AO5Yh7Qac/nSeP/3zjTK0w=="
+    },
+    "@types/d3-transition": {
+      "version": "3.0.9",
+      "resolved": "https://registry.npmmirror.com/@types/d3-transition/-/d3-transition-3.0.9.tgz",
+      "integrity": "sha512-uZS5shfxzO3rGlu0cC3bjmMFKsXv+SmZZcgp0KD22ts4uGXp5EVYGzu/0YdwZeKmddhcAccYtREJKkPfXkZuCg==",
+      "requires": {
+        "@types/d3-selection": "*"
+      }
+    },
+    "@types/d3-zoom": {
+      "version": "3.0.8",
+      "resolved": "https://registry.npmmirror.com/@types/d3-zoom/-/d3-zoom-3.0.8.tgz",
+      "integrity": "sha512-iqMC4/YlFCSlO8+2Ii1GGGliCAY4XdeG748w5vQUbevlbDu0zSjH/+jojorQVBK/se0j6DUFNPBGSqD3YWYnDw==",
+      "requires": {
+        "@types/d3-interpolate": "*",
+        "@types/d3-selection": "*"
+      }
+    },
     "@types/estree": {
       "version": "1.0.6",
       "resolved": "https://registry.npmmirror.com/@types/estree/-/estree-1.0.6.tgz",
-      "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==",
-      "dev": true
+      "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw=="
     },
     "@types/hast": {
       "version": "3.0.4",
@@ -19448,11 +19837,34 @@
         "vue-demi": "*"
       }
     },
+    "@xyflow/svelte": {
+      "version": "0.1.32",
+      "resolved": "https://registry.npmmirror.com/@xyflow/svelte/-/svelte-0.1.32.tgz",
+      "integrity": "sha512-IS4aXbLC4SYdPuolMBzD2cMpyYOnK+S6i8ot+gQXdPpZJhrRpDiTEr8gFJgeLK2dHnW0n4p8uOALhtePyOPULw==",
+      "requires": {
+        "@svelte-put/shortcut": "3.1.1",
+        "@xyflow/system": "0.0.53",
+        "classcat": "^5.0.4"
+      }
+    },
+    "@xyflow/system": {
+      "version": "0.0.53",
+      "resolved": "https://registry.npmmirror.com/@xyflow/system/-/system-0.0.53.tgz",
+      "integrity": "sha512-QTWieiTtvNYyQAz1fxpzgtUGXNpnhfh6vvZa7dFWpWS2KOz6bEHODo/DTK3s07lDu0Bq0Db5lx/5M5mNjb9VDQ==",
+      "requires": {
+        "@types/d3-drag": "^3.0.7",
+        "@types/d3-selection": "^3.0.10",
+        "@types/d3-transition": "^3.0.8",
+        "@types/d3-zoom": "^3.0.8",
+        "d3-drag": "^3.0.0",
+        "d3-selection": "^3.0.0",
+        "d3-zoom": "^3.0.0"
+      }
+    },
     "acorn": {
-      "version": "8.11.3",
-      "resolved": "https://registry.npmmirror.com/acorn/-/acorn-8.11.3.tgz",
-      "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==",
-      "dev": true
+      "version": "8.14.1",
+      "resolved": "https://registry.npmmirror.com/acorn/-/acorn-8.14.1.tgz",
+      "integrity": "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg=="
     },
     "acorn-jsx": {
       "version": "5.3.2",
@@ -19574,6 +19986,12 @@
       "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
       "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="
     },
+    "aria-query": {
+      "version": "5.3.2",
+      "resolved": "https://registry.npmmirror.com/aria-query/-/aria-query-5.3.2.tgz",
+      "integrity": "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==",
+      "peer": true
+    },
     "arr-diff": {
       "version": "4.0.0",
       "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz",
@@ -19758,6 +20176,12 @@
         "proxy-from-env": "^1.1.0"
       }
     },
+    "axobject-query": {
+      "version": "4.1.0",
+      "resolved": "https://registry.npmmirror.com/axobject-query/-/axobject-query-4.1.0.tgz",
+      "integrity": "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==",
+      "peer": true
+    },
     "babel-plugin-polyfill-corejs2": {
       "version": "0.3.3",
       "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.3.3.tgz",
@@ -20097,6 +20521,11 @@
         }
       }
     },
+    "classcat": {
+      "version": "5.0.5",
+      "resolved": "https://registry.npmmirror.com/classcat/-/classcat-5.0.5.tgz",
+      "integrity": "sha512-JhZUT7JFcQy/EzW605k/ktHtncoo9vnyW/2GspNYwFlN1C/WmjuV/xtS04e9SOkL2sTdw0VAZ2UGCcQ9lR6p6w=="
+    },
     "clean-regexp": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/clean-regexp/-/clean-regexp-1.0.0.tgz",
@@ -20181,6 +20610,12 @@
       "integrity": "sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w==",
       "dev": true
     },
+    "clsx": {
+      "version": "2.1.1",
+      "resolved": "https://registry.npmmirror.com/clsx/-/clsx-2.1.1.tgz",
+      "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==",
+      "peer": true
+    },
     "collection-visit": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz",
@@ -20466,6 +20901,72 @@
       "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.0.11.tgz",
       "integrity": "sha512-sa6P2wJ+CAbgyy4KFssIb/JNMLxFvKF1pCYCSXS8ZMuqZnMsrxqI2E5sPyoTpxoPU/gVZMzr2zjOfg8GIZOMsw=="
     },
+    "d3-color": {
+      "version": "3.1.0",
+      "resolved": "https://registry.npmmirror.com/d3-color/-/d3-color-3.1.0.tgz",
+      "integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA=="
+    },
+    "d3-dispatch": {
+      "version": "3.0.1",
+      "resolved": "https://registry.npmmirror.com/d3-dispatch/-/d3-dispatch-3.0.1.tgz",
+      "integrity": "sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg=="
+    },
+    "d3-drag": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmmirror.com/d3-drag/-/d3-drag-3.0.0.tgz",
+      "integrity": "sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==",
+      "requires": {
+        "d3-dispatch": "1 - 3",
+        "d3-selection": "3"
+      }
+    },
+    "d3-ease": {
+      "version": "3.0.1",
+      "resolved": "https://registry.npmmirror.com/d3-ease/-/d3-ease-3.0.1.tgz",
+      "integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w=="
+    },
+    "d3-interpolate": {
+      "version": "3.0.1",
+      "resolved": "https://registry.npmmirror.com/d3-interpolate/-/d3-interpolate-3.0.1.tgz",
+      "integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==",
+      "requires": {
+        "d3-color": "1 - 3"
+      }
+    },
+    "d3-selection": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmmirror.com/d3-selection/-/d3-selection-3.0.0.tgz",
+      "integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ=="
+    },
+    "d3-timer": {
+      "version": "3.0.1",
+      "resolved": "https://registry.npmmirror.com/d3-timer/-/d3-timer-3.0.1.tgz",
+      "integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA=="
+    },
+    "d3-transition": {
+      "version": "3.0.1",
+      "resolved": "https://registry.npmmirror.com/d3-transition/-/d3-transition-3.0.1.tgz",
+      "integrity": "sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==",
+      "requires": {
+        "d3-color": "1 - 3",
+        "d3-dispatch": "1 - 3",
+        "d3-ease": "1 - 3",
+        "d3-interpolate": "1 - 3",
+        "d3-timer": "1 - 3"
+      }
+    },
+    "d3-zoom": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmmirror.com/d3-zoom/-/d3-zoom-3.0.0.tgz",
+      "integrity": "sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==",
+      "requires": {
+        "d3-dispatch": "1 - 3",
+        "d3-drag": "2 - 3",
+        "d3-interpolate": "1 - 3",
+        "d3-selection": "2 - 3",
+        "d3-transition": "2 - 3"
+      }
+    },
     "dargs": {
       "version": "7.0.0",
       "resolved": "https://registry.npmjs.org/dargs/-/dargs-7.0.0.tgz",
@@ -21356,6 +21857,12 @@
       "integrity": "sha512-HPpKPUBQcAsZOsHAFwTtIKcYlCje62XB7SEAcxjtmW6TD1WVpkS6i6/hOVtTZIl4zGj/mBqpFVGvaDneik+VoQ==",
       "dev": true
     },
+    "esm-env": {
+      "version": "1.2.2",
+      "resolved": "https://registry.npmmirror.com/esm-env/-/esm-env-1.2.2.tgz",
+      "integrity": "sha512-Epxrv+Nr/CaL4ZcFGPJIYLWFom+YeV1DqMLHJoEd9SYRxNbaFruBwfEX/kkHUJf55j2+TUbmDcmuilbP1TmXHA==",
+      "peer": true
+    },
     "espree": {
       "version": "9.5.1",
       "resolved": "https://registry.npmjs.org/espree/-/espree-9.5.1.tgz",
@@ -21384,6 +21891,15 @@
         }
       }
     },
+    "esrap": {
+      "version": "1.4.5",
+      "resolved": "https://registry.npmmirror.com/esrap/-/esrap-1.4.5.tgz",
+      "integrity": "sha512-CjNMjkBWWZeHn+VX+gS8YvFwJ5+NDhg8aWZBSFJPR8qQduDNjbJodA2WcwCm7uQa5Rjqj+nZvVmceg1RbHFB9g==",
+      "peer": true,
+      "requires": {
+        "@jridgewell/sourcemap-codec": "^1.4.15"
+      }
+    },
     "esrecurse": {
       "version": "4.3.0",
       "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz",
@@ -22572,6 +23088,15 @@
         }
       }
     },
+    "is-reference": {
+      "version": "3.0.3",
+      "resolved": "https://registry.npmmirror.com/is-reference/-/is-reference-3.0.3.tgz",
+      "integrity": "sha512-ixkJoqQvAP88E6wLydLGGqCJsrFUnqoH6HnaczB8XmDH1oaWU+xxdptvikTgaEhtZ53Ky6YXiBuUI2WXLMCwjw==",
+      "peer": true,
+      "requires": {
+        "@types/estree": "^1.0.6"
+      }
+    },
     "is-regex": {
       "version": "1.1.4",
       "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz",
@@ -23124,6 +23649,12 @@
         "lie": "3.1.1"
       }
     },
+    "locate-character": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmmirror.com/locate-character/-/locate-character-3.0.0.tgz",
+      "integrity": "sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA==",
+      "peer": true
+    },
     "locate-path": {
       "version": "6.0.0",
       "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
@@ -25859,6 +26390,39 @@
       "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==",
       "dev": true
     },
+    "svelte": {
+      "version": "5.25.3",
+      "resolved": "https://registry.npmmirror.com/svelte/-/svelte-5.25.3.tgz",
+      "integrity": "sha512-J9rcZ/xVJonAoESqVGHHZhrNdVbrCfkdB41BP6eiwHMoFShD9it3yZXApVYMHdGfCshBsZCKsajwJeBbS/M1zg==",
+      "peer": true,
+      "requires": {
+        "@ampproject/remapping": "^2.3.0",
+        "@jridgewell/sourcemap-codec": "^1.5.0",
+        "@sveltejs/acorn-typescript": "^1.0.5",
+        "@types/estree": "^1.0.5",
+        "acorn": "^8.12.1",
+        "aria-query": "^5.3.1",
+        "axobject-query": "^4.1.0",
+        "clsx": "^2.1.1",
+        "esm-env": "^1.2.1",
+        "esrap": "^1.4.3",
+        "is-reference": "^3.0.3",
+        "locate-character": "^3.0.0",
+        "magic-string": "^0.30.11",
+        "zimmerframe": "^1.1.2"
+      },
+      "dependencies": {
+        "magic-string": {
+          "version": "0.30.17",
+          "resolved": "https://registry.npmmirror.com/magic-string/-/magic-string-0.30.17.tgz",
+          "integrity": "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==",
+          "peer": true,
+          "requires": {
+            "@jridgewell/sourcemap-codec": "^1.5.0"
+          }
+        }
+      }
+    },
     "svg-baker": {
       "version": "1.7.0",
       "resolved": "https://registry.npmjs.org/svg-baker/-/svg-baker-1.7.0.tgz",
@@ -28046,6 +28610,12 @@
       "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==",
       "dev": true
     },
+    "zimmerframe": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmmirror.com/zimmerframe/-/zimmerframe-1.1.2.tgz",
+      "integrity": "sha512-rAbqEGa8ovJy4pyBxZM70hg4pE6gDgaQ0Sl9M3enG3I0d6H4XSAM3GeNGLKnsBpuijUow064sf7ww1nutC5/3w==",
+      "peer": true
+    },
     "zwitch": {
       "version": "2.0.4",
       "resolved": "https://registry.npmmirror.com/zwitch/-/zwitch-2.0.4.tgz",

+ 1 - 0
package.json

@@ -32,6 +32,7 @@
     "docs:preview": "vitepress preview docs"
   },
   "dependencies": {
+    "@tinyflow-ai/vue": "^0.0.9",
     "@traptitech/markdown-it-katex": "^3.6.0",
     "@vicons/ionicons5": "^0.13.0",
     "@vue-office/pdf": "^2.0.10",

+ 254 - 0
pnpm-lock.yaml

@@ -8,6 +8,9 @@ importers:
 
   .:
     dependencies:
+      '@tinyflow-ai/vue':
+        specifier: ^0.0.9
+        version: 0.0.9(svelte@5.25.7)(vue@3.5.13(typescript@4.9.5))
       '@traptitech/markdown-it-katex':
         specifier: ^3.6.0
         version: 3.6.0
@@ -1442,9 +1445,27 @@ packages:
   '@surma/rollup-plugin-off-main-thread@2.2.3':
     resolution: {integrity: sha512-lR8q/9W7hZpMWweNiAKU7NQerBnzQQLvi8qnTDU/fxItPhtZVMbPV3lbCwjhIlNBe9Bbr5V+KHshvWmVSG9cxQ==}
 
+  '@svelte-put/shortcut@3.1.1':
+    resolution: {integrity: sha512-2L5EYTZXiaKvbEelVkg5znxqvfZGZai3m97+cAiUBhLZwXnGtviTDpHxOoZBsqz41szlfRMcamW/8o0+fbW3ZQ==}
+    peerDependencies:
+      svelte: ^3.55.0 || ^4.0.0 || ^5.0.0
+
+  '@sveltejs/acorn-typescript@1.0.5':
+    resolution: {integrity: sha512-IwQk4yfwLdibDlrXVE04jTZYlLnwsTT2PIOQQGNLWfjavGifnk1JD1LcZjZaBTRcxZu2FfPfNLOE04DSu9lqtQ==}
+    peerDependencies:
+      acorn: ^8.9.0
+
   '@sxzz/popperjs-es@2.11.7':
     resolution: {integrity: sha512-Ccy0NlLkzr0Ex2FKvh2X+OyERHXJ88XJ1MXtsI9y9fGexlaXaVTPzBCRBwIxFkORuOb+uBqeu+RqnpgYTEZRUQ==}
 
+  '@tinyflow-ai/ui@0.0.9':
+    resolution: {integrity: sha512-dEm3/LGsqA8YOiQYyGu9XdzVvskmNsjrHhRGsT4CEA/jCzAN6ef82qH5AGrSUWljTvBghZOT3OLzjwycVYKaYQ==}
+
+  '@tinyflow-ai/vue@0.0.9':
+    resolution: {integrity: sha512-+/N8zpKUV85b5yLPprn0Ux7qMgzGWChHrHqR23+mMa6l9xznpL2B+tUP6UyXjF47snf7h4oypJSkyeMDl7fVFg==}
+    peerDependencies:
+      vue: ^3.5.13
+
   '@traptitech/markdown-it-katex@3.6.0':
     resolution: {integrity: sha512-CnJzTWxsgLGXFdSrWRaGz7GZ1kUUi8g3E9HzJmeveX1YwVJavrKYqysktfHZQsujdnRqV5O7g8FPKEA/aeTkOQ==}
 
@@ -1467,6 +1488,24 @@ packages:
   '@types/crypto-js@4.2.2':
     resolution: {integrity: sha512-sDOLlVbHhXpAUAL0YHDUUwDZf3iN4Bwi4W6a0W0b+QcAezUbRtH4FVb+9J4h+XFPW7l/gQ9F8qC7P+Ec4k8QVQ==}
 
+  '@types/d3-color@3.1.3':
+    resolution: {integrity: sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==}
+
+  '@types/d3-drag@3.0.7':
+    resolution: {integrity: sha512-HE3jVKlzU9AaMazNufooRJ5ZpWmLIoc90A37WU2JMmeq28w1FQqCZswHZ3xR+SuxYftzHq6WU6KJHvqxKzTxxQ==}
+
+  '@types/d3-interpolate@3.0.4':
+    resolution: {integrity: sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==}
+
+  '@types/d3-selection@3.0.11':
+    resolution: {integrity: sha512-bhAXu23DJWsrI45xafYpkQ4NtcKMwWnAC/vKrd2l+nxMFuvOT3XMYTIj2opv8vq8AO5Yh7Qac/nSeP/3zjTK0w==}
+
+  '@types/d3-transition@3.0.9':
+    resolution: {integrity: sha512-uZS5shfxzO3rGlu0cC3bjmMFKsXv+SmZZcgp0KD22ts4uGXp5EVYGzu/0YdwZeKmddhcAccYtREJKkPfXkZuCg==}
+
+  '@types/d3-zoom@3.0.8':
+    resolution: {integrity: sha512-iqMC4/YlFCSlO8+2Ii1GGGliCAY4XdeG748w5vQUbevlbDu0zSjH/+jojorQVBK/se0j6DUFNPBGSqD3YWYnDw==}
+
   '@types/estree@0.0.39':
     resolution: {integrity: sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==}
 
@@ -1756,6 +1795,14 @@ packages:
   '@vueuse/shared@9.13.0':
     resolution: {integrity: sha512-UrnhU+Cnufu4S6JLCPZnkWh0WwZGUp72ktOF2DFptMlOs3TOdVv8xJN53zhHGARmVOsz5KqOls09+J1NR6sBKw==}
 
+  '@xyflow/svelte@0.1.34':
+    resolution: {integrity: sha512-gz1l0xAmHUEbhY+gzeOUwAlVbTOVpWcoCeIBqhtdRIGon24zlGeUzaAX7B6n36dmVUrumU34YbLuZvLSfI5DVQ==}
+    peerDependencies:
+      svelte: ^3.0.0 || ^4.0.0 || ^5.0.0
+
+  '@xyflow/system@0.0.54':
+    resolution: {integrity: sha512-DBoQTcSQ2620WMfakCcjRLrlqalWcZBPgMNrfSAybnVeyZm73rT1592GAXPcC3eoVmWcvGfBgqwAmmNtlrowdw==}
+
   JSONStream@1.3.5:
     resolution: {integrity: sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ==}
     hasBin: true
@@ -1832,6 +1879,10 @@ packages:
   argparse@2.0.1:
     resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==}
 
+  aria-query@5.3.2:
+    resolution: {integrity: sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==}
+    engines: {node: '>= 0.4'}
+
   arr-diff@4.0.0:
     resolution: {integrity: sha512-YVIQ82gZPGBebQV/a8dar4AitzCQs0jjXwMPZllpXMaGjXPYVUawSxQrRsjhjupyVxEvbHgUmIhKVlND+j02kA==}
     engines: {node: '>=0.10.0'}
@@ -1927,6 +1978,10 @@ packages:
   axios@1.7.9:
     resolution: {integrity: sha512-LhLcE7Hbiryz8oMDdDptSrWowmB4Bl6RCt6sIJKpRB4XtVf0iEgewX3au/pJqm+Py1kCASkb/FFKjxQaLtxJvw==}
 
+  axobject-query@4.1.0:
+    resolution: {integrity: sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==}
+    engines: {node: '>= 0.4'}
+
   babel-plugin-polyfill-corejs2@0.4.12:
     resolution: {integrity: sha512-CPWT6BwvhrTO2d8QVorhTCQw9Y43zOu7G9HigcfxvepOU6b8o3tcWad6oVgZIsZCTt42FFv97aA7ZJsbM4+8og==}
     peerDependencies:
@@ -2079,6 +2134,9 @@ packages:
     resolution: {integrity: sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==}
     engines: {node: '>=0.10.0'}
 
+  classcat@5.0.5:
+    resolution: {integrity: sha512-JhZUT7JFcQy/EzW605k/ktHtncoo9vnyW/2GspNYwFlN1C/WmjuV/xtS04e9SOkL2sTdw0VAZ2UGCcQ9lR6p6w==}
+
   clean-regexp@1.0.0:
     resolution: {integrity: sha512-GfisEZEJvzKrmGWkvfhgzcz/BllN1USeqD2V6tg14OAOgaCD2Z/PUEuxnAZ/nPvmaHRG7a8y77p1T/IRQ4D1Hw==}
     engines: {node: '>=4'}
@@ -2099,6 +2157,10 @@ packages:
     resolution: {integrity: sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w==}
     engines: {node: '>=0.8'}
 
+  clsx@2.1.1:
+    resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==}
+    engines: {node: '>=6'}
+
   collection-visit@1.0.0:
     resolution: {integrity: sha512-lNkKvzEeMBBjUGHZ+q6z9pSJla0KWAQPvtzhEV9+iGyQYG+pBpl7xKDhxoNSOZH2hhv0v5k0y2yAM4o4SjoSkw==}
     engines: {node: '>=0.10.0'}
@@ -2278,6 +2340,44 @@ packages:
   csstype@3.1.3:
     resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==}
 
+  d3-color@3.1.0:
+    resolution: {integrity: sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==}
+    engines: {node: '>=12'}
+
+  d3-dispatch@3.0.1:
+    resolution: {integrity: sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==}
+    engines: {node: '>=12'}
+
+  d3-drag@3.0.0:
+    resolution: {integrity: sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==}
+    engines: {node: '>=12'}
+
+  d3-ease@3.0.1:
+    resolution: {integrity: sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==}
+    engines: {node: '>=12'}
+
+  d3-interpolate@3.0.1:
+    resolution: {integrity: sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==}
+    engines: {node: '>=12'}
+
+  d3-selection@3.0.0:
+    resolution: {integrity: sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==}
+    engines: {node: '>=12'}
+
+  d3-timer@3.0.1:
+    resolution: {integrity: sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==}
+    engines: {node: '>=12'}
+
+  d3-transition@3.0.1:
+    resolution: {integrity: sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==}
+    engines: {node: '>=12'}
+    peerDependencies:
+      d3-selection: 2 - 3
+
+  d3-zoom@3.0.0:
+    resolution: {integrity: sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==}
+    engines: {node: '>=12'}
+
   dargs@7.0.0:
     resolution: {integrity: sha512-2iy1EkLdlBzQGvbweYRFxmFath8+K7+AKB0TlhHWkNuH+TmovaMH/Wp7V7R4u7f4SnX3OgLsU9t1NI9ioDnUpg==}
     engines: {node: '>=8'}
@@ -2744,6 +2844,9 @@ packages:
     deprecated: This version is no longer supported. Please see https://eslint.org/version-support for other options.
     hasBin: true
 
+  esm-env@1.2.2:
+    resolution: {integrity: sha512-Epxrv+Nr/CaL4ZcFGPJIYLWFom+YeV1DqMLHJoEd9SYRxNbaFruBwfEX/kkHUJf55j2+TUbmDcmuilbP1TmXHA==}
+
   espree@9.6.1:
     resolution: {integrity: sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==}
     engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
@@ -2752,6 +2855,9 @@ packages:
     resolution: {integrity: sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==}
     engines: {node: '>=0.10'}
 
+  esrap@1.4.6:
+    resolution: {integrity: sha512-F/D2mADJ9SHY3IwksD4DAXjTt7qt7GWUf3/8RhCNWmC/67tyb55dpimHmy7EplakFaflV0R/PC+fdSPqrRHAQw==}
+
   esrecurse@4.3.0:
     resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==}
     engines: {node: '>=4.0'}
@@ -3334,6 +3440,9 @@ packages:
     resolution: {integrity: sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==}
     engines: {node: '>=0.10.0'}
 
+  is-reference@3.0.3:
+    resolution: {integrity: sha512-ixkJoqQvAP88E6wLydLGGqCJsrFUnqoH6HnaczB8XmDH1oaWU+xxdptvikTgaEhtZ53Ky6YXiBuUI2WXLMCwjw==}
+
   is-regex@1.2.1:
     resolution: {integrity: sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==}
     engines: {node: '>= 0.4'}
@@ -3584,6 +3693,9 @@ packages:
   localforage@1.10.0:
     resolution: {integrity: sha512-14/H1aX7hzBBmmh7sGPd+AOMkkIrHM3Z1PAyGgZigA1H1p5O5ANnMyWzvpAETtG68/dC4pC0ncy3+PPGzXZHPg==}
 
+  locate-character@3.0.0:
+    resolution: {integrity: sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA==}
+
   locate-path@5.0.0:
     resolution: {integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==}
     engines: {node: '>=8'}
@@ -4727,6 +4839,10 @@ packages:
     resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==}
     engines: {node: '>= 0.4'}
 
+  svelte@5.25.7:
+    resolution: {integrity: sha512-0fzXbXaKfSvFUs6Wxev2h4CoEhexZotbTF9EJ4+Cg7MHW64ZnZ9+xUedZyEpgj0Tt9HrYGv9aASHkqjn9b/cPw==}
+    engines: {node: '>=18'}
+
   svg-baker-runtime@1.4.7:
     resolution: {integrity: sha512-Zorfwwj5+lWjk/oxwSMsRdS2sPQQdTmmsvaSpzU+i9ZWi3zugHLt6VckWfnswphQP0LmOel3nggpF5nETbt6xw==}
 
@@ -5313,6 +5429,9 @@ packages:
     resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==}
     engines: {node: '>=10'}
 
+  zimmerframe@1.1.2:
+    resolution: {integrity: sha512-rAbqEGa8ovJy4pyBxZM70hg4pE6gDgaQ0Sl9M3enG3I0d6H4XSAM3GeNGLKnsBpuijUow064sf7ww1nutC5/3w==}
+
   zwitch@2.0.4:
     resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==}
 
@@ -6729,8 +6848,30 @@ snapshots:
       magic-string: 0.25.9
       string.prototype.matchall: 4.0.12
 
+  '@svelte-put/shortcut@3.1.1(svelte@5.25.7)':
+    dependencies:
+      svelte: 5.25.7
+
+  '@sveltejs/acorn-typescript@1.0.5(acorn@8.14.0)':
+    dependencies:
+      acorn: 8.14.0
+
   '@sxzz/popperjs-es@2.11.7': {}
 
+  '@tinyflow-ai/ui@0.0.9(svelte@5.25.7)':
+    dependencies:
+      '@floating-ui/dom': 1.6.13
+      '@xyflow/svelte': 0.1.34(svelte@5.25.7)
+    transitivePeerDependencies:
+      - svelte
+
+  '@tinyflow-ai/vue@0.0.9(svelte@5.25.7)(vue@3.5.13(typescript@4.9.5))':
+    dependencies:
+      '@tinyflow-ai/ui': 0.0.9(svelte@5.25.7)
+      vue: 3.5.13(typescript@4.9.5)
+    transitivePeerDependencies:
+      - svelte
+
   '@traptitech/markdown-it-katex@3.6.0':
     dependencies:
       katex: 0.16.21
@@ -6747,6 +6888,27 @@ snapshots:
 
   '@types/crypto-js@4.2.2': {}
 
+  '@types/d3-color@3.1.3': {}
+
+  '@types/d3-drag@3.0.7':
+    dependencies:
+      '@types/d3-selection': 3.0.11
+
+  '@types/d3-interpolate@3.0.4':
+    dependencies:
+      '@types/d3-color': 3.1.3
+
+  '@types/d3-selection@3.0.11': {}
+
+  '@types/d3-transition@3.0.9':
+    dependencies:
+      '@types/d3-selection': 3.0.11
+
+  '@types/d3-zoom@3.0.8':
+    dependencies:
+      '@types/d3-interpolate': 3.0.4
+      '@types/d3-selection': 3.0.11
+
   '@types/estree@0.0.39': {}
 
   '@types/estree@1.0.6': {}
@@ -7079,6 +7241,23 @@ snapshots:
       - '@vue/composition-api'
       - vue
 
+  '@xyflow/svelte@0.1.34(svelte@5.25.7)':
+    dependencies:
+      '@svelte-put/shortcut': 3.1.1(svelte@5.25.7)
+      '@xyflow/system': 0.0.54
+      classcat: 5.0.5
+      svelte: 5.25.7
+
+  '@xyflow/system@0.0.54':
+    dependencies:
+      '@types/d3-drag': 3.0.7
+      '@types/d3-selection': 3.0.11
+      '@types/d3-transition': 3.0.9
+      '@types/d3-zoom': 3.0.8
+      d3-drag: 3.0.0
+      d3-selection: 3.0.0
+      d3-zoom: 3.0.0
+
   JSONStream@1.3.5:
     dependencies:
       jsonparse: 1.3.1
@@ -7159,6 +7338,8 @@ snapshots:
 
   argparse@2.0.1: {}
 
+  aria-query@5.3.2: {}
+
   arr-diff@4.0.0: {}
 
   arr-flatten@1.1.0: {}
@@ -7258,6 +7439,8 @@ snapshots:
     transitivePeerDependencies:
       - debug
 
+  axobject-query@4.1.0: {}
+
   babel-plugin-polyfill-corejs2@0.4.12(@babel/core@7.26.7):
     dependencies:
       '@babel/compat-data': 7.26.5
@@ -7446,6 +7629,8 @@ snapshots:
       isobject: 3.0.1
       static-extend: 0.1.2
 
+  classcat@5.0.5: {}
+
   clean-regexp@1.0.0:
     dependencies:
       escape-string-regexp: 1.0.5
@@ -7467,6 +7652,8 @@ snapshots:
 
   clone@2.1.2: {}
 
+  clsx@2.1.1: {}
+
   collection-visit@1.0.0:
     dependencies:
       map-visit: 1.0.0
@@ -7643,6 +7830,42 @@ snapshots:
 
   csstype@3.1.3: {}
 
+  d3-color@3.1.0: {}
+
+  d3-dispatch@3.0.1: {}
+
+  d3-drag@3.0.0:
+    dependencies:
+      d3-dispatch: 3.0.1
+      d3-selection: 3.0.0
+
+  d3-ease@3.0.1: {}
+
+  d3-interpolate@3.0.1:
+    dependencies:
+      d3-color: 3.1.0
+
+  d3-selection@3.0.0: {}
+
+  d3-timer@3.0.1: {}
+
+  d3-transition@3.0.1(d3-selection@3.0.0):
+    dependencies:
+      d3-color: 3.1.0
+      d3-dispatch: 3.0.1
+      d3-ease: 3.0.1
+      d3-interpolate: 3.0.1
+      d3-selection: 3.0.0
+      d3-timer: 3.0.1
+
+  d3-zoom@3.0.0:
+    dependencies:
+      d3-dispatch: 3.0.1
+      d3-drag: 3.0.0
+      d3-interpolate: 3.0.1
+      d3-selection: 3.0.0
+      d3-transition: 3.0.1(d3-selection@3.0.0)
+
   dargs@7.0.0: {}
 
   data-view-buffer@1.0.2:
@@ -8262,6 +8485,8 @@ snapshots:
     transitivePeerDependencies:
       - supports-color
 
+  esm-env@1.2.2: {}
+
   espree@9.6.1:
     dependencies:
       acorn: 8.14.0
@@ -8272,6 +8497,10 @@ snapshots:
     dependencies:
       estraverse: 5.3.0
 
+  esrap@1.4.6:
+    dependencies:
+      '@jridgewell/sourcemap-codec': 1.5.0
+
   esrecurse@4.3.0:
     dependencies:
       estraverse: 5.3.0
@@ -8886,6 +9115,10 @@ snapshots:
     dependencies:
       isobject: 3.0.1
 
+  is-reference@3.0.3:
+    dependencies:
+      '@types/estree': 1.0.6
+
   is-regex@1.2.1:
     dependencies:
       call-bound: 1.0.3
@@ -9126,6 +9359,8 @@ snapshots:
     dependencies:
       lie: 3.1.1
 
+  locate-character@3.0.0: {}
+
   locate-path@5.0.0:
     dependencies:
       p-locate: 4.1.0
@@ -10362,6 +10597,23 @@ snapshots:
 
   supports-preserve-symlinks-flag@1.0.0: {}
 
+  svelte@5.25.7:
+    dependencies:
+      '@ampproject/remapping': 2.3.0
+      '@jridgewell/sourcemap-codec': 1.5.0
+      '@sveltejs/acorn-typescript': 1.0.5(acorn@8.14.0)
+      '@types/estree': 1.0.6
+      acorn: 8.14.0
+      aria-query: 5.3.2
+      axobject-query: 4.1.0
+      clsx: 2.1.1
+      esm-env: 1.2.2
+      esrap: 1.4.6
+      is-reference: 3.0.3
+      locate-character: 3.0.0
+      magic-string: 0.30.17
+      zimmerframe: 1.1.2
+
   svg-baker-runtime@1.4.7:
     dependencies:
       deepmerge: 1.3.2
@@ -11133,4 +11385,6 @@ snapshots:
 
   yocto-queue@0.1.0: {}
 
+  zimmerframe@1.1.2: {}
+
   zwitch@2.0.4: {}

+ 4 - 0
pnpm-workspace.yaml

@@ -0,0 +1,4 @@
+onlyBuiltDependencies:
+  - '@vue-office/pdf'
+  - esbuild
+  - vue-demi

+ 31 - 0
public/gpts.json

@@ -0,0 +1,31 @@
+{
+    "tag": [
+        "图像",
+        "论文",
+        "文件",
+        "识别",
+        "PDF",
+        "新闻",
+        "医生",
+        "老师",
+        "Logo"
+    ],
+    "gpts": [
+        {
+            "gid": "gpt-4-all",
+            "name": "gpt-4-all",
+            "logo": "https://cos.aitutu.cc/gpts/gpt4all.jpg",
+            "info": "集合官方GPT-4、联网,多模态(gpt-4v),绘图功能(dall-e3),限制不支持function等",
+            "use_cnt": "8624",
+            "bad": "0"
+        },
+        {
+            "gid": "gpt-4-gizmo-g-2fkFE8rbu",
+            "name": "DALL·E",
+            "logo": "https://files.oaiusercontent.com/file-SxYQO0Fq1ZkPagkFtg67DRVb?se=2123-10-12T23%3A57%3A32Z&sp=r&sv=2021-08-06&sr=b&rscc=max-age%3D31536000%2C%20immutable&rscd=attachment%3B%20filename%3Dagent_3.webp&sig=pLlQh8oUktqQzhM09SDDxn5aakqFuM2FAPptuA0mbqc%3D",
+            "info": "让我将你的想象变成图像 – 基于 ChatGPT",
+            "use_cnt": "3087",
+            "bad": "1"
+        }
+    ]
+}

+ 2 - 0
src/api/mjapi.ts

@@ -11,7 +11,9 @@ export interface gptsType{
     logo:string
     info:string
     use_cnt?:string
+    id:string
     bad?:string|number
+    modelName:string
 }
  //const { addChat, updateChat, updateChatSome, getChatByUuidAndIndex } = useChat()
 export function upImg(file:any   ):Promise<any>

+ 4 - 1
src/api/model.ts

@@ -4,10 +4,13 @@ import request from '@/utils/request/req';
  * 查询未隐藏模型
  * @returns 
  */
-export function modelList() {
+export function modelList(category: string) {
 	return request({
 		url: '/system/model/modelList',
 		method: 'get',
+		params: {
+            category: category
+        }
 	})
 }
 

+ 6 - 2
src/api/openapi.ts

@@ -192,6 +192,8 @@ interface subModelType{
     signal?:AbortSignal
     model?:string
     uuid?:string|number
+    chatType: number
+    appId: string
 }
 function getHeaderAuthorization(){
     // if(!gptServerStore.myData.OPENAI_API_KEY){
@@ -236,7 +238,6 @@ export const subModel= async (opt: subModelType)=>{
         frequency_penalty = gStore.frequency_penalty??frequency_penalty;
         max_tokens= gStore.max_tokens;
     }
-   
     let body ={
             max_tokens ,
             model ,
@@ -246,6 +247,8 @@ export const subModel= async (opt: subModelType)=>{
             "messages": opt.message
            ,stream:true
            ,kid:gptConfigStore.myData.kid
+           ,chat_type: opt.chatType
+           ,appId: opt.appId
         }
 
         let headers=   {'Content-Type': 'application/json;charset=UTF-8',
@@ -267,8 +270,9 @@ export const subModel= async (opt: subModelType)=>{
                  if(data=='[DONE]') opt.onMessage({text:'',isFinish:true})
                  else {
                     try{
+                        // TODO 思考处理,DeepSeek  API 字段reasoning_content ,本地部署标签<think> 
                         const obj= JSON.parse(data );
-                        opt.onMessage({text:obj.choices[0].delta?.content??'' ,isFinish:obj.choices[0].finish_reason!=null })
+                        opt.onMessage({text:obj.choices[0].delta?.content??obj.choices[0].delta?.reasoning_content??'' ,isFinish:obj.choices[0].finish_reason!=null })
                     }catch{
                         opt.onMessage({
                             text: data,

BIN
src/assets/01.png


+ 5 - 4
src/components/common/PromptStore/index.vue

@@ -70,6 +70,8 @@ let intervalId: string | number | NodeJS.Timer | undefined;
 
 // 获取支付二维码
 async function getPayUrl(money: string, name: string) {
+	message.success('本系统仅用于演示,暂不支持此功能!')
+	return;
 	showMeVisible.value = true;
 	const response = await payUrl({ money: money, name: name });
 	imageUrl.value = response.data.url;
@@ -81,9 +83,8 @@ async function getPayUrl(money: string, name: string) {
 
 // 跳转到支付地址
 async function getPayUrl1(money: string, name: string) {
-	if(loading.value) {
-		return
-	}
+	message.success('本系统仅用于演示,暂不支持此功能!')
+	return;
 	loading.value = true
 	const [err, result] = await to(getSPayUrl({ money: money, name: name }));
 	if (err) {
@@ -212,7 +213,7 @@ const tableData = ref([]);
 const fetchData1 = async () => {
 	try {
 		// 发起一个请求
-		const [err, result] = await to(modelList());
+		const [err, result] = await to(modelList('chat'));
 
 		if (err) {
 			message.error(err.message)

+ 3 - 1
src/router/index.ts

@@ -10,6 +10,7 @@ import fanyilayout from '@/views/fanyi/layout.vue'
 import pptlayout from '@/views/ppt/layout.vue'
 import musiclayout from '@/views/suno/layout.vue'
 import knowledgelayout from '@/views/knowledge/layout.vue'
+import wxlayout from '@/views/wxbot/layout.vue'
 
 
 const routes: RouteRecordRaw[] = [
@@ -55,6 +56,7 @@ const routes: RouteRecordRaw[] = [
   },
 
   {
+    
     path: '/draw',
     name: 'Rootdraw',
     component: mjlayout,
@@ -141,7 +143,7 @@ const routes: RouteRecordRaw[] = [
   {
     path: '/wxbot',
     name: 'Wxbot',
-    component: mjlayout,
+    component: wxlayout,
     redirect: '/wxbot/t',
     children: [
       {

+ 1 - 1
src/store/modules/app/helper.ts

@@ -14,7 +14,7 @@ export interface AppState {
 }
 
 export function defaultSetting(): AppState {
-  return { siderCollapsed: false,isChat:true, theme: 'dark', language: 'zh-CN' }
+  return { siderCollapsed: false,isChat:true, theme: 'light', language: 'zh-CN' }
 }
 
 export function getLocalSetting(): AppState {

+ 1 - 1
src/utils/request/req.ts

@@ -90,7 +90,7 @@ service.interceptors.response.use(
       // 退出登录
       message.error('无效的会话,或者会话已过期,请重新登录。')
       useUserStore().logout().then(() => {
-        location.href = '/login';
+          location.href = '#/login';
       });
     } else if (code === HttpStatus.SERVER_ERROR) {
       // console.log(msg);

+ 31 - 11
src/views/chat/components/Message/Text.vue

@@ -15,7 +15,7 @@ import whisperText from '@/views/mj/whisperText.vue'
 import MjTextAttr from '@/views/mj/mjTextAttr.vue'
 import aiTextSetting from '@/views/mj/aiTextSetting.vue'
 import aiSetAuth from '@/views/mj/aiSetAuth.vue'
-import { isApikeyError, isAuthSessionError, isTTS } from '@/api'
+import { isApikeyError, isAuthSessionError, isTTS, mlog } from '@/api'
 
 interface Props {
   inversion?: boolean
@@ -51,7 +51,7 @@ mdi.use(mdKatex, { blockClass: 'katexmath-block rounded-md p-[10px]', errorColor
 const wrapClass = computed(() => {
   return [
     'text-wrap',
-    'min-w-[20px]',
+    'min-w-[20px]','max-w-[810px]',
     'rounded-md',
     isMobile.value ? 'p-2' : 'px-3 py-2',
     props.inversion ? 'bg-[#d2f9d1]' : 'bg-[#f4f6f8]',
@@ -62,11 +62,30 @@ const wrapClass = computed(() => {
 })
 
 const text = computed(() => {
-  const value = props.text ?? ''
-  // if (!props.asRawText)
-  //   return mdi.render(value)
-  // return value
-  return mdi.render(value)
+  let value = props.text ?? ''
+  if (!props.asRawText){
+    value = value.replace(/\\\( *(.*?) *\\\)/g, '$$$1$$');
+    //value = value.replace(/\\\((.*?)\\\)/g, '$$$1$$');
+    value = value.replace(/\\\[ *(.*?) *\\\]/g, '$$$$$1$$$$');
+    //
+    value= value.replaceAll('\\[',"$$$$")
+    value= value.replaceAll('\\]',"$$$$")   
+
+    //思考过程处理
+    //value= value.replace(/<think>([\s\S]*?)<\/think>/g, (match: string, content: string) => { 
+    value= value.replace(/<think>([\s\S]*?)(?=<\/think>|$)/g, (match: string, content: string) => { 
+      const processedContent: string = content
+        .split('\n')
+        .map(line => line.trim() ? '>' + line : line)  
+        .join('\n').replace(/(\r?\n)+/g, '\n>\n');
+       
+      return ">Thinking..."+(processedContent) ;
+    });
+    value= value.replaceAll('</think>','')
+    //mlog('replace', value)
+    return mdi.render(value) 
+  }
+  return value
 })
 
 function highlightBlock(str: string, lang?: string) {
@@ -121,15 +140,16 @@ onUnmounted(() => {
         <aiTextSetting v-if="!inversion && isApikeyError(text)"/>
         <aiSetAuth v-if="!inversion && isAuthSessionError(text)" />
           
-        <dallText :chat="chat" v-if="chat.model=='dall-e-3' || chat.model=='dall-e-2'" class="whitespace-pre-wrap" />
+        <dallText :chat="chat" v-if=" chat.model && chat.model?.indexOf('chat') == -1" class="whitespace-pre-wrap" />
         <mjText v-if="chat.mjID" class="whitespace-pre-wrap" :chat="chat" :mdi="mdi"></mjText>
         <ttsText v-else-if="chat.model && isTTS(chat.model) && chat.text=='ok'" :chat="chat"/>
         <template v-else>
-          <div v-if="!asRawText" class="markdown-body " :class="{ 'markdown-body-generate': loading }" v-html="text" />
-          <div style="font-size: 17px; font-family: 'Karla';" v-else class="whitespace-pre-wrap" v-text="text" />
+          <div v-if="!asRawText" class="markdown-body" :class="{ 'markdown-body-generate': loading }" v-html="text" />
+          <div v-else class="whitespace-pre-wrap" v-text="text" />
         </template>
       </div>
       <whisperText v-else-if="text=='whisper' && chat.opt?.lkey "  :chat="chat" />
+      <div v-else-if="asRawText" class="whitespace-pre-wrap" v-text="text" />
       <div v-else class="markdown-body "  style="--color-fg-default:#24292f"  v-html="text" />
       <!-- <div v-else class="whitespace-pre-wrap" v-text="text" /> -->
       <MjTextAttr :image="chat.opt?.images[0]" v-if="chat.opt?.images"></MjTextAttr>
@@ -144,4 +164,4 @@ onUnmounted(() => {
 
 <style lang="less">
 @import url(./style.less);
-</style>
+</style>

+ 19 - 19
src/views/chat/components/Message/index.vue

@@ -9,7 +9,7 @@ import { t } from '@/locales'
 import { useBasicLayout } from '@/hooks/useBasicLayout'
 import { copyToClip } from '@/utils/copy'
 import { homeStore } from '@/store'
-import { getSeed, mlog ,mjImgUrl} from '@/api'
+import { getSeed, mlog} from '@/api' 
 
 interface Props {
   dateTime?: string
@@ -24,7 +24,7 @@ interface Props {
 interface Emit {
   (ev: 'regenerate'): void
   (ev: 'delete'): void
-	(ev: 'edit'): void
+  (ev: 'edit'): void
 }
 
 const props = defineProps<Props>()
@@ -39,7 +39,7 @@ const message = useMessage()
 
 const textRef = ref<HTMLElement>()
 
-const asRawText = ref(props.inversion)
+const asRawText = ref(props.inversion && homeStore.myData.session.isCloseMdPreview)
 
 const messageRef = ref<HTMLElement>()
 
@@ -55,7 +55,7 @@ const options = computed(() => {
       key: 'delete',
       icon: iconRender({ icon: 'ri:delete-bin-line' }),
     },
-		{
+    {
       label: t('common.edit'),
       key: 'edit',
       icon: iconRender({ icon: 'ri:edit-2-line' }),
@@ -78,9 +78,9 @@ const options = computed(() => {
   return common
 })
 
-function handleSelect(key: 'copyText' | 'delete' | 'edit' | 'toggleRenderType' |'tts') {
+function handleSelect(key: 'copyText' | 'delete' | 'edit' | 'toggleRenderType' | 'tts') {
   switch (key) {
-    case 'tts':
+    case 'tts': 
       homeStore.setMyData({act:'gpt.ttsv2', actData:{ index:props.index , uuid:props.chat.uuid, text:props.text } });
       return;
     case 'copyText':
@@ -91,8 +91,8 @@ function handleSelect(key: 'copyText' | 'delete' | 'edit' | 'toggleRenderType' |
       return
     case 'delete':
       emit('delete')
-			return
-		case 'edit':
+      return
+    case 'edit':
       emit('edit')
   }
 }
@@ -123,7 +123,7 @@ function handleRegenerate2() {
   mlog('重新发送!');
   homeStore.setMyData({act:'gpt.resubmit', actData:{ index:props.index , uuid:props.chat.uuid } });
 }
-
+ 
 </script>
 
 <template>
@@ -140,23 +140,23 @@ function handleRegenerate2() {
     </div>
     <div class="overflow-hidden text-sm " :class="[inversion ? 'items-end' : 'items-start']">
       <p class="text-xs group  text-[#b4bbc4] flex  items-center space-x-2 " :class="[inversion ? 'justify-end' : 'justify-start']">
-        <span style="font-size: 14px; font-family: 'Karla';">{{ dateTime }}</span>
+        <span>{{ dateTime }}</span>
         <span v-if="chat.model"  class="text-[#b4bbc4]/50">{{ chat.model }}</span>
         <!-- <span>{{ chat.opt?.progress }}</span> -->
         <template  v-if="chat.opt?.status=='SUCCESS'">
           <SvgIcon icon="ri:restart-line" @click="sendReload"  class="cursor-pointer text-neutral-300 hover:text-neutral-800 dark:hover:text-neutral-300 " ></SvgIcon>
-
+          
           <div @click="getSeed(chat, message )" class="cursor-pointer">
             <span v-if="chat.opt?.seed">Seed:{{ chat.opt?.seed }}</span>
             <span v-else>Seed</span>
           </div>
-          <a :href=" mjImgUrl(chat.opt?.imageUrl)" class="hidden group-hover:block active  cursor-pointer underline " target="_blank">{{ $t('mj.ulink') }}</a>
+          <!-- <a :href=" mjImgUrl(chat.opt?.imageUrl)" class="hidden group-hover:block active  cursor-pointer underline " target="_blank">{{ $t('mj.ulink') }}</a> -->
         </template>
       </p>
-
+      
       <div  class="flex items-end gap-1 mt-2"
-        :class="[inversion ? 'flex-row-reverse' : 'flex-row']" >
-        <TextComponent
+        :class="[inversion ? 'flex-row-reverse' : 'flex-row']" > 
+        <TextComponent 
           ref="textRef"
           :inversion="inversion"
           :error="error"
@@ -165,8 +165,8 @@ function handleRegenerate2() {
           :as-raw-text="asRawText"
           :chat="chat"
         />
-
-        <div class="flex flex-col" v-if="!chat.mjID && chat.model!='dall-e-3' && chat.model!='dall-e-2' ">
+        <!-- <div class="flex flex-col" v-if="!chat.mjID && chat.model!='dall-e-3' && chat.model!='dall-e-2' "> -->
+        <div class="flex flex-col" v-if="!chat.mjID">
           <!-- <button
             v-if="!inversion "
             class="mb-2 transition text-neutral-300 hover:text-neutral-800 dark:hover:text-neutral-300"
@@ -185,7 +185,7 @@ function handleRegenerate2() {
             :trigger="isMobile ? 'click' : 'hover'"
             :placement="!inversion ? 'right' : 'left'"
             :options="options"
-            @select="handleSelect"
+            @select="handleSelect" 
           >
             <button class="transition text-neutral-300 hover:text-neutral-800 dark:hover:text-neutral-200">
               <SvgIcon icon="ri:more-2-fill" />
@@ -195,4 +195,4 @@ function handleRegenerate2() {
       </div>
     </div>
   </div>
-</template>
+</template>

+ 4 - 1
src/views/chat/components/Message/style.less

@@ -13,6 +13,9 @@
 	ul {
 		list-style-type: disc;
 	}
+	img{
+		max-width: 400px;
+	}
 
 	pre code,
 	pre tt {
@@ -132,4 +135,4 @@ html.dark {
 			padding: 24px 16px 16px 16px;
 		}
 	}
-}
+}

+ 648 - 650
src/views/chat/index.vue

@@ -61,372 +61,372 @@ const isChat = computed(() => appStore.isChat)
 
 // 未知原因刷新页面,loading 状态不会重置,手动重置
 dataSources.value.forEach((item, index) => {
-  if (item.loading)
-    updateChatSome(+uuid, index, { loading: false })
+	if (item.loading)
+		updateChatSome(+uuid, index, { loading: false })
 })
 
 function handleSubmit() {
-  //onConversation() //把这个放到aiGpt
-  let message = prompt.value;
-  if (!message || message.trim() === '')
-    return
-   if (loading.value) return;
-   loading.value = true
-  homeStore.setMyData({act:'gpt.submit', actData:{ prompt:prompt.value, uuid } });
-   prompt.value='';
+	//onConversation() //把这个放到aiGpt
+	let message = prompt.value;
+	if (!message || message.trim() === '')
+		return
+	if (loading.value) return;
+	loading.value = true
+	homeStore.setMyData({act:'gpt.submit', actData:{ prompt:prompt.value, uuid } });
+	prompt.value='';
 }
 
 async function onConversation() {
-  let message = prompt.value
-
-  if (loading.value)
-    return
-
-  if (!message || message.trim() === '')
-    return
-
-  controller = new AbortController()
-
-  addChat(
-    +uuid,
-    {
-      dateTime: new Date().toLocaleString(),
-      text: message,
-      inversion: true,
-      error: false,
-      conversationOptions: null,
-      requestOptions: { prompt: message, options: null },
-    },
-  )
-  scrollToBottom()
-
-  loading.value = true
-  prompt.value = ''
-
-  let options: Chat.ConversationRequest = {}
-  const lastContext = conversationList.value[conversationList.value.length - 1]?.conversationOptions
-
-  if (lastContext && usingContext.value)
-    options = { ...lastContext }
-
-  addChat(
-    +uuid,
-    {
-      dateTime: new Date().toLocaleString(),
-      text: '思考中',
-      loading: true,
-      inversion: false,
-      error: false,
-      conversationOptions: null,
-      requestOptions: { prompt: message, options: { ...options } },
-    },
-  )
-  scrollToBottom()
-
-  try {
-    let lastText = ''
-    const fetchChatAPIOnce = async () => {
-      await fetchChatAPIProcess<Chat.ConversationResponse>({
-        prompt: message,
-        options,
-        signal: controller.signal,
-        onDownloadProgress: ({ event }) => {
-          const xhr = event.target
-          const { responseText } = xhr
-          // Always process the final line
-          const lastIndex = responseText.lastIndexOf('\n', responseText.length - 2)
-          let chunk = responseText
-          if (lastIndex !== -1)
-            chunk = responseText.substring(lastIndex)
-          try {
-            const data = JSON.parse(chunk)
-            updateChat(
-              +uuid,
-              dataSources.value.length - 1,
-              {
-                dateTime: new Date().toLocaleString(),
-                text: lastText + (data.text ?? ''),
-                inversion: false,
-                error: false,
-                loading: true,
-                conversationOptions: { conversationId: data.conversationId, parentMessageId: data.id },
-                requestOptions: { prompt: message, options: { ...options } },
-              },
-            )
-
-            if (openLongReply && data.detail.choices[0].finish_reason === 'length') {
-              options.parentMessageId = data.id
-              lastText = data.text
-              message = ''
-              return fetchChatAPIOnce()
-            }
-
-            scrollToBottomIfAtBottom()
-          }
-          catch (error) {
-            //
-          }
-        },
-      })
-      updateChatSome(+uuid, dataSources.value.length - 1, { loading: false })
-    }
-
-    await fetchChatAPIOnce()
-  }
-  catch (error: any) {
-    const errorMessage = error?.message ?? t('common.wrong')
-
-    if (error.message === 'canceled') {
-      updateChatSome(
-        +uuid,
-        dataSources.value.length - 1,
-        {
-          loading: false,
-        },
-      )
-      scrollToBottomIfAtBottom()
-      return
-    }
-
-    const currentChat = getChatByUuidAndIndex(+uuid, dataSources.value.length - 1)
-
-    if (currentChat?.text && currentChat.text !== '') {
-      updateChatSome(
-        +uuid,
-        dataSources.value.length - 1,
-        {
-          text: `${currentChat.text}\n[${errorMessage}]`,
-          error: false,
-          loading: false,
-        },
-      )
-      return
-    }
-
-    updateChat(
-      +uuid,
-      dataSources.value.length - 1,
-      {
-        dateTime: new Date().toLocaleString(),
-        text: errorMessage,
-        inversion: false,
-        error: true,
-        loading: false,
-        conversationOptions: null,
-        requestOptions: { prompt: message, options: { ...options } },
-      },
-    )
-    scrollToBottomIfAtBottom()
-  }
-  finally {
-    loading.value = false
-  }
+	let message = prompt.value
+
+	if (loading.value)
+		return
+
+	if (!message || message.trim() === '')
+		return
+
+	controller = new AbortController()
+
+	addChat(
+		+uuid,
+		{
+			dateTime: new Date().toLocaleString(),
+			text: message,
+			inversion: true,
+			error: false,
+			conversationOptions: null,
+			requestOptions: { prompt: message, options: null },
+		},
+	)
+	scrollToBottom()
+
+	loading.value = true
+	prompt.value = ''
+
+	let options: Chat.ConversationRequest = {}
+	const lastContext = conversationList.value[conversationList.value.length - 1]?.conversationOptions
+
+	if (lastContext && usingContext.value)
+		options = { ...lastContext }
+
+	addChat(
+		+uuid,
+		{
+			dateTime: new Date().toLocaleString(),
+			text: '思考中',
+			loading: true,
+			inversion: false,
+			error: false,
+			conversationOptions: null,
+			requestOptions: { prompt: message, options: { ...options } },
+		},
+	)
+	scrollToBottom()
+
+	try {
+		let lastText = ''
+		const fetchChatAPIOnce = async () => {
+			await fetchChatAPIProcess<Chat.ConversationResponse>({
+				prompt: message,
+				options,
+				signal: controller.signal,
+				onDownloadProgress: ({ event }) => {
+					const xhr = event.target
+					const { responseText } = xhr
+					// Always process the final line
+					const lastIndex = responseText.lastIndexOf('\n', responseText.length - 2)
+					let chunk = responseText
+					if (lastIndex !== -1)
+						chunk = responseText.substring(lastIndex)
+					try {
+						const data = JSON.parse(chunk)
+						updateChat(
+							+uuid,
+							dataSources.value.length - 1,
+							{
+								dateTime: new Date().toLocaleString(),
+								text: lastText + (data.text ?? ''),
+								inversion: false,
+								error: false,
+								loading: true,
+								conversationOptions: { conversationId: data.conversationId, parentMessageId: data.id },
+								requestOptions: { prompt: message, options: { ...options } },
+							},
+						)
+
+						if (openLongReply && data.detail.choices[0].finish_reason === 'length') {
+							options.parentMessageId = data.id
+							lastText = data.text
+							message = ''
+							return fetchChatAPIOnce()
+						}
+
+						scrollToBottomIfAtBottom()
+					}
+					catch (error) {
+						//
+					}
+				},
+			})
+			updateChatSome(+uuid, dataSources.value.length - 1, { loading: false })
+		}
+
+		await fetchChatAPIOnce()
+	}
+	catch (error: any) {
+		const errorMessage = error?.message ?? t('common.wrong')
+
+		if (error.message === 'canceled') {
+			updateChatSome(
+				+uuid,
+				dataSources.value.length - 1,
+				{
+					loading: false,
+				},
+			)
+			scrollToBottomIfAtBottom()
+			return
+		}
+
+		const currentChat = getChatByUuidAndIndex(+uuid, dataSources.value.length - 1)
+
+		if (currentChat?.text && currentChat.text !== '') {
+			updateChatSome(
+				+uuid,
+				dataSources.value.length - 1,
+				{
+					text: `${currentChat.text}\n[${errorMessage}]`,
+					error: false,
+					loading: false,
+				},
+			)
+			return
+		}
+
+		updateChat(
+			+uuid,
+			dataSources.value.length - 1,
+			{
+				dateTime: new Date().toLocaleString(),
+				text: errorMessage,
+				inversion: false,
+				error: true,
+				loading: false,
+				conversationOptions: null,
+				requestOptions: { prompt: message, options: { ...options } },
+			},
+		)
+		scrollToBottomIfAtBottom()
+	}
+	finally {
+		loading.value = false
+	}
 }
 
 async function onRegenerate(index: number) {
-  if (loading.value)
-    return
-
-  controller = new AbortController()
-
-  const { requestOptions } = dataSources.value[index]
-
-  let message = requestOptions?.prompt ?? ''
-
-  let options: Chat.ConversationRequest = {}
-
-  if (requestOptions.options)
-    options = { ...requestOptions.options }
-
-  loading.value = true
-
-  updateChat(
-    +uuid,
-    index,
-    {
-      dateTime: new Date().toLocaleString(),
-      text: '',
-      inversion: false,
-      error: false,
-      loading: true,
-      conversationOptions: null,
-      requestOptions: { prompt: message, options: { ...options } },
-    },
-  )
-
-  try {
-    let lastText = ''
-    const fetchChatAPIOnce = async () => {
-      await fetchChatAPIProcess<Chat.ConversationResponse>({
-        prompt: message,
-        options,
-        signal: controller.signal,
-        onDownloadProgress: ({ event }) => {
-          const xhr = event.target
-          const { responseText } = xhr
-          // Always process the final line
-          const lastIndex = responseText.lastIndexOf('\n', responseText.length - 2)
-          let chunk = responseText
-          if (lastIndex !== -1)
-            chunk = responseText.substring(lastIndex)
-          try {
-            const data = JSON.parse(chunk)
-            updateChat(
-              +uuid,
-              index,
-              {
-                dateTime: new Date().toLocaleString(),
-                text: lastText + (data.text ?? ''),
-                inversion: false,
-                error: false,
-                loading: true,
-                conversationOptions: { conversationId: data.conversationId, parentMessageId: data.id },
-                requestOptions: { prompt: message, options: { ...options } },
-              },
-            )
-
-            if (openLongReply && data.detail.choices[0].finish_reason === 'length') {
-              options.parentMessageId = data.id
-              lastText = data.text
-              message = ''
-              return fetchChatAPIOnce()
-            }
-          }
-          catch (error) {
-            //
-          }
-        },
-      })
-      updateChatSome(+uuid, index, { loading: false })
-    }
-    await fetchChatAPIOnce()
-  }
-  catch (error: any) {
-    if (error.message === 'canceled') {
-      updateChatSome(
-        +uuid,
-        index,
-        {
-          loading: false,
-        },
-      )
-      return
-    }
-
-    const errorMessage = error?.message ?? t('common.wrong')
-
-    updateChat(
-      +uuid,
-      index,
-      {
-        dateTime: new Date().toLocaleString(),
-        text: errorMessage,
-        inversion: false,
-        error: true,
-        loading: false,
-        conversationOptions: null,
-        requestOptions: { prompt: message, options: { ...options } },
-      },
-    )
-  }
-  finally {
-    loading.value = false
-  }
+	if (loading.value)
+		return
+
+	controller = new AbortController()
+
+	const { requestOptions } = dataSources.value[index]
+
+	let message = requestOptions?.prompt ?? ''
+
+	let options: Chat.ConversationRequest = {}
+
+	if (requestOptions.options)
+		options = { ...requestOptions.options }
+
+	loading.value = true
+
+	updateChat(
+		+uuid,
+		index,
+		{
+			dateTime: new Date().toLocaleString(),
+			text: '',
+			inversion: false,
+			error: false,
+			loading: true,
+			conversationOptions: null,
+			requestOptions: { prompt: message, options: { ...options } },
+		},
+	)
+
+	try {
+		let lastText = ''
+		const fetchChatAPIOnce = async () => {
+			await fetchChatAPIProcess<Chat.ConversationResponse>({
+				prompt: message,
+				options,
+				signal: controller.signal,
+				onDownloadProgress: ({ event }) => {
+					const xhr = event.target
+					const { responseText } = xhr
+					// Always process the final line
+					const lastIndex = responseText.lastIndexOf('\n', responseText.length - 2)
+					let chunk = responseText
+					if (lastIndex !== -1)
+						chunk = responseText.substring(lastIndex)
+					try {
+						const data = JSON.parse(chunk)
+						updateChat(
+							+uuid,
+							index,
+							{
+								dateTime: new Date().toLocaleString(),
+								text: lastText + (data.text ?? ''),
+								inversion: false,
+								error: false,
+								loading: true,
+								conversationOptions: { conversationId: data.conversationId, parentMessageId: data.id },
+								requestOptions: { prompt: message, options: { ...options } },
+							},
+						)
+
+						if (openLongReply && data.detail.choices[0].finish_reason === 'length') {
+							options.parentMessageId = data.id
+							lastText = data.text
+							message = ''
+							return fetchChatAPIOnce()
+						}
+					}
+					catch (error) {
+						//
+					}
+				},
+			})
+			updateChatSome(+uuid, index, { loading: false })
+		}
+		await fetchChatAPIOnce()
+	}
+	catch (error: any) {
+		if (error.message === 'canceled') {
+			updateChatSome(
+				+uuid,
+				index,
+				{
+					loading: false,
+				},
+			)
+			return
+		}
+
+		const errorMessage = error?.message ?? t('common.wrong')
+
+		updateChat(
+			+uuid,
+			index,
+			{
+				dateTime: new Date().toLocaleString(),
+				text: errorMessage,
+				inversion: false,
+				error: true,
+				loading: false,
+				conversationOptions: null,
+				requestOptions: { prompt: message, options: { ...options } },
+			},
+		)
+	}
+	finally {
+		loading.value = false
+	}
 }
 
 function handleExport() {
-  if (loading.value)
-    return
-
-  const d = dialog.warning({
-    title: t('chat.exportImage'),
-    content: t('chat.exportImageConfirm'),
-    positiveText: t('common.yes'),
-    negativeText: t('common.no'),
-    onPositiveClick: async () => {
-      try {
-        d.loading = true
-        const ele = document.getElementById('image-wrapper')
-        const canvas = await html2canvas(ele as HTMLDivElement, {
-          useCORS: true,
-        })
-        const imgUrl = canvas.toDataURL('image/png')
-        const tempLink = document.createElement('a')
-        tempLink.style.display = 'none'
-        tempLink.href = imgUrl
-        tempLink.setAttribute('download', 'chat-shot.png')
-        if (typeof tempLink.download === 'undefined')
-          tempLink.setAttribute('target', '_blank')
-
-        document.body.appendChild(tempLink)
-        tempLink.click()
-        document.body.removeChild(tempLink)
-        window.URL.revokeObjectURL(imgUrl)
-        d.loading = false
-        ms.success(t('chat.exportSuccess'))
-        Promise.resolve()
-      }
-      catch (error: any) {
-        ms.error(t('chat.exportFailed'))
-      }
-      finally {
-        d.loading = false
-      }
-    },
-  })
+	if (loading.value)
+		return
+
+	const d = dialog.warning({
+		title: t('chat.exportImage'),
+		content: t('chat.exportImageConfirm'),
+		positiveText: t('common.yes'),
+		negativeText: t('common.no'),
+		onPositiveClick: async () => {
+			try {
+				d.loading = true
+				const ele = document.getElementById('image-wrapper')
+				const canvas = await html2canvas(ele as HTMLDivElement, {
+					useCORS: true,
+				})
+				const imgUrl = canvas.toDataURL('image/png')
+				const tempLink = document.createElement('a')
+				tempLink.style.display = 'none'
+				tempLink.href = imgUrl
+				tempLink.setAttribute('download', 'chat-shot.png')
+				if (typeof tempLink.download === 'undefined')
+					tempLink.setAttribute('target', '_blank')
+
+				document.body.appendChild(tempLink)
+				tempLink.click()
+				document.body.removeChild(tempLink)
+				window.URL.revokeObjectURL(imgUrl)
+				d.loading = false
+				ms.success(t('chat.exportSuccess'))
+				Promise.resolve()
+			}
+			catch (error: any) {
+				ms.error(t('chat.exportFailed'))
+			}
+			finally {
+				d.loading = false
+			}
+		},
+	})
 }
 
 function handleDelete(index: number) {
-  if (loading.value)
-    return
-
-  dialog.warning({
-    title: t('chat.deleteMessage'),
-    content: t('chat.deleteMessageConfirm'),
-    positiveText: t('common.yes'),
-    negativeText: t('common.no'),
-    onPositiveClick: () => {
-      chatStore.deleteChatByUuid(+uuid, index)
-    },
-  })
+	if (loading.value)
+		return
+
+	dialog.warning({
+		title: t('chat.deleteMessage'),
+		content: t('chat.deleteMessageConfirm'),
+		positiveText: t('common.yes'),
+		negativeText: t('common.no'),
+		onPositiveClick: () => {
+			chatStore.deleteChatByUuid(+uuid, index)
+		},
+	})
 }
 
 function handleClear() {
-  if (loading.value)
-    return
-
-  dialog.warning({
-    title: t('chat.clearChat'),
-    content: t('chat.clearChatConfirm'),
-    positiveText: t('common.yes'),
-    negativeText: t('common.no'),
-    onPositiveClick: () => {
-      chatStore.clearChatByUuid(+uuid)
-    },
-  })
+	if (loading.value)
+		return
+
+	dialog.warning({
+		title: t('chat.clearChat'),
+		content: t('chat.clearChatConfirm'),
+		positiveText: t('common.yes'),
+		negativeText: t('common.no'),
+		onPositiveClick: () => {
+			chatStore.clearChatByUuid(+uuid)
+		},
+	})
 }
 
 function handleEnter(event: KeyboardEvent) {
-  if (!isMobile.value) {
-    if (event.key === 'Enter' && !event.shiftKey) {
-      event.preventDefault()
-      handleSubmit()
-    }
-  }
-  else {
-    if (event.key === 'Enter' && event.ctrlKey) {
-      event.preventDefault()
-      handleSubmit()
-    }
-  }
+	if (!isMobile.value) {
+		if (event.key === 'Enter' && !event.shiftKey) {
+			event.preventDefault()
+			handleSubmit()
+		}
+	}
+	else {
+		if (event.key === 'Enter' && event.ctrlKey) {
+			event.preventDefault()
+			handleSubmit()
+		}
+	}
 }
 
 function handleStop() {
-  if (loading.value) {
-    homeStore.setMyData({act:'abort'});
-    controller.abort()
-    loading.value = false
-  }
+	if (loading.value) {
+		homeStore.setMyData({act:'abort'});
+		controller.abort()
+		loading.value = false
+	}
 }
 function handleEdit(index: number) {
   if (loading.value) return;
@@ -462,122 +462,122 @@ function handleEdit(index: number) {
 // 搜索选项计算,这里使用value作为索引项,所以当出现重复value时渲染异常(多项同时出现选中效果)
 // 理想状态下其实应该是key作为索引项,但官方的renderOption会出现问题,所以就需要value反renderLabel实现
 const searchOptions = computed(() => {
-  if (prompt.value.startsWith('/')) {
-    const abc= promptTemplate.value.filter((item: { key: string }) => item.key.toLowerCase().includes(prompt.value.substring(1).toLowerCase())).map((obj: { value: any }) => {
-      return {
-        label: obj.value,
-        value: obj.value,
-      }
-    })
-    mlog('搜索选项', abc);
-    return abc;
-  }else if(prompt.value=='@'){
-    const abc=  gptsUlistStore.myData.slice(0,10).map( (v:gptsType) => {
-      return {
-        label:v.info,
-        gpts:v,
-        value:v.gid
-      }
-    })
-   return abc ;
-  }else {
-    return []
-  }
+	if (prompt.value.startsWith('/')) {
+		const abc= promptTemplate.value.filter((item: { key: string }) => item.key.toLowerCase().includes(prompt.value.substring(1).toLowerCase())).map((obj: { value: any }) => {
+			return {
+				label: obj.value,
+				value: obj.value,
+			}
+		})
+		mlog('搜索选项', abc);
+		return abc;
+	}else if(prompt.value=='@'){
+		const abc=  gptsUlistStore.myData.slice(0,10).map( (v:gptsType) => {
+			return {
+				label:v.info,
+				gpts:v,
+				value:v.gid
+			}
+		})
+		return abc ;
+	}else {
+		return []
+	}
 })
 
 const goUseGpts= async ( item: gptsType)=>{
-    const saveObj= {model:  `${ item.gid }`   ,gpts:item}
-    gptConfigStore.setMyData(saveObj);
-    if(chatStore.active){ //保存到对话框
-        const chatSet = new chatSetting( chatStore.active );
-        if( chatSet.findIndex()>-1 ) chatSet.save( saveObj )
-    }
-    ms.success(t('mjchat.success2'));
-    // const gptUrl= `https://gpts.ddaiai.com/open/gptsapi/use`;
-    // myFetch(gptUrl,item );
+	const saveObj= {model:  `${ item.modelName }`   ,gpts:item}
+	gptConfigStore.setMyData(saveObj);
+	if(chatStore.active){ //保存到对话框
+		const chatSet = new chatSetting( chatStore.active );
+		if( chatSet.findIndex()>-1 ) chatSet.save( saveObj )
+	}
+	ms.success(t('mjchat.success2'));
+	// const gptUrl= `https://gpts.ddaiai.com/open/gptsapi/use`;
+	// myFetch(gptUrl,item );
 
-    mlog('go local ', homeStore.myData.local );
-    if(homeStore.myData.local!=='Chat') router.replace({name:'Chat',params:{uuid:chatStore.active}});
+	mlog('go local ', homeStore.myData.local );
+	if(homeStore.myData.local!=='Chat') router.replace({name:'Chat',params:{uuid:chatStore.active}});
 
-    gptsUlistStore.setMyData( item );
+	gptsUlistStore.setMyData( item );
 
 }
 
 // value反渲染key
 const renderOption = (option: { label: string,gpts?:gptsType }) => {
-  if( prompt.value=='@'){
-    //return [ h( NAvatar,{src:'https://cos.aitutu.cc/gpts/gpt4all.jpg',size:"small",round:true}),option.label ]
-    return [h("div",{class:'flex justify-start items-center'
-    , onclick:()=>{
-      if(option.gpts)   goUseGpts(option.gpts) ;
-      prompt.value='';
-      setTimeout(() =>  prompt.value='', 80);
-    }}
-    ,[h(NAvatar,{src:option.gpts?.logo, "fallback-src" : 'https://cos.aitutu.cc/gpts/3.5net.png',size:"small",round:true, class:"w-8 h-8"})
-    , h('span', { class: 'pl-1' }, option.gpts?.name  )
-    , h('span', { class: 'line-clamp-1 flex-1 pl-1 opacity-50' }, option.label  )
-    ])]
-  }
-  for (const i of promptTemplate.value) {
-    if (i.value === option.label)
-      return [i.key]
-  }
-  return []
+	if( prompt.value=='@'){
+		//return [ h( NAvatar,{src:'https://cos.aitutu.cc/gpts/gpt4all.jpg',size:"small",round:true}),option.label ]
+		return [h("div",{class:'flex justify-start items-center'
+				, onclick:()=>{
+					if(option.gpts)   goUseGpts(option.gpts) ;
+					prompt.value='';
+					setTimeout(() =>  prompt.value='', 80);
+				}}
+			,[h(NAvatar,{src:option.gpts?.logo, "fallback-src" : 'https://cos.aitutu.cc/gpts/3.5net.png',size:"small",round:true, class:"w-8 h-8"})
+				, h('span', { class: 'pl-1' }, option.gpts?.name  )
+				, h('span', { class: 'line-clamp-1 flex-1 pl-1 opacity-50' }, option.label  )
+			])]
+	}
+	for (const i of promptTemplate.value) {
+		if (i.value === option.label)
+			return [i.key]
+	}
+	return []
 }
 
 const placeholder = computed(() => {
-  if (isMobile.value)
-    return t('chat.placeholderMobile')
-  return t('chat.placeholder')
+	if (isMobile.value)
+		return t('chat.placeholderMobile')
+	return t('chat.placeholder')
 })
 
 const buttonDisabled = computed(() => {
-  return loading.value || !prompt.value || prompt.value.trim() === ''
+	return loading.value || !prompt.value || prompt.value.trim() === ''
 })
 
 const footerClass = computed(() => {
-  let classes = ['p-4']
-  if (isMobile.value)
-    classes = ['sticky', 'left-0', 'bottom-0', 'right-0', 'p-2', 'pr-3'] //, 'overflow-hidden'
-  return classes
+	let classes = ['p-4']
+	if (isMobile.value)
+		classes = ['sticky', 'left-0', 'bottom-0', 'right-0', 'p-2', 'pr-3'] //, 'overflow-hidden'
+	return classes
 })
 
 
 onMounted(() => {
-  scrollToBottom()
-  if (inputRef.value && !isMobile.value)
-    inputRef.value?.focus()
-  // 查询公告信息
-  selectNotice()
-  // 查询通知信息
-  selectInform()
+	scrollToBottom()
+	if (inputRef.value && !isMobile.value)
+		inputRef.value?.focus()
+	// 查询公告信息
+	selectNotice()
+	// 查询通知信息
+	selectInform()
 })
 
 onUnmounted(() => {
 
-  if (loading.value)   controller.abort()
-  homeStore.setMyData({isLoader:false});
+	if (loading.value)   controller.abort()
+	homeStore.setMyData({isLoader:false});
 })
 
 const local= computed(()=>homeStore.myData.local );
 watch(()=>homeStore.myData.act,(n)=>{
-    if(n=='draw')  scrollToBottom();
-    if(n=='scrollToBottom') scrollToBottom();
-    if(n=='scrollToBottomIfAtBottom') scrollToBottomIfAtBottom();
-    if(n=='gpt.submit' || n=='gpt.resubmit'){ loading.value = true;}
-    if(n=='stopLoading'){ loading.value = false;}
+	if(n=='draw')  scrollToBottom();
+	if(n=='scrollToBottom') scrollToBottom();
+	if(n=='scrollToBottomIfAtBottom') scrollToBottomIfAtBottom();
+	if(n=='gpt.submit' || n=='gpt.resubmit'){ loading.value = true;}
+	if(n=='stopLoading'){ loading.value = false;}
 });
 const st =ref({inputme:true});
 
 watch( ()=>loading.value ,(n)=> homeStore.setMyData({isLoader:n }))
 
 const ychat = computed( ()=>{
-  let text= prompt.value
-  if (loading.value) text= "";
-  else {
-    scrollToBottomIfAtBottom();
-  }
-  return { text, dateTime: t('chat.preview')} as Chat.Chat;
+	let text= prompt.value
+	if (loading.value) text= "";
+	else {
+		scrollToBottomIfAtBottom();
+	}
+	return { text, dateTime: t('chat.preview')} as Chat.Chat;
 })
 
 const showModal = ref(false);
@@ -585,260 +585,258 @@ const modalContent = ref('<h2>暂无内容</h2>');
 const informContent = ref([]);
 const noticeId = ref('');
 async function selectNotice() {
-  const [err, result] = await to(getNotice());
-  console.log("result?.data",result?.data)
-  if (result?.data) {
-    showModal.value = true
-    noticeId.value = result.data.noticeId
-    modalContent.value = result.data.noticeContent
-  }
+	const [err, result] = await to(getNotice());
+	console.log("result?.data",result?.data)
+	if (result?.data) {
+		showModal.value = true
+		noticeId.value = result.data.noticeId
+		modalContent.value = result.data.noticeContent
+	}
 }
 async function selectInform() {
-  const [err, result] = await to(getInform());
-  if (result?.rows) {
-    informContent.value = result.rows.length ? result.rows : []
-  }
+	const [err, result] = await to(getInform());
+	if (result?.rows) {
+		informContent.value = result.rows.length ? result.rows : []
+	}
 }
 
 async function handleClose(){
-  await to(readNotice(noticeId.value));
+	await to(readNotice(noticeId.value));
 }
 const gptsList = ref<gptsType[]>([]);
 const gptsFilterList = ref<gptsType[]>([]);
 const getRandowNum = (Min:number, Max: number):number =>{
-  const Range = Max - Min + 1
-  const Rand = Math.random()
-  return Min + Math.floor(Rand * Range)
+	const Range = Max - Min + 1
+	const Rand = Math.random()
+	return Min + Math.floor(Rand * Range)
 }
 const load= async ()=>{
 
-    // const gptUrl= homeStore.myData.session.gptUrl?  homeStore.myData.session.gptUrl :'';
-    // mlog('load',gptUrl );
-    //  let d;
-    // if( homeStore.myData.session.gptUrl ){
-    //    d = await my2Fetch( homeStore.myData.session.gptUrl  );
-    // }else {
-
-    //     d = await myFetch('https://gpts.ddaiai.com/open/gpts');
-    // }
-
-    const params = { pageNum: 1, pageSize: 20 };
-        const [err, result] = await to(getGpts(params));
-        if(err){
-            console.log("err===",err)
-        }else{
-            gptsList.value = result.rows as unknown as gptsType[];
-        }
-
-
-     // gptsList.value = d.gpts as gptsType[];
-    if(gptsList.value.length && gptsList.value.length > 3) {
-      gptsFilterList.value = gptsList.value.slice(0, 4)
-    }
+	// const gptUrl= homeStore.myData.session.gptUrl?  homeStore.myData.session.gptUrl :'';
+	// mlog('load',gptUrl );
+	//  let d;
+	// if( homeStore.myData.session.gptUrl ){
+	//    d = await my2Fetch( homeStore.myData.session.gptUrl  );
+	// }else {
+
+	//     d = await myFetch('https://gpts.ddaiai.com/open/gpts');
+	// }
+
+	const params = { pageNum: 1, pageSize: 20 };
+	const [err, result] = await to(getGpts(params));
+	if(err){
+		console.log("err===",err)
+	}else{
+		gptsList.value = result.rows as unknown as gptsType[];
+	}
+
+
+	// gptsList.value = d.gpts as gptsType[];
+	if(gptsList.value.length && gptsList.value.length > 3) {
+		gptsFilterList.value = gptsList.value.slice(0, 4)
+	}
 }
 const refresh = () => {
-  gptsFilterList.value = []
-  let num = gptsList.value[getRandowNum(0, gptsList.value.length - 1)]
-  let num1 = gptsList.value[getRandowNum(0, gptsList.value.length - 1)]
-  let num2 = gptsList.value[getRandowNum(0, gptsList.value.length - 1)]
-  let num3 = gptsList.value[getRandowNum(0, gptsList.value.length - 1)]
-  let arr = [num, num1, num2, num3]
-  if(Array.from(new Set(arr)).length != 4) {
-    refresh()
-    return
-  }
-  gptsFilterList.value = [num, num1, num2, num3]
+	gptsFilterList.value = []
+	let num = gptsList.value[getRandowNum(0, gptsList.value.length - 1)]
+	let num1 = gptsList.value[getRandowNum(0, gptsList.value.length - 1)]
+	let num2 = gptsList.value[getRandowNum(0, gptsList.value.length - 1)]
+	let num3 = gptsList.value[getRandowNum(0, gptsList.value.length - 1)]
+	let arr = [num, num1, num2, num3]
+	if(Array.from(new Set(arr)).length != 4) {
+		refresh()
+		return
+	}
+	gptsFilterList.value = [num, num1, num2, num3]
 }
 load()
 
 </script>
 
 <template>
-  <NModal
-  v-model:show="showModal"
-   closable @on-after-leave=""
-   :mask-closable="false"
-    preset="dialog"
-    title="公告详情"
-    positive-text="我已知晓"
-    @positive-click="handleClose"
-   >
-   <div v-html="modalContent"></div>
-  </NModal>
-
-  <div class="flex flex-col w-full h-full chat-content" :class="[isMobile? '' : 'chat-content-noMobile']">
-   <!-- v-if="isMobile" -->
-    <!-- <HeaderComponent
-      :haveData="!!dataSources.length"
-      :using-context="usingContext"
-      @export="handleExport"
-      @handle-clear="handleClear"
-    /> -->
-
-    <main class="flex-1 overflow-hidden">
-
-      <template v-if="gptConfigStore.myData.kid">
-        <div class="flex  mt-4  text-neutral-300 chat-header">
-           <SvgIcon icon="material-symbols:book" class="mr-1 text-2xl" ></SvgIcon>
-           <span>{{ gptConfigStore.myData.kName }}</span>
-        </div>
-      </template>
-
-      <div id="scrollRef" ref="scrollRef" class="h-full overflow-hidden overflow-y-auto">
-
-        <div
-          id="image-wrapper"
-          class="w-full max-w-screen-xl m-auto dark:bg-[#101014]"
-          :class="[isMobile ? 'p-2' : 'p-4']"
-        >
-          <template v-if="!dataSources.length">
-            <div v-if="homeStore.myData.session.notify" v-html="homeStore.myData.session.notify" class="text-neutral-300 mt-4">
-
-            </div>
-            <div class="gpts-box" v-else>
-              <h1>{{ href }}</h1>
-              <div class="annou" v-if="informContent.length" :style="{'margin-bottom': isMobile ? '15px' : '30px'}">
-                <div class="ai-icon">
-                  <IconSvg icon="chatGPT" :width="isMobile ? '32px' : '64px'" :height="isMobile ? '32px' : '64px'"></IconSvg>
-                </div>
-                <div class="text" :style="{padding: isMobile? '22px 10px' : '22px 68px'}">
-                  <p class="title">{{ t('chat.annouce') }}</p>
-                  <!-- <p v-for="(item,index) in t('chat.annouceContent').split(';')" :key="index">{{ item }}</p> -->
-                  <div v-for="(item, index) in informContent.slice(0, 1)" :key="index" >
-                    <!-- <p style="margin-top: 10px; font-size: 18px">{{ item.noticeTitle }}</p> -->
-                    <div v-html="item.noticeContent"></div>
-                  </div>
-                </div>
-              </div>
-              <div class="help" v-if="gptsFilterList && gptsFilterList.length">
-                <div class="ai-icon">
-                  <IconSvg icon="chatGPT" :width="isMobile ? '32px' : '64px'" :height="isMobile ? '32px' : '64px'"></IconSvg>
-                </div>
-                <div class="text" :style="{padding: isMobile? '22px 10px' : '22px 68px', 'font-size': isMobile? '14px' : '16px', 'line-height': isMobile? '20px' : '28px'}">
-                  <p class="title">
-                    {{ t('chat.helpTitle') }}
-                  </p>
-                  <p v-for="(item,index) in t('chat.helpcontent').split(';')" :key="index">{{ item }}</p>
-                  <div class="gpts-list">
-                    <div class="refresh" @click="refresh">
-                      <IconSvg icon="refresh"></IconSvg>&nbsp;{{ t('chat.refresh') }}
-                    </div>
-                    <div v-for="v in gptsFilterList" :key="v.name" class="gpts-item" :style="{width: isMobile ? '100%' : 'calc(50% - 20px)', marginRight: '20px', padding: isMobile ? '5px 8px' : '14px 10px', 'margin-bottom': isMobile ? '8px' : '20px'}">
-                      <NImage :src="v.logo" :preview-disabled="true" lazy
-                      class="group-hover:scale-[130%] duration-300 shrink-0 overflow-hidden bg-base object-cover rounded-full bc-avatar w-[80px] h-[80px]" :style="{width: isMobile ? '23px' : '46px', height: isMobile ? '23px' : '46px'}">
-                          <template #placeholder>
-                            <div class="w-full h-full justify-center items-center flex"  >
-                            <SvgIcon icon="line-md:downloading-loop" class="text-[60px] text-green-300"   ></SvgIcon>
-                            </div>
-                          </template>
-                      </NImage>
-                      <div :style="{width: `calc(100% - ${isMobile ? '43px' : '66px'})`, float: 'left', marginLeft: '10px'}">
-                        <p class="info" :title="v.info"> {{ v.info }}</p>
-                        <p @click="goUseGpts(v)" class="name"> {{ t('chat.used') }} {{ v.name }}</p>
-                      </div>
-                    </div>
-                  </div>
-                </div>
-              </div>
-            </div>
-            <!-- <div class="flex items-center justify-center mt-4 text-center text-neutral-300" v-else>
-              <SvgIcon icon="ri:bubble-chart-fill" class="mr-2 text-3xl" />
-              <span>Aha~</span>
-            </div> -->
-          </template>
-
-          <template v-else>
-            <div>
-              <Message
-                v-for="(item, index) of dataSources"
-                :key="index"
-                :date-time="item.dateTime"
-                :text="item.text"
-                :inversion="item.inversion"
-                :error="item.error"
-                :loading="item.loading"
-                @regenerate="onRegenerate(index)"
-                @delete="handleDelete(index)"
+	<NModal
+		v-model:show="showModal"
+		closable @on-after-leave=""
+		:mask-closable="false"
+		preset="dialog"
+		title="公告详情"
+		positive-text="我已知晓"
+		@positive-click="handleClose"
+	>
+		<div v-html="modalContent"></div>
+	</NModal>
+
+	<div class="flex flex-col w-full h-full chat-content" :class="[isMobile? '' : 'chat-content-noMobile']">
+		<!-- v-if="isMobile" -->
+		<!-- <HeaderComponent
+			:haveData="!!dataSources.length"
+			:using-context="usingContext"
+			@export="handleExport"
+			@handle-clear="handleClear"
+		/> -->
+
+		<main class="flex-1 overflow-hidden">
+
+			<div id="scrollRef" ref="scrollRef" class="h-full overflow-hidden overflow-y-auto">
+
+				<div
+					id="image-wrapper"
+					class="w-full max-w-screen-xl m-auto dark:bg-[#101014]"
+					:class="[isMobile ? 'p-2' : 'p-4']"
+				>
+					<template v-if="!dataSources.length">
+						<div v-if="homeStore.myData.session.notify" v-html="homeStore.myData.session.notify" class="text-neutral-300 mt-4">
+
+						</div>
+
+						<div class="gpts-box" v-else>
+							<br>
+							<br>
+							<div  v-if="local!=='draw'">
+<!--							<h1>{{ href }}</h1>-->
+							<div class="annou" v-if="informContent.length" :style="{'margin-bottom': isMobile ? '15px' : '30px'}">
+								<div class="ai-icon">
+									<IconSvg icon="chatGPT" :width="isMobile ? '32px' : '64px'" :height="isMobile ? '32px' : '64px'"></IconSvg>
+								</div>
+								<div class="text" :style="{padding: isMobile? '22px 10px' : '22px 68px'}">
+									<p class="title">{{ t('chat.annouce') }}</p>
+									<!-- <p v-for="(item,index) in t('chat.annouceContent').split(';')" :key="index">{{ item }}</p> -->
+									<div v-for="(item, index) in informContent.slice(0, 1)" :key="index" >
+										<!-- <p style="margin-top: 10px; font-size: 18px">{{ item.noticeTitle }}</p> -->
+										<div v-html="item.noticeContent"></div>
+									</div>
+								</div>
+							</div>
+							<div class="help" v-if="gptsFilterList && gptsFilterList.length">
+								<div class="ai-icon">
+									<IconSvg icon="chatGPT" :width="isMobile ? '32px' : '64px'" :height="isMobile ? '32px' : '64px'"></IconSvg>
+								</div>
+								<div class="text" :style="{padding: isMobile? '22px 10px' : '22px 68px', 'font-size': isMobile? '14px' : '16px', 'line-height': isMobile? '20px' : '28px'}">
+									<p class="title">
+										{{ t('chat.helpTitle') }}
+									</p>
+									<p v-for="(item,index) in t('chat.helpcontent').split(';')" :key="index">{{ item }}</p>
+									<div class="gpts-list">
+										<div class="refresh" @click="refresh">
+											<IconSvg icon="refresh"></IconSvg>&nbsp;{{ t('chat.refresh') }}
+										</div>
+										<div v-for="v in gptsFilterList" :key="v.name" class="gpts-item" :style="{width: isMobile ? '100%' : 'calc(50% - 20px)', marginRight: '20px', padding: isMobile ? '5px 8px' : '14px 10px', 'margin-bottom': isMobile ? '8px' : '20px'}">
+											<NImage :src="v.logo" :preview-disabled="true" lazy
+															class="group-hover:scale-[130%] duration-300 shrink-0 overflow-hidden bg-base object-cover rounded-full bc-avatar w-[80px] h-[80px]" :style="{width: isMobile ? '23px' : '46px', height: isMobile ? '23px' : '46px'}">
+												<template #placeholder>
+													<div class="w-full h-full justify-center items-center flex"  >
+														<SvgIcon icon="line-md:downloading-loop" class="text-[60px] text-green-300"   ></SvgIcon>
+													</div>
+												</template>
+											</NImage>
+											<div :style="{width: `calc(100% - ${isMobile ? '43px' : '66px'})`, float: 'left', marginLeft: '10px'}">
+												<p class="info" :title="v.info"> {{ v.info }}</p>
+												<p @click="goUseGpts(v)" class="name"> {{ t('chat.used') }} {{ v.name }}</p>
+											</div>
+										</div>
+									</div>
+								</div>
+							</div>
+						</div>
+						</div>
+						<!-- <div class="flex items-center justify-center mt-4 text-center text-neutral-300" v-else>
+							<SvgIcon icon="ri:bubble-chart-fill" class="mr-2 text-3xl" />
+							<span>Aha~</span>
+						</div> -->
+					</template>
+
+					<template v-else>
+						<div>
+							<Message
+								v-for="(item, index) of dataSources"
+								:key="index"
+								:date-time="item.dateTime"
+								:text="item.text"
+								:inversion="item.inversion"
+								:error="item.error"
+								:loading="item.loading"
+								@regenerate="onRegenerate(index)"
+								@delete="handleDelete(index)"
 								@edit="handleEdit(index)"
-                :chat="item"
-                :index="index"
-              />
-              <Message  v-if="ychat.text && !homeStore.myData.session.isCloseMdPreview"
-              :key="dataSources.length" :inversion="true"
-              :date-time="$t('mj.typing')"
-              :chat="ychat"
-              :text="ychat.text"
-              :index="dataSources.length"
-              />
-              <div class="sticky bottom-0 left-0 flex justify-center">
-                <NButton v-if="loading" type="warning" @click="handleStop">
-                  <template #icon>
-                    <SvgIcon icon="ri:stop-circle-line" />
-                  </template>
-                  {{ t('common.stopResponding') }}
-                </NButton>
-              </div>
-            </div>
-          </template>
-        </div>
-      </div>
-    </main>
-
-    <footer :class="footerClass" class="footer-content" v-if="local!=='draw'">
-      <div class="w-full max-w-screen-xl m-auto">
-        <aiGptInput @handle-clear="handleClear" @export="handleExport" v-if="['gpt-4o-mini','gpt-3.5-turbo-16k'].indexOf(gptConfigStore.myData.model)>-1 || st.inputme "
-         v-model:modelValue="prompt" :disabled="buttonDisabled"
-         :searchOptions="searchOptions"  :renderOption="renderOption"
-          />
-        <div class="flex items-center justify-between space-x-2" v-else>
-          <!--
-          <HoverButton v-if="!isMobile" @click="handleClear">
-            <span class="text-xl text-[#4f555e] dark:text-white">
-              <SvgIcon icon="ri:delete-bin-line" />
-            </span>
-          </HoverButton>
-          <HoverButton v-if="!isMobile" @click="handleExport">
-            <span class="text-xl text-[#4f555e] dark:text-white">
-              <SvgIcon icon="ri:download-2-line" />
-            </span>
-          </HoverButton>
-          <HoverButton @click="toggleUsingContext">
-            <span class="text-xl" :class="{ 'text-[#4b9e5f]': usingContext, 'text-[#a8071a]': !usingContext }">
-              <SvgIcon icon="ri:chat-history-line" />
-            </span>
-          </HoverButton>
-          -->
-
-          <NAutoComplete v-model:value="prompt" :options="searchOptions" :render-label="renderOption">
-            <template #default="{ handleInput, handleBlur, handleFocus }">
-              <NInput
-                ref="inputRef"
-                v-model:value="prompt"
-                type="textarea"
-                :placeholder="placeholder"
-                :autosize="{ minRows: 1, maxRows: isMobile ? 4 : 8 }"
-                @input="handleInput"
-                @focus="handleFocus"
-                @blur="handleBlur"
-                @keypress="handleEnter"
-              />
-            </template>
-          </NAutoComplete>
-          <NButton type="primary" :disabled="buttonDisabled" @click="handleSubmit">
-            <template #icon>
+								:chat="item"
+								:index="index"
+							/>
+							<Message  v-if="ychat.text && !homeStore.myData.session.isCloseMdPreview"
+												:key="dataSources.length" :inversion="true"
+												:date-time="$t('mj.typing')"
+												:chat="ychat"
+												:text="ychat.text"
+												:index="dataSources.length"
+							/>
+							<div class="sticky bottom-0 left-0 flex justify-center">
+								<NButton v-if="loading" type="warning" @click="handleStop">
+									<template #icon>
+										<SvgIcon icon="ri:stop-circle-line" />
+									</template>
+									{{ t('common.stopResponding') }}
+								</NButton>
+							</div>
+						</div>
+					</template>
+				</div>
+			</div>
+		</main>
+
+		<footer :class="footerClass" class="footer-content" v-if="local!=='draw'">
+			<div class="w-full max-w-screen-xl m-auto">
+				<aiGptInput @handle-clear="handleClear" @export="handleExport" v-if="['gpt-4o-mini','gpt-3.5-turbo-16k'].indexOf(gptConfigStore.myData.model)>-1 || st.inputme "
+										v-model:modelValue="prompt" :disabled="buttonDisabled"
+										:searchOptions="searchOptions"  :renderOption="renderOption"
+				/>
+				<div class="flex items-center justify-between space-x-2" v-else>
+					<!--
+					<HoverButton v-if="!isMobile" @click="handleClear">
+						<span class="text-xl text-[#4f555e] dark:text-white">
+							<SvgIcon icon="ri:delete-bin-line" />
+						</span>
+					</HoverButton>
+					<HoverButton v-if="!isMobile" @click="handleExport">
+						<span class="text-xl text-[#4f555e] dark:text-white">
+							<SvgIcon icon="ri:download-2-line" />
+						</span>
+					</HoverButton>
+					<HoverButton @click="toggleUsingContext">
+						<span class="text-xl" :class="{ 'text-[#4b9e5f]': usingContext, 'text-[#a8071a]': !usingContext }">
+							<SvgIcon icon="ri:chat-history-line" />
+						</span>
+					</HoverButton>
+					-->
+
+					<NAutoComplete v-model:value="prompt" :options="searchOptions" :render-label="renderOption">
+						<template #default="{ handleInput, handleBlur, handleFocus }">
+							<NInput
+								ref="inputRef"
+								v-model:value="prompt"
+								type="textarea"
+								:placeholder="placeholder"
+								:autosize="{ minRows: 1, maxRows: isMobile ? 4 : 8 }"
+								@input="handleInput"
+								@focus="handleFocus"
+								@blur="handleBlur"
+								@keypress="handleEnter"
+							/>
+						</template>
+					</NAutoComplete>
+					<NButton type="primary" :disabled="buttonDisabled" @click="handleSubmit">
+						<template #icon>
               <span class="dark:text-black">
                 <SvgIcon icon="ri:send-plane-fill" />
               </span>
-            </template>
-          </NButton>
+						</template>
+					</NButton>
 
-        </div>
-      </div>
-    </footer>
-  </div>
+				</div>
+			</div>
+		</footer>
+	</div>
 
-  <drawListVue />
-  <aiGPT @finished="loading = false" />
-  <AiSiderInput v-if="isMobile"  :button-disabled="false" />
+	<drawListVue />
+	<aiGPT @finished="loading = false" />
+	<AiSiderInput v-if="isMobile"  :button-disabled="false" />
 
 </template>

+ 1 - 1
src/views/fanyi/components/textComponent.vue

@@ -149,7 +149,7 @@ onMounted(() => {
 
 async function getModelList() {
 	try {
-		const res = await modelList();
+		const res = await modelList('chat');
 		modelListData.value = res.data;
 		model.value = modelListData.value[0]?.modelDescribe || "";
 	} catch (error) {

+ 16 - 5
src/views/knowledge/index.vue

@@ -26,9 +26,10 @@ import {
 import to from "await-to-js";
 import { useRouter } from "vue-router";
 import { t } from "@/locales";
+import { modelList } from "@/api/model";
 
 onMounted(() => {
-	fetchData();
+	fetchData(),getModelList();
 });
 
 const router = useRouter();
@@ -102,12 +103,20 @@ const placement = ref<DrawerPlacement>("right");
 
 const getVector = reactive([
 	{ label: "weaviate", value: "weaviate" },
-	{ label: "milvus", value: "milvus" },
+	{ label: "milvus", value: "milvus" }, 
 ]);
 
-const getVectorModel = reactive([
-	{ label: "text-embedding-3-small", value: "text-embedding-3-small" },
-]);
+const getVectorModel = ref([]);
+
+async function getModelList() {
+	try {
+		const res = await modelList('vector');
+		getVectorModel.value = res.data;
+	} catch (error) {
+		console.error("获取模型列表失败:", error);
+		message.error("获取模型列表失败");
+	}
+}
 
 const createColumns = () => {
 	return [
@@ -317,6 +326,8 @@ const columns = ref(createColumns());
 								<n-select
 									:options="getVectorModel"
 									v-model:value="formValue.vectorModel"
+									value-field="modelDescribe"
+									label-field="modelName"
 									placeholder="请选择向量模型"
 									clearable
 								></n-select>

+ 2 - 2
src/views/login/index.vue

@@ -90,7 +90,7 @@ let intervalId: string | number | NodeJS.Timer | undefined;
 const POLLING_INTERVAL = 3000;
 
 async function handleWxLogin() {
-
+	
 	showModal.value = true
 	//获取二维码信息
 	const [err1, res1] = await to(getMpQrCode());
@@ -101,7 +101,7 @@ async function handleWxLogin() {
 		ticket.value = res1.data.ticket;
 		intervalId = setInterval(slectLoginType, POLLING_INTERVAL);
 	}
-
+	
 }
 
 // 1. 定时查询是否登录成功

+ 11 - 5
src/views/mj/aiGpt.vue

@@ -12,7 +12,7 @@ import { t } from "@/locales";
 const emit = defineEmits(['finished']);
 const { addChat , updateChatSome } = useChat()
 const chatStore = useChatStore()
-const st=ref({uuid:'1002', index:-1 });
+const st=ref({uuid:'1002', index:-1, chatType:0, appId:'' });
 const controller = ref<AbortController>( );;// new AbortController();
 const dataSources = computed(() => chatStore.getChatByUuid(+st.value.uuid))
 const ms= useMessage();
@@ -52,8 +52,10 @@ watch(()=>homeStore.myData.act, async (n)=>{
 
         let  uuid2 =  dd.uuid?? uuid;
         st.value.uuid =  uuid2 ;
+        st.value.chatType = dd.chatType;
+        st.value.appId = dd.appId??'';
         const chatSet = new chatSetting(   +st.value.uuid  );
-        const nGptStore =   chatSet.getGptConfig()  ;
+        const nGptStore =   chatSet.getGptConfig();
          mlog('gpt.submit', dd , dd.uuid,  nGptStore ) ;
         let model = nGptStore.model ;//gptConfigStore.myData.model
 
@@ -145,8 +147,10 @@ watch(()=>homeStore.myData.act, async (n)=>{
         let historyMesg=  await getMessage();
         mlog('historyMesg', historyMesg );
         //return ;
-        let message= [ {  "role": "system", "content": getSystemMessage(  +uuid2) },
-                ...historyMesg ];
+        // let message= [ {  "role": "system", "content": getSystemMessage(  +uuid2) },
+        //         ...historyMesg ];
+        let message= [...historyMesg ];
+                
         if( dd.fileBase64 && dd.fileBase64.length>0 ){
             if(isCanBase64Model(model)){ 
                 let obj={
@@ -307,7 +311,9 @@ const submit= (model:string, message:any[],opt?:any)=>{
                     goFinish()
                 },
                 signal: controller.value.signal,
-                kid: ''
+                kid: '',
+                chatType: st.value.chatType,
+                appId: st.value.appId
             }).then(()=>goFinish() ).catch(e=>{
                 if(e.message!='canceled')  textRz.value.push("\n"+t('mj.fail')+":\n```\n"+(e.reason??JSON.stringify(e,null,2)) +"\n```\n")
                 goFinish();

+ 47 - 11
src/views/mj/aiGptInput.vue

@@ -39,7 +39,7 @@ const { iconRender } = useIconRender();
 //import FormData from 'form-data'
 const route = useRoute();
 const chatStore = useChatStore();
-const emit = defineEmits(["update:modelValue", "export", "handleClear"]);
+const emit = defineEmits(["update:modelValue", "update:chatType","export", "handleClear"]);
 const props = defineProps<{
 	modelValue: string;
 	disabled?: boolean;
@@ -53,12 +53,14 @@ const st = ref<{
 	isShow: boolean;
 	showMic: boolean;
 	micStart: boolean;
+	chatType: boolean;
 }>({
 	fileBase64: [],
 	isLoad: 0,
 	isShow: false,
 	showMic: false,
 	micStart: false,
+	chatType: false,
 });
 const { isMobile } = useBasicLayout();
 const placeholder = computed(() => {
@@ -94,6 +96,8 @@ const handleSubmit = () => {
 	let obj = {
 		prompt: mvalue.value,
 		fileBase64: st.value.fileBase64,
+		chatType: st.value.chatType? 1 : 0,
+		appId: gptConfigStore.myData.gpts? gptConfigStore.myData.gpts.id : ''
 	};
 	homeStore.setMyData({ act: "gpt.submit", actData: obj });
 	mvalue.value = "";
@@ -109,6 +113,11 @@ const mvalue = computed({
 		emit("update:modelValue", value);
 	},
 });
+const chatTypeBn = computed({
+	get() {
+		return st.value.chatType ? "info" : "";
+	},
+});
 function selectFile(input: any) {
 	const file = input.target.files[0];
 	upFile(file);
@@ -215,7 +224,6 @@ const paste = (e: ClipboardEvent) => {
 	let rz = getFileFromClipboard(e);
 	if (rz.length > 0) upFile(rz[0]);
 };
-
 const sendMic = (e: any) => {
 	mlog("sendMic", e);
 	st.value.showMic = false;
@@ -279,6 +287,22 @@ const handleSelectASR = (key: string | number) => {
 	if (key == "asr") goASR();
 	if (key == "whisper") st.value.showMic = true;
 };
+/**
+ * 校验字符串的大小
+ * @param inputStr 输入的字符
+ * @param maxLength 字符串长度
+ */
+const truncateText = (inputStr:any, maxLength = 20) => {
+	// 处理空值情况
+	if (!inputStr) return ''
+	// 类型安全校验
+	const str = String(inputStr)
+	// 判断并截断
+	return str.length > maxLength
+		? `${str.slice(0, maxLength)}...`
+		: str
+}
+
 const show = ref(false);
 function handleExport() {
 	emit("export");
@@ -335,14 +359,14 @@ function handleClear() {
 						<template v-if="nGptStore.gpts">
 							<SvgIcon icon="ri:apps-fill" />
 							<span class="line-clamp-1 overflow-hidden">{{
-								nGptStore.gpts.name
-							}}</span>
+									nGptStore.gpts.name
+								}}</span>
 						</template>
 						<template v-else>
 							<SvgIcon icon="heroicons:sparkles" />
-							<span>{{
-								nGptStore.modelLabel ? nGptStore.modelLabel : "gpt-4o-mini"
-							}}</span>
+							<span>模型:{{
+									nGptStore.modelLabel ? truncateText(nGptStore.modelLabel,20) : "deepseek-r1:1.5b"
+								}} {{nGptStore.kid?'知识库:'+truncateText(nGptStore.kName,10):''}}</span>
 						</template>
 						<SvgIcon icon="icon-park-outline:right" />
 					</div>
@@ -395,6 +419,18 @@ function handleClear() {
 						width="22px"
 						height="22px"
 					></IconSvg>
+					<n-tooltip trigger="hover">
+						<template #trigger>
+							<n-tag :bordered="false" :type="chatTypeBn" :round="true" style="margin-top:-3px;margin-left:-5px">
+								<IconSvg  style="margin:0px 5px"  @click="st.chatType = !st.chatType"
+									icon="search"
+									width="22px"
+									height="22px"
+								></IconSvg>
+							</n-tag>
+						</template>
+						联网搜索
+				</n-tooltip>
 				</div>
 				<IconSvg
 					@click="handleClear"
@@ -439,8 +475,8 @@ function handleClear() {
 							<p class="py-1" v-text="$t('mj.tokenInfo2')"></p>
 							<p class="text-right">
 								<NButton @click="st.isShow = true" type="info" size="small">{{
-									$t("setting.setting")
-								}}</NButton>
+										$t("setting.setting")
+									}}</NButton>
 							</p>
 						</div>
 					</NPopover>
@@ -555,8 +591,8 @@ function handleClear() {
 						<p class="py-1" v-text="$t('mj.tokenInfo2')"></p>
 						<p class="text-right">
 							<NButton @click="st.isShow = true" type="info" size="small">{{
-								$t("setting.setting")
-							}}</NButton>
+									$t("setting.setting")
+								}}</NButton>
 						</p>
 					</div>
 				</NPopover>

+ 197 - 197
src/views/mj/aiModel.vue

@@ -22,228 +22,228 @@ onMounted(() => { fetchData(),fetchDataGetKnowledge() });
 
 const config = ref([])
 const fetchData = async () => {
-    try {
-       // 发起一个请求
-      const [err, result] = await to(modelList());
-
-      if (err) {
-        message.error(err.message)
-        config.value = []; // 设置为空数组,避免迭代错误
-      } else {
-         config.value = result.data;
-      }
-    } catch (error) {
-      console.error('Error fetching data:', error);
-    }
+	try {
+		// 发起一个请求
+		const [err, result] = await to(modelList('chat'));
+
+		if (err) {
+			message.error(err.message)
+			config.value = []; // 设置为空数组,避免迭代错误
+		} else {
+			config.value = result.data;
+		}
+	} catch (error) {
+		console.error('Error fetching data:', error);
+	}
 };
 
 const fetchDataGetKnowledge = async () => {
-    if(getToken()){
-        try {
-       // 发起一个请求
-      const [err, result] = await to(getKnowledge());
-      console.log("result===", result.rows)
-      if (err) {
-        ms.error(err.message)
-      } else {
-        options.value = result.rows.map((item: any) => ({
-            label: item.kname, // 假设后台返回的数据有 'name' 字段
-            value: item.id     // 假设每个数据项都有一个唯一的 'id' 字段
-        }));
-
-        // 请求成功
-        options.value.push({ label: 'please select', value: '' });
-      }
-    } catch (error) {
-      console.error('Error fetching data:', error);
-    }
-    }
-  };
+	if(getToken()){
+		try {
+			// 发起一个请求
+			const [err, result] = await to(getKnowledge());
+			console.log("result===", result.rows)
+			if (err) {
+				ms.error(err.message)
+			} else {
+				options.value = result.rows.map((item: any) => ({
+					label: item.kname, // 假设后台返回的数据有 'name' 字段
+					value: item.id     // 假设每个数据项都有一个唯一的 'id' 字段
+				}));
+
+				// 请求成功
+				options.value.push({ label: '暂不配置', value: '' });
+			}
+		} catch (error) {
+			console.error('Error fetching data:', error);
+		}
+	}
+};
 
 const st= ref({openMore:false });
 const voiceList= computed(()=>{
-    let rz=[];
-    for(let o of "alloy,echo,fable,onyx,nova,shimmer".split(/[ ,]+/ig))rz.push({label:o,value:o})
-    return rz;
+	let rz=[];
+	for(let o of "alloy,echo,fable,onyx,nova,shimmer".split(/[ ,]+/ig))rz.push({label:o,value:o})
+	return rz;
 });
 const modellist = computed(() => { //
-    let rz =[ ];
-    for(let o of config.value){
-        rz.push({label:o.modelDescribe,value:o.modelName})
-    }
-
-    if(gptConfigStore.myData.userModel){
-        let arr = gptConfigStore.myData.userModel.split(/[ ,]+/ig);
-        for(let o of arr ){
-             rz.push({label:o,value:o})
-        }
-    }
-    //服务端的 CUSTOM_MODELS 设置
-    if( homeStore.myData.session.cmodels ){
-        let delModel:string[] = [];
-        let addModel:string[]=[];
-        homeStore.myData.session.cmodels.split(/[ ,]+/ig).map( (v:string)=>{
-            if(v.indexOf('-')==0){
-                delModel.push(v.substring(1))
-            }else{
-                addModel.push(v);
-            }
-        });
-        mlog('cmodels',delModel,addModel);
-        rz= rz.filter(v=> delModel.indexOf(v.value)==-1 );
-        addModel.map(o=>rz.push({label:o,value:o}) )
-    }
-
-    let uniqueArray: { label: string, value: string }[] = Array.from(
-        new Map(rz.map(item => [JSON.stringify(item), item]))
-        .values()
-    );
-    return uniqueArray ;
+	let rz =[ ];
+	for(let o of config.value){
+		rz.push({label:o.modelDescribe,value:o.modelName})
+	}
+
+	if(gptConfigStore.myData.userModel){
+		let arr = gptConfigStore.myData.userModel.split(/[ ,]+/ig);
+		for(let o of arr ){
+			rz.push({label:o,value:o})
+		}
+	}
+	//服务端的 CUSTOM_MODELS 设置
+	if( homeStore.myData.session.cmodels ){
+		let delModel:string[] = [];
+		let addModel:string[]=[];
+		homeStore.myData.session.cmodels.split(/[ ,]+/ig).map( (v:string)=>{
+			if(v.indexOf('-')==0){
+				delModel.push(v.substring(1))
+			}else{
+				addModel.push(v);
+			}
+		});
+		mlog('cmodels',delModel,addModel);
+		rz= rz.filter(v=> delModel.indexOf(v.value)==-1 );
+		addModel.map(o=>rz.push({label:o,value:o}) )
+	}
+
+	let uniqueArray: { label: string, value: string }[] = Array.from(
+		new Map(rz.map(item => [JSON.stringify(item), item]))
+			.values()
+	);
+	return uniqueArray ;
 });
 const ms= useMessage();
 
 const saveChat=(type:string)=>{
-     chatSet.save(  nGptStore.value );
-     gptConfigStore.setMyData( nGptStore.value );
-     homeStore.setMyData({act:'saveChat'});
-     if(type!='hide')ms.success( t('common.saveSuccess'));
-     emit('close');
+	chatSet.save(  nGptStore.value );
+	gptConfigStore.setMyData( nGptStore.value );
+	homeStore.setMyData({act:'saveChat'});
+	if(type!='hide')ms.success( t('common.saveSuccess'));
+	emit('close');
 }
 
 // 添加一个空选项
 const options = ref([]);
 
-  const onSelectChange = (newValue: any) => {
-    const option = options.value.find(optionValue => optionValue.value === newValue);
-    nGptStore.value.kName = option.label;
-  };
+const onSelectChange = (newValue: any) => {
+	const option = options.value.find(optionValue => optionValue.value === newValue);
+	nGptStore.value.kName = option.label;
+};
 
-  const onSelectChange1 = (newValue: any) => {
-    const option = modellist.value.find(optionValue => optionValue.value === newValue);
-    nGptStore.value.modelLabel = option.label;
-  };
+const onSelectChange1 = (newValue: any) => {
+	const option = modellist.value.find(optionValue => optionValue.value === newValue);
+	nGptStore.value.modelLabel = option.label;
+};
 
 watch(()=>nGptStore.value.model,(n)=>{
-    nGptStore.value.gpts=undefined;
-    let max=4096;
-    if( n.indexOf('vision')>-1){
-        max=4096;
-    }else if( n.indexOf('gpt-4')>-1 ||  n.indexOf('16k')>-1 ){ //['16k','8k','32k','gpt-4'].indexOf(n)>-1
-        max=4096*2;
-    }else if( n.toLowerCase().includes('claude-3') ){
-         max=4096*2;
-    }
-    config.value.maxToken=max/2;
-    if(nGptStore.value.max_tokens> config.value.maxToken ) nGptStore.value.max_tokens= config.value.maxToken;
+	nGptStore.value.gpts=undefined;
+	let max=4096;
+	if( n.indexOf('vision')>-1){
+		max=4096;
+	}else if( n.indexOf('gpt-4')>-1 ||  n.indexOf('16k')>-1 ){ //['16k','8k','32k','gpt-4'].indexOf(n)>-1
+		max=4096*2;
+	}else if( n.toLowerCase().includes('claude-3') ){
+		max=4096*2;
+	}
+	config.value.maxToken=max/2;
+	if(nGptStore.value.max_tokens> config.value.maxToken ) nGptStore.value.max_tokens= config.value.maxToken;
 })
 
 const reSet=()=>{
-    gptConfigStore.setInit();
-    nGptStore.value= gptConfigStore.myData;
+	gptConfigStore.setInit();
+	nGptStore.value= gptConfigStore.myData;
 }
 
 </script>
 <template>
-<section class="mb-5 justify-between items-center"  >
-     <div style="margin-bottom: 8px;"><span class="text-red-500">*</span>  {{ $t('mjset.model') }}</div>
-    <n-select class="change-select" v-model:value="nGptStore.model" :options="modellist" @update:value="onSelectChange1" size="small"   />
-</section>
-
-<section class="mb-5 flex justify-between items-center"  >
-    <n-input  class="change-select"  :placeholder="$t('mjchat.modlePlaceholder')" v-model:value="nGptStore.userModel">
-      <template #prefix>
-        {{ $t('mjchat.myModle') }}
-      </template>
-    </n-input>
- </section>
-
- <section class="mb-5 justify-between items-center"  >
-     <div  style="margin-bottom: 8px;">{{ $t('mjchat.knowledgeBase') }} </div>
-    <n-select class="change-select" v-model:value="nGptStore.kid" :options="options" @update:value="onSelectChange" size="small"   />
-</section>
-
- <section class=" flex justify-between items-center"  >
-     <div style="margin-bottom: 8px;"> {{ $t('mjchat.historyCnt') }}
-     </div>
-     <div class=" flex justify-end items-center w-[80%] max-w-[240px]">
-        <div class=" w-[200px]"><n-slider class="change-slider" v-model:value="nGptStore.talkCount" :step="1" :max="50" /></div>
-        <div  class="w-[40px] text-right">{{ nGptStore.talkCount }}</div>
-    </div>
-</section>
-<div class="mb-5 text-[12px] text-gray-300 dark:text-gray-300/20">{{ $t('mjchat.historyToken') }}</div>
-
- <section class=" flex justify-between items-center"  >
-     <div> {{ $t('mjchat.historyTCnt') }}
-     </div>
-     <div class=" flex justify-end items-center w-[80%] max-w-[240px]">
-        <div class=" w-[200px]"><n-slider class="change-slider" v-model:value="nGptStore.max_tokens" :step="1" :max="1280000" :min="1" /></div>
-        <div  class="w-[100px] text-right">{{ nGptStore.max_tokens }}</div>
-    </div>
-</section>
-<div class="mb-5 text-[16px] text-gray-300 dark:text-gray-300/20">{{ $t('mjchat.historyTCntInfo') }}  </div>
-
- <section class="mb-5 change-select"  >
-    <div style="margin-bottom: 8px;">{{ $t('mjchat.role') }}</div>
-    <div>
-     <n-input  type="textarea"  :placeholder=" $t('mjchat.rolePlaceholder') "   v-model:value="nGptStore.systemMessage" :autosize="{ minRows: 3 }"
-    />
-    </div>
- </section>
-
-<template v-if="st.openMore">
-    <section class=" flex justify-between items-center "  >
-        <div>{{ $t('mj.temperature') }}</div>
-        <div class=" flex justify-end items-center w-[80%] max-w-[240px]">
-            <div class=" w-[200px]"><n-slider class="change-slider" v-model:value="nGptStore.temperature" :step="0.01" :max="1" /></div>
-            <div  class="w-[40px] text-right">{{ nGptStore.temperature }}</div>
-        </div>
-    </section>
-    <div class="mb-5 text-[12px] text-gray-300 dark:text-gray-300/20"> {{ $t('mj.temperatureInfo') }}</div>
-
-
-    <section class=" flex justify-between items-center "  >
-        <div> {{ $t('mj.top_p') }}</div>
-        <div class=" flex justify-end items-center w-[80%] max-w-[240px]">
-            <div class=" w-[200px]"><n-slider class="change-slider" v-model:value="nGptStore.top_p" :step="0.01" :max="1" /></div>
-            <div  class="w-[40px] text-right">{{ nGptStore.top_p }}</div>
-        </div>
-    </section>
-    <div class="mb-5 text-[12px] text-gray-300 dark:text-gray-300/20">{{ $t('mj.top_pInfo') }}</div>
-
-    <section class=" flex justify-between items-center "  >
-        <div> {{ $t('mj.presence_penalty') }}</div>
-        <div class=" flex justify-end items-center w-[80%] max-w-[240px]">
-            <div class=" w-[200px]"><n-slider class="change-slider" v-model:value="nGptStore.presence_penalty" :step="0.01" :max="1" /></div>
-            <div  class="w-[40px] text-right">{{ nGptStore.presence_penalty }}</div>
-        </div>
-    </section>
-    <div class="mb-5 text-[12px] text-gray-300 dark:text-gray-300/20">{{ $t('mj.presence_penaltyInfo') }} </div>
-
-
-    <section class=" flex justify-between items-center "  >
-        <div>{{ $t('mj.frequency_penalty') }}</div>
-        <div class=" flex justify-end items-center w-[80%] max-w-[240px]">
-            <div class=" w-[200px]"><n-slider class="change-slider" v-model:value="nGptStore.frequency_penalty" :step="0.01" :max="1" /></div>
-            <div  class="w-[40px] text-right">{{ nGptStore.frequency_penalty }}</div>
-        </div>
-    </section>
-    <div class="mb-5 text-[12px] text-gray-300 dark:text-gray-300/20">{{ $t('mj.frequency_penaltyInfo') }}</div>
-
-    <section class="mb-4 justify-between items-center change-select"  >
-        <div style="margin-bottom: 8px;">{{ $t('mj.tts_voice') }}</div>
-        <n-select v-model:value="nGptStore.tts_voice" :options="voiceList" size="small"   />
-    </section>
-
-
-</template>
-<div v-else class="text-right cursor-pointer mb-4" @click="st.openMore=true">
-    <NTag  type="primary" round size="small" :bordered="false" class="!cursor-pointer">More...</NTag>
-</div>
-
-<section class=" text-right flex justify-end space-x-2 model-button"  >
-    <NButton :bordered="false"  @click="reSet()">{{ $t('mj.setBtBack') }}</NButton>
-    <!-- <NButton type="primary" @click="saveChat">{{ $t('mj.setBtSaveChat') }}</NButton>
-    <NButton type="primary" @click="save">{{ $t('mj.setBtSaveSys') }}</NButton> -->
-    <NButton :bordered="false" @click="saveChat('no')">{{ $t('common.save') }}</NButton>
- </section>
+	<section class="mb-5 justify-between items-center"  >
+		<div style="margin-bottom: 8px;"><span class="text-red-500">*</span>  {{ $t('mjset.model') }}</div>
+		<n-select class="change-select" v-model:value="nGptStore.model" :options="modellist" @update:value="onSelectChange1" size="small"   />
+	</section>
+
+	<section class="mb-5 flex justify-between items-center"  >
+		<n-input  class="change-select"  :placeholder="$t('mjchat.modlePlaceholder')" v-model:value="nGptStore.userModel">
+			<template #prefix>
+				{{ $t('mjchat.myModle') }}
+			</template>
+		</n-input>
+	</section>
+
+	<section class="mb-5 justify-between items-center"  >
+		<div  style="margin-bottom: 8px;">{{ $t('mjchat.knowledgeBase') }} </div>
+		<n-select class="change-select" v-model:value="nGptStore.kid" :options="options" @update:value="onSelectChange" size="small"   />
+	</section>
+
+	<section class=" flex justify-between items-center"  >
+		<div style="margin-bottom: 8px;"> {{ $t('mjchat.historyCnt') }}
+		</div>
+		<div class=" flex justify-end items-center w-[80%] max-w-[240px]">
+			<div class=" w-[200px]"><n-slider class="change-slider" v-model:value="nGptStore.talkCount" :step="1" :max="50" /></div>
+			<div  class="w-[40px] text-right">{{ nGptStore.talkCount }}</div>
+		</div>
+	</section>
+	<div class="mb-5 text-[12px] text-gray-300 dark:text-gray-300/20">{{ $t('mjchat.historyToken') }}</div>
+
+	<section class=" flex justify-between items-center"  >
+		<div> {{ $t('mjchat.historyTCnt') }}
+		</div>
+		<div class=" flex justify-end items-center w-[80%] max-w-[240px]">
+			<div class=" w-[200px]"><n-slider class="change-slider" v-model:value="nGptStore.max_tokens" :step="1" :max="1280000" :min="1" /></div>
+			<div  class="w-[100px] text-right">{{ nGptStore.max_tokens }}</div>
+		</div>
+	</section>
+	<div class="mb-5 text-[16px] text-gray-300 dark:text-gray-300/20">{{ $t('mjchat.historyTCntInfo') }}  </div>
+
+	<section class="mb-5 change-select"  >
+		<div style="margin-bottom: 8px;">{{ $t('mjchat.role') }}</div>
+		<div>
+			<n-input  type="textarea"  :placeholder=" $t('mjchat.rolePlaceholder') "   v-model:value="nGptStore.systemMessage" :autosize="{ minRows: 3 }"
+			/>
+		</div>
+	</section>
+
+	<template v-if="st.openMore">
+		<section class=" flex justify-between items-center "  >
+			<div>{{ $t('mj.temperature') }}</div>
+			<div class=" flex justify-end items-center w-[80%] max-w-[240px]">
+				<div class=" w-[200px]"><n-slider class="change-slider" v-model:value="nGptStore.temperature" :step="0.01" :max="1" /></div>
+				<div  class="w-[40px] text-right">{{ nGptStore.temperature }}</div>
+			</div>
+		</section>
+		<div class="mb-5 text-[12px] text-gray-300 dark:text-gray-300/20"> {{ $t('mj.temperatureInfo') }}</div>
+
+
+		<section class=" flex justify-between items-center "  >
+			<div> {{ $t('mj.top_p') }}</div>
+			<div class=" flex justify-end items-center w-[80%] max-w-[240px]">
+				<div class=" w-[200px]"><n-slider class="change-slider" v-model:value="nGptStore.top_p" :step="0.01" :max="1" /></div>
+				<div  class="w-[40px] text-right">{{ nGptStore.top_p }}</div>
+			</div>
+		</section>
+		<div class="mb-5 text-[12px] text-gray-300 dark:text-gray-300/20">{{ $t('mj.top_pInfo') }}</div>
+
+		<section class=" flex justify-between items-center "  >
+			<div> {{ $t('mj.presence_penalty') }}</div>
+			<div class=" flex justify-end items-center w-[80%] max-w-[240px]">
+				<div class=" w-[200px]"><n-slider class="change-slider" v-model:value="nGptStore.presence_penalty" :step="0.01" :max="1" /></div>
+				<div  class="w-[40px] text-right">{{ nGptStore.presence_penalty }}</div>
+			</div>
+		</section>
+		<div class="mb-5 text-[12px] text-gray-300 dark:text-gray-300/20">{{ $t('mj.presence_penaltyInfo') }} </div>
+
+
+		<section class=" flex justify-between items-center "  >
+			<div>{{ $t('mj.frequency_penalty') }}</div>
+			<div class=" flex justify-end items-center w-[80%] max-w-[240px]">
+				<div class=" w-[200px]"><n-slider class="change-slider" v-model:value="nGptStore.frequency_penalty" :step="0.01" :max="1" /></div>
+				<div  class="w-[40px] text-right">{{ nGptStore.frequency_penalty }}</div>
+			</div>
+		</section>
+		<div class="mb-5 text-[12px] text-gray-300 dark:text-gray-300/20">{{ $t('mj.frequency_penaltyInfo') }}</div>
+
+		<section class="mb-4 justify-between items-center change-select"  >
+			<div style="margin-bottom: 8px;">{{ $t('mj.tts_voice') }}</div>
+			<n-select v-model:value="nGptStore.tts_voice" :options="voiceList" size="small"   />
+		</section>
+
+
+	</template>
+	<div v-else class="text-right cursor-pointer mb-4" @click="st.openMore=true">
+		<NTag  type="primary" round size="small" :bordered="false" class="!cursor-pointer">More...</NTag>
+	</div>
+
+	<section class=" text-right flex justify-end space-x-2 model-button"  >
+		<NButton :bordered="false"  @click="reSet()">{{ $t('mj.setBtBack') }}</NButton>
+		<!-- <NButton type="primary" @click="saveChat">{{ $t('mj.setBtSaveChat') }}</NButton>
+		<NButton type="primary" @click="save">{{ $t('mj.setBtSaveSys') }}</NButton> -->
+		<NButton :bordered="false" @click="saveChat('no')">{{ $t('common.save') }}</NButton>
+	</section>
 </template>

+ 62 - 0
src/views/wxbot/layout.vue

@@ -0,0 +1,62 @@
+<script setup lang='ts'>
+import { computed } from 'vue'
+import { NLayout, NLayoutContent } from 'naive-ui'
+import { useRouter } from 'vue-router'
+import Permission from '../chat/layout/Permission.vue'
+import { useBasicLayout } from '@/hooks/useBasicLayout'
+import { homeStore, useAppStore, useAuthStore, useChatStore } from '@/store'
+import { aiSider ,aiFooter} from '@/views/mj'
+import aiMobileMenu from '@/views/mj/aiMobileMenu.vue'; 
+
+const router = useRouter()
+const appStore = useAppStore()
+const chatStore = useChatStore()
+const authStore = useAuthStore()
+
+router.replace({ name: 'Wxbot', params: { uuid: chatStore.active } })
+homeStore.setMyData({local:'wxbot'});
+const { isMobile } = useBasicLayout()
+
+const collapsed = computed(() => appStore.siderCollapsed)
+
+const needPermission = computed(() => !!authStore.session?.auth && !authStore.token)
+
+const getMobileClass = computed(() => {
+  if (isMobile.value)
+    return ['rounded-none', 'shadow-none' ]
+  return [ 'shadow-md', 'dark:border-neutral-800' ] //'border', 'rounded-md',
+})
+
+const getContainerClass = computed(() => {
+  return [
+    'h-full',
+    { 'abc': !isMobile.value && !collapsed.value },
+  ]
+}) 
+</script>
+
+<template>
+  <div class="dark:bg-[#24272e] transition-all p-0" :class="[isMobile ? 'h55' : 'h-full' ]">
+    <div class="h-full overflow-hidden" :class="getMobileClass">
+      <NLayout class="z-40 transition" :class="getContainerClass" has-sider  :sider-placement="isMobile?'left': 'right'">
+        <aiSider v-if="!isMobile"/>
+       
+        <NLayoutContent class="h-full">
+          <RouterView v-slot="{ Component, route }">
+            <component :is="Component" :key="route.fullPath" />
+          </RouterView>
+        </NLayoutContent>
+         <!-- <Sider /> -->
+      </NLayout>
+    </div>
+    <Permission :visible="needPermission" />
+  </div>
+   <aiMobileMenu v-if="isMobile"   /> 
+  <aiFooter/>
+  <player/>
+</template>
+<style  >
+.h55{
+  height: calc(100% - 55px);
+}
+</style>

+ 1 - 1
vercel.json

@@ -9,4 +9,4 @@
       "destination": "/api/proxy"
     }
   ]
-}
+}

+ 4 - 4
vite.config.ts

@@ -4,7 +4,7 @@ import vue from '@vitejs/plugin-vue'
 import { VitePWA } from 'vite-plugin-pwa'
 import { createSvgIconsPlugin } from 'vite-plugin-svg-icons'
 
-function setupPlugins(env: Record<string, string>) {
+function setupPlugins(env) {
   return [
     vue(),
     env.VITE_GLOB_APP_PWA === 'true' && VitePWA({
@@ -24,7 +24,7 @@ function setupPlugins(env: Record<string, string>) {
       // 指定 symbolId 格式
       symbolId: 'icon-[name]',
     }),
-  ].filter(Boolean) // 过滤掉 falsy 值
+  ].filter(Boolean); // 过滤掉 falsy 值
 }
 
 export default defineConfig(({ mode }) => {
@@ -50,12 +50,12 @@ export default defineConfig(({ mode }) => {
         '/uploads': {
           target: viteEnv.VITE_APP_API_BASE_URL,
           changeOrigin: true, // 允许跨域
-          // rewrite: path => path.replace('/api/', '/'),
+          //rewrite: path => path.replace('/api/', '/'),
         },
         '/openapi': {
           target: viteEnv.VITE_APP_API_BASE_URL,
           changeOrigin: true, // 允许跨域
-          // rewrite: path => path.replace('/api/', '/'),
+          //rewrite: path => path.replace('/api/', '/'),
         },
       },
     },