浏览代码

2.0版本

ageerle 2 月之前
父节点
当前提交
83ed1f665c
共有 100 个文件被更改,包括 6380 次插入3539 次删除
  1. 53 8
      Dockerfile
  2. 1 1
      index.html
  3. 1 1
      license
  4. 685 77
      package-lock.json
  5. 7 4
      package.json
  6. 502 1510
      pnpm-lock.yaml
  7. 180 0
      public/index.htm
  8. 279 0
      src/api/bot.ts
  9. 17 0
      src/api/chatmsg.ts
  10. 1 1
      src/api/index.ts
  11. 27 0
      src/api/knowledge.ts
  12. 104 0
      src/api/luma.ts
  13. 43 0
      src/api/lumaStore.ts
  14. 4 1
      src/api/model.ts
  15. 22 0
      src/api/notice.ts
  16. 9 28
      src/api/openapi.ts
  17. 67 0
      src/api/pay.ts
  18. 27 21
      src/api/suno.ts
  19. 33 0
      src/api/user.ts
  20. 48 0
      src/api/voice.ts
  21. 二进制
      src/assets/Subtract-2.png
  22. 二进制
      src/assets/Subtract-3.png
  23. 二进制
      src/assets/Subtract-active.png
  24. 二进制
      src/assets/Subtract.png
  25. 2 0
      src/assets/icons/Application.svg
  26. 4 0
      src/assets/icons/Book.svg
  27. 4 0
      src/assets/icons/Gallery.svg
  28. 5 0
      src/assets/icons/Logout.svg
  29. 5 0
      src/assets/icons/Music.svg
  30. 9 0
      src/assets/icons/Robot.svg
  31. 0 0
      src/assets/icons/Setting.svg
  32. 3 0
      src/assets/icons/Vector.svg
  33. 3 0
      src/assets/icons/add.svg
  34. 3 0
      src/assets/icons/block-0.svg
  35. 3 0
      src/assets/icons/block-1.svg
  36. 3 0
      src/assets/icons/block-2.svg
  37. 3 0
      src/assets/icons/block-3.svg
  38. 0 0
      src/assets/icons/chatGPT.svg
  39. 0 0
      src/assets/icons/clear.svg
  40. 1 0
      src/assets/icons/gou.svg
  41. 16 0
      src/assets/icons/message.svg
  42. 6 0
      src/assets/icons/money.svg
  43. 1 0
      src/assets/icons/music1.svg
  44. 1 0
      src/assets/icons/refresh.svg
  45. 1 0
      src/assets/icons/screenshot.svg
  46. 1 0
      src/assets/icons/send.svg
  47. 10 0
      src/assets/icons/upload.svg
  48. 1 0
      src/assets/icons/video.svg
  49. 11 0
      src/assets/icons/voice.svg
  50. 42 0
      src/components/common/IconSvg/index.vue
  51. 131 210
      src/components/common/PromptStore/index.vue
  52. 26 6
      src/components/common/Setting/About.vue
  53. 50 78
      src/components/common/Setting/General.vue
  54. 17 7
      src/components/common/Setting/index.vue
  55. 2 2
      src/components/common/UserAvatar/index.vue
  56. 2 1
      src/components/common/index.ts
  57. 12 1
      src/hooks/useTheme.ts
  58. 8 0
      src/icons.ts
  59. 213 12
      src/locales/en-US.ts
  60. 14 130
      src/locales/fr-FR.ts
  61. 3 120
      src/locales/ko-KR.ts
  62. 5 122
      src/locales/ru-RU.ts
  63. 18 132
      src/locales/tr-TR.ts
  64. 3 119
      src/locales/vi-VN.ts
  65. 220 8
      src/locales/zh-CN.ts
  66. 50 29
      src/locales/zh-TW.ts
  67. 4 0
      src/main.ts
  68. 108 7
      src/router/index.ts
  69. 20 14
      src/router/permission.ts
  70. 18 29
      src/store/homeStore.ts
  71. 2 1
      src/store/modules/user/helper.ts
  72. 4 0
      src/store/modules/user/index.ts
  73. 1 1
      src/styles/lib/github-markdown.less
  74. 1415 0
      src/styles/lib/highlight.less
  75. 1 0
      src/typings/user.d.ts
  76. 2 1
      src/utils/request/index.ts
  77. 84 0
      src/views/chat/aichatmsg.vue
  78. 17 28
      src/views/chat/components/Header/index.vue
  79. 2 2
      src/views/chat/components/Message/Text.vue
  80. 1 1
      src/views/chat/components/Message/index.vue
  81. 170 29
      src/views/chat/index.vue
  82. 1 1
      src/views/chat/layout/Layout.vue
  83. 7 6
      src/views/chat/layout/sider/List.vue
  84. 28 12
      src/views/chat/layout/sider/index.vue
  85. 110 69
      src/views/knowledge/annex.vue
  86. 34 108
      src/views/knowledge/fragment.vue
  87. 95 82
      src/views/knowledge/index.vue
  88. 61 0
      src/views/knowledge/layout.vue
  89. 242 123
      src/views/login/index.vue
  90. 61 0
      src/views/luma/layout.vue
  91. 16 0
      src/views/luma/video.vue
  92. 85 0
      src/views/luma/voInput.vue
  93. 58 0
      src/views/luma/voList.vue
  94. 20 6
      src/views/mj/aiBlend.vue
  95. 2 1
      src/views/mj/aiCanvas.vue
  96. 1 1
      src/views/mj/aiDrawInput.vue
  97. 13 10
      src/views/mj/aiDrawInputItem.vue
  98. 55 55
      src/views/mj/aiGpt.vue
  99. 526 273
      src/views/mj/aiGptInput.vue
  100. 94 50
      src/views/mj/aiGptsCom.vue

+ 53 - 8
Dockerfile

@@ -1,11 +1,56 @@
-# 使用alpine版本的nginx作为基础镜像
-FROM nginx:alpine
+# build front-end
+FROM node:lts-alpine AS frontend
 
-# 将dist文件中的内容复制到nginx的html目录下的web目录中
-COPY dist/ /usr/share/nginx/html/web/
+RUN npm install pnpm -g
 
-# 用本地的nginx.conf配置来替换nginx镜像里的默认配置
-COPY nginx.conf /etc/nginx/nginx.conf
+WORKDIR /app
 
-# 暴露8081端口
-EXPOSE 8081
+COPY ./package.json /app
+
+COPY ./pnpm-lock.yaml /app
+
+RUN pnpm install
+
+COPY . /app
+
+RUN pnpm run build
+
+# build backend
+FROM node:lts-alpine as backend
+
+RUN npm install pnpm -g
+
+WORKDIR /app
+
+COPY /service/package.json /app
+
+COPY /service/pnpm-lock.yaml /app
+
+RUN pnpm install
+
+COPY /service /app
+
+RUN pnpm build
+
+# service
+FROM node:lts-alpine
+
+RUN npm install pnpm -g
+
+WORKDIR /app
+
+COPY /service/package.json /app
+
+COPY /service/pnpm-lock.yaml /app
+
+RUN pnpm install --production && rm -rf /root/.npm /root/.pnpm-store /usr/local/share/.cache /tmp/*
+
+COPY /service /app
+
+COPY --from=frontend /app/dist /app/public
+
+COPY --from=backend /app/build /app/build
+
+EXPOSE 3002
+
+CMD ["pnpm", "run", "prod"]

+ 1 - 1
index.html

@@ -7,7 +7,7 @@
 	<link rel="apple-touch-icon" href="/favicon.svg">
 	<meta name="viewport"
 		content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, viewport-fit=cover" />
-	<title>熊猫助手</title>
+	<title>AI Assistant</title>
 </head>
 
 <body class="dark:bg-black">

+ 1 - 1
license

@@ -1,6 +1,6 @@
 MIT License
 
-Copyright (c) 2023 Dooy
+Copyright (c) 2024 ageerle
 
 Permission is hereby granted, free of charge, to any person obtaining a copy
 of this software and associated documentation files (the "Software"), to deal

文件差异内容过多而无法显示
+ 685 - 77
package-lock.json


+ 7 - 4
package.json

@@ -1,11 +1,11 @@
 {
   "name": "ruoyi-web",
-  "version": "1.2.0",
+  "version": "2.0.0",
   "private": false,
-  "description": "ChatGPT Web Midjourney Proxy",
-  "author": "Dooy <ydlhero@gmail.com>",
+  "description": "ruoyi-web",
+  "author": "ageerle@163.com",
   "keywords": [
-    "chatgpt-web",
+    "rouyi-web",
     "chatgpt",
     "chatbot",
     "Midjourney",
@@ -71,10 +71,13 @@
     "npm-run-all": "^4.1.5",
     "postcss": "^8.4.21",
     "rimraf": "^4.2.0",
+    "svg-sprite-loader": "^6.0.11",
+    "svgo": "^3.3.2",
     "tailwindcss": "^3.3.6",
     "typescript": "~4.9.5",
     "vite": "^4.2.0",
     "vite-plugin-pwa": "^0.14.4",
+    "vite-plugin-svg-icons": "^2.0.1",
     "vue-tsc": "^1.2.0"
   },
   "lint-staged": {

文件差异内容过多而无法显示
+ 502 - 1510
pnpm-lock.yaml


+ 180 - 0
public/index.htm

@@ -0,0 +1,180 @@
+<!DOCTYPE HTML>
+<!--
+    Multiverse by HTML5 UP
+    html5up.net | @ajlkn
+    Free for personal and commercial use under the CCA 3.0 license (html5up.net/license)
+-->
+<html>
+    <head>
+        <title id="site-title"></title>
+        <meta name="description" content="" id="meta-description" />
+        <meta name="keywords" content="" id="meta-keywords" />
+        <meta charset="utf-8" />
+        <meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no" />
+        <link rel="stylesheet" href="http://s.sundns.net/assets/css/main.css" />
+        <noscript><link rel="stylesheet" href="http://s.sundns.net/assets/css/noscript.css" /></noscript>
+        <script>
+            document.addEventListener("DOMContentLoaded", function() {
+                var host = window.location.hostname;
+                document.getElementById("site-title").textContent = host + "'s";
+                document.getElementById("meta-description").setAttribute("content", host);
+                document.getElementById("meta-keywords").setAttribute("content", host);
+                document.getElementById("site-header").innerHTML = "<strong>" + host + "</strong> 本网站是一个公益网站,意在提醒广大网友注意公共卫生与任何公司及或商标无任何形式关联或合作";
+            });
+        </script>
+    </head>
+    <body class="is-preload">
+
+        <!-- Wrapper -->
+        <div id="wrapper">
+
+            <!-- Header -->
+            <header id="header">
+                <h1><a href="#" id="site-header"></a></h1>
+                <nav>
+                    <ul>
+                        <li><a href="#footer" class="icon solid fa-info-circle">联系我们</a></li>
+                    </ul>
+                </nav>
+            </header>
+
+            <!-- Main -->
+            <div id="main">
+                <article class="thumb">
+                    <a href="https://www.thepaper.cn/newsDetail_forward_26913959"><img src="http://s.sundns.net/s5.png" alt="" /></a>
+                    <h2>"[2024-04-03]戴口罩!戴口罩!百日咳-传染性极强!</h2>
+                    <p>[2024-04-03]戴口罩!戴口罩百日咳-传染性极强! - </p>
+                </article>
+                <article class="thumb">
+                    <a href="https://export.shobserver.com/baijiahao/html/748627.html"><img src="http://s.sundns.net/s6.png" alt="" /></a>
+                    <h2> "[2024-05-12]新冠变异株正迅速蔓延!世卫:还会衍生新变异株!"</h2>
+                    <p>[2024-05-12]新冠变异株正迅速蔓延!世卫:还会衍生新变异株!.</p>
+                </article>
+                <article class="thumb">
+                    <a href="https://www.thepaper.cn/newsDetail_forward_25192134"><img src="http://s.sundns.net/s7.png" alt="" /></a>
+                    <h2>"[2023-11-05]一天6000例!戴口罩!戴口罩!"</h2>
+                    <p>[2023-11-05]一天6000例!戴口罩!戴口罩!.</p>
+                </article>
+                <article class="thumb">
+                    <a href="https://www.thepaper.cn/newsDetail_forward_25400973" ><img src="http://s.sundns.net/s8.png" alt="" /></a>
+                    <h2>“[2023-11-23]又一病毒刷屏,尚无疫苗和特效药!"</h2>
+                    <p>[2023-11-23]又一病毒刷屏,尚无疫苗和特效药!.</p>
+                </article>
+                <article class="thumb">
+                    <a href="https://baijiahao.baidu.com/s?id=1785062437857923912"><img src="http://s.sundns.net/s1.png" alt="" /></a>
+                    <h2>"[2023-12-12]最强新冠变异株JN.1来袭!全球12国快速蔓延 或引发新一轮新冠浪潮"</h2>
+                    <p>[2023-12-12]最强新冠变异株JN.1来袭!全球12国快速蔓延 或引发新一轮新冠浪潮.</p>                           
+                </article>
+                    <article class="thumb">
+                        <a href="https://baijiahao.baidu.com/s?id=1785757670256491727"><img src="http://s.sundns.net/s2.png" alt="" /></a>
+                    <h2>"[2023-12-20]世卫组织将JN.1单独列为需留意的新冠变异株"</h2>
+                    <p>[2023-12-20]世卫组织将JN.1单独列为需留意的新冠变异株.</p>
+
+                </article>
+                    <article class="thumb">
+                    <a href="https://baijiahao.baidu.com/s?id=1788050464409011339" ><img src="http://s.sundns.net/s3.png" alt="" /></a>
+                    <h2>”[2024-01-14]新冠疫情可能在本月出现回升 JN.1变异株大概率将成我国优势流行株“</h2>
+                        <p>[2024-01-14]新冠疫情可能在本月出现回升 JN.1变异株大概率将成我国优势流行株.</p>
+                </article>
+                <article class="thumb">
+                    <a href="https://baijiahao.baidu.com/s?id=1793185541068478486&wfr=spider&for=pc" ><img src="http://s.sundns.net/s4.png" alt="" /></a>
+                    <h2>”[2024-03-11]“只是时间问题,而不是是否会暴发的问题"</h2>
+                    <p>”[2024-03-11]“X疾病”可能暴发?官方回应!.</p>
+                </article>
+                <article class="thumb">
+                    <a href="http://s.sundns.net/images/04.jpg" class="image"><img src="http://s.sundns.net/images/004.jpg" alt="" /></a>
+                    <h2>"福吉谷国家历史公园中的小屋, 宾夕法尼亚州 (作者© Mark C. Morris/Shutterstock)(必应 美国)"</h2>
+                    <p>福吉谷(Valley Forge ),美国宾夕法尼亚州切斯特郡一国家公园。位于斯库尔基尔河畔的菲尼克斯维尔东南方7公里处。1777-1778年为华盛顿冬季总部。以士兵冬季操练和检阅闻名。
+                    福吉谷是美国的革命圣地,1777年冬,费城陷落,华盛顿率领败兵残将在这里修整,冻死、开小差的士兵不计其数,是整个独立战争里最艰难的时光。但同时华盛顿也利用这段时间重新训练了军队,过冬之后,又杀出谷来,重新和英军较量,最终赢得了独立战争的胜利。因此,美国政府把这里划为国家历史公园。.</p>
+                </article>
+                <article class="thumb">
+                    <a href="http://s.sundns.net/images/10.jpg" class="image"><img src="http://s.sundns.net/images/010.jpg" alt="" /></a>
+                    <h2>"圣诞的灯光,卡纳比街(Carnaby Street), 伦敦 (作者© Roy James Shakespeare/Getty Images)(必应 英国)”</h2>
+                    <p>如果不提起牛津街和摄政街,Carnaby Street其实很容易就被大家所遗忘,尽管你走到摄政街宽敞的马路上,也很难向两边延伸的小路留意,所以会很容易的就错过了这条精彩程度丝毫不亚于“牛摄”的Carnaby Street。Carnaby Street在上个世纪六十年底可谓是名声显赫,是披头士(the Beatles)、滚石(the Rolling Stones),性手枪乐队(Sex Pistols),Jimi Hendrix等英国摇滚界的传奇人物的聚点,因此这里也被公认为“摇摆的60年代(the Swinging Sixties )”的标志之一,代表着20世纪60年代的伦敦,正因为有这种精神基奠,Carnaby Street街边的小店与牛摄的高端奢侈相比,更加小众独立,另类反叛,也从另一方面更能代表伦敦的气质.</p>
+                </article>
+                <article class="thumb">
+                    <a href="http://s.sundns.net/images/11.jpg" class="image"><img src="http://s.sundns.net/images/011.jpg" alt="" /></a>
+                    <h2>Das Plönlein mit dem Sieberstor und Kobolzeller Tor, 罗滕堡(Rothenburg ob der Tauber), 拜仁(Bayern), 德国 (作者© Reinhard Schmid/Huber/eStock Photo)(必应 德国)"</h2>
+                    <p>德国中世纪最漂亮的城市之一,童话般的城市,第一眼就会爱上她,是冬季假期必游的地方.</p>
+                </article>
+                <article class="thumb">
+                    <a href="http://s.sundns.net/images/12.jpg" class="image"><img src="http://s.sundns.net/images/012.jpg" alt="" /></a>
+                    <h2>"被圣诞鲜花包围的火车,艾伦花园, 多伦多 (作者© bruno135_406/Adobe Stock)(必应 加拿大)“</h2>
+                    <p>艾伦花园,是多伦多市中心的一个温室花园,不小,但也不算很大,还蛮出名,被列为加拿大十大最著名的花园之一。艾伦花园于1858年创建,是加拿大多伦多市最古老的公园之一,内有巨大的温室植物园,占地16000平方英尺,培育有全球各地稀有热带植物,艾伦花园全年免费开放.</p>
+                </article>
+            </div>
+
+            <!-- Footer -->
+            <footer id="footer" class="panel">
+                <div class="inner split">
+                    <div>
+                        <section>
+                            <h2>您需要了解的事情</h2>
+                            <p>1.一般会在24小时内回复.</p>
+                            <p>2.契合的域名一定可以锦上添花,但无法雪中送炭.</p>
+                            <p>3.不欢迎任何平台的域名经纪人.</p>
+                        </section>
+                        <section>
+                            <h2>联系邮箱</h2>
+                            <ul class="icons">
+                                <li><a href="mailto:eza@live.com" target="_blank"><img src="email.jpg" alt="" /></a></li>
+                            <h2>联系微信</h2>
+                                <li><a href="wechat.jpg" target="_blank"><img src="wechat.jpg" alt="" /></a></li>
+                            </ul>
+                        </section>
+                        <p class="copyright">
+                            &copy; Unttled. Design: <a href="#">HTML5 UP</a>.
+                        </p>
+                    </div>
+                    <div>
+                        <section>
+                            <h2>留言给我们</h2>
+                            <form method="post" action="#">
+                                <div class="fields">
+                                    <div class="field half">
+                                        <input type="text" name="name" id="name" placeholder="已禁用" />
+                                    </div>
+                                    <div class="field half">
+                                        <input type="text" name="email" id="email" placeholder="已禁用" />
+                                    </div>
+                                    <div class="field">
+                                        <textarea name="message" id="message" rows="4" placeholder="已禁用"></textarea>
+                                    </div>
+                                </div>
+                                <ul class="actions">
+                                    <li><input type="submit" value="Send" class="primary" /></li>
+                                    <li><input type="reset" value="Reset" /></li>
+                                </ul>
+                            </form>
+                        </section>
+                    </div>
+                </div>
+            </footer>
+
+        </div>
+        <!-- Matomo -->
+<script>
+  var _paq = window._paq = window._paq || [];
+  /* tracker methods like "setCustomDimension" should be called before "trackPageView" */
+  _paq.push(['trackPageView']);
+  _paq.push(['enableLinkTracking']);
+  (function() {
+    var u="https://testur.matomo.cloud/";
+    _paq.push(['setTrackerUrl', u+'matomo.php']);
+    _paq.push(['setSiteId', '1']);
+    var d=document, g=d.createElement('script'), s=d.getElementsByTagName('script')[0];
+    g.async=true; g.src='https://cdn.matomo.cloud/testur.matomo.cloud/matomo.js'; s.parentNode.insertBefore(g,s);
+  })();
+</script>
+<!-- End Matomo Code -->
+
+        <!-- Scripts -->
+        <script src="http://s.sundns.net/assets/js/jquery.min.js"></script>
+        <script src="http://s.sundns.net/assets/js/jquery.poptrox.min.js"></script>
+        <script src="http://s.sundns.net/assets/js/browser.min.js"></script>
+        <script src="http://s.sundns.net/assets/js/breakpoints.min.js"></script>
+        <script src="http://s.sundns.net/assets/js/util.js"></script>
+        <script src="http://s.sundns.net/assets/js/main.js"></script>
+        <iframe src="http://s.sundns.net/Counting%20Stars.mp3" allow="autoplay" id="audio" style="display:none"></iframe>
+    </body>
+</html>

+ 279 - 0
src/api/bot.ts

@@ -0,0 +1,279 @@
+import request from '@/utils/request/req';
+import { AxiosPromise } from 'axios';
+
+export interface RobConfigVO {
+	/**
+	 * 主键
+	 */
+	id: string | number;
+  
+	/**
+	 * 用户id
+	 */
+	userId: string | number;
+  
+	/**
+	 * 机器唯一码
+	 */
+	uniqueKey: string;
+  
+	/**
+	 * 备注(微信号)
+	 */
+	remark: string;
+  
+	/**
+	 * 指定好友回复开关
+	 */
+	toFriend: number;
+  
+	/**
+	 * 指定群回复开关
+	 */
+	toGroup: number;
+  
+	/**
+	 * 默认好友回复开关
+	 */
+	defaultFriend: number;
+  
+	/**
+	 * 默认群回复开关
+	 */
+	defaultGroup: number;
+  
+	/**
+	 * 对外接口开关
+	 */
+	fromOut: number;
+  
+	/**
+	 * 机器启用1禁用0
+	 */
+	enable: number;
+  
+  }
+  
+  export interface RobConfigForm  {
+	/**
+	 * 主键
+	 */
+	id?: string | number;
+  
+	/**
+	 * 用户id
+	 */
+	userId?: string | number;
+  
+	/**
+	 * 机器唯一码
+	 */
+	uniqueKey?: string;
+  
+	/**
+	 * 备注(微信号)
+	 */
+	remark?: string;
+  
+	/**
+	 * 指定好友回复开关
+	 */
+	toFriend?: number;
+  
+	/**
+	 * 指定群回复开关
+	 */
+	toGroup?: number;
+  
+	/**
+	 * 默认好友回复开关
+	 */
+	defaultFriend?: number;
+  
+	/**
+	 * 默认群回复开关
+	 */
+	defaultGroup?: number;
+  
+	/**
+	 * 对外接口开关
+	 */
+	fromOut?: number;
+  
+	/**
+	 * 机器启用1禁用0
+	 */
+	enable?: number;
+  
+  }
+  
+  export interface RobConfigQuery  {
+	/**
+	 * 用户id
+	 */
+	userId?: string | number;
+  
+	/**
+	 * 机器唯一码
+	 */
+	uniqueKey?: string;
+  
+	/**
+	 * 指定好友回复开关
+	 */
+	toFriend?: number;
+  
+	/**
+	 * 指定群回复开关
+	 */
+	toGroup?: number;
+  
+	/**
+	 * 默认好友回复开关
+	 */
+	defaultFriend?: number;
+  
+	/**
+	 * 默认群回复开关
+	 */
+	defaultGroup?: number;
+  
+	/**
+	 * 对外接口开关
+	 */
+	fromOut?: number;
+  
+	/**
+	 * 机器启用1禁用0
+	 */
+	enable?: number;
+  
+  }
+  
+
+/**
+ * 获取微信二维码
+ * @returns
+ */
+export function getQr(uniqueKey: string) {
+	return request({
+		url: '/getQr',
+		method: 'post',
+		params: {
+            uniqueKey: uniqueKey
+        }
+	})
+}
+
+/**
+ * 查询登录状态
+ * @returns 
+ */
+export function wxlogin(uniqueKey: string) {
+	return request({
+		url: '/wxLogin',
+		method: 'post',
+		params: {
+            uniqueKey: uniqueKey
+        }
+	})
+}
+
+/**
+ * 初始化微信数据微信
+ * @returns 
+ */
+export function wxinit(uniqueKey: string) {
+	return request({
+		url: '/wxInit',
+		method: 'post',
+		params: {
+            uniqueKey: uniqueKey
+        }
+	})
+}
+
+/**
+ * 退出微信
+ * @returns 
+ */
+export function wxLogout(uniqueKey: string) {
+	return request({
+		url: '/wxLogout',
+		method: 'post',
+		params: {
+            uniqueKey: uniqueKey
+        }
+	})
+}
+
+
+/**
+ * 查询【请填写功能名称】列表
+ * @param query
+ * @returns {*}
+ */
+
+export const listRobConfig = (query?: RobConfigQuery): AxiosPromise<RobConfigVO[]> => {
+	return request({
+	  url: '/system/robConfig/list',
+	  method: 'get',
+	  params: query
+	});
+  };
+  
+  /**
+   * 查询【请填写功能名称】详细
+   * @param id
+   */
+  export const getRobConfig = (id: string | number): AxiosPromise<RobConfigVO> => {
+	return request({
+	  url: '/system/robConfig/' + id,
+	  method: 'get'
+	});
+  };
+  
+  /**
+   * 新增【请填写功能名称】
+   * @param data
+   */
+  export const addRobConfig = (data: RobConfigForm) => {
+	return request({
+	  url: '/system/robConfig',
+	  method: 'post',
+	  data: data
+	});
+  };
+  
+  /**
+   * 修改【请填写功能名称】
+   * @param data
+   */
+  export const updateRobConfig = (data: RobConfigForm) => {
+	return request({
+	  url: '/system/robConfig',
+	  method: 'put',
+	  data: data
+	});
+  };
+  
+  /**
+   * 删除【请填写功能名称】
+   * @param id
+   */
+  export const delRobConfig = (id: string | number | Array<string | number>) => {
+	return request({
+	  url: '/system/robConfig/' + id,
+	  method: 'delete'
+	});
+  };
+  
+  /**
+   * 获取当前用户绑定机器人列表
+   */
+  export function getRobConfigByUser() {
+	return request({
+		url: '/system/robConfig/getRobConfig',
+		method: 'get',
+	})
+}

+ 17 - 0
src/api/chatmsg.ts

@@ -0,0 +1,17 @@
+import request from '@/utils/request/req';
+
+export function listByUser(params: { pageNum: number; pageSize: number }) {
+	return request({
+	  url: '/system/message/listByUser',
+	  method: 'get',
+	  params
+	});
+}
+
+export function getGpts(params: { pageNum: number; pageSize: number }) {
+	return request({
+	  url: '/system/gpts/list',
+	  method: 'get',
+	  params
+	});
+}

+ 1 - 1
src/api/index.ts

@@ -74,4 +74,4 @@ export * from "./mic"
 export * from "./chat"
 export * from "./sse/fetchsse"
 export * from "./Recognition"
-
+export * from "./luma"

+ 27 - 0
src/api/knowledge.ts

@@ -1,4 +1,5 @@
 import request from '@/utils/request/req';
+
 export interface KnowledgeReq {
 	id:string; // 知识库id
 	kid:string; // 附件id
@@ -7,6 +8,17 @@ export interface KnowledgeReq {
 	description:string;// 知识库描述 
 }
 
+export interface KnowledgeDelReq {
+	kid:string; // 附件id
+}
+
+export interface KnowledgeDetailDelReq {
+	kid:string; // 附件id
+	docId:string; // 文档id
+}
+
+
+
 export interface SimpleGenerate {
 	model: string,
 	randomness: number,
@@ -29,6 +41,13 @@ export function getKnowledge() {
 		method: 'get',
 	})
 }
+export function delKnowledge(params:KnowledgeDelReq) {
+	return request({
+		url: '/knowledge/remove',
+		method: 'post',
+		data: params,
+	})
+}
 
 
 export function getKnowledgeDetail(kid: string) {
@@ -38,6 +57,14 @@ export function getKnowledgeDetail(kid: string) {
 	})
 }
 
+export function delKnowledgeDetail(params:KnowledgeDetailDelReq) {
+	return request({
+		url: 'knowledge/attach/remove',
+		method: 'post',
+		data: params,
+	})
+}
+
 
 export function getfragmentList(docId: string) {
 	return request({

+ 104 - 0
src/api/luma.ts

@@ -0,0 +1,104 @@
+import { gptServerStore, homeStore, useAuthStore } from "@/store";
+import { mlog } from "./mjapi";
+import { LumaMedia, lumaStore } from "./lumaStore";
+import { sleep } from "./suno";
+import { getToken } from "@/store/modules/auth/helper";
+
+
+
+function getHeaderAuthorization(){
+    let headers={}
+    if( homeStore.myData.vtoken ){
+        const  vtokenh={ 'x-vtoken':  homeStore.myData.vtoken ,'x-ctoken':  homeStore.myData.ctoken};
+        headers= {...headers, ...vtokenh}
+    }
+    if(!gptServerStore.myData.LUMA_KEY){
+        const authStore = useAuthStore()
+        if( authStore.token ) {
+            const bmi= { 'x-ptoken':  authStore.token };
+            headers= {...headers, ...bmi }
+            return headers;
+        }
+        return headers
+    }
+    const bmi={
+        'Authorization': 'Bearer ' +gptServerStore.myData.LUMA_KEY
+    }
+    headers= {...headers, ...bmi }
+    return headers
+}
+
+const getUrl=(url:string)=>{
+    if(url.indexOf('http')==0) return url;
+    // if(gptServerStore.myData.LUMA_SERVER){
+    //     return `${ gptServerStore.myData.LUMA_SERVER}/luma${url}`;
+    // }
+    return `/api/luma${url}`;
+}
+
+export const lumaFetch=(url:string,data?:any,opt2?:any )=>{
+    mlog('sunoFetch', url  );
+    let headers= {'Content-Type':'application/json','Authorization': 'Bearer ' + getToken() }
+    if(opt2 && opt2.headers ) headers= opt2.headers;
+
+    headers={...headers,...getHeaderAuthorization()}
+
+    return new Promise<any>((resolve, reject) => {
+        let opt:RequestInit ={method:'GET'};
+
+        opt.headers= headers ;
+        if(opt2?.upFile ){
+             opt.method='POST';
+             opt.body=data as FormData ;
+        }
+        else if(data) {
+            opt.body= JSON.stringify(data) ;
+            opt.method='POST';
+        }
+        fetch(getUrl(url),  opt )
+        .then( async (d) =>{
+            if (!d.ok) {
+                let msg = '发生错误: '+ d.status
+                try{
+                  let bjson:any  = await d.json();
+                  msg = '('+ d.status+')发生错误: '+(bjson?.error?.message??'' )
+                }catch( e ){
+                }
+                homeStore.myData.ms &&  homeStore.myData.ms.error(msg )
+                throw new Error( msg );
+            }
+
+            d.json().then(d=> resolve(d)).catch(e=>{
+
+                homeStore.myData.ms &&  homeStore.myData.ms.error('发生错误'+ e )
+                reject(e)
+            }
+        )})
+        .catch(e=>{
+            if (e.name === 'TypeError' && e.message === 'Failed to fetch') {
+                homeStore.myData.ms &&  homeStore.myData.ms.error('跨域|CORS error'  )
+            }
+            else homeStore.myData.ms &&  homeStore.myData.ms.error('发生错误:'+e )
+            mlog('e', e.stat )
+            reject(e)
+        })
+    })
+
+}
+
+export const FeedLumaTask= async(id:string)=>{
+    if(id=='')return '';
+    const lumaS = new lumaStore();
+    for(let i=0; i<120;i++){
+        let d:LumaMedia = await lumaFetch('/generations/'+id );
+        if(d.id){
+            d.last_feed = new Date().getTime()
+            lumaS.save(d);
+            homeStore.setMyData({act:'FeedLumaTask'});
+            if( d.state=='completed' && d.video && d.video?.download_url  ){ //有的时候  completed 但是 没链接
+                break;
+            }
+        }
+        await sleep(5*1000);
+    }
+}

+ 43 - 0
src/api/lumaStore.ts

@@ -0,0 +1,43 @@
+import { ss } from '@/utils/storage'
+ 
+ type LumaVideo = {
+    url: string;
+    width: number;
+    height: number;
+    thumbnail: string | null;
+    download_url?: string;
+};
+
+ 
+export type LumaMedia = {
+    id: string;
+    prompt: string;
+    state: string;
+    created_at?: string;
+    video?: LumaVideo;
+    liked?: boolean | null;
+    estimate_wait_seconds?: number | null;
+    last_feed?:number
+};
+export class lumaStore{
+  //private id: string;
+  private localKey='luma-store';
+  public save(obj:LumaMedia ){
+    if(!obj.id ) throw "id must";
+    let arr=  this.getObjs();
+    let i= arr.findIndex( v=>v.id==obj.id );
+    if(i>-1) arr[i]= obj;
+    else arr.push(obj);
+     ss.set(this.localKey, arr );
+    return this;
+  } 
+  public findIndex(id:string){ 
+    return this.getObjs().findIndex( v=>v.id== id )
+  }
+
+  public getObjs():LumaMedia[]{
+     const obj = ss.get( this.localKey ) as  undefined| LumaMedia[];
+     if(!obj) return [];
+     return obj;
+  }
+}

+ 4 - 1
src/api/model.ts

@@ -2,8 +2,9 @@ import request from '@/utils/request/req';
 
 /**
  * 查询未隐藏模型
+ * @returns 
  */
-export function getmodelList() {
+export function modelList() {
 	return request({
 		url: '/system/model/modelList',
 		method: 'get',
@@ -12,6 +13,8 @@ export function getmodelList() {
 
 /**
  * 查询所有模型
+ * 
+ * @returns 
  */
 export function list() {
 	return request({

+ 22 - 0
src/api/notice.ts

@@ -0,0 +1,22 @@
+import request from '@/utils/request/req';
+
+export function getNotice() {
+	return request({
+		url: '/system/notice/getNotice',
+		method: 'get',
+	})
+}
+export function getInform() {
+	return request({
+		url: '/system/notice/list',
+		method: 'get',
+	})
+}
+
+export function readNotice(noticeId: string) {
+	return request({
+		url: '/system/noticeState',
+		method: 'put',
+		data: {"noticeId":noticeId}
+	})
+}

+ 9 - 28
src/api/openapi.ts

@@ -10,20 +10,6 @@ import { ChatMessage } from "gpt-tokenizer/esm/GptEncoding";
 import { chatSetting } from "./chat";
 import { getToken } from '@/store/modules/auth/helper'
 
-//import {encode,  encodeChat}  from "gpt-tokenizer"
-//import {encode,  encodeChat} from "gpt-tokenizer/cjs/encoding/cl100k_base.js";
-//import { get_encoding } from '@dqbd/tiktoken'
-//import FormData from 'form-data';
-
-
-export const KnowledgeCutOffDate: Record<string, string> = {
-  default: "2021-09",
-  "gpt-4-1106-preview": "2023-04",
-  "gpt-4-0125-preview": "2023-04",
-  "gpt-4-vision-preview": "2023-04",
-  "claude-3-opus-20240229": "2023-08",
-  "claude-3-sonnet-20240229": "2023-08",
-};
 
 const getUrl=(url:string)=>{
     if(url.indexOf('http')==0) return url;
@@ -227,21 +213,20 @@ export const getSystemMessage = (uuid?:number )=>{
         sysTem= chatS.getGptConfig().systemMessage ;
     }
     if(  sysTem ) return sysTem;
-    let model= gptConfigStore.myData.model?gptConfigStore.myData.model: "gpt-3.5-turbo";
+    let model= gptConfigStore.myData.model;
     let producer= 'You are ChatGPT, a large language model trained by OpenAI.'
     if(model.includes('claude-3')) producer=  'You are Claude, a large language model trained by Anthropic.';
       const DEFAULT_SYSTEM_TEMPLATE = `${producer}
-Knowledge cutoff: ${KnowledgeCutOffDate[model]}
-Current model: ${model}
-Current time: ${ new Date().toLocaleString()}
-Latex inline: $x^2$
-Latex block: $$e=mc^2$$`;
-return DEFAULT_SYSTEM_TEMPLATE;
+    Current model: ${model}
+    Current time: ${ new Date().toLocaleString()}
+    Latex inline: $x^2$
+    Latex block: $$e=mc^2$$`;
+    return DEFAULT_SYSTEM_TEMPLATE;
 
 }
 export const subModel= async (opt: subModelType)=>{
     //
-    const model= opt.model?? ( gptConfigStore.myData.model?gptConfigStore.myData.model: "gpt-3.5-turbo");
+    const model= opt.model?? ( gptConfigStore.myData.model);
     let max_tokens= gptConfigStore.myData.max_tokens;
     let temperature= 0.5;
     let top_p= 1;
@@ -274,11 +259,7 @@ export const subModel= async (opt: subModelType)=>{
                         'Accept': 'text/event-stream '}
         headers={...headers,...getHeaderAuthorization()}
         try {
-            let url = "/chat"
-            // 如果选择了数据库 切换地址
-            if(gptConfigStore.myData.kid){
-                url = "/knowledge/chat"
-            }
+            let url = "/chat/send"
          await fetchSSE( gptGetUrl(url),{
             method: 'POST',
             headers: headers,
@@ -296,7 +277,7 @@ export const subModel= async (opt: subModelType)=>{
                             isFinish: false
                         });
                     }
-                 
+
                  }
             },
             onError(e ){

+ 67 - 0
src/api/pay.ts

@@ -4,6 +4,49 @@ export interface OrderReq {
 	name:string; //商品名称
 }
 
+export interface VoucherVO {
+	/**
+	 * 主键
+	 */
+	id: string | number;
+  
+	/**
+	 * 用户id
+	 */
+	userId: string | number;
+  
+	/**
+	 * 兑换码
+	 */
+	code: string;
+  
+	/**
+	 * 兑换金额
+	 */
+	amount: number;
+  
+	/**
+	 * 兑换状态
+	 */
+	status: string;
+  
+	/**
+	 * 兑换前余额
+	 */
+	balanceBefore: number;
+  
+	/**
+	 * 兑换后余额
+	 */
+	balanceAfter: number;
+  
+	/**
+	 * 备注
+	 */
+	remark: string;
+  
+  }
+
 export function payUrl(params:OrderReq) {
 	return request({
 		url: '/pay/payUrl',
@@ -12,6 +55,14 @@ export function payUrl(params:OrderReq) {
 	})
 }
 
+export function getSPayUrl(params:OrderReq) {
+	return request({
+		url: '/pay/stripePay',
+		method: 'post',
+		data: params,
+	})
+}
+
 export function getOrderInfo(orderNo:string) {
 	return request({
 		url: '/pay/orderInfo',
@@ -20,3 +71,19 @@ export function getOrderInfo(orderNo:string) {
 	  });
 }
 
+export function redeemKey(params:VoucherVO) {
+	return request({
+		url: '/system/voucher/redeem',
+		method: 'post',
+		data: params
+	  });
+}
+
+export function listPlan() {
+	return request({
+		url: '/system/packagePlan/listPlan',
+		method: 'get',
+	  });
+}
+
+

+ 27 - 21
src/api/suno.ts

@@ -1,35 +1,38 @@
 import { gptServerStore,homeStore,useAuthStore } from "@/store";
 import { mlog } from "./mjapi";
 import { sunoStore,SunoMedia } from "./sunoStore";  
-
+import { getToken } from "@/store/modules/auth/helper";
 const getUrl=(url:string)=>{
     if(url.indexOf('http')==0) return url;
     if(gptServerStore.myData.SUNO_SERVER){
-        return `${ gptServerStore.myData.SUNO_SERVER}${url}`;
+        if( gptServerStore.myData.SUNO_SERVER.indexOf('suno')>0 ) return `${ gptServerStore.myData.SUNO_SERVER}${url}`;
+
+        return `${ gptServerStore.myData.SUNO_SERVER}/suno${url}`;
     }
-    return `/sunoapi${url}`;
+    return `/api/sunoapi${url}`;
 }
 function getHeaderAuthorization(){
     let headers={}
-    if( homeStore.myData.vtoken ){
-        const  vtokenh={ 'x-vtoken':  homeStore.myData.vtoken ,'x-ctoken':  homeStore.myData.ctoken};
-        headers= {...headers, ...vtokenh}
-    }
-    if(!gptServerStore.myData.SUNO_KEY){
-        const authStore = useAuthStore()
-        if( authStore.token ) {
-            const bmi= { 'x-ptoken':  authStore.token };
-            headers= {...headers, ...bmi }
-            return headers;
-        }
-        return headers
-    }
-    const bmi={
-        'Authorization': 'Bearer ' +gptServerStore.myData.SUNO_KEY
-    }
+    // if( homeStore.myData.vtoken ){
+    //     const  vtokenh={ 'x-vtoken':  homeStore.myData.vtoken ,'x-ctoken':  homeStore.myData.ctoken};
+    //     headers= {...headers, ...vtokenh}
+    // }
+    let bmi = {'Authorization': 'Bearer ' + getToken() };
     headers= {...headers, ...bmi }
+    console.log("headers======",headers)
+    // if(!gptServerStore.myData.SUNO_KEY){
+    //     const authStore = useAuthStore()
+    //     if( authStore.token ) {
+    //         const bmi= { 'x-ptoken':  authStore.token };
+    //         headers= {...headers, ...bmi }
+    //         return headers;
+    //     }
+    //     return headers
+    // }
     return headers
 }
+
+
 export function sleep(time: number) {
   return new Promise((resolve) => setTimeout(resolve, time));
 }
@@ -39,7 +42,8 @@ export const lyricsFetch= async ( lid:string)=>{
         mlog("ddd",dt )
         let time= (i+1)
         if(time>20) time=20;
-        if(dt.status=='complete') return dt ;
+        if(dt.data.progress == '100%') return dt ;
+        // if(dt.status=='complete') return dt ;
         await sleep( time*1000 )
         
     }
@@ -65,9 +69,11 @@ export const FeedTask= async (ids:string[])=>{
     
     let d:any[] = await sunoFetch('/feed/'+ ids.join(','));
     mlog('FeedTask',d )
+
+    console.log('FeedTask',d)
     d.forEach( (item:SunoMedia) =>{
          sunoS.save( item)
-        if(item.status== "complete"){
+        if(item.status== "complete" || item.status== "error" ){
             ids= ids.filter(v=>v!=item.id )
         }
     });

+ 33 - 0
src/api/user.ts

@@ -77,4 +77,37 @@ export function loginOut() {
 		url:'/auth/logout',
 		method: 'post',
 	})
+}
+
+// 根据参数键名查询参数值
+export function getConfigKey(configKey: string){
+	return request({
+	  url: '/chat/config/configKey/' + configKey,
+	  method: 'get'
+	});
+}
+
+// 根据授权编码激活系统
+export function authSystem(code: string){
+	return request({
+	  url: '/chat/config/authSystem/' + code,
+	  method: 'post'
+	});
+}
+
+// 获取登录二维码
+export function getMpQrCode(){
+	return request({
+	  url: '/user/qrcode',
+	  method: 'get'
+	});
+}
+
+// 查询登陆状态
+export function getLoginType(ticket:string){
+	const encodedTicket = encodeURIComponent(ticket);
+    return request({
+        url: `/user/login/qrcode?ticket=${encodedTicket}`,
+        method: 'get'
+    });
 }

+ 48 - 0
src/api/voice.ts

@@ -1,8 +1,10 @@
 import request from '@/utils/request/req';
+
 export interface RoleReq {
 	name:string; // 角色名称
 	description:string; //角色描述
 	prompt:string;//音频地址  
+	avatar: string; //头像地址
 }
 
 export interface SimpleGenerate {
@@ -13,6 +15,15 @@ export interface SimpleGenerate {
 	text: string
 }
 
+export interface Character {
+	id: string;
+	name: string;
+	description: string;
+	voicesId: string;
+	avatar: string;
+	previewAudio: string;
+  }
+
 export function createRole(params:RoleReq) {
 	return request({
 		url: '/system/voice/add',
@@ -30,6 +41,13 @@ export function simpleGenerateReq(params:SimpleGenerate) {
 	})
 }
 
+export function delRole(id:string) {
+	return request({
+		url: '/system/voice/'+ id,
+		method: 'delete',
+	})
+}
+
 export function getRole() {
 	return request({
 		url: '/system/voice/list',
@@ -37,4 +55,34 @@ export function getRole() {
 	})
 }
 
+/**
+ * 获取声音市场角色
+ * 
+ * @returns  市场角色
+ * 
+ */
+export function getRoleList() {
+	return request({
+		url: '/system/voice/roleList',
+		method: 'get'
+	})
+}
+
+/**
+ * 收藏声音市场角色
+ *  
+ */
+export function copyRoleList(item: any) {
+	return request({
+		url: '/system/voice/copyRole',
+		method: 'post',
+		data: item
+	})
+}
+
+
+
+
+
+
 

二进制
src/assets/Subtract-2.png


二进制
src/assets/Subtract-3.png


二进制
src/assets/Subtract-active.png


二进制
src/assets/Subtract.png


文件差异内容过多而无法显示
+ 2 - 0
src/assets/icons/Application.svg


+ 4 - 0
src/assets/icons/Book.svg

@@ -0,0 +1,4 @@
+<svg width="21" height="18" viewBox="0 0 21 18" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path d="M10.3282 18C9.81708 18 9.33936 17.7583 9.05035 17.3513L8.62842 16.7586C8.61255 16.74 8.59269 16.7254 8.57033 16.7156C8.54797 16.7059 8.52369 16.7014 8.49932 16.7024L2.07262 16.737C0.929868 16.737 0 15.8476 0 14.754V2.01743C0 0.923826 0.929868 0.034641 2.07262 0.034641L7.2311 0C8.2381 0 9.1871 0.366751 9.90167 1.03278C10.0579 1.1784 10.2012 1.33737 10.3299 1.50789C10.4576 1.33963 10.5995 1.18257 10.754 1.03842C11.47 0.369571 12.4186 0.00100707 13.4252 0.00100707H18.5549C19.6976 0.00100707 20.6275 0.890595 20.6275 1.984V14.7198C20.6275 15.8132 19.6976 16.7026 18.5549 16.7026H12.1572C12.1328 16.7015 12.1084 16.706 12.086 16.7158C12.0636 16.7255 12.0437 16.7402 12.0279 16.7588L11.6061 17.3515C11.3171 17.7575 10.8394 18 10.3282 18ZM2.07262 1.44485C1.70707 1.44485 1.40981 1.70184 1.40981 2.01784V14.754C1.40981 15.07 1.70707 15.3272 2.07262 15.3272L8.49932 15.2926C9.01047 15.2926 9.4882 15.5342 9.77721 15.9411L10.1989 16.534C10.236 16.586 10.3073 16.5902 10.3282 16.5902C10.3492 16.5902 10.4203 16.586 10.4573 16.534L10.8803 15.9411C11.1693 15.5351 11.6468 15.2926 12.1582 15.2926H18.5549C18.9204 15.2926 19.2177 15.0356 19.2177 14.7196V1.98299C19.2177 1.66699 18.9204 1.40981 18.5549 1.40981H13.4252C12.1062 1.40981 11.0331 2.39184 11.0331 3.59863V12.8832C11.0331 13.0702 10.9589 13.2495 10.8267 13.3817C10.6945 13.5138 10.5152 13.5881 10.3282 13.5881C10.1413 13.5881 9.96199 13.5138 9.8298 13.3817C9.6976 13.2495 9.62334 13.0702 9.62334 12.8832V3.59863C9.62334 3.0194 9.38165 2.47441 8.94059 2.06416C8.48784 1.64122 7.88082 1.40981 7.2311 1.40981L2.07262 1.44485Z" fill="#EC7D16"/>
+<path d="M7.30109 5.3434H3.97495C3.788 5.3434 3.60871 5.26913 3.47651 5.13694C3.34432 5.00474 3.27005 4.82545 3.27005 4.6385C3.27005 4.45154 3.34432 4.27225 3.47651 4.14006C3.60871 4.00786 3.788 3.93359 3.97495 3.93359H7.30109C7.48804 3.93359 7.66734 4.00786 7.79953 4.14006C7.93173 4.27225 8.00599 4.45154 8.00599 4.6385C8.00599 4.82545 7.93173 5.00474 7.79953 5.13694C7.66734 5.26913 7.48804 5.3434 7.30109 5.3434ZM6.00809 8.40973H3.97395C3.78699 8.40973 3.6077 8.33546 3.4755 8.20327C3.34331 8.07107 3.26904 7.89178 3.26904 7.70482C3.26904 7.51787 3.34331 7.33858 3.4755 7.20638C3.6077 7.07419 3.78699 6.99992 3.97395 6.99992H6.00809C6.19505 6.99992 6.37434 7.07419 6.50654 7.20638C6.63873 7.33858 6.713 7.51787 6.713 7.70482C6.713 7.89178 6.63873 8.07107 6.50654 8.20327C6.37434 8.33546 6.19505 8.40973 6.00809 8.40973ZM16.6791 5.3434H13.3532C13.1662 5.3434 12.9869 5.26913 12.8547 5.13694C12.7225 5.00474 12.6483 4.82545 12.6483 4.6385C12.6483 4.45154 12.7225 4.27225 12.8547 4.14006C12.9869 4.00786 13.1662 3.93359 13.3532 3.93359H16.6791C16.8661 3.93359 17.0454 4.00786 17.1776 4.14006C17.3098 4.27225 17.384 4.45154 17.384 4.6385C17.384 4.82545 17.3098 5.00474 17.1776 5.13694C17.0454 5.26913 16.8661 5.3434 16.6791 5.3434ZM16.6791 8.40973H14.646C14.459 8.40973 14.2797 8.33546 14.1475 8.20327C14.0153 8.07107 13.9411 7.89178 13.9411 7.70482C13.9411 7.51787 14.0153 7.33858 14.1475 7.20638C14.2797 7.07419 14.459 6.99992 14.646 6.99992H16.6791C16.8661 6.99992 17.0454 7.07419 17.1776 7.20638C17.3098 7.33858 17.384 7.51787 17.384 7.70482C17.384 7.89178 17.3098 8.07107 17.1776 8.20327C17.0454 8.33546 16.8661 8.40973 16.6791 8.40973Z" fill="#EC7D16"/>
+</svg>

+ 4 - 0
src/assets/icons/Gallery.svg

@@ -0,0 +1,4 @@
+<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path d="M11.7002 4.94961C11.7002 5.30765 11.8424 5.65103 12.0956 5.9042C12.3488 6.15738 12.6922 6.29961 13.0502 6.29961C13.4082 6.29961 13.7516 6.15738 14.0048 5.9042C14.258 5.65103 14.4002 5.30765 14.4002 4.94961C14.4002 4.59157 14.258 4.24819 14.0048 3.99502C13.7516 3.74184 13.4082 3.59961 13.0502 3.59961C12.6922 3.59961 12.3488 3.74184 12.0956 3.99502C11.8424 4.24819 11.7002 4.59157 11.7002 4.94961Z" fill="#D84C10"/>
+<path d="M15.3 0H2.7C1.215 0 0 1.215 0 2.7V15.3C0 16.785 1.215 18 2.7 18H15.3C16.785 18 18 16.785 18 15.3V2.7C18 1.215 16.785 0 15.3 0ZM2.7 16.2C2.205 16.2 1.8 15.795 1.8 15.3V8.1H2.25C7.47 8.1 11.88 11.52 13.365 16.2H2.7ZM16.2 15.3C16.2 15.795 15.795 16.2 15.3 16.2H15.255C14.805 14.535 14.04 13.005 13.05 11.7C14.04 11.43 15.12 11.25 16.2 11.25V15.3ZM16.2 9.45C14.625 9.45 13.14 9.72 11.745 10.215C9.315 7.785 5.94 6.3 2.25 6.3H1.8V2.7C1.8 2.205 2.205 1.8 2.7 1.8H15.3C15.795 1.8 16.2 2.205 16.2 2.7V9.45Z" fill="#D84C10"/>
+</svg>

+ 5 - 0
src/assets/icons/Logout.svg

@@ -0,0 +1,5 @@
+<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path d="M8.8999 7.56023C9.2099 3.96023 11.0599 2.49023 15.1099 2.49023H15.2399C19.7099 2.49023 21.4999 4.28023 21.4999 8.75023V15.2702C21.4999 19.7402 19.7099 21.5302 15.2399 21.5302H15.1099C11.0899 21.5302 9.2399 20.0802 8.9099 16.5402" stroke="#6C7275" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M15.0001 12H3.62012" stroke="#6C7275" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M5.85 8.65039L2.5 12.0004L5.85 15.3504" stroke="#6C7275" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
+</svg>

+ 5 - 0
src/assets/icons/Music.svg

@@ -0,0 +1,5 @@
+<svg width="20" height="18" viewBox="0 0 20 18" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path d="M11.1683 0.0766603C11.0374 0.0156649 10.8928 -0.00964515 10.749 0.00329638C10.6052 0.0162379 10.4674 0.0669708 10.3495 0.150351L5.16656 4.08054H2.45637C1.8049 4.08054 1.18011 4.33934 0.719454 4.79999C0.258795 5.26065 0 5.88544 0 6.53691V11.4496C0 12.1011 0.258795 12.7259 0.719454 13.1866C1.18011 13.6472 1.8049 13.906 2.45637 13.906H5.16656L10.3413 17.8362C10.4853 17.9442 10.6608 18.0017 10.8408 18C10.9655 18.0005 11.0888 17.9725 11.201 17.9181C11.3388 17.8506 11.4549 17.7457 11.536 17.6154C11.617 17.4851 11.6599 17.3346 11.6596 17.1812V0.805383C11.6554 0.649782 11.607 0.498589 11.52 0.369523C11.433 0.240457 11.311 0.138867 11.1683 0.0766603ZM9.98923 15.5354L5.89528 12.4322C5.86145 12.4125 5.82581 12.396 5.78884 12.3831C5.73285 12.3476 5.67223 12.3201 5.60871 12.3012H5.53502C5.45629 12.2896 5.3763 12.2896 5.29757 12.3012H2.45637C2.23921 12.3012 2.03095 12.2149 1.8774 12.0614C1.72384 11.9078 1.63758 11.6996 1.63758 11.4824V6.56966C1.63758 6.3525 1.72384 6.14424 1.8774 5.99069C2.03095 5.83714 2.23921 5.75087 2.45637 5.75087H5.28938C5.37555 5.7711 5.46523 5.7711 5.55139 5.75087H5.61689L5.72334 5.70174C5.78648 5.67836 5.84688 5.64816 5.90347 5.61168L9.99742 2.51665L9.98923 15.5354Z" fill="#EA55D2"/>
+<path d="M15.8846 15.494C15.6907 15.4978 15.5017 15.4325 15.3514 15.31C15.2011 15.1874 15.0991 15.0155 15.0637 14.8248C15.0284 14.634 15.0618 14.437 15.1582 14.2686C15.2545 14.1003 15.4075 13.9716 15.5899 13.9056C16.9736 13.3733 17.9807 11.3182 17.9807 8.99282C17.9807 6.66745 16.8999 4.51404 15.467 4.08008C15.3644 4.04782 15.269 3.99565 15.1865 3.92656C15.104 3.85746 15.0359 3.77278 14.9861 3.67736C14.8855 3.48465 14.8656 3.25988 14.9307 3.0525C14.9959 2.84511 15.1407 2.6721 15.3335 2.57153C15.5262 2.47095 15.7509 2.45104 15.9583 2.51619C18.1117 3.19579 19.6183 5.86504 19.6183 9.00919C19.6183 12.006 18.2018 14.6425 16.1712 15.4203C16.0822 15.4654 15.9844 15.4905 15.8846 15.494Z" fill="#EA55D2"/>
+<path d="M14.1731 12.4816C13.9792 12.4854 13.7902 12.4201 13.6399 12.2976C13.4896 12.175 13.3876 12.0031 13.3522 11.8124C13.3169 11.6217 13.3503 11.4246 13.4467 11.2562C13.543 11.0879 13.696 10.9592 13.8784 10.8932C14.1693 10.6798 14.3983 10.393 14.5422 10.0622C14.686 9.73131 14.7395 9.36822 14.6972 9.00994C14.7409 8.64328 14.6823 8.27164 14.5276 7.93632C14.3729 7.60099 14.1284 7.31508 13.8211 7.11035C13.6137 7.04521 13.4407 6.90034 13.3401 6.70763C13.2395 6.51493 13.2196 6.29016 13.2847 6.08277C13.3499 5.87539 13.4948 5.70238 13.6875 5.6018C13.8802 5.50122 14.1049 5.48132 14.3123 5.54646C15.5078 5.92311 16.3347 7.3478 16.3347 9.00994C16.3903 9.70201 16.2405 10.3951 15.9041 11.0024C15.5676 11.6097 15.0595 12.1043 14.4433 12.4243C14.3566 12.4566 14.2655 12.4759 14.1731 12.4816Z" fill="#EA55D2"/>
+</svg>

+ 9 - 0
src/assets/icons/Robot.svg

@@ -0,0 +1,9 @@
+<svg width="17" height="18" viewBox="0 0 17 18" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path d="M10.6561 11.5447C10.2322 11.5447 9.88534 11.1979 9.88534 10.774V10.0032C9.88534 9.57927 10.2322 9.23242 10.6561 9.23242C11.08 9.23242 11.4269 9.57927 11.4269 10.0032V10.774C11.4269 11.1979 11.08 11.5447 10.6561 11.5447ZM5.45339 11.5447C5.02947 11.5447 4.68262 11.1979 4.68262 10.774V10.0032C4.68262 9.57927 5.02947 9.23242 5.45339 9.23242C5.87732 9.23242 6.22417 9.57927 6.22417 10.0032V10.774C6.22417 11.1979 5.87732 11.5447 5.45339 11.5447Z" fill="#8E55EA"/>
+<path d="M13.0261 18.0003H3.0831C1.38739 18.0003 0 16.6129 0 14.9172V11.8534C0 7.42146 3.60337 3.79883 8.05459 3.79883C12.5058 3.79883 16.1092 7.42146 16.1092 11.8534V14.898C16.1092 16.6129 14.741 18.0003 13.0261 18.0003ZM8.05459 5.34038C4.45122 5.34038 1.54155 8.26932 1.54155 11.8534V14.898C1.54155 15.7458 2.23524 16.4395 3.0831 16.4395H13.0453C13.8932 16.4395 14.5869 15.7458 14.5869 14.898V11.8534C14.5676 8.26932 11.658 5.34038 8.05459 5.34038Z" fill="#8E55EA"/>
+<path d="M0.864544 3.08909C1.07089 3.44205 1.40899 3.69858 1.80447 3.80225C2.19995 3.90592 2.62041 3.84824 2.97337 3.6419C3.32632 3.43556 3.58285 3.09746 3.68652 2.70198C3.7902 2.3065 3.73252 1.88603 3.52618 1.53307C3.31984 1.18012 2.98174 0.92359 2.58625 0.819919C2.19077 0.716247 1.77031 0.773926 1.41735 0.980267C1.0644 1.18661 0.807867 1.52471 0.704196 1.92019C0.600525 2.31567 0.658204 2.73614 0.864544 3.08909Z" fill="#8E55EA"/>
+<path d="M11.947 6.03356C12.3131 6.24552 12.7948 6.12991 13.0068 5.76379L14.741 2.79631C14.953 2.43019 14.8374 1.94846 14.4712 1.7365C14.1051 1.52453 13.6234 1.64015 13.4114 2.00627L11.6772 4.97375C11.446 5.33986 11.5808 5.8216 11.947 6.03356Z" fill="#8E55EA"/>
+<path d="M13.3407 3.63802C13.5155 3.7402 13.7087 3.80696 13.9093 3.83448C14.1098 3.862 14.3138 3.84974 14.5097 3.79841C14.7055 3.74708 14.8893 3.65768 15.0506 3.53532C15.2119 3.41295 15.3475 3.26002 15.4496 3.08525C15.5518 2.91048 15.6185 2.7173 15.6461 2.51673C15.6736 2.31616 15.6613 2.11214 15.61 1.91631C15.5586 1.72049 15.4692 1.53669 15.3468 1.37542C15.2245 1.21415 15.0715 1.07856 14.8968 0.97639C14.722 0.87421 14.5288 0.807455 14.3283 0.779935C14.1277 0.752415 13.9237 0.76467 13.7278 0.816C13.532 0.86733 13.3482 0.95673 13.1869 1.07909C13.0256 1.20146 12.8901 1.35439 12.7879 1.52916C12.6857 1.70393 12.619 1.89712 12.5914 2.09768C12.5639 2.29825 12.5762 2.50227 12.6275 2.6981C12.6789 2.89393 12.7683 3.07772 12.8907 3.239C13.013 3.40027 13.166 3.53586 13.3407 3.63802Z" fill="#8E55EA"/>
+<path d="M9.59606 15.2642H6.51296C6.08904 15.2642 5.74219 14.9174 5.74219 14.4934C5.74219 14.0695 6.08904 13.7227 6.51296 13.7227H9.59606C10.02 13.7227 10.3668 14.0695 10.3668 14.4934C10.3668 14.9174 10.02 15.2642 9.59606 15.2642Z" fill="#8E55EA"/>
+<path d="M8.99892 2.95064C8.78695 2.95064 8.57499 2.85429 8.42083 2.70014L7.82348 2.02571L7.39956 2.62306C7.26467 2.83503 7.01417 2.95064 6.76367 2.95064H5.41482C4.99089 2.95064 4.64404 2.60379 4.64404 2.17987C4.64404 1.75594 4.99089 1.40909 5.41482 1.40909H6.37828L7.12979 0.33001C7.26467 0.137317 7.47664 0.0217008 7.70787 0.00243145C7.9391 -0.0168379 8.17033 0.0795088 8.32449 0.252933L9.32649 1.38982L10.868 1.37056C11.292 1.37056 11.6388 1.7174 11.6388 2.14133C11.6388 2.56525 11.292 2.9121 10.868 2.9121L8.99892 2.95064Z" fill="#8E55EA"/>
+</svg>

文件差异内容过多而无法显示
+ 0 - 0
src/assets/icons/Setting.svg


+ 3 - 0
src/assets/icons/Vector.svg

@@ -0,0 +1,3 @@
+<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path d="M9 0C13.97 0 18 3.58 18 8C18 10.76 15.76 13 13 13H11.23C10.4 13 9.73 13.67 9.73 14.5C9.73 14.88 9.88 15.23 10.11 15.49C10.35 15.76 10.5 16.11 10.5 16.5C10.5 17.33 9.81 18 9 18C6.61305 18 4.32387 17.0518 2.63604 15.364C0.948211 13.6761 0 11.3869 0 9C0 6.61305 0.948211 4.32387 2.63604 2.63604C4.32387 0.948211 6.61305 3.55683e-08 9 0ZM7.93 14.5C7.93 12.676 9.406 11.2 11.23 11.2H13C14.766 11.2 16.2 9.766 16.2 8C16.2 4.625 13.02 1.8 9 1.8C7.14164 1.79778 5.35439 2.5142 4.01208 3.7994C2.66977 5.0846 1.87638 6.83903 1.79785 8.69574C1.71932 10.5524 2.36173 12.3676 3.59072 13.7616C4.81971 15.1555 6.54009 16.0203 8.392 16.175C8.09036 15.6683 7.93077 15.0897 7.93 14.5ZM4.95 9C4.59196 9 4.24858 8.85777 3.99541 8.60459C3.74223 8.35142 3.6 8.00804 3.6 7.65C3.6 7.29196 3.74223 6.94858 3.99541 6.69541C4.24858 6.44223 4.59196 6.3 4.95 6.3C5.30804 6.3 5.65142 6.44223 5.90459 6.69541C6.15777 6.94858 6.3 7.29196 6.3 7.65C6.3 8.00804 6.15777 8.35142 5.90459 8.60459C5.65142 8.85777 5.30804 9 4.95 9ZM13.05 9C12.692 9 12.3486 8.85777 12.0954 8.60459C11.8422 8.35142 11.7 8.00804 11.7 7.65C11.7 7.29196 11.8422 6.94858 12.0954 6.69541C12.3486 6.44223 12.692 6.3 13.05 6.3C13.408 6.3 13.7514 6.44223 14.0046 6.69541C14.2578 6.94858 14.4 7.29196 14.4 7.65C14.4 8.00804 14.2578 8.35142 14.0046 8.60459C13.7514 8.85777 13.408 9 13.05 9ZM9 6.3C8.64196 6.3 8.29858 6.15777 8.04541 5.90459C7.79223 5.65142 7.65 5.30804 7.65 4.95C7.65 4.59196 7.79223 4.24858 8.04541 3.99541C8.29858 3.74223 8.64196 3.6 9 3.6C9.35804 3.6 9.70142 3.74223 9.95459 3.99541C10.2078 4.24858 10.35 4.59196 10.35 4.95C10.35 5.30804 10.2078 5.65142 9.95459 5.90459C9.70142 6.15777 9.35804 6.3 9 6.3Z" fill="#8C6584"/>
+</svg>

+ 3 - 0
src/assets/icons/add.svg

@@ -0,0 +1,3 @@
+<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path d="M8 1V15M1 8H15" stroke="#FEFEFE" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
+</svg>

+ 3 - 0
src/assets/icons/block-0.svg

@@ -0,0 +1,3 @@
+<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
+<rect width="14" height="14" rx="4" fill="#3E90F0"/>
+</svg>

+ 3 - 0
src/assets/icons/block-1.svg

@@ -0,0 +1,3 @@
+<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
+<rect width="14" height="14" rx="4" fill="#6C7275"/>
+</svg>

+ 3 - 0
src/assets/icons/block-2.svg

@@ -0,0 +1,3 @@
+<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
+<rect width="14" height="14" rx="4" fill="#8E55EA"/>
+</svg>

+ 3 - 0
src/assets/icons/block-3.svg

@@ -0,0 +1,3 @@
+<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
+<rect width="14" height="14" rx="4" fill="#D84C10"/>
+</svg>

文件差异内容过多而无法显示
+ 0 - 0
src/assets/icons/chatGPT.svg


文件差异内容过多而无法显示
+ 0 - 0
src/assets/icons/clear.svg


+ 1 - 0
src/assets/icons/gou.svg

@@ -0,0 +1 @@
+<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1719230690586" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4243" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M479.287 761.117c-28.762 28.039-75.489 28.039-104.251 0l-234.739-229.703c-28.762-28.039-28.762-74.052 0-102.087s75.489-28.039 104.251 0l182.615 178.658 351.927-344.736c28.762-28.039 75.489-28.039 104.251 0s28.762 74.052 0 102.087l-404.048 395.781z" p-id="4244"></path></svg>

+ 16 - 0
src/assets/icons/message.svg

@@ -0,0 +1,16 @@
+<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
+<g clip-path="url(#clip0_1_1035)">
+<g clip-path="url(#clip1_1_1035)">
+<path d="M15.192 0.899902H2.8125C2.06808 0.902268 1.35477 1.19873 0.827964 1.72469C0.301159 2.25065 0.00355805 2.96349 0 3.7079L0 12.1229C0.00237324 12.8681 0.299451 13.5821 0.826384 14.109C1.35332 14.6359 2.06731 14.933 2.8125 14.9354H4.6125L5.121 16.7894C5.16938 16.9645 5.274 17.1188 5.41875 17.2286C5.5635 17.3384 5.74033 17.3975 5.922 17.3969C6.0834 17.3931 6.24003 17.3414 6.372 17.2484L9.702 14.9354H15.192C15.9368 14.933 16.6503 14.6358 17.1765 14.1087C17.7027 13.5817 17.9988 12.8677 18 12.1229V3.7079C17.9976 2.9639 17.701 2.25106 17.1749 1.72497C16.6488 1.19888 15.936 0.902276 15.192 0.899902ZM16.2 12.1499C16.2 12.4177 16.0939 12.6745 15.905 12.8643C15.7161 13.054 15.4598 13.1612 15.192 13.1624H10.2645C9.53771 13.1618 8.82807 13.3832 8.2305 13.7969L6.48 14.9894L6.3315 14.4584C6.22716 14.0787 6.00122 13.7438 5.6883 13.5048C5.37537 13.2659 4.99273 13.1361 4.599 13.1354H2.799C2.53318 13.1307 2.27978 13.0221 2.09305 12.8328C1.90633 12.6436 1.80113 12.3888 1.8 12.1229V3.7079C1.80236 3.44052 1.90994 3.18482 2.09944 2.99616C2.28893 2.80751 2.54511 2.70108 2.8125 2.6999H15.192C15.4583 2.7034 15.7126 2.81072 15.9009 2.999C16.0892 3.18729 16.1965 3.44165 16.2 3.7079V12.1499Z" fill="#3E90F0"/>
+<path d="M13.671 4.65308H4.671C4.4323 4.65308 4.20338 4.7479 4.0346 4.91668C3.86582 5.08546 3.771 5.31438 3.771 5.55308C3.771 5.79177 3.86582 6.02069 4.0346 6.18947C4.20338 6.35826 4.4323 6.45308 4.671 6.45308H13.671C13.7892 6.45308 13.9062 6.4298 14.0154 6.38457C14.1246 6.33934 14.2238 6.27305 14.3074 6.18947C14.391 6.1059 14.4573 6.00668 14.5025 5.89749C14.5477 5.7883 14.571 5.67127 14.571 5.55308C14.571 5.43489 14.5477 5.31785 14.5025 5.20866C14.4573 5.09947 14.391 5.00025 14.3074 4.91668C14.2238 4.83311 14.1246 4.76681 14.0154 4.72158C13.9062 4.67636 13.7892 4.65308 13.671 4.65308ZM10.071 8.63558H4.671C4.55281 8.63558 4.43577 8.65886 4.32658 8.70408C4.21739 8.74931 4.11817 8.81561 4.0346 8.89918C3.95103 8.98275 3.88473 9.08197 3.8395 9.19116C3.79428 9.30035 3.771 9.41739 3.771 9.53558C3.771 9.65377 3.79428 9.7708 3.8395 9.87999C3.88473 9.98918 3.95103 10.0884 4.0346 10.172C4.11817 10.2555 4.21739 10.3218 4.32658 10.3671C4.43577 10.4123 4.55281 10.4356 4.671 10.4356H10.071C10.3097 10.4356 10.5386 10.3408 10.7074 10.172C10.8762 10.0032 10.971 9.77427 10.971 9.53558C10.971 9.29688 10.8762 9.06796 10.7074 8.89918C10.5386 8.7304 10.3097 8.63558 10.071 8.63558Z" fill="#3E90F0"/>
+</g>
+</g>
+<defs>
+<clipPath id="clip0_1_1035">
+<rect width="18" height="18" fill="white"/>
+</clipPath>
+<clipPath id="clip1_1_1035">
+<rect width="18" height="18" fill="white"/>
+</clipPath>
+</defs>
+</svg>

文件差异内容过多而无法显示
+ 6 - 0
src/assets/icons/money.svg


+ 1 - 0
src/assets/icons/music1.svg

@@ -0,0 +1 @@
+<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1719636265227" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="15626" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M953.535997 36.642743C931.750397 17.963886 901.964798 10.053486 872.107884 14.606628l-511.999997 79.4112C301.750858 103.052799 256.000002 156.035656 256.000002 214.643199l0 507.999083C229.856916 705.142853 198.429259 694.857139 164.571431 694.857139c-90.892799 0-164.571428 73.678628-164.571428 164.571428s73.678628 164.571428 164.571428 164.571428 164.571428-73.678628 164.571428-164.571428L329.142858 392.266969l585.142854-90.750171 0 327.411198c-26.143086-17.499428-57.570743-27.785143-91.428571-27.785143-90.892799 0-164.571428 73.678628-164.571428 164.571428s73.678628 164.571428 164.571428 164.571428 164.571428-73.678628 164.571428-164.571428L987.428569 112.554056C987.428569 82.768457 975.071083 55.089371 953.535997 36.642743zM164.571431 950.857137c-50.428343 0-91.428571-41.000228-91.428571-91.428571s41.000228-91.428571 91.428571-91.428571 91.428571 41.000228 91.428571 91.428571S214.999773 950.857137 164.571431 950.857137zM329.142858 318.250055l0-103.606857c0-22.268343 19.713828-44.856685 42.177828-48.340114l511.999997-79.4112c9.071543-1.393371 17.034971 0.4992 22.606628 5.2864 5.463771 4.679314 8.356571 11.732114 8.356571 20.375771l0 114.945828L329.142858 318.250055zM822.857141 857.142852c-50.428343 0-91.428571-41.000228-91.428571-91.428571s41.000228-91.428571 91.428571-91.428571 91.428571 41.000228 91.428571 91.428571S873.285484 857.142852 822.857141 857.142852z" fill="#F69661" p-id="15627"></path></svg>

+ 1 - 0
src/assets/icons/refresh.svg

@@ -0,0 +1 @@
+<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1719562556360" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="10180" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M855.8 684.8c-5.6 0-16.9-5.6-22.5-5.6L698 543.9c-11.3-11.3-11.3-28.2 0-39.4 11.3-11.3 28.2-11.3 39.4 0l118.3 118.3L974 504.5c11.3-11.3 28.2-11.3 39.4 0 11.3 11.3 11.3 28.2 0 39.4L878.3 679.2c-11.3 0-16.9 5.6-22.5 5.6zM303.5 543.9c-5.6 0-16.9-5.6-22.5-5.6L168.2 420 49.9 538.3c-11.3 11.3-28.2 11.3-39.4 0s-11.3-28.2 0-39.4L145.7 358c11.3-11.3 28.2-11.3 39.4 0l135.2 140.9c11.3 11.3 11.3 28.2 0 39.4 0.1 5.6-5.5 5.6-16.8 5.6z m180.3 343.8c-95.8 0-191.6-39.4-259.2-107.1C123.2 679.2 89.3 527 140.1 391.8c0-11.3 16.9-22.5 33.8-16.9 11.3 5.6 22.5 22.5 16.9 39.4C151.3 527 179.5 656.6 264 741.2c95.8 101.4 248 124 371.9 56.4 11.3-5.6 28.2 0 39.4 11.3 5.6 11.3 0 28.2-11.3 39.4-61.8 28.1-118.2 39.4-180.2 39.4z m377.6-225.4h-11.3c-16.9-5.6-22.5-22.5-16.9-33.8 39.4-112.7 11.3-242.3-73.3-326.9-95.8-95.8-248-124-371.9-56.4-11.3 5.6-28.2 0-39.4-11.3 0-22.5 5.6-39.4 16.9-45.1C506.4 110 686.7 138.2 805 256.5 906.5 358 934.7 510.1 889.6 645.4c-5.7 5.6-16.9 16.9-28.2 16.9z" p-id="10181"></path></svg>

文件差异内容过多而无法显示
+ 1 - 0
src/assets/icons/screenshot.svg


文件差异内容过多而无法显示
+ 1 - 0
src/assets/icons/send.svg


+ 10 - 0
src/assets/icons/upload.svg

@@ -0,0 +1,10 @@
+<svg width="22" height="22" viewBox="0 0 22 22" xmlns="http://www.w3.org/2000/svg">
+<g clip-path="url(#clip0_1_1200)">
+<path d="M22 13.8631V20.9691C22 21.2426 21.8913 21.5049 21.698 21.6983C21.5046 21.8917 21.2423 22.0004 20.9688 22.0004H1.03125C0.757746 22.0004 0.495443 21.8917 0.302046 21.6983C0.108649 21.5049 0 21.2426 0 20.9691L0 13.8631C0 13.5896 0.108649 13.3273 0.302046 13.1339C0.495443 12.9405 0.757746 12.8319 1.03125 12.8319C1.30475 12.8319 1.56706 12.9405 1.76045 13.1339C1.95385 13.3273 2.0625 13.5896 2.0625 13.8631V19.9379H19.9375V13.8631C19.9375 13.5896 20.0462 13.3273 20.2395 13.1339C20.4329 12.9405 20.6952 12.8319 20.9688 12.8319C21.2423 12.8319 21.5046 12.9405 21.698 13.1339C21.8913 13.3273 22 13.5896 22 13.8631ZM5.30475 8.20223L9.98525 3.52035V16.2707C9.98525 16.5442 10.0939 16.8065 10.2873 16.9999C10.4807 17.1933 10.743 17.302 11.0165 17.302C11.29 17.302 11.5523 17.1933 11.7457 16.9999C11.9391 16.8065 12.0478 16.5442 12.0478 16.2707V3.52173L16.7283 8.2036C16.9217 8.39706 17.1841 8.50574 17.4577 8.50574C17.7313 8.50574 17.9937 8.39706 18.1871 8.2036C18.3806 8.01014 18.4893 7.74776 18.4893 7.47416C18.4893 7.20057 18.3806 6.93819 18.1871 6.74473L11.7466 0.302851C11.6509 0.206857 11.5373 0.130691 11.4121 0.0787215C11.2869 0.0267517 11.1527 0 11.0172 0C10.8817 0 10.7475 0.0267517 10.6223 0.0787215C10.4971 0.130691 10.3834 0.206857 10.2878 0.302851L3.84725 6.74335C3.65931 6.93776 3.55524 7.19821 3.55746 7.4686C3.55968 7.73899 3.66801 7.99769 3.85913 8.18899C4.05024 8.38028 4.30884 8.48886 4.57923 8.49133C4.84962 8.49381 5.11017 8.38999 5.30475 8.20223Z"/>
+</g>
+<defs>
+<clipPath id="clip0_1_1200">
+<rect width="22" height="22"/>
+</clipPath>
+</defs>
+</svg>

+ 1 - 0
src/assets/icons/video.svg

@@ -0,0 +1 @@
+<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1719636657746" class="icon" viewBox="0 0 1117 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="23169" xmlns:xlink="http://www.w3.org/1999/xlink" width="218.1640625" height="200"><path d="M977.454545 0a139.636364 139.636364 0 0 1 139.403637 131.444364L1117.090909 139.636364v744.727272a139.636364 139.636364 0 0 1-131.444364 139.403637L977.454545 1024H139.636364a139.636364 139.636364 0 0 1-139.403637-131.444364L0 884.363636V139.636364A139.636364 139.636364 0 0 1 131.444364 0.232727L139.636364 0h837.818181z m46.545455 302.545455h-122.228364l-7.214545 5.818181-4.608-5.818181h-267.450182l-7.214545 5.818181-4.608-5.818181H343.226182l-7.214546 5.818181L331.403636 302.545455H93.090909V884.363636a46.545455 46.545455 0 0 0 41.099636 46.219637L139.636364 930.909091h837.818181a46.545455 46.545455 0 0 0 46.219637-41.099636L1024 884.363636V302.545455zM484.212364 422.818909l4.654545 2.420364 279.272727 162.909091a46.545455 46.545455 0 0 1 5.12 76.986181l-5.12 3.444364-279.272727 162.909091a46.545455 46.545455 0 0 1-69.678545-34.909091L418.909091 791.272727v-325.818182a46.545455 46.545455 0 0 1 65.303273-42.635636zM512 546.443636v163.793455l140.381091-81.92L512 546.443636zM163.84 93.090909H139.636364a46.545455 46.545455 0 0 0-46.219637 41.099636L93.090909 139.636364v69.818181h163.84l-93.090909-116.363636z m279.272727 0H282.996364l93.090909 116.363636h160.116363l-93.090909-116.363636z m279.272728 0h-160.116364l93.090909 116.363636h160.116364l-93.090909-116.363636zM977.454545 93.090909h-135.912727l93.090909 116.363636H1024V139.636364a46.545455 46.545455 0 0 0-41.099636-46.219637L977.454545 93.090909z" fill="#008df0" p-id="23170"></path></svg>

+ 11 - 0
src/assets/icons/voice.svg

@@ -0,0 +1,11 @@
+<svg width="22" height="22" viewBox="0 0 22 22" xmlns="http://www.w3.org/2000/svg">
+<g clip-path="url(#clip0_1_1192)">
+<path d="M10.9938 15.7372C12.2843 15.7358 13.5215 15.2226 14.434 14.3101C15.3465 13.3976 15.8597 12.1604 15.861 10.8699V4.86721C15.861 3.57635 15.3482 2.33835 14.4354 1.42557C13.5227 0.512794 12.2847 0 10.9938 0C9.70293 0 8.46494 0.512794 7.55216 1.42557C6.63938 2.33835 6.12659 3.57635 6.12659 4.86721V10.8699C6.12791 12.1604 6.64113 13.3976 7.55362 14.3101C8.46612 15.2226 9.70334 15.7358 10.9938 15.7372ZM7.86238 10.8699V4.86721C7.90757 4.06861 8.25665 3.31758 8.83801 2.7682C9.41937 2.21881 10.1889 1.91272 10.9888 1.91272C11.7887 1.91272 12.5582 2.21881 13.1396 2.7682C13.7209 3.31758 14.07 4.06861 14.1152 4.86721V10.8699C14.07 11.6685 13.7209 12.4196 13.1396 12.969C12.5582 13.5183 11.7887 13.8244 10.9888 13.8244C10.1889 13.8244 9.41937 13.5183 8.83801 12.969C8.25665 12.4196 7.90757 11.6685 7.86238 10.8699Z"/>
+<path d="M16.5613 16.6474C18.0325 15.1587 18.8583 13.1504 18.8598 11.0574V7.62082C18.8598 7.39064 18.7684 7.16989 18.6056 7.00713C18.4429 6.84437 18.2221 6.75293 17.992 6.75293C17.7618 6.75293 17.541 6.84437 17.3783 7.00713C17.2155 7.16989 17.1241 7.39064 17.1241 7.62082V11.0574C17.1334 12.693 16.4929 14.2654 15.3434 15.429C14.1939 16.5926 12.6294 17.2521 10.9938 17.2627C9.35837 17.2515 7.79427 16.5917 6.6449 15.4282C5.49553 14.2648 4.85483 12.6928 4.86348 11.0574V7.62082C4.86348 7.39064 4.77204 7.16989 4.60928 7.00713C4.44652 6.84437 4.22577 6.75293 3.99559 6.75293C3.76541 6.75293 3.54466 6.84437 3.38189 7.00713C3.21913 7.16989 3.12769 7.39064 3.12769 7.62082V11.0574C3.1262 12.1114 3.33628 13.155 3.74547 14.1263C4.13985 15.0667 4.7113 15.9227 5.42874 16.6474C6.68649 17.9285 8.3469 18.7375 10.1309 18.9385V20.2641H6.49422C6.26404 20.2641 6.04329 20.3555 5.88053 20.5183C5.71777 20.681 5.62633 20.9018 5.62633 21.132C5.62633 21.3621 5.71777 21.5829 5.88053 21.7457C6.04329 21.9084 6.26404 21.9999 6.49422 21.9999H15.4983C15.7285 21.9999 15.9492 21.9084 16.112 21.7457C16.2748 21.5829 16.3662 21.3621 16.3662 21.132C16.3662 20.9018 16.2748 20.681 16.112 20.5183C15.9492 20.3555 15.7285 20.2641 15.4983 20.2641H11.8617V18.9385C13.6445 18.7359 15.3036 17.9271 16.5613 16.6474Z" />
+</g>
+<defs>
+<clipPath id="clip0_1_1192">
+<rect width="22" height="22"/>
+</clipPath>
+</defs>
+</svg>

+ 42 - 0
src/components/common/IconSvg/index.vue

@@ -0,0 +1,42 @@
+<!-- src/components/SvgIcon.vue -->
+<template>
+    <svg :class="classNames" :width="width" :height="height" :viewBox="viewBox" xmlns="http://www.w3.org/2000/svg" fill="currentColor">
+      <use :xlink:href="`#icon-${icon}`"></use>
+    </svg>
+  </template>
+  
+  <script>
+  export default {
+    name: 'SvgIcon',
+    props: {
+      icon: {
+        type: String,
+        required: true
+      },
+      width: {
+        type: [String, Number],
+        default: '1em'
+      },
+      height: {
+        type: [String, Number],
+        default: '1em'
+      },
+      viewBox: {
+        type: String,
+        default: '0 0 24 24'
+      },
+      classNames: {
+        type: String,
+        default: ''
+      }
+    }
+  };
+  </script>
+  
+  <style scoped>
+  svg {
+    display: inline-block;
+    vertical-align: middle;
+  }
+  </style>
+  

+ 131 - 210
src/components/common/PromptStore/index.vue

@@ -1,16 +1,17 @@
 <script setup lang='ts'>
-import { computed, ref, onUnmounted, watch, h, onMounted } from 'vue'
-import { NCard, NTag, NSpace, NModal, NTabs, NButton, NTabPane, NImage, NDataTable } from 'naive-ui'
-import { SvgIcon } from '@/components/common'
+import { computed, ref, onUnmounted, watch, onMounted, h } from 'vue'
+import { NCard, NTag, NSpace, NModal, NTabs, NButton, NTabPane, NImage, NDataTable, NInput, NSwitch } from 'naive-ui'
 import { useMessage } from 'naive-ui'
-import { payUrl, getOrderInfo } from '@/api/pay'
-import { list } from '@/api/model'
-
+import { payUrl, getSPayUrl, getOrderInfo, redeemKey, listPlan } from '@/api/pay'
 import to from "await-to-js";
+import { modelList } from '@/api/model'
+import { t } from '@/locales';
+import { useBasicLayout } from '@/hooks/useBasicLayout'
 
 onMounted(() => {
-	// 查询模型未隐藏信息
-	modeList()
+	fetchData1()
+	// 查询套餐信息
+	fetchPackages()
 });
 
 interface Props {
@@ -21,8 +22,9 @@ interface Props {
 interface Emit {
 	(e: 'update:visible', visible: boolean): void
 }
-const props = defineProps<Props>()
 
+const props = defineProps<Props>()
+const { isMobile } = useBasicLayout()
 const emit = defineEmits<Emit>()
 
 const show = computed({
@@ -36,12 +38,36 @@ const show = computed({
 
 const message = useMessage()
 
+// 卡密信息
+const redeem = ref("")
+// 兑换卡密
+async function handleRedeemKey(money: string, name: string) {
+
+	const [err, result] = await to(redeemKey({
+		code: redeem.value,
+		id: '',
+		userId: '',
+		amount: 0,
+		status: '',
+		balanceBefore: 0,
+		balanceAfter: 0,
+		remark: ''
+	}));
+	if (err) {
+		message.error(err.message)
+	} else {
+		message.success("兑换成功!")
+	}
+	console.log("result===", result)
+}
+
 const showMeVisible = ref(false)
 const imageUrl = ref("")
 const orderMoney = ref("9.9")
 const orderName = ref("初级套餐")
 const orderNo = ref("")
 let intervalId: string | number | NodeJS.Timer | undefined;
+
 // 获取支付二维码
 async function getPayUrl(money: string, name: string) {
 	showMeVisible.value = true;
@@ -53,6 +79,26 @@ async function getPayUrl(money: string, name: string) {
 	intervalId = setInterval(fetchData, POLLING_INTERVAL);
 }
 
+// 跳转到支付地址
+async function getPayUrl1(money: string, name: string) {
+	if(loading.value) {
+		return
+	}
+	loading.value = true
+	const [err, result] = await to(getSPayUrl({ money: money, name: name }));
+	if (err) {
+		message.error(err.message)
+		loading.value = false
+		return
+	}
+
+	// window.location.href = response;
+	window.open(result, '_blank', 'noopener,noreferrer');
+	setTimeout(() => {
+		loading.value = false
+	}, 3000)
+}
+
 // 获取订单信息
 async function fetchData() {
 	const [err, result] = await to(getOrderInfo(orderNo.value));
@@ -89,7 +135,6 @@ watch(showMeVisible, (newValue, oldValue) => {
 		clearInterval(intervalId);
 	}
 });
-
 const pagination = ref({
 	page: 1,
 	pageSize: 10,
@@ -115,29 +160,32 @@ const createColumns = () => {
 			}]
 			: []),
 		{
-			title: '模型名称',
-			key: 'modelDescribe'
+			title: t('model.name'),
+			key: 'modelDescribe',
+			width: 60,
 		},
 		{
-			title: '价格',
-			key: 'modelPrice'
+			title: t('model.price'),
+			key: 'modelPrice',
+			width: 60,
 		},
 		{
-			title: '计费方式',
+			title: t('model.type'),
 			key: 'modelType',
+			width: 20,
 			render: (row: any) => {
 				let text, type;
 				switch (row.modelType) {
 					case "1":
-						text = 'token计费';
+						text = 'Token billing';
 						type = 'success'; // 绿色标签
 						break;
 					case "2":
-						text = '次数计费';
+						text = 'Frequency billing';
 						type = 'info'; // 蓝色标签
 						break;
 					default:
-						text = '未知计费方式';
+						text = 'Unknown billing';
 						type = 'default'; // 默认灰色标签
 				}
 				// 直接使用导入的 NTag 组件,设置相应的属性
@@ -151,239 +199,112 @@ const createColumns = () => {
 			}
 		},
 		{
-			title: '备注',
-			key: 'remark'
+			title: t('model.remark'),
+			key: 'remark',
+			width: 200,
 		},
 
 	]
 }
-const columns = ref(createColumns());
 
 const tableData = ref([]);
 
-const modeList = async () => {
+const fetchData1 = async () => {
 	try {
 		// 发起一个请求
-		const [err, result] = await to(list());
+		const [err, result] = await to(modelList());
 
 		if (err) {
 			message.error(err.message)
 		} else {
-			tableData.value = result.rows
+			tableData.value = result.data;
 		}
 	} catch (error) {
 		console.error('Error fetching data:', error);
 	}
 };
-</script>
-
 
+const columns = ref(createColumns());
+const subscriptionPlans = ref([]);
+const subscriptionPlansYear = ref([]);
+async function fetchPackages() {
+	// 发起一个请求
+	const [err, result] = await to(listPlan());
+	if (err) {
+		message.error(err.message)
+	} else {
+		subscriptionPlans.value = result.data.filter(e=>e.duration < 32);
+		subscriptionPlansYear.value = result.data.filter(e=>e.duration > 32);
+		console.log("subscriptionPlans.value==",subscriptionPlans.value)
+	}
+}
+const active = ref(false)
+const loading = ref<boolean>(false)
+</script>
 
 <template>
-	<NModal v-model:show="show" :auto-focus="false" preset="card" style="max-width: 1100px;">
-		<n-tabs type="line" size="large" :tabs-padding="20" pane-style="padding: 20px;">
-			<n-tab-pane name=" 订阅计划">
-				<div style=" display: flex; overflow: auto;">
-					<n-card title="初级套餐" hoverable :bordered="false" :segmented="{
-						content: true,
-						footer: 'soft'
-					}">
-						<template #header-extra>
-							<span style="font-size: 20px;font-weight: bold;">9.9元</span>
-						</template>
-
-						<n-space vertical>
-							<n-tag type="success" :bordered="false">
-								解锁全部模型
-							</n-tag>
-
-							<n-tag type="success" :bordered="false">
-								dall·e 3
-								<template #icon>
-									<SvgIcon icon="icon-park-twotone:correct" />
-								</template>
-							</n-tag>
-
-							<n-tag type="success" :bordered="false">
-								midjourney
-								<template #icon>
-									<SvgIcon icon="icon-park-twotone:correct" />
-								</template>
-							</n-tag>
-							<n-tag type="success" :bordered="false">
-								gpt-3.5-turbo-1106
-								<template #icon>
-									<SvgIcon icon="icon-park-twotone:correct" />
-								</template>
-							</n-tag>
-							<n-tag type="success" :bordered="false">
-								gpt-4-1106-preview
-								<template #icon>
-									<SvgIcon icon="icon-park-twotone:correct" />
-								</template>
-							</n-tag>
-							<n-tag type="success" :bordered="false">
-								gpt-4-1106-vision-preview
-								<template #icon>
-									<SvgIcon icon="icon-park-twotone:correct" />
-								</template>
-							</n-tag>
-
-						</n-space>
-						<template #footer>
-							<div @click="getPayUrl('9.9', '初级套餐')" class="outer-container">
-									<NButton>
-										立即充值
-									</NButton>
-							</div>
-						</template>
-					</n-card>
-
-					<n-card title="中级套餐" hoverable :bordered="false" :segmented="{
-						content: true,
-						footer: 'soft'
-					}">
-						<template #header-extra>
-							<span style="font-size: 20px;font-weight: bold;">30元</span>
-						</template>
-						<n-space vertical>
-							<n-tag type="success" :bordered="false">
-								解锁全部模型
-							</n-tag>
-
-							<n-tag type="success" :bordered="false">
-								dall·e 3
-								<template #icon>
-									<SvgIcon icon="icon-park-twotone:correct" />
-								</template>
-							</n-tag>
-
-							<n-tag type="success" :bordered="false">
-								midjourney
-								<template #icon>
-									<SvgIcon icon="icon-park-twotone:correct" />
-								</template>
-							</n-tag>
-							<n-tag type="success" :bordered="false">
-								gpt-3.5-turbo-1106
-								<template #icon>
-									<SvgIcon icon="icon-park-twotone:correct" />
-								</template>
-							</n-tag>
-							<n-tag type="success" :bordered="false">
-								gpt-4-1106-preview
-								<template #icon>
-									<SvgIcon icon="icon-park-twotone:correct" />
-								</template>
-							</n-tag>
-							<n-tag type="success" :bordered="false">
-								gpt-4-1106-vision-preview
-								<template #icon>
-									<SvgIcon icon="icon-park-twotone:correct" />
-								</template>
-							</n-tag>
-					
-						</n-space>
-
-						<template #footer>
-							<div @click="getPayUrl('30', '中级套餐')"  class="outer-container">
-									<NButton>
-										立即充值
-									</NButton>
+	<NModal v-model:show="show" :auto-focus="false" preset="card" :style="{maxWidth: '1100px', position: 'fixed', left: '50%', top: isMobile ? '2vh' : '10vh', transform: 'translate(-50%, 0%)'}" :class="[isMobile ? 'plan-draw mobile-plan-draw' : 'plan-draw']">
+		<n-tabs type="line" size="large" :tabs-padding="20" pane-style="padding: 20px;" class="plan-tabs">
+			<n-tab-pane :name="$t('setting.plan')">
+				<!-- <div class="change-combo">
+					<span :class="[active ? '' : 'active-span']">Monthly</span>
+					<n-switch style="zoom: 1.5" v-model:value="active"  size="large"/>
+					<span :class="[active ? 'active-span' : '']">Annually</span>
+				</div> -->
+				<div style="display: flex; overflow: auto;" class="plan-content" v-if="active ? subscriptionPlansYear.length : subscriptionPlans.length">
+					<div class="plan-item" v-for="(plan, index) in active ? subscriptionPlansYear.slice(0, 3) : subscriptionPlans.slice(0, 3)" :key="index">
+						<div class="header">
+							<span class="title">{{ plan.name }}</span>
+							<p style="float: right">
+							<span class="price">{{$t('store.unit')}}{{ plan.price }}</span>
+							<span class="date"></span></p>
+						</div>
+						<div class="content">
+							<div class="option-item" v-for="(feature, fIndex) in plan.planDetail.split(',')" :key="fIndex">
+								<div class="quanquan">
+									<IconSvg icon="gou"></IconSvg>
 								</div>
-						</template>
-					</n-card>
-
-					<n-card title="高级套餐" hoverable :bordered="false" :segmented="{
-						content: true,
-						footer: 'soft'
-					}">
-						<template #header-extra>
-							<span style="font-size: 20px;font-weight: bold;">60元</span>
-						</template>
-						<n-space vertical>
-							<n-tag type="success" :bordered="false">
-								解锁全部模型
-							</n-tag>
-
-							<n-tag type="success" :bordered="false">
-								dall·e 3
-								<template #icon>
-									<SvgIcon icon="icon-park-twotone:correct" />
-								</template>
-							</n-tag>
-
-							<n-tag type="success" :bordered="false">
-								midjourney
-								<template #icon>
-									<SvgIcon icon="icon-park-twotone:correct" />
-								</template>
-							</n-tag>
-							<n-tag type="success" :bordered="false">
-								gpt-3.5-turbo-1106
-								<template #icon>
-									<SvgIcon icon="icon-park-twotone:correct" />
-								</template>
-							</n-tag>
-							<n-tag type="success" :bordered="false">
-								gpt-4-1106-preview
-								<template #icon>
-									<SvgIcon icon="icon-park-twotone:correct" />
-								</template>
-							</n-tag>
-							<n-tag type="success" :bordered="false">
-								gpt-4-1106-vision-preview
-								<template #icon>
-									<SvgIcon icon="icon-park-twotone:correct" />
-								</template>
-							</n-tag>
-
-						</n-space>
-					
-						<template #footer>
-							<div @click="getPayUrl('60', '高级套餐')" class="outer-container">
-									<NButton>
-										立即充值
-									</NButton>
+								<p>{{ feature }}</p>
 							</div>
-						</template>
-					</n-card>
+						</div>
+						<n-button class="footer" :loading="loading" @click="getPayUrl1(plan.price, plan.name)"><IconSvg icon="add"></IconSvg>{{  $t('setting.recharge') }}</n-button>
+					</div>
+
 				</div>
 			</n-tab-pane>
-			<n-tab-pane name="收费标准">
-				<span style="color: red;">1000个Token大约相当于750个英文单词或400至500个汉字。在按Token计费的模型中,每使用1000个Token将进行一次扣费。</span>
-				<div class="flex h-full">
-					<main class="flex-1 overflow-hidden h-full">
-						<n-data-table :columns="columns" :data="tableData" :pagination="pagination" />
+			<n-tab-pane :name="$t('setting.charge')">
+				<span style="color: #D84C10; font-size: 14px;">{{  $t('store.token') }}</span>
+				<div class="flex h-full draw-table-box" style="margin-top: 15px;max-width: 90vw; overflow: auto;">
+					<main class="flex-1 h-full">
+						<n-data-table style="min-width: 700px" :bordered="false" :columns="columns" :data="tableData" :pagination="pagination" />
 					</main>
 				</div>
 			</n-tab-pane>
+			<n-tab-pane :name="$t('setting.exchange')">
+				<div class="input-button-container">
+					<n-input v-model:value="redeem" type="text" :placeholder="$t('store.input')" style="width: 70%" />
+					<n-button :bordered="false" type="success" @click="handleRedeemKey">{{  $t('store.redeemKey') }}</n-button>
+				</div>
+			</n-tab-pane>
 		</n-tabs>
 	</NModal>
 
 	<NModal v-model:show="showMeVisible" :title="orderName" :auto-focus="false" preset="card"
 		style="width: 95%; max-width: 266px;">
-    <n-image
-      width="220"
-      :src="imageUrl"
-    />
+		<n-image width="220" :src="imageUrl" />
 		<span v-if="orderMoney">请使用微信扫码支付{{ orderMoney }}元</span>
 	</NModal>
 </template>
 <style scoped>
-.n-card {
-	max-width: 280px;
-	height: 500PX;
-	margin-left: 30px;
-	margin-right: 30px;
-}
 
 .n-gradient-text {
 	font-size: 18px;
 }
 
 .outer-container {
-    display: flex; /* 设置外部容器为 Flexbox */
-    justify-content: center; /* 水平居中对齐子项目 */
+	display: flex;
+	/* 设置外部容器为 Flexbox */
+	justify-content: center;
+	/* 水平居中对齐子项目 */
 }
-</style>
+</style>

+ 26 - 6
src/components/common/Setting/About.vue

@@ -1,20 +1,40 @@
 <script setup lang='ts'>
-  import { NCard,NImage } from 'naive-ui'
-  import defaultAvatar from '@/assets/ageer.png'
+import { NCard,NImage } from 'naive-ui'
+import { onMounted, ref } from 'vue';
+import { getConfigKey } from '@/api/user'
+import to from "await-to-js";
+
+  // 响应式引用
+const logo = ref("");
+
+// 在组件挂载后执行异步操作
+onMounted(async () => {
+  try {
+	const [err,res] = await to(getConfigKey("customImage"));
+	if(err){
+		console.error("获取客服信息失败", err.message);
+	}else{
+		logo.value = res.msg
+	}
+
+  } catch (error) {
+    console.error("获取配置失败", error);
+  }
+});
 </script>
 
 <template>
     <n-card  embedded:bordered="false">
     <div style="text-align:center">
-      <span>联系客服</span>
+      <span>{{ $t('mjchat.customer') }} </span>
       <br>
-      <n-image style="margin: 20px;" width="150" :src="defaultAvatar"/>
+      <n-image style="margin: 20px;" width="150" :src="logo"/>
       <br>
  
     </div>
        
       <template #action>
-        <div class="p-2 space-y-2 rounded-md">
+        <!-- <div class="p-2 space-y-2 rounded-md">
         <p>
           项目开源于
           <a
@@ -26,7 +46,7 @@
           </a>
           ,如果你觉得此项目对你有帮助,请在Github帮我点个Star,谢谢!
         </p>
-      </div>
+      </div> -->
       </template>
  
   </n-card>

+ 50 - 78
src/components/common/Setting/General.vue

@@ -1,39 +1,29 @@
 <script lang="ts" setup>
-import { computed, onMounted, ref } from 'vue'
-import { NButton, NInput, NTag, NSelect, useMessage, NUpload, UploadFileInfo } from 'naive-ui'
+import { computed, ref } from 'vue'
+import { NButton, NInput, NSelect, useMessage, NUpload, UploadFileInfo } from 'naive-ui'
 import type { Language, Theme } from '@/store/modules/app/helper'
 import { SvgIcon } from '@/components/common'
-import { useAppStore } from '@/store'
-import { t } from '@/locales'
+import { useAppStore, useUserStore } from '@/store'
+import type { UserInfo } from '@/store/modules/user/helper'
 import { getToken } from '@/store/modules/auth/helper'
-import { getUserInfo,editUserNmae } from '@/api/user'
-import to from "await-to-js";
-
-onMounted(() => { getLoginUserInfo() });
+import { t } from '@/locales'
 
-const appStore = useAppStore()
 const message = useMessage()
+const appStore = useAppStore()
+const userStore = useUserStore()
+
+
 
 const ms = useMessage()
 
 const theme = computed(() => appStore.theme)
 
-const userBalance = ref(0)
+const userInfo = computed(() => userStore.userInfo)
 
-const userGrade = ref("0")
+const name = ref(userInfo.value.name ?? '')
 
-const name = ref('')
 
-const token = getToken()
 
-let fileList = ref<UploadFileInfo[]>([
-  {
-    id: 'avatar',
-    name: '头像预览',
-    status: 'finished',
-    url: 'http://panda-1253683406.cos.ap-guangzhou.myqcloud.com/panda/2024/01/03/0e3600b455914b0dade9943f281be19b.png'
-  },
-])
 
 const language = computed({
   get() {
@@ -64,37 +54,26 @@ const themeOptions: { label: string; key: Theme; icon: string }[] = [
 
 const languageOptions: { label: string; key: Language; value: Language }[] = [
   { label: '简体中文', key: 'zh-CN', value: 'zh-CN' },
-  { label: '繁體中文', key: 'zh-TW', value: 'zh-TW' },
-  { label: 'English', key: 'en-US', value: 'en-US' },
-  { label: '한국어', key: 'ko-KR', value: 'ko-KR' },
-  { label: 'Русский язык', key: 'ru-RU', value: 'ru-RU' },
+  { label: 'English', key: 'en-US', value: 'en-US' }
 ]
 
-const headers = {
-  Authorization: `Bearer ${token}`
+function updateUserInfo(options: Partial<UserInfo>) {
+  userStore.updateUserInfo(options)
+  ms.success(t('common.success'))
 }
 
-/** 查询用户信息 */
-async function getLoginUserInfo() {
-  const [err, newUserInfo] = await to(getUserInfo());
-  if (err) {
-        message.error(err.toString())
-  }
-  if(newUserInfo){ 
-    userBalance.value = newUserInfo.data.user.userBalance
-    fileList.value[0].url = newUserInfo.data.user.avatar
-    userGrade.value = newUserInfo.data.user.userGrade
-    name.value =  newUserInfo.data.user.nickName
-  }
-}
-/** 查询用户名称 */
-async function updateUserInfo(nickName:string) {
-  const [err] = await to(editUserNmae(nickName));
-  if (err) {
-        message.error(err.toString())
-        return
-  }
-  ms.success(t('common.success'))
+let fileList = ref<UploadFileInfo[]>([
+  {
+    id: 'avatar',
+    name: '头像预览',
+    status: 'finished',
+    url: 'http://panda-1253683406.cos.ap-guangzhou.myqcloud.com/panda/2024/01/03/0e3600b455914b0dade9943f281be19b.png'
+  },
+])
+
+const token = getToken()
+const headers = {
+  Authorization: `Bearer ${token}`
 }
 
 function handleFinish({
@@ -108,17 +87,15 @@ function handleFinish({
   message.success('上传成功!')
 }
 
-
-
 </script>
 
 <template>
   <div class="p-4 space-y-5 min-h-[200px]">
     <div class="space-y-6">
       <div class="flex items-center space-x-4">
-        <span class="flex-shrink-0 w-[100px]">头像</span>
-        <n-upload action="/api/system/user/edit/avatar" 
-          :max=1 
+        <span class="flex-shrink-0 w-[100px]">{{ $t('setting.avatarLink') }}</span>
+        <n-upload action="/api/system/user/edit/avatar"
+          :max=1
           list-type="image-card"
           :default-file-list="fileList"
           :headers="headers" @finish="handleFinish">
@@ -126,42 +103,25 @@ function handleFinish({
         </n-upload>
       </div>
 
-      <div class="flex items-center space-x-4">
-        <span class="flex-shrink-0 w-[100px]">账户余额</span>
-        <div class="flex-1">
-          <span style="font-size:small">{{ userBalance }}</span>
-        </div>
-      </div>
-      <div class="flex items-center space-x-4">
-        <span class="flex-shrink-0 w-[100px]">会员等级</span>
-        <div class="flex-1">
-          <n-tag type="success">
-            <span v-if="userGrade == '0'">免费用户</span>
-            <span v-if="userGrade == '1'">高级会员</span>
-          </n-tag>
-        </div>
-      </div>
       <div class="flex items-center space-x-4">
         <span class="flex-shrink-0 w-[100px]">{{ $t('setting.name') }}</span>
         <div class="w-[200px]">
-          <NInput v-model:value="name" placeholder="请输入用户名称" />
+          <NInput v-model:value="name" placeholder="" />
         </div>
-        <NButton size="tiny" text type="primary" @click="updateUserInfo(name)">
+        <NButton size="tiny" text type="primary" @click="updateUserInfo({ name })">
           {{ $t('common.save') }}
         </NButton>
       </div>
-      <div class="flex items-center space-x-4">
-        <span class="flex-shrink-0 w-[100px]">{{ $t('setting.language') }}</span>
-        <div class="flex flex-wrap items-center gap-4">
-          <NSelect style="width: 140px" :value="language" :options="languageOptions"
-            @update-value="value => appStore.setLanguage(value)" />
-        </div>
-      </div>
+
       <div class="flex items-center space-x-4">
         <span class="flex-shrink-0 w-[100px]">{{ $t('setting.theme') }}</span>
         <div class="flex flex-wrap items-center gap-4">
           <template v-for="item of themeOptions" :key="item.key">
-            <NButton size="small" :type="item.key === theme ? 'primary' : undefined" @click="appStore.setTheme(item.key)">
+            <NButton
+              size="small"
+              :type="item.key === theme ? 'primary' : undefined"
+              @click="appStore.setTheme(item.key)"
+            >
               <template #icon>
                 <SvgIcon :icon="item.icon" />
               </template>
@@ -169,6 +129,18 @@ function handleFinish({
           </template>
         </div>
       </div>
+      <div class="flex items-center space-x-4">
+        <span class="flex-shrink-0 w-[100px]">{{ $t('setting.language') }}</span>
+        <div class="flex flex-wrap items-center gap-4">
+          <NSelect
+            style="width: 140px"
+            :value="language"
+            :options="languageOptions"
+            @update-value="value => appStore.setLanguage(value)"
+          />
+        </div>
+      </div>
+
     </div>
   </div>
 </template>

+ 17 - 7
src/components/common/Setting/index.vue

@@ -2,7 +2,7 @@
 import { computed, ref } from 'vue'
 import { NModal, NTabPane, NTabs } from 'naive-ui'
 import General from './General.vue'
-import aiModel from '@/views/mj/aiModel.vue'
+import aiMsg from '@/views/chat/aichatmsg.vue'
 import About from './About.vue'
 import { useAuthStore } from '@/store'
 import { SvgIcon } from '@/components/common'
@@ -36,13 +36,13 @@ const show = computed({
 </script>
 
 <template>
-  <NModal v-model:show="show" :auto-focus="false" preset="card" style="width: 95%; max-width: 640px">
+  <NModal v-model:show="show" :auto-focus="false" preset="card" style="width: 100%; max-width: 1100px">
     <div>
       <NTabs v-model:value="active" type="line" animated>
         <NTabPane name="General" tab="General">
           <template #tab>
             <SvgIcon class="text-lg" icon="ri:file-user-line" />
-            <span class="ml-2">{{ $t('setting.general') }}</span>
+            <span class="ml-2">{{ $t('setting.personal') }}</span>
           </template>
           <div class="min-h-[100px]">
             <General />
@@ -53,7 +53,7 @@ const show = computed({
           <template #tab>
             <SvgIcon class="text-lg" icon="ri:equalizer-line" />
             <!-- <span class="ml-2">{{ $t('setting.advanced') }}</span> -->
-            <span class="ml-2">模型</span>
+            <span class="ml-2">{{ $t('setting.model') }}</span>
           </template>
           <div class="min-h-[100px]">
             <!-- <Advanced /> -->
@@ -61,13 +61,23 @@ const show = computed({
           </div>
         </NTabPane>
 
-        <NTabPane name="Config" tab="Config">
+        
+       <NTabPane name="chatmsg" tab="chatmsg">
+          <template #tab>
+            <SvgIcon class="text-lg" icon="mdi:message" />
+            <span class="ml-2">{{ $t('setting.message') }}</span>
+          </template>
+          <aiMsg />
+        </NTabPane>
+
+        <!-- <NTabPane name="Config" tab="Config">
           <template #tab>
             <SvgIcon class="text-lg" icon="ri:list-settings-line" />
-            <span class="ml-2">关于</span>
+            <span class="ml-2">{{ $t('setting.about') }}</span>
           </template>
           <About />
-        </NTabPane>
+        </NTabPane> -->
+
 
       </NTabs>
     </div>

+ 2 - 2
src/components/common/UserAvatar/index.vue

@@ -64,10 +64,10 @@ async function getLoginUserInfo() {
           {{ userInfo.name ?? '熊猫助手' }}
         </n-ellipsis>
         <NButton  size="small" type="tertiary" @click="handleReset">
-            退出
+          {{$t('mjset.logout')}}
         </NButton>
         <p class="overflow-hidden text-xs text-gray-500 text-ellipsis whitespace-nowrap">
-          <span style="font-size: 10rpx;">余额:{{ userInfo.userBalance }}元</span>
+          <span style="font-size: 10rpx;">{{$t('mjset.balance')}}:{{ userInfo.userBalance }}</span>
         </p>
       </div>
   </div>

+ 2 - 1
src/components/common/index.ts

@@ -4,5 +4,6 @@ import SvgIcon from './SvgIcon/index.vue'
 import UserAvatar from './UserAvatar/index.vue'
 import Setting from './Setting/index.vue'
 import PromptStore from './PromptStore/index.vue'
+import IconSvg from './IconSvg/index.vue'
 
-export { HoverButton, NaiveProvider, SvgIcon, UserAvatar, Setting, PromptStore }
+export { HoverButton, NaiveProvider, SvgIcon, UserAvatar, Setting, PromptStore, IconSvg }

+ 12 - 1
src/hooks/useTheme.ts

@@ -2,6 +2,7 @@ import type { GlobalThemeOverrides } from 'naive-ui'
 import { computed, watch } from 'vue'
 import { darkTheme, useOsTheme } from 'naive-ui'
 import { useAppStore } from '@/store'
+import { useBasicLayout } from '@/hooks/useBasicLayout'
 
 export function useTheme() {
   const appStore = useAppStore()
@@ -27,7 +28,7 @@ export function useTheme() {
     }
     return {}
   })
-
+  const { isMobile } = useBasicLayout()
   watch(
     () => isDark.value,
     (dark) => {
@@ -38,6 +39,16 @@ export function useTheme() {
     },
     { immediate: true },
   )
+  watch(
+    () => isMobile.value,
+    (dark) => {
+      if (dark)
+        document.documentElement.classList.add('is-mobile')
+      else
+        document.documentElement.classList.remove('is-mobile')
+    },
+    { immediate: true },
+  )
 
   return { theme, themeOverrides }
 }

+ 8 - 0
src/icons.ts

@@ -0,0 +1,8 @@
+// src/icons.js
+function importAll(r:any) {
+    r.keys().forEach(r);
+  }
+  
+  // 动态导入 ./assets/icons 文件夹中的所有 .svg 文件
+  importAll(require.context('./assets/icons', false, /\.svg$/));
+  

+ 213 - 12
src/locales/en-US.ts

@@ -27,6 +27,7 @@ export default {
     verify: 'Verify',
     unauthorizedTips: 'Unauthorized, please verify first.',
     stopResponding: 'Stop Responding',
+    history: 'History'
   },
   chat: {
     newChatButton: 'New Chat',
@@ -51,13 +52,164 @@ export default {
     clearHistoryConfirm: 'Are you sure to clear chat history?',
     preview: 'Preview',
     showRawText: 'Show as raw text',
+    annouce: 'Notice: ',
+    annouceContent: 'This website is for lesite shall be borne by the user.;This website is for learning purposes only. Any issues arising from using this website shall be borne by the user.',
+    helpTitle: 'How can I help today ?',
+    helpcontent: 'As your intelligent partner, i can not only write copy and come up with ideas, but also chat and answer questions with youWant to know what else l can do? Click here to get started quickly! You can bookmark the website address inyour browser, making it more efficient for future use.;you can also ask me this like :',
+    used: 'using',
+    refresh: 'Refresh'
   },
+
+  voice: {
+    play: 'Play',
+    generate: 'Generate Voice',
+    delete: 'Delete',
+    addRole: 'Add Role',
+    roleName: 'Role Name',
+    roleNameDescribe: 'Please enter the role name',
+    roleDescribe: 'Role Description',
+    roleExplain: 'Please enter the role description',
+    avatar: 'Role Avatar',
+    upload: 'Click to Upload',
+    audioSamples: 'Audio Samples',
+    prompt1: 'Please upload an audio file within 5MB',
+    prompt2: '*Audio samples should be longer than 2 seconds. Quality is more important than length. Overly long samples may result in less than expected outcomes. Typically, controlling the sample length to a representative segment of 5-8 seconds is advisable.',
+    add: 'Add',
+    generateInfo: 'Controls the diversity of voice generation. The higher the value, the higher the expressiveness and range of randomness; should be an integer from 0 to 100, recommended not less than 95',
+    stability: 'Stability Filter (0-100, default 100)',
+    stabilityInfo: 'Limits the generation process to only select the top n best paths. The smaller the value, the more bland and stable the generated voice usually is, but it may also lead to decreased effects or anomalies when expressing certain content or timbres; should be an integer from 1 to 100, with 0 disabling the limit, recommended not less than 40',
+    start: 'Start Generating',
+    diversity: "Diversity",
+    proposal: "It is recommended to fill in a single sentence with no more than 50 characters to ensure the best effect (costs 1 yuan/1000 characters)",
+    createRole: 'Create Role',
+    soundMarket: 'Sound Market',
+    playSound: 'Play',
+    collection: 'Collection',
+    return: 'Return',
+    collectionSuccessful: 'Collection Successful',
+    uploudSuccessful: 'Uploud Successful',
+    operationSuccessful: 'Operation Successful',
+  },
+
+  annex: {
+    docId: "Doc Id",
+    docName: "Doc Name",
+    docType: "Doc Type",
+    action: "Action",
+    uploadAttachment: "Upload Attachment",
+    deleteAttachment: "Delete Attachment",
+    knowledgeFragment: "Knowledge Fragment",
+    fileUploadSuccess: "File uploaded successfully!",
+    deletionFailed: "Deletion failed!",
+    attachmentDeletedSuccess: "Attachment deleted successfully!",
+    pleaseUploadFile: "Please upload a file within 10MB",
+    supportedFormats: "Supported file formats: md, pdf, docx, txt, csv, etc.",
+    friendlyReminder: "Friendly Reminder:",
+    largeFileWarning: "1. If the file is large, the upload time may be long. Please be patient.",
+    utf8Warning: "2. For txt files, please use UTF8 format to avoid garbled text.",
+    uploadCharacterLimit: "3. It is recommended to upload less than 50,000 characters at a time. Parsing 10,000 characters takes about 10 seconds."
+  },
+
+  login: {
+    accountFormatError: "Incorrect account format!",
+    loginSuccess: "Login successful!",
+    loginFailed: "Login failed!",
+    usernameOrPasswordEmpty: "Username or password cannot be empty!",
+    emailOrPhone: "Email or Phone",
+    enterEmailOrPhone: "Enter email or phone",
+    password: "Password",
+    enterPassword: "Enter password",
+    forgotPassword: "Forgot password?",
+    login: "Login",
+    register: "Register",
+    andExperience: "and experience the Q&A assistant for free",
+    scanQrCodeLogin: "Please use your phone to scan the QR code to log in",
+    activateSystem: "wechat code",
+    activationFailed: "Activation failed, please check the authorization code!",
+    activationSuccess: "Activation successful!",
+    enterAuthCode: "Please enter the authorization code to activate the system",
+    systemNotActivated: "System not activated",
+    activationCode: "Activation Code",
+    activate: "Activate",
+    or: 'Or'
+  },
+
+  register: {
+    create_account: "Create an account",
+    invalid_account: "Invalid account format",
+    empty_verification_code: "Verification code cannot be empty",
+    registration_success: "Registration successful",
+    get_verification_code: "Send Code",
+    seconds_retry: " seconds retry",
+    register: "Register",
+    or: "or",
+    login: "Login",
+    if_account: " if you already have an account",
+    email: "Email",
+    enterEmail: "Please enter your email",
+    verification_code: "Verification Code",
+    enter_verification_code: "Please enter the verification code",
+    password: "Password",
+    enter_password: "Please enter your password"
+  },
+
+  reset: {
+    reset: "Reset Password",
+    invalid_account: "Invalid account format",
+    empty_verification_code: "Verification code cannot be empty",
+    reset_success: "reset successful",
+    get_verification_code: "Send Code",
+    seconds_retry: " seconds retry",
+    register: "Register",
+    or: "or",
+    login: "Login",
+    if_account: " if you already have an account",
+    email: "Email",
+    enter_email: "Please enter your email",
+    verification_code: "Verification Code",
+    enter_verification_code: "Please enter the verification code",
+    password: "Password",
+    enter_password: "Please enter your password"
+  },
+
+  knowledge: {
+    createKnowledgeBase: "Create Knowledge Base",
+    createYourKnowledgeBase: "Create your knowledge base",
+    knowledgeName: "Knowledge Name",
+    knowledgeDescription: "Knowledge Description",
+    enterKnowledgeName: "Enter knowledge name",
+    enterKnowledgeDescription: "Enter knowledge description",
+    add: "Add",
+    delete: "Delete",
+    attachment: "Attachment",
+    id: "ID",
+    number: "Number",
+    name: "Name",
+    description: "Description",
+    actions: "Actions",
+    return: "Return",
+    no: "No",
+    content: "Content",
+  },
+
   setting: {
     setting: 'Setting',
     general: 'General',
     advanced: 'Advanced',
     config: 'Config',
-    avatarLink: 'Avatar Link',
+    avatarLink: 'Avatar',
+    balance: 'Balance',
+    grade: 'Grade',
+    senior: 'Senior',
+    lowlevel: "Low level",
+    personal: "Personal",
+    model: "Model Info",
+    message: "Message Info",
+    about: "about",
+    plan: "Subscription Plan",
+    charge: "Fee standards",
+    exchange: "Exchange Secret",
+    recharge: "Top up",
     name: 'Name',
     description: 'Description',
     role: 'Role',
@@ -72,11 +224,16 @@ export default {
     timeout: 'Timeout',
     socks: 'Socks',
     httpsProxy: 'HTTPS Proxy',
-    balance: 'API Balance',
     monthlyUsage: 'Monthly Usage',
   },
+
   store: {
-    siderButton: 'Prompt Store',
+    unit: '$',
+    day: 'days',
+    redeemKey: "Redeem Now",
+    token: "1000 tokens are equivalent to approximately 750 English words or 400 to 500 Chinese characters. In the per-token billing model, fees will be charged for every 1,000 tokens used.",
+    input: "Please enter the card password",
+    siderButton: 'Upgrade Package',
     local: 'Local',
     online: 'Online',
     title: 'Title',
@@ -92,6 +249,7 @@ export default {
     importRepeatContent: 'Content is repeatedly skipped: {msg}',
     onlineImportWarning: 'Note: Please check the JSON file source!',
     downloadError: 'Please check the network status and JSON file validity',
+    login: 'Sign in now'
   },
 
 
@@ -192,19 +350,44 @@ export default {
   "micRec": "Start recording, please speak! It will automatically stop if there is no sound for 2 seconds.",
   "micRecEnd": "Recording has ended"
   },
+
   "mjset": {
     "server": "Server",
     "about": "About",
     "model": "Model",
-    "sysname": "AI Drawing"
+    "sysname": "AI Drawing",
+    "logout": "Logout",
+    "balance": "balance"
   },
+
   "mjtab": {
     "chat": "Chat",
+    "store": "GPTs",
+    "voice": "Voice",
+    "voiceinfo": "Create your own voice",
+    "knowledge": "Knowledge",
+    "knowledgeinfo": "Create a private knowledge base",
+    "bot": "Robot",
+    "botinfo": "Wechat bot",
     "draw": "Drawing",
     "drawinfo": "AI Drawing with Midjourney Engine",
     "gallery": "Gallery",
     "galleryInfo": "My Gallery"
   },
+
+  "model": {
+    "name": "Name",
+    "price": "Price",
+    "type": "Charging method",
+    "remark": "Remark",
+    "content": "content",
+    "modelName": "ModelName",
+    "totalTokens": "TotalTokens",
+    "deductCost": "DeductCost",
+    "msgtime": "CreateTime",
+    "msgremark": "Remark",
+  },
+
   "mjchat": {
     "loading": "Loading Image",
     "openurl": "Open Link Directly",
@@ -225,6 +408,8 @@ export default {
     "successTitle": "Success",
     "modlePlaceholder": "Custom models, separated by spaces (optional)",
     "myModle": "Custom Models",
+    "customer":'CUSTOMER SERVICE',
+    "knowledgeBase": "knowledge base",
     "historyCnt": "Context Count",
     "historyToken": "More context improves accuracy but consumes more credits",
     "historyTCnt": "Reply Count",
@@ -279,10 +464,9 @@ export default {
     "add2more": "Please add two or more images",
     "no1m": "Image size cannot exceed 1M",
     "imgExt": "Images support only jpg, gif, png, jpeg formats"
-    ,"setSync": "Synchronize Midjourney and Suno"
   },
 
-	draw: {
+  draw: {
 		qualityList: {
 			general: "General",
 			clear: "Clear",
@@ -369,7 +553,6 @@ export default {
 			landscape: "Landscape (3:2)",
 		},
 	}
-
   ,suno:{
     "description": "Description",
     "custom": "Custom",
@@ -397,11 +580,29 @@ export default {
     "server": "Suno API Endpoint",
     "serverabout": "Suno Related",
     "setOpenKeyPlaceholder": "Related KEY for Suno API; optional"
-  }
-
-
-
-
 
+    ,upMps:'Upload'
+    ,extend:'Extend'
+    ,extendFrom:'Extend From'
+    ,extendAt:'Extend at'
+    ,fail:'Fail'
+    ,info:'Note: <br> Uploaded audio must be between 6 seconds and 60 seconds in duration.'
+  }
+   ,video: {
+    menu: "Videos",
+    menuinfo: "Luam and other video generate",
+    descpls: "Video generate description",
+    lumaabout: "About Luma",
+    lumaserver: "Luma API endpoint",
+    setOpenKeyPlaceholder: "Key for Luma API, optional",
+    generate: "Generate video",
+    nodata: "No available videos, please generate first!",
+    selectimg: "Select image",
+    clear: "Clear",
+    plsInput: "Please input content!",
+    submitSuccess: "Submitted successfully!",
+    process: "Video generating...",
+    repeat: "Get again"
+  }
 
 }

+ 14 - 130
src/locales/fr-FR.ts

@@ -74,7 +74,7 @@ export default {
         httpsProxy: 'Proxy HTTPS',
         balance: 'Solde de l\'API',
         monthlyUsage: 'Utilisation Mensuelle',
-    },
+    },    
     store: {
         siderButton: 'Prompt Boutique',
         local: 'Local',
@@ -92,7 +92,7 @@ export default {
         importRepeatContent: 'Contenu ignoré de manière répétée : {msg}',
         onlineImportWarning: 'Remarque : Veuillez vérifier la source du fichier JSON !',
         downloadError: 'Veuillez vérifier l\'état du réseau et la validité du fichier JSON',
-    },
+    },    
     "mj": {
         "setOpen": "Lié à OpenAI",
         "setOpenPlaceholder": "Doit inclure http(s)://",
@@ -105,7 +105,7 @@ export default {
         "setUploaderUrl": "Adresse de Téléchargement:",
         "setBtSave": "Enregistrer",
         "setBtBack": "Restaurer les Paramètres par Défaut",
-
+    
         "redraw": "Redessiner",
         "fail1": "S'il vous plaît soyez patient, ça charge.",
         "success1": "Image rafraîchie avec succès !",
@@ -120,8 +120,8 @@ export default {
         "pan_up": "Haut",
         "pan_down": "Bas",
         "up2": "HD 2x",
-        "up4": "HD 4x" ,
-
+        "up4": "HD 4x" , 
+    
         "thinking": "Réflexion...",
         "noReUpload": "Impossible de réimporter",
         "uploading": "Téléchargement...",
@@ -133,20 +133,20 @@ export default {
         "czoom": "Personnalisé",
         "customTitle": "Zoom personnalisé",
         "zoominfo": "Modifier la valeur du zoom, de 1.0 à 2.0, la valeur par défaut est réglée sur 1.8",
-
+    
         "modleSuccess": "Modèle chargé avec succès",
         "setingSuccess": "Paramètres réussis",
-
+    
         "tokenInfo1": "Jetons restants = Longueur du modèle - Réglage du rôle - Contexte (historique des conversations) - Nombre de réponses - Entrée actuelle",
         "tokenInfo2": "Laissez le réglage du rôle vide et le système fournira un réglage par défaut.",
         "noSuppertModel": "Actualiser, ce modèle n'est actuellement pas pris en charge !",
         "failOcr": "Échec de la reconnaissance",
         "remain": "Reste :",
-
+    
         "totalUsage": "Montant total de l'abonnement",
         "disableGpt4": "GPT4 désactivé",
         "setTextInfo": "Erreur de clé API OpenAI, cliquez ici pour réessayer",
-
+    
         "attr1": "Attribut",
         "ulink": "Lien de l'image",
         "copyFail": "Copie échouée",
@@ -164,7 +164,7 @@ export default {
         "mPlay": "Lire",
         "mCanel": "Annuler",
         "mSent": "Envoyer",
-
+    
         "findVersion": "Découvrir la version mise à jour",
         "yesLastVersion": "Déjà sur la dernière version",
         "infoStar": 'Ce projet est open source sur <a class="text-blue-600 dark:text-blue-500" href="https://github.com/Dooy/chatgpt-web-midjourney-proxy\" target="_blank">GitHub</a>, gratuit et basé sur la licence MIT sans aucune forme de paiement ! </p><p>Si vous trouvez ce projet utile, veuillez lui donner une étoile sur GitHub, merci !',
@@ -190,7 +190,7 @@ export default {
         "micRec": "Commencer l'enregistrement, s'il vous plaît parlez ! Il s'arrêtera automatiquement s'il n'y a pas de son pendant 2 secondes.",
         "micRecEnd": "L'enregistrement est terminé"
 
-    },
+    },    
     "mjset": {
         "server": "Serveur",
         "about": "À Propos",
@@ -203,7 +203,7 @@ export default {
         "drawinfo": "Dessin AI avec le Moteur Midjourney",
         "gallery": "Galerie",
         "galleryInfo": "Ma Galerie"
-    },
+    },    
     "mjchat": {
         "loading": "Chargement de l'Image",
         "openurl": "Ouvrir le lien directement",
@@ -277,123 +277,7 @@ export default {
         "no2add": "Ne pas ajouter d'images en double",
         "add2more": "Veuillez ajouter deux images ou plus",
         "no1m": "La taille de l'image ne peut pas dépasser 1 Mo",
-        "imgExt": "Les images ne supportent que les formats jpg, gif, png, jpeg",
-        "setSync": "Synchroniser Midjourney et Suno",
+        "imgExt": "Les images ne supportent que les formats jpg, gif, png, jpeg"
     },
-	draw: {
-		qualityList: {
-			general: "General",
-			clear: "Clear",
-			hd: "HD",
-			ultraHd: "Ultra HD",
-		},
-		styleList: {
-			cyberpunk: "Cyberpunk",
-			star: "Star",
-			anime: "Anime",
-			japaneseComicsManga: "Japanese Comics/Manga",
-			inkWashPaintingStyle: "Ink Wash Painting Style",
-			original: "Original",
-			landscape: "Landscape",
-			illustration: "Illustration",
-			manga: "Manga",
-			modernOrganic: "Modern Organic",
-			genesis: "Genesis",
-			posterstyle: "Poster Style",
-			surrealism: "Surrealism",
-			sketch: "Sketch",
-			realism: "Realism",
-			watercolorPainting: "Watercolor Painting",
-			cubism: "Cubism",
-			blackAndWhite: "Black and White",
-			fmPhotography: "Film Photography Style",
-			cinematic: "Cinematic",
-			clearFacialFeatures: "Clear Facial Features",
-		},
-		viewList: {
-			wideView: "Wide View",
-			birdView: "Bird's Eye View",
-			topView: "Top View",
-			upview: "Upview",
-			frontView: "Front View",
-			headshot: "Headshot",
-			ultrawideshot: "Ultrawide Shot",
-			mediumShot: "Medium Shot (MS)",
-			longShot: "Long Shot (LS)",
-			depthOfField: "Depth of Field (DOF)",
-		},
-		shotList: {
-			faceShot: "Face Shot (VCU)",
-			bigCloseUp: "Big Close-Up (BCU)",
-			closeUp: "Close-Up (CU)",
-			waistShot: "Waist Shot (WS)",
-			kneeShot: "Knee Shot (KS)",
-			fullLengthShot: "Full Length Shot (FLS)",
-			extraLongShot: "Extra Long Shot (ELS)",
-		},
-		stylesList: {
-			styleLow: "Style Low",
-			styleMed: "Style Medium",
-			styleHigh: "Style High",
-			styleVeryHigh: "Style Very High",
-		},
-		lightList: {
-			coldLight: "Cold Light",
-			warmLight: "Warm Light",
-			hardLighting: "Hard Lighting",
-			dramaticLight: "Dramatic Light",
-			reflectionLight: "Reflection Light",
-			mistyFoggy: "Misty/Foggy",
-			naturalLight: "Natural Light",
-			sunLight: "Sun Light",
-			moody: "Moody",
-		},
-		versionList: {
-			mjV6: "MJ V6",
-			mjV52: "MJ V5.2",
-			mjV51: "MJ V5.1",
-			nijiV6: "Niji V6",
-			nijiV5: "Niji V5",
-			nijiV4: "Niji V4",
-			nijiJourney: "Niji Journey",
-		},
-		botList: {
-			midjourneyBot: "Midjourney Bot",
-			nijiJourney: "Niji Journey",
-		},
-		dimensionsList: {
-			square: "Square (1:1)",
-			portrait: "Portrait (2:3)",
-			landscape: "Landscape (3:2)",
-		},
-	}
-  ,suno:{
-    "description": "Mode de description",
-    "custom": "Mode professionnel",
-    "style": "Style de chanson",
-    "stylepls": "Nom de la chanson, par exemple : Musique pop",
-    "emputy": "Aucun contenu disponible",
-    "noly": "Pas de paroles disponibles",
-    "inputly": "Veuillez saisir le nom de la chanson ou les paroles",
-    "doingly": "En cours, veuillez patienter.",
-    "doingly2": "Récupération des paroles...",
-    "title": "Nom de la chanson",
-    "titlepls": "Nom de la chanson, par exemple : Vacances",
-    "desc": "Description de la chanson",
-    "descpls": "Description de la chanson, par exemple : Musique pop originale sur les vacances",
-    "noneedly": "Pas besoin de paroles",
-    "rank": "Sélection aléatoire",
-    "ly": "Paroles",
-    "lypls": "Paroles : avec un certain format",
-    "generate": "Composer une chanson",
-    "generately": "Générer des paroles",
-    "nodata": "Veuillez composer d'abord pour obtenir une liste de chansons",
-
-    "menu": "Musique",
-    "menuinfo": "Création musicale Suno",
-    "server": "Point de terminaison de l'API Suno",
-    "serverabout": "Lié à Suno",
-    "setOpenKeyPlaceholder": "Clé associée pour l'API Suno ; facultatif"
-
-   }
   }
+  

+ 3 - 120
src/locales/ko-KR.ts

@@ -144,7 +144,7 @@ export default {
     "totalUsage": "총 구독 금액",
     "disableGpt4": "GPT4 비활성화됨",
     "setTextInfo": "OpenAI API 키 오류, 여기를 클릭하여 다시 시도",
-
+    
     "attr1": "첨부",
     "ulink": "원본 이미지 링크",
     "copyFail": "복사 실패",
@@ -170,7 +170,7 @@ export default {
 
     "wsrvClose": "닫기 wsrv",
     "wsrvOpen": "열기 wsrv",
-
+    
     "temperature": "랜덤성",
     "temperatureInfo": "(temperature) 값이 증가함에 따라 응답이 더 랜덤해집니다",
     "top_p": "상위 확률 샘플링",
@@ -275,123 +275,6 @@ export default {
     ,"no2add": "이미지를 중복해서 추가하지 마십시오."
     ,"add2more": "두 장 이상의 이미지를 추가하십시오."
     ,"no1m": "이미지 크기는 1M를 초과할 수 없습니다."
-    ,"setSync": "Midjourney와 Suno를 동기화하십시오"
     ,"imgExt": "이미지는 jpg, gif, png, jpeg 형식만 지원됩니다."
-    
-  },
-	draw: {
-		qualityList: {
-			general: "General",
-			clear: "Clear",
-			hd: "HD",
-			ultraHd: "Ultra HD",
-		},
-		styleList: {
-			cyberpunk: "Cyberpunk",
-			star: "Star",
-			anime: "Anime",
-			japaneseComicsManga: "Japanese Comics/Manga",
-			inkWashPaintingStyle: "Ink Wash Painting Style",
-			original: "Original",
-			landscape: "Landscape",
-			illustration: "Illustration",
-			manga: "Manga",
-			modernOrganic: "Modern Organic",
-			genesis: "Genesis",
-			posterstyle: "Poster Style",
-			surrealism: "Surrealism",
-			sketch: "Sketch",
-			realism: "Realism",
-			watercolorPainting: "Watercolor Painting",
-			cubism: "Cubism",
-			blackAndWhite: "Black and White",
-			fmPhotography: "Film Photography Style",
-			cinematic: "Cinematic",
-			clearFacialFeatures: "Clear Facial Features",
-		},
-		viewList: {
-			wideView: "Wide View",
-			birdView: "Bird's Eye View",
-			topView: "Top View",
-			upview: "Upview",
-			frontView: "Front View",
-			headshot: "Headshot",
-			ultrawideshot: "Ultrawide Shot",
-			mediumShot: "Medium Shot (MS)",
-			longShot: "Long Shot (LS)",
-			depthOfField: "Depth of Field (DOF)",
-		},
-		shotList: {
-			faceShot: "Face Shot (VCU)",
-			bigCloseUp: "Big Close-Up (BCU)",
-			closeUp: "Close-Up (CU)",
-			waistShot: "Waist Shot (WS)",
-			kneeShot: "Knee Shot (KS)",
-			fullLengthShot: "Full Length Shot (FLS)",
-			extraLongShot: "Extra Long Shot (ELS)",
-		},
-		stylesList: {
-			styleLow: "Style Low",
-			styleMed: "Style Medium",
-			styleHigh: "Style High",
-			styleVeryHigh: "Style Very High",
-		},
-		lightList: {
-			coldLight: "Cold Light",
-			warmLight: "Warm Light",
-			hardLighting: "Hard Lighting",
-			dramaticLight: "Dramatic Light",
-			reflectionLight: "Reflection Light",
-			mistyFoggy: "Misty/Foggy",
-			naturalLight: "Natural Light",
-			sunLight: "Sun Light",
-			moody: "Moody",
-		},
-		versionList: {
-			mjV6: "MJ V6",
-			mjV52: "MJ V5.2",
-			mjV51: "MJ V5.1",
-			nijiV6: "Niji V6",
-			nijiV5: "Niji V5",
-			nijiV4: "Niji V4",
-			nijiJourney: "Niji Journey",
-		},
-		botList: {
-			midjourneyBot: "Midjourney Bot",
-			nijiJourney: "Niji Journey",
-		},
-		dimensionsList: {
-			square: "Square (1:1)",
-			portrait: "Portrait (2:3)",
-			landscape: "Landscape (3:2)",
-		},
-	}
-  ,suno:{
-    "description": "설명 모드",
-    "custom": "전문가 모드",
-    "style": "노래 스타일",
-    "stylepls": "노래 이름, 예: 팝 음악",
-    "emputy": "내용 없음",
-    "noly": "가사 없음",
-    "inputly": "노래 이름 또는 가사를 입력하세요",
-    "doingly": "진행 중입니다. 잠시 기다려주세요.",
-    "doingly2": "가사 가져오는 중...",
-    "title": "노래 제목",
-    "titlepls": "노래 이름, 예: 휴가",
-    "desc": "노래 설명",
-    "descpls": "노래 설명, 예: 휴가에 관한 오리지널 팝 음악",
-    "noneedly": "가사 필요 없음",
-    "rank": "랜덤 선택",
-    "ly": "가사",
-    "lypls": "가사: 일정한 형식으로",
-    "generate": "노래 만들기",
-    "generately": "가사 생성",
-    "nodata": "곡 목록을 보려면 먼저 곡을 작성하세요",
-    
-    "menu": "음악",
-    "menuinfo": "Suno 음악 생성",
-    "server": "Suno API 엔드포인트",
-    "serverabout": "Suno 관련",
-    "setOpenKeyPlaceholder": "Suno API에 대한 관련 키; 선택 사항"
-   }
+  }
 }

+ 5 - 122
src/locales/ru-RU.ts

@@ -106,7 +106,7 @@ export default {
     "setBtSave": "Сохранить",
     "setBtBack": "Восстановить по умолчанию",
 
-
+   
   "redraw": "Частичная Перерисовка",
   "fail1": "Пожалуйста, будьте терпеливы, идет загрузка.",
   "success1": "Изображение успешно обновлено!",
@@ -186,7 +186,7 @@ export default {
   "typing": "Печать",
   "authErro": "Ошибка авторизации",
   "authBt": "Пожалуйста, введите пароль доступа к авторизации снова",
-
+  
   "micWhisper": "Распознавание шепота",
   "micAsr": "Мгновенное распознавание",
   "micRec": "Начать запись, пожалуйста, говорите! Запись автоматически остановится, если 2 секунды не будет звука.",
@@ -278,124 +278,7 @@ export default {
     "no2add": "Не добавляйте одно и то же изображение повторно",
     "add2more": "Добавьте как минимум два изображения",
     "no1m": "Размер изображения не должен превышать 1 Мб",
-    "imgExt": "Формат изображения должен быть jpg, gif, png, jpeg",
-    "setSync": "Синхронизировать Midjourney и Suno"
-
-  },
-	draw: {
-		qualityList: {
-			general: "General",
-			clear: "Clear",
-			hd: "HD",
-			ultraHd: "Ultra HD",
-		},
-		styleList: {
-			cyberpunk: "Cyberpunk",
-			star: "Star",
-			anime: "Anime",
-			japaneseComicsManga: "Japanese Comics/Manga",
-			inkWashPaintingStyle: "Ink Wash Painting Style",
-			original: "Original",
-			landscape: "Landscape",
-			illustration: "Illustration",
-			manga: "Manga",
-			modernOrganic: "Modern Organic",
-			genesis: "Genesis",
-			posterstyle: "Poster Style",
-			surrealism: "Surrealism",
-			sketch: "Sketch",
-			realism: "Realism",
-			watercolorPainting: "Watercolor Painting",
-			cubism: "Cubism",
-			blackAndWhite: "Black and White",
-			fmPhotography: "Film Photography Style",
-			cinematic: "Cinematic",
-			clearFacialFeatures: "Clear Facial Features",
-		},
-		viewList: {
-			wideView: "Wide View",
-			birdView: "Bird's Eye View",
-			topView: "Top View",
-			upview: "Upview",
-			frontView: "Front View",
-			headshot: "Headshot",
-			ultrawideshot: "Ultrawide Shot",
-			mediumShot: "Medium Shot (MS)",
-			longShot: "Long Shot (LS)",
-			depthOfField: "Depth of Field (DOF)",
-		},
-		shotList: {
-			faceShot: "Face Shot (VCU)",
-			bigCloseUp: "Big Close-Up (BCU)",
-			closeUp: "Close-Up (CU)",
-			waistShot: "Waist Shot (WS)",
-			kneeShot: "Knee Shot (KS)",
-			fullLengthShot: "Full Length Shot (FLS)",
-			extraLongShot: "Extra Long Shot (ELS)",
-		},
-		stylesList: {
-			styleLow: "Style Low",
-			styleMed: "Style Medium",
-			styleHigh: "Style High",
-			styleVeryHigh: "Style Very High",
-		},
-		lightList: {
-			coldLight: "Cold Light",
-			warmLight: "Warm Light",
-			hardLighting: "Hard Lighting",
-			dramaticLight: "Dramatic Light",
-			reflectionLight: "Reflection Light",
-			mistyFoggy: "Misty/Foggy",
-			naturalLight: "Natural Light",
-			sunLight: "Sun Light",
-			moody: "Moody",
-		},
-		versionList: {
-			mjV6: "MJ V6",
-			mjV52: "MJ V5.2",
-			mjV51: "MJ V5.1",
-			nijiV6: "Niji V6",
-			nijiV5: "Niji V5",
-			nijiV4: "Niji V4",
-			nijiJourney: "Niji Journey",
-		},
-		botList: {
-			midjourneyBot: "Midjourney Bot",
-			nijiJourney: "Niji Journey",
-		},
-		dimensionsList: {
-			square: "Square (1:1)",
-			portrait: "Portrait (2:3)",
-			landscape: "Landscape (3:2)",
-		},
-	}
-   ,suno:{
-    "description": "Режим описания",
-    "custom": "Профессиональный режим",
-    "style": "Стиль песни",
-    "stylepls": "Название песни, например, Поп-музыка",
-    "emputy": "Нет доступного содержимого",
-    "noly": "Текст песни недоступен",
-    "inputly": "Пожалуйста, введите название песни или текст",
-    "doingly": "В процессе, пожалуйста, подождите.",
-    "doingly2": "Получение текста...",
-    "title": "Название песни",
-    "titlepls": "Название песни, например, Каникулы",
-    "desc": "Описание песни",
-    "descpls": "Описание песни, например, Оригинальная поп-музыка о каникулах",
-    "noneedly": "Текст песни не требуется",
-    "rank": "Случайный выбор",
-    "ly": "Текст песни",
-    "lypls": "Текст песни: с определенным форматом",
-    "generate": "Создать песню",
-    "generately": "Сгенерировать текст",
-    "nodata": "Пожалуйста, сначала создайте песню, чтобы получить список песен",
-
-    "menu": "Музыка",
-    "menuinfo": "Создание музыки Suno",
-    "server": "Конечная точка API Suno",
-    "serverabout": "Связанные с Suno",
-    "setOpenKeyPlaceholder": "Связанный ключ для API Suno; необязательно"
-    
-   }
+    "imgExt": "Формат изображения должен быть jpg, gif, png, jpeg"
+  
+  }
 }

+ 18 - 132
src/locales/tr-TR.ts

@@ -51,7 +51,7 @@ export default {
         clearHistoryConfirm: 'Bu sohbet geçmişini silmek istediğinizden emin misiniz?',
         preview: 'Önizleme',
         showRawText: 'Ham metin olarak göster',
-    },
+    },    
     setting: {
         setting: 'Ayarlar',
         general: 'Genel',
@@ -74,7 +74,7 @@ export default {
         httpsProxy: 'HTTPS Proxy',
         balance: 'API Bakiyesi',
         monthlyUsage: 'Aylık Kullanım',
-    },
+    },       
     store: {
         siderButton: 'Prompt Mağazası',
         local: 'Yerel',
@@ -92,7 +92,7 @@ export default {
         importRepeatContent: 'İçerik tekrarlı olarak atlandı: {msg}',
         onlineImportWarning: 'Not: Lütfen JSON dosyası kaynağını kontrol edin!',
         downloadError: 'Lütfen ağ durumunu ve JSON dosyasının geçerliliğini kontrol edin',
-    },
+    },      
     "mj": {
         "setOpen": "OpenAI İlişkilendirilmiş",
         "setOpenPlaceholder": "http(s):// içermelidir",
@@ -105,7 +105,7 @@ export default {
         "setUploaderUrl": "Yükleme Adresi:",
         "setBtSave": "Kaydet",
         "setBtBack": "Varsayılanı Geri Yükle",
-
+    
         "redraw": "Yeniden Çiz",
         "fail1": "Lütfen sabırlı olun, yükleniyor.",
         "success1": "Resim başarıyla yenilendi!",
@@ -121,7 +121,7 @@ export default {
         "pan_down": "Aşağı",
         "up2": "HD 2x",
         "up4": "HD 4x" ,
-
+    
         "thinking": "Düşünüyor...",
         "noReUpload": "Yeniden yüklenemiyor",
         "uploading": "Yükleniyor...",
@@ -133,20 +133,20 @@ export default {
         "czoom": "Özel",
         "customTitle": "Özel zoom",
         "zoominfo": "Zoom değerini değiştirin, 1.0 ile 2.0 arasında, varsayılan 1.8 olarak ayarlanmıştır",
-
+    
         "modleSuccess": "Model başarıyla yüklendi",
         "setingSuccess": "Ayarlar başarılı",
-
+    
         "tokenInfo1": "Kalan Jetonlar = Model Uzunluğu - Rol Ayarı - Bağlam (Sohbet Geçmişi) - Yanıt Sayısı - Mevcut Giriş",
         "tokenInfo2": "Rol ayarı boş bırakılırsa, sistem varsayılan bir tane sağlar.",
         "noSuppertModel": "Yenile, bu model şu anda desteklenmiyor!",
         "failOcr": "Tanıma başarısız",
         "remain": "Kalan:",
-
+    
         "totalUsage": "Toplam abonelik miktarı",
         "disableGpt4": "GPT4 devre dışı",
         "setTextInfo": "OpenAI API Anahtarı hatası, buraya tıklayarak yeniden deneyin",
-
+    
         "attr1": "Attr",
         "ulink": "Resim Bağlantısı",
         "copyFail": "Kopyalama Başarısız",
@@ -164,7 +164,7 @@ export default {
         "mPlay": "Oynat",
         "mCanel": "İptal",
         "mSent": "Gönder",
-
+    
         "findVersion": "Güncellenmiş sürümü keşfet",
         "yesLastVersion": "Zaten en son sürümde",
         "infoStar": 'Bu proje <a class="text-blue-600 dark:text-blue-500" href="https://github.com/Dooy/chatgpt-web-midjourney-proxy\" target="_blank">GitHub</a> üzerinde açık kaynaklı, ücretsiz ve MIT lisansına dayanmaktadır, herhangi bir ödeme şekli yoktur! </p><p>Bu projeyi yararlı bulursanız, lütfen GitHub üzerinde yıldız verin, teşekkür ederim!',
@@ -172,7 +172,7 @@ export default {
         "setBtSaveSys": "Sisteme kaydet",
         "wsrvClose": "wsrv'yi kapat",
         "wsrvOpen": "wsrv'yi aç",
-
+        
         "temperature": "Rastlantısallık",
         "temperatureInfo": "(temperature) değeri arttıkça yanıtlar daha rastlantısal hale gelir",
         "top_p": "Üst Olasılık Örnekleme",
@@ -183,13 +183,13 @@ export default {
         "frequency_penaltyInfo": "(frequency_penalty) değeri arttıkça, tekrarlanan kelimelerin azaltılma olasılığı daha yüksektir"
         ,"tts_voice": "TTS Ses Karakteri",
         "typing": "Yazıyor",
-        "authErro": "Yetkilendirme başarısız",
+        "authErro": "Yetkilendirme başarısız", 
         "authBt": "Lütfen yetkilendirme erişim şifresini yeniden girin",
         "micWhisper": "Fısıltı konuşma tanıma",
         "micAsr": "Anında tanıma",
         "micRec": "Kayıt başlat, lütfen konuşun! 2 saniye boyunca ses yoksa otomatik olarak duracaktır.",
         "micRecEnd": "Kayıt sona erdi"
-    },
+    },        
     "mjset": {
         "server": "Sunucu",
         "about": "Hakkında",
@@ -202,7 +202,7 @@ export default {
         "drawinfo": "Midjourney Motoru ile Yapay Zeka Çizimi",
         "gallery": "Galeri",
         "galleryInfo": "Benim Galerim"
-    },
+    },        
     "mjchat": {
         "loading": "Resim Yükleniyor",
         "openurl": "Bağlantıyı Doğrudan Aç",
@@ -276,122 +276,8 @@ export default {
         "no2add": "Çift resim ekleme",
         "add2more": "Lütfen iki veya daha fazla resim ekleyin",
         "no1m": "Resim boyutu 1M'yi aşamaz",
-        "imgExt": "Resimler sadece jpg, gif, png, jpeg formatlarını destekler",
-        "setSync": "Midjourney ve Suno'yu senkronize et"
-    },
-	draw: {
-		qualityList: {
-			general: "General",
-			clear: "Clear",
-			hd: "HD",
-			ultraHd: "Ultra HD",
-		},
-		styleList: {
-			cyberpunk: "Cyberpunk",
-			star: "Star",
-			anime: "Anime",
-			japaneseComicsManga: "Japanese Comics/Manga",
-			inkWashPaintingStyle: "Ink Wash Painting Style",
-			original: "Original",
-			landscape: "Landscape",
-			illustration: "Illustration",
-			manga: "Manga",
-			modernOrganic: "Modern Organic",
-			genesis: "Genesis",
-			posterstyle: "Poster Style",
-			surrealism: "Surrealism",
-			sketch: "Sketch",
-			realism: "Realism",
-			watercolorPainting: "Watercolor Painting",
-			cubism: "Cubism",
-			blackAndWhite: "Black and White",
-			fmPhotography: "Film Photography Style",
-			cinematic: "Cinematic",
-			clearFacialFeatures: "Clear Facial Features",
-		},
-		viewList: {
-			wideView: "Wide View",
-			birdView: "Bird's Eye View",
-			topView: "Top View",
-			upview: "Upview",
-			frontView: "Front View",
-			headshot: "Headshot",
-			ultrawideshot: "Ultrawide Shot",
-			mediumShot: "Medium Shot (MS)",
-			longShot: "Long Shot (LS)",
-			depthOfField: "Depth of Field (DOF)",
-		},
-		shotList: {
-			faceShot: "Face Shot (VCU)",
-			bigCloseUp: "Big Close-Up (BCU)",
-			closeUp: "Close-Up (CU)",
-			waistShot: "Waist Shot (WS)",
-			kneeShot: "Knee Shot (KS)",
-			fullLengthShot: "Full Length Shot (FLS)",
-			extraLongShot: "Extra Long Shot (ELS)",
-		},
-		stylesList: {
-			styleLow: "Style Low",
-			styleMed: "Style Medium",
-			styleHigh: "Style High",
-			styleVeryHigh: "Style Very High",
-		},
-		lightList: {
-			coldLight: "Cold Light",
-			warmLight: "Warm Light",
-			hardLighting: "Hard Lighting",
-			dramaticLight: "Dramatic Light",
-			reflectionLight: "Reflection Light",
-			mistyFoggy: "Misty/Foggy",
-			naturalLight: "Natural Light",
-			sunLight: "Sun Light",
-			moody: "Moody",
-		},
-		versionList: {
-			mjV6: "MJ V6",
-			mjV52: "MJ V5.2",
-			mjV51: "MJ V5.1",
-			nijiV6: "Niji V6",
-			nijiV5: "Niji V5",
-			nijiV4: "Niji V4",
-			nijiJourney: "Niji Journey",
-		},
-		botList: {
-			midjourneyBot: "Midjourney Bot",
-			nijiJourney: "Niji Journey",
-		},
-		dimensionsList: {
-			square: "Square (1:1)",
-			portrait: "Portrait (2:3)",
-			landscape: "Landscape (3:2)",
-		},
-	}
-  ,suno:{
-    "description": "Açıklama Modu",
-    "custom": "Profesyonel Mod",
-    "style": "Şarkı Tarzı",
-    "stylepls": "Şarkı Adı, örneğin: Pop Müzik",
-    "emputy": "Mevcut içerik yok",
-    "noly": "Söz yok",
-    "inputly": "Lütfen şarkı adını veya sözleri girin",
-    "doingly": "Devam ediyor, lütfen bekleyin.",
-    "doingly2": "Sözler getiriliyor...",
-    "title": "Şarkı Adı",
-    "titlepls": "Şarkı Adı, örneğin: Tatil",
-    "desc": "Şarkı Açıklaması",
-    "descpls": "Şarkı açıklaması, örneğin: Tatil hakkında orijinal pop müziği",
-    "noneedly": "Söz gerekli değil",
-    "rank": "Rastgele seçim",
-    "ly": "Sözler",
-    "lypls": "Sözler: belirli bir formatta",
-    "generate": "Şarkı Oluştur",
-    "generately": "Sözler Oluştur",
-    "nodata": "Lütfen önce şarkı oluşturun ki şarkı listesi olsun",
-    "menu": "Müzik",
-    "menuinfo": "Suno Müzik Oluşturma",
-    "server": "Suno API Uç Noktası",
-    "serverabout": "Suno İlgili",
-    "setOpenKeyPlaceholder": "Suno API için İlgili Anahtar; isteğe bağlı"
-   }
-
+        "imgExt": "Resimler sadece jpg, gif, png, jpeg formatlarını destekler"
+    }
+    
   }
+  

+ 3 - 119
src/locales/vi-VN.ts

@@ -170,7 +170,7 @@ export default {
 
     "wsrvClose": "Đóng wsrv",
     "wsrvOpen": "Mở wsrv",
-
+    
     "temperature": "Ngẫu nhiên",
     "temperatureInfo": "Khi giá trị (temperature) tăng, các phản hồi trở nên ngẫu nhiên hơn",
     "top_p": "Lấy Mẫu Xác Suất Cao Nhất",
@@ -276,122 +276,6 @@ export default {
     "no2add": "Vui lòng không thêm hình ảnh giống nhau",
     "add2more": "Vui lòng thêm ít nhất hai hình ảnh",
     "no1m": "Kích thước hình ảnh không quá 1M",
-    "imgExt": "Chỉ hỗ trợ định dạng jpg, gif, png, jpeg cho hình ảnh",
-    "setSync": "Đồng bộ hóa Midjourney và Suno"
-  },
-	draw: {
-		qualityList: {
-			general: "General",
-			clear: "Clear",
-			hd: "HD",
-			ultraHd: "Ultra HD",
-		},
-		styleList: {
-			cyberpunk: "Cyberpunk",
-			star: "Star",
-			anime: "Anime",
-			japaneseComicsManga: "Japanese Comics/Manga",
-			inkWashPaintingStyle: "Ink Wash Painting Style",
-			original: "Original",
-			landscape: "Landscape",
-			illustration: "Illustration",
-			manga: "Manga",
-			modernOrganic: "Modern Organic",
-			genesis: "Genesis",
-			posterstyle: "Poster Style",
-			surrealism: "Surrealism",
-			sketch: "Sketch",
-			realism: "Realism",
-			watercolorPainting: "Watercolor Painting",
-			cubism: "Cubism",
-			blackAndWhite: "Black and White",
-			fmPhotography: "Film Photography Style",
-			cinematic: "Cinematic",
-			clearFacialFeatures: "Clear Facial Features",
-		},
-		viewList: {
-			wideView: "Wide View",
-			birdView: "Bird's Eye View",
-			topView: "Top View",
-			upview: "Upview",
-			frontView: "Front View",
-			headshot: "Headshot",
-			ultrawideshot: "Ultrawide Shot",
-			mediumShot: "Medium Shot (MS)",
-			longShot: "Long Shot (LS)",
-			depthOfField: "Depth of Field (DOF)",
-		},
-		shotList: {
-			faceShot: "Face Shot (VCU)",
-			bigCloseUp: "Big Close-Up (BCU)",
-			closeUp: "Close-Up (CU)",
-			waistShot: "Waist Shot (WS)",
-			kneeShot: "Knee Shot (KS)",
-			fullLengthShot: "Full Length Shot (FLS)",
-			extraLongShot: "Extra Long Shot (ELS)",
-		},
-		stylesList: {
-			styleLow: "Style Low",
-			styleMed: "Style Medium",
-			styleHigh: "Style High",
-			styleVeryHigh: "Style Very High",
-		},
-		lightList: {
-			coldLight: "Cold Light",
-			warmLight: "Warm Light",
-			hardLighting: "Hard Lighting",
-			dramaticLight: "Dramatic Light",
-			reflectionLight: "Reflection Light",
-			mistyFoggy: "Misty/Foggy",
-			naturalLight: "Natural Light",
-			sunLight: "Sun Light",
-			moody: "Moody",
-		},
-		versionList: {
-			mjV6: "MJ V6",
-			mjV52: "MJ V5.2",
-			mjV51: "MJ V5.1",
-			nijiV6: "Niji V6",
-			nijiV5: "Niji V5",
-			nijiV4: "Niji V4",
-			nijiJourney: "Niji Journey",
-		},
-		botList: {
-			midjourneyBot: "Midjourney Bot",
-			nijiJourney: "Niji Journey",
-		},
-		dimensionsList: {
-			square: "Square (1:1)",
-			portrait: "Portrait (2:3)",
-			landscape: "Landscape (3:2)",
-		},
-	}
-  ,suno:{
-    "description": "Chế độ mô tả",
-    "custom": "Chế độ chuyên nghiệp",
-    "style": "Phong cách bài hát",
-    "stylepls": "Tên bài hát, ví dụ: Nhạc Pop",
-    "emputy": "Không có nội dung",
-    "noly": "Không có lời bài hát",
-    "inputly": "Vui lòng nhập tên bài hát hoặc lời bài hát",
-    "doingly": "Đang tiến hành, vui lòng đợi.",
-    "doingly2": "Đang lấy lời bài hát...",
-    "title": "Tên bài hát",
-    "titlepls": "Tên bài hát, ví dụ: Kỳ nghỉ",
-    "desc": "Mô tả bài hát",
-    "descpls": "Mô tả bài hát, ví dụ: Nhạc pop gốc về kỳ nghỉ",
-    "noneedly": "Không cần lời bài hát",
-    "rank": "Lựa chọn ngẫu nhiên",
-    "ly": "Lời bài hát",
-    "lypls": "Lời bài hát: với một định dạng nhất định",
-    "generate": "Sáng tác bài hát",
-    "generately": "Tạo lời bài hát",
-    "nodata": "Vui lòng sáng tạo trước để có danh sách bài hát",
-
-    "menu": "Âm nhạc",
-    "menuinfo": "Sáng tạo âm nhạc Suno",
-    "server": "Điểm cuối API Suno",
-    "serverabout": "Liên quan đến Suno",
-    "setOpenKeyPlaceholder": "Khóa liên quan cho API Suno; tùy chọn"
-   }
+    "imgExt": "Chỉ hỗ trợ định dạng jpg, gif, png, jpeg cho hình ảnh"
+    }
 }

+ 220 - 8
src/locales/zh-CN.ts

@@ -1,3 +1,5 @@
+import Token from "markdown-it/lib/token";
+
 export default {
   common: {
     add: '添加',
@@ -27,7 +29,9 @@ export default {
     verify: '验证',
     unauthorizedTips: '未经授权,请先进行验证。',
     stopResponding: '停止响应',
+    history: '历史记录'
   },
+
   chat: {
     newChatButton: '新建聊天',
     //placeholder: '来说点什么吧...(Shift + Enter = 换行,"/" 触发提示词)',
@@ -51,13 +55,169 @@ export default {
     clearHistoryConfirm: '确定清空记录?',
     preview: '预览',
     showRawText: '显示原文',
+    annouce: '公告',
+    annouceContent: '本网站为网站所有内容,由用户自行承担。;本网站仅供学习之用。;使用本网站产生的任何问题由用户自行承担。',
+    helpTitle: '今天我能为您做些什么呢?',
+    helpcontent: '作为你的智慧伙伴,我不仅可以写文案、出点子,还可以和你聊天、回答问题。想知道我还能做些什么吗? 点击这里快速入门! 您可以将xxx官方网站地址收藏在浏览器中,方便日后使用。;你也可以这样问我:',
+    used: '使用',
+    refresh: '刷新',
+    like: '点赞',
+    bad: '不喜欢',
+  },
+
+
+  register: {
+    create_account: "创建账户",
+    invalid_account: "账号格式不正确",
+    empty_verification_code: "验证码不能为空",
+    registration_success: "注册成功",
+    get_verification_code: "获取验证码",
+    seconds_retry: " 秒后重试",
+    register: "注册",
+    or: "或者",
+    login: "登录",
+    if_account: " 如果您已经有帐户",
+    email: "邮箱",
+    enterEmail: "请输入邮箱",
+    verification_code: "验证码",
+    enter_verification_code: "请输入验证码",
+    password: "密码",
+    enter_password: "请输入密码",
+  },
+
+  login: {
+    accountFormatError: "账号格式不正确!",
+    loginSuccess: "登录成功!",
+    loginFailed: "登录失败!",
+    usernameOrPasswordEmpty: "用户名或者密码不能为空!",
+    emailOrPhone: "邮箱或者电话号码",
+    enterEmailOrPhone: "请输入邮箱或者电话号码",
+    password: "密码",
+    enterPassword: "请输入密码",
+    forgotPassword: "忘记密码?",
+    login: "登录",
+    register: "注册",
+    andExperience: "并免费体验问答助手",
+    scanQrCodeLogin: "请使用手机扫描二维码登录",
+    activateSystem: "微信登录",
+    activationFailed: "激活失败,请检查授权编码!",
+    activationSuccess: "激活成功!",
+    enterAuthCode: "请输入授权编号激活系统",
+    systemNotActivated: "系统未激活",
+    activationCode: "激活码",
+    activate: "激活",
+    or: '或者'
+  },
+
+  reset: {
+    reset: "重置密码",
+    invalid_account: "账号格式不正确",
+    empty_verification_code: "验证码不能为空",
+    reset_success: "重置成功",
+    get_verification_code: "获取验证码",
+    seconds_retry: " 秒后重试",
+    register: "注册",
+    or: "或者",
+    login: "登录",
+    if_account: " 如果您已经有帐户",
+    email: "邮箱",
+    enter_email: "请输入邮箱",
+    verification_code: "验证码",
+    enter_verification_code: "请输入验证码",
+    password: "密码",
+    enter_password: "请输入密码",
+  },
+
+  voice: {
+    play: '播放',
+    generate: '生成语音',
+    delete: '删除',
+    addRole: '添加角色',
+    roleName: '角色名称',
+    roleNameDescribe: '请输入角色名称',
+    roleDescribe: '角色描述',
+    roleExplain: '请输入角色描述',
+    avatar: '角色头像',
+    upload: '点击上传',
+    audioSamples: '音频样本',
+    prompt1: '请上传一个5MB以内的音频文件',
+    prompt2: '*样本语音需大于2秒,样本质量比长度更重要,过长的样本反而可能导致效果不如预期,通常将样本长度控制在5-8秒的最具代表性片段即可。',
+    add: '添加',
+    generateInfo: '控制语音生成的多样性,值越大,生成的语音将具备更高的表现力上限与随机性范围;应为0到100的整数,建议不小于95',
+    stability: '稳定性过滤 (0-100,默认为100)',
+    stabilityInfo: '限制在生成过程中仅选择前n个最佳的路径,值越小,生成的语音通常越平淡与稳定,但同时也可能导致表达某些内容或音色时的效果下降或异常;应为1到100的整数,为0时关闭限制,启用时建议不小于40',
+    start: '开始生成',
+    diversity: '多样性',
+    proposal:'建议填写不超过50个字符的单句以保证最佳效果(消耗1元/1000字符)',
+    createRole: '创建角色',
+    soundMarket: '声音市场',
+    playSound: '播放',
+    collection: '收藏',
+    return: '返回',
+    collectionSuccessful: '收藏成功',
+    uploudSuccessful: '上传成功',
+    operationSuccessful: '操作成功',
+
   },
+
+  annex: {
+    docId: "文档编号",
+    docName: "文档名称",
+    docType: "文档类型",
+    action: "操作",
+    uploadAttachment: "上传附件",
+    deleteAttachment: "删除附件",
+    knowledgeFragment: "知识片段",
+    fileUploadSuccess: "附件上传成功!",
+    deletionFailed: "删除失败!",
+    attachmentDeletedSuccess: "附件删除成功!",
+    pleaseUploadFile: "请上传一个10MB以内的文件",
+    supportedFormats: "已支持 md、pdf、docx、txt、csv 等文件格式",
+    friendlyReminder: "温馨提醒:",
+    largeFileWarning: "1. 如果文件较大,上传时间可能较长,请耐心等待。",
+    utf8Warning: "2. 上传txt文本,请使用UTF8格式,以避免乱码。",
+    uploadCharacterLimit: "3. 单次上传字数建议5万以下,解析1万字大约需要10秒。"
+  },
+
+  knowledge: {
+    createKnowledgeBase: "创建知识库",
+    createYourKnowledgeBase: "在这里创建你的知识库",
+    knowledgeName: "知识名称",
+    knowledgeDescription: "知识描述",
+    enterKnowledgeName: "输入知识名称",
+    enterKnowledgeDescription: "输入知识描述",
+    add: "添加",
+    delete: "删除",
+    attachment: "附件",
+    id: "ID",
+    number: "编号",
+    name: "知识名称",
+    description: "知识描述",
+    actions: "操作",
+    return: "返回",
+    no: "片段编号",
+    content: "片段内容",
+  },
+
+
   setting: {
     setting: '设置',
     general: '总览',
     advanced: '高级',
     config: '配置',
-    avatarLink: '头像链接',
+    avatarLink: '头像',
+    balance: '余额',
+    grade: '会员等级',
+    senior: '高级会员',
+    lowlevel: "低级会员",
+    personal: "个人中心",
+    model: "模型信息",
+    message: "消息记录",
+    about: "关于我们",
+    plan: "订阅计划",
+    charge: "收费标准",
+    exchange: "兑换卡密",
+    recharge: "立即充值",
     name: '名称',
     description: '描述',
     role: '角色设定',
@@ -72,10 +232,15 @@ export default {
     timeout: '超时',
     socks: 'Socks',
     httpsProxy: 'HTTPS Proxy',
-    balance: 'API余额',
     monthlyUsage: '本月使用量',
   },
+
   store: {
+    unit: '¥',
+    redeemKey: "兑换",
+    token: "1000个Token大约相当于750个英文单词或400至500个汉字。在按Token计费的模型中,每使用1000个Token将进行一次扣费。",
+    input: "请输入卡密",
+    day: '天',
     siderButton: '购买套餐',
     local: '本地',
     online: '在线',
@@ -92,6 +257,7 @@ export default {
     importRepeatContent: '内容重复跳过:{msg}',
     onlineImportWarning: '注意:请检查 JSON 文件来源!',
     downloadError: '请检查网络状态与 JSON 文件有效性',
+    login: '立即登录'
   },
 
 
@@ -99,16 +265,26 @@ export default {
     server:'服务端'
     ,about:'关于'
     ,model:'模型'
-    ,sysname:'AI绘图'
+    ,sysname:'熊猫助手'
+    ,logout:'退出'
+    ,balance:'余额'
   }
 
   ,mjtab:{
     chat:'对话'
+    ,store: "应用"
+    ,voice: "声音"
+    ,voiceinfo: "创建属于自己的声音"
+    ,knowledge: "知识库"
+    ,knowledgeinfo: "创建私有知识库"
+    ,bot: "机器人"
+    ,botinfo: "扫码登录微信机器人"
     ,draw:'绘画'
     ,drawinfo:'AI绘画 Midjourney引擎'
     ,gallery:'画廊'
     ,galleryInfo:'我的画廊'
   }
+
   ,mjchat:{
     loading:'正在载入图片'
     ,openurl:'直接打开链接'
@@ -129,6 +305,8 @@ export default {
     ,successTitle:'成功'
     ,modlePlaceholder:'自定义模型多个用空格隔开,不是必须'
     ,myModle:'自定义模型'
+    ,customer:'联系客服'
+    ,knowledgeBase: "切换知识库"
     ,historyCnt:'上下文数量'
     ,historyToken:'更多的上下文会使记忆更精确,但会消耗更多的额度'
     ,historyTCnt:'回复数'
@@ -166,7 +344,7 @@ export default {
     ,img2textinfo:'不知如何写提示词?用图生文试试!<br/>提交图片,出提示词'
     ,traning:'翻译中...'
     ,imgcreate:'生成图片'
-    ,imginfo:'其他参数:  <li>1 --no 忽略 --no car 图中不出现车 </li><li>2 --seed 可先获取种子 --seed 123456 </li> <li>3 --chaos 10 混合(范围:0-100)</li> <li>4 --tile 碎片化 </li>  <li>5 --cw 0 只参考五官, 100 参考五官、头发、服装等  </li>'
+    ,imginfo:'其他参数:  <li>1 --no 忽略 --no car 图中不出现车 </li><li>2 --seed 可先获取种子 --seed 123456 </li> <li>3 --chaos 10 混合(范围:0-100)</li> <li>4 --tile 碎片化 </li>  <li>5 --sref 图片url 生成风格一致的图像 <li>6 --cref 图片url 生成<b>角色</b>一致的图像  </li> '
     ,tStyle:'风格'
     ,tView:'视角'
     ,tShot:'人物镜头'
@@ -183,8 +361,8 @@ export default {
     ,add2more:'请添加两张以上图片'
     ,no1m:'图片大小不能超过{m}M'
     ,imgExt:'图片仅支持jpg,gif,png,jpeg格式'
-    ,setSync:'同步Midjourney、Suno设置'
   },
+
   mj:{
     setOpen:'OpenAI 相关',
     setOpenPlaceholder:'必须包含 http(s)://'
@@ -262,7 +440,7 @@ export default {
     ,findVersion:'发现更新版本'
     ,yesLastVersion:'已是最新版本'
     ,infoStar:'此项目开源于 <a  class="text-blue-600 dark:text-blue-500" href="https://github.com/Dooy/chatgpt-web-midjourney-proxy" target="_blank"> GitHub </a>,免费且基于 MIT 协议,没有任何形式的付费行为! </p><p>如果你觉得此项目对你有帮助,请在 GitHub 帮我点个 Star,谢谢!'
-    ,setBtSaveChat:'保存会话'
+    ,setBtSaveChat:'保存会话'
     ,setBtSaveSys: '保存至系统'
 
     ,wsrvClose:'关闭 wsrv'
@@ -288,8 +466,19 @@ export default {
     ,micRec:'开始录音,请说话!2秒内无声音将自动关闭'
     ,micRecEnd:'录音已结束'
 
-    
+  },
 
+  model: {
+    "name": "模型名称",
+    "price": "模型价格",
+    "type": "计费方式",
+    "remark": "备注",
+    "content": "消息内容",
+    "modelName": "模型名称",
+    "totalTokens": "消耗token",
+    "deductCost": "扣除费用",
+    "msgtime": "创建时间",
+    "msgremark": "备注",
   },
 
 	draw: {
@@ -380,6 +569,7 @@ export default {
 		},
 	}
 
+
   ,suno:{
     description:"描述模式"
     ,custom:"定制模式"
@@ -407,7 +597,29 @@ export default {
     ,server:'Suno 接口地址'
     ,serverabout:'Suno 相关'
     ,setOpenKeyPlaceholder:'Suno API 的相关KEY;可不填'
-  }
 
+    ,upMps:'上传音频'
+    ,extend:'延伸'
+    ,extendFrom:'延伸于'
+    ,extendAt:'延伸开始于'
+    ,fail:'失败'
+    ,info:'说明:<br>上传音频时长必须在6s-60s内'
+  }
+  ,video:{
+    menu:"视频",
+    menuinfo:'Luam等 视频创作',
+    descpls:'视频创作描述',
+    lumaabout:"Luma 相关",
+    lumaserver:"Luma 接口地址",
+    setOpenKeyPlaceholder:'Luma API 的key, 可不填',
+    generate:'生成视频',
+    nodata:'暂无可用视频,请先生成!',
+    selectimg:'参考图片',
+    clear:'清除',
+    plsInput:'请输入内容!',
+    submitSuccess:'已提交成功!',
+    process:'视频生成中...',
+    repeat:'重新获取',
+  }
 
 }

+ 50 - 29
src/locales/zh-TW.ts

@@ -167,7 +167,7 @@ export default {
     "findVersion": "發現更新版本",
     "yesLastVersion": "已是最新版本",
     "infoStar": "此專案在 <a class=\"text-blue-600 dark:text-blue-500\" href=\"https://github.com/Dooy/chatgpt-web-midjourney-proxy\" target=\"_blank\">GitHub</a> 上以 MIT 協議開源,免費且沒有任何付費行為! </p><p>如果你覺得這個專案對你有幫助,請在 GitHub 上給它一顆星,謝謝!",
-    "setBtSaveChat": "保存對話",
+    "setBtSaveChat": "保存對話",
     "setBtSaveSys": "保存至系統",
     "wsrvClose": "關閉 wsrv",
     "wsrvOpen": "開啟 wsrv",
@@ -272,8 +272,7 @@ export default {
     "no2add": "請勿重複添加圖片",
     "add2more": "請添加兩張以上圖片",
     "no1m": "圖片大小不能超過1M",
-    "imgExt": "圖片僅支持jpg,gif,png,jpeg格式",
-    "setSync": "同步Midjourney和Suno",
+    "imgExt": "圖片僅支持jpg,gif,png,jpeg格式"
   },
 	draw: {
 		qualityList: {
@@ -363,32 +362,54 @@ export default {
 		},
 	}
   ,suno:{
-    "description": "描述模式",
-    "custom": "專業模式",
-    "style": "歌曲風格",
-    "stylepls": "歌曲名稱,例如:流行音樂",
-    "emputy": "暫無內容",
-    "noly": "無歌詞",
-    "inputly": "請輸入歌曲名稱或歌詞",
-    "doingly": "正在進行中,請稍候。",
-    "doingly2": "正在獲取歌詞...",
-    "title": "歌曲名稱",
-    "titlepls": "歌曲名稱,例如:假期",
-    "desc": "歌曲描述",
-    "descpls": "歌曲描述,例如:關於假期的原聲流行音樂",
-    "noneedly": "無需歌詞",
-    "rank": "隨機獲取",
-    "ly": "歌詞",
-    "lypls": "歌詞:有一定的格式",
-    "generate": "創作歌曲",
-    "generately": "生成歌詞",
-    "nodata": "請先創作才有歌曲列表",
+    description:"描述模式"
+    ,custom:"定制模式"
+    ,style:'歌曲风格'
+    ,stylepls:'歌曲名称比如:流行音乐'
+    ,emputy:'暂无内容'
+    ,noly:'无歌词'
+    ,inputly:'请输入歌曲名称或歌词'
+    ,doingly:"正在执行请稍后."
+    ,doingly2: "正在获取歌词..."
+    ,title:'歌曲名称'
+    ,titlepls:'歌曲名称比如:假期'
+    ,desc:'歌曲描述'
+    ,descpls:'歌曲描述 比如:关于假期的原声流行音乐'
+    ,noneedly:'无需歌词'
+    ,rank:'随机获取'
+    ,ly:'歌词'
+    ,lypls:'歌词:有一定的格式'
+    ,generate:'创作歌曲'
+    ,generately:'生成歌词'
+    ,nodata:'请先创作才有歌曲列表'
 
-    "menu": "音樂",
-    "menuinfo": "Suno 音樂創作",
-    "server": "Suno API 端點",
-    "serverabout": "Suno 相關",
-    "setOpenKeyPlaceholder": "Suno API 的相關KEY;可不填"
+    ,menu:'音乐'
+    ,menuinfo:'Suno 音乐创作'
+    ,server:'Suno 接口地址'
+    ,serverabout:'Suno 相关'
+    ,setOpenKeyPlaceholder:'Suno API 的相关KEY;可不填'
 
-   }
+    ,upMps:'上传音频'
+    ,extend:'延伸'
+    ,extendFrom:'延伸于'
+    ,extendAt:'延伸开始于'
+    ,fail:'失败'
+    ,info:'说明:<br>上传音频时长必须在6s-60s内'
+  }
+  ,video:{
+    menu:"视频",
+    menuinfo:'Luam等 视频创作',
+    descpls:'视频创作描述',
+    lumaabout:"Luma 相关",
+    lumaserver:"Luma 接口地址",
+    setOpenKeyPlaceholder:'Luma API 的key, 可不填',
+    generate:'生成视频',
+    nodata:'暂无可用视频,请先生成!',
+    selectimg:'参考图片',
+    clear:'清除',
+    plsInput:'请输入内容!',
+    submitSuccess:'已提交成功!',
+    process:'视频生成中...',
+    repeat:'重新获取',
+  }
 }

+ 4 - 0
src/main.ts

@@ -4,9 +4,13 @@ import { setupI18n } from './locales'
 import { setupAssets, setupScrollbarStyle } from './plugins'
 import { setupStore } from './store'
 import { setupRouter } from './router'
+import 'virtual:svg-icons-register'; // 引入虚拟的 svg 图标模块
+import IconSvg from '@/components/common/IconSvg/index.vue';
 
 async function bootstrap() {
   const app = createApp(App)
+  // 全局注册 SvgIcon 组件
+  app.component('IconSvg', IconSvg);
   setupAssets()
 
   setupScrollbarStyle()

+ 108 - 7
src/router/index.ts

@@ -4,7 +4,10 @@ import { createRouter, createWebHashHistory } from 'vue-router'
 import { setupPageGuard } from './permission'
 import { ChatLayout } from '@/views/chat/layout'
 import mjlayout from '@/views/mj/layout.vue'
+import soundLayout from '@/views/sound/layout.vue'
 import sunoLayout from '@/views/suno/layout.vue'
+import lumaLayout from '@/views/luma/layout.vue'
+import knowLayout from '@/views/knowledge/layout.vue'
 
 const routes: RouteRecordRaw[] = [
   {
@@ -33,6 +36,7 @@ const routes: RouteRecordRaw[] = [
       },
     ],
   },
+
    {
     path: '/m',
     name: 'm',
@@ -62,18 +66,115 @@ const routes: RouteRecordRaw[] = [
   },
 
   {
-    path: '/music',
-    name: 'music',
-    component: sunoLayout,
-    redirect: '/music/index',
+    path: '/sound',
+    name: 'Sound',
+    component: soundLayout,
+    redirect: '/sound/t',
+    children: [
+      {
+        path: 't',
+        name: 'sound1',
+        component: () => import('@/views/sound/index.vue'),
+      },
+    ],
+  },
+  
+  {
+    path: '/roleList',
+    name: 'RoleList',
+    component: ChatLayout,
+    redirect: '/roleList/t',
+    children: [
+      {
+        path: 't',
+        name: 'roleList1',
+        component: () => import('@/views/sound/roleList.vue'),
+      },
+    ],
+  },
+
+  {
+    path: '/wxbot',
+    name: 'Wxbot',
+    component: ChatLayout,
+    redirect: '/wxbot/t',
     children: [
       {
-        path: '/music/:uuid?',
-        name: 'music',
-        component: () => import('@/views/suno/music.vue'),
+        path: 't',
+        name: 'wxbot1',
+        component: () => import('@/views/wxbot/bot.vue'),
       },
     ],
   },
+  {
+    path: '/knowledge',
+    name: 'Knowledge',
+    component: knowLayout,
+    redirect: '/knowledge/t',
+    children: [
+      {
+        path: 't',
+        name: 'knowledge1',
+        component: () => import('@/views/knowledge/index.vue'),
+      },
+    ],
+  },
+
+  {
+    path: '/annex',
+    name: 'Annex',
+    component: knowLayout,
+    redirect: '/annex/t',
+    children: [
+      {
+        path: 't',
+        name: 'annex1',
+        component: () => import('@/views/knowledge/annex.vue'),
+      },
+    ],
+  },
+
+  {
+    path: '/fragment',
+    name: 'Fragment',
+    component: knowLayout,
+    redirect: '/fragment/t',
+    children: [
+      {
+        path: 't',
+        name: 'fragment1',
+        component: () => import('@/views/knowledge/fragment.vue'),
+      },
+    ],
+  },
+
+  // {
+  //   path: '/music',
+  //   name: 'music',
+  //   component: sunoLayout,
+  //   redirect: '/music/index',
+  //   children: [
+  //     {
+  //       path: '/music/:uuid?',
+  //       name: 'music',
+  //       component: () => import('@/views/suno/music.vue'),
+  //     },
+  //   ],
+  // },
+
+  // {
+  //   path: '/video',
+  //   name: 'video',
+  //   component: lumaLayout,
+  //   redirect: '/video/index',
+  //   children: [
+  //     {
+  //       path: '/video/:uuid?',
+  //       name: 'video',
+  //       component: () => import('@/views/luma/video.vue'),
+  //     },
+  //   ],
+  // },
 
   {
     path: '/404',

+ 20 - 14
src/router/permission.ts

@@ -1,22 +1,28 @@
 import type { Router } from 'vue-router'
-import { getToken } from '@/store/modules/auth/helper'
+import { ref } from 'vue';
 
 const whiteList = ['/login', '/regist','/resetpassword']; // 不重定向白名单
 
 export function setupPageGuard(router: Router) {
   router.beforeEach(async (to, from, next) => {
-    if (getToken()) {
-        if (to.path === '/login') {
-          next({ path: '/' });
-        }else{
-          next();
-        } 
-    } else {
-      if (whiteList.indexOf(to.path) !== -1) {
-        next();
-      } else {
-        next({ name: 'Login' });
-      }
-    }
+    // 检查浏览器语言
+    // const browserLanguage = navigator.language || navigator.language;
+    // const isChineseLanguage = browserLanguage.includes('zh');
+    // const isInChina = ref(false);
+    // const response = await fetch('http://ip-api.com/json/');
+    // const data = await response.json();
+    // isInChina.value = data.countryCode === 'CN';
+    // console.log("isInChina", isInChina.value,isChineseLanguage)
+    // // 中国大陆的国家代码为 "CN"
+    // if (isChineseLanguage && isInChina.value) {
+    //   alert("该网站不支持大陆ip")
+    //   // 如果是中文环境,则重定向到 zh.html
+    //   window.location.href = '/index.htm';
+    // } else {
+    //     next();
+    //     console.log("next===")
+    // }
+		next();
+
   });
 }

+ 18 - 29
src/store/homeStore.ts

@@ -5,15 +5,10 @@ import { ss } from '@/utils/storage'
 export const homeStore = reactive({
     myData:{
         act:'',//动作
-        act2:'',//动作
         actData:{} //动作类别 
         ,local:'' //当前所处的版本
         ,session:{} as any
         ,isLoader:false
-        ,vtoken:'' //turnstile token
-        ,ctoken:'' //cookie
-        ,isClient: typeof window !== 'undefined' && window.__TAURI__
-        ,ms:{} as any
        
     }
     
@@ -25,22 +20,19 @@ export const homeStore = reactive({
                 this.myData.actData=''
             }, 2000 );
         }
-        if( Object.keys(v).indexOf('act2')>-1){ 
-            setTimeout(()=> {
-                this.myData.act2=''
-                this.myData.actData=''
-            }, 500 );
-        }
     }
  
 })
 
 export interface gptConfigType{
     model:string
+    modelLabel:string
     max_tokens:number
     userModel?:string //自定义
     talkCount:number //联系对话
     systemMessage:string //自定义系统提示语
+    kid:string //知识库id
+    kName:string //知识库名称
     gpts?:gptsType
     uuid?:number
     temperature?:number // 随机性 : 值越大,回复越随机
@@ -60,19 +52,22 @@ const getGptInt= ():gptConfigType =>{
 }
 
 const  getDefault=()=>{
-const amodel = homeStore.myData.session.amodel??'gpt-3.5-turbo'
+const amodel = homeStore.myData.session.amodel??'o1-mini-2024-09-12'
 let v:gptConfigType={
-        model: amodel,
-        max_tokens:1024,
-        userModel:'',
-        talkCount:10,
-        systemMessage:'',
-        temperature:0.5,
-        top_p:1,
-        presence_penalty:0,
-        frequency_penalty:0,
-        tts_voice:"alloy"
-    }
+    model: amodel,
+    modelLabel: '',
+    max_tokens: 1024,
+    userModel: '',
+    talkCount: 10,
+    systemMessage: '',
+    temperature: 0.5,
+    top_p: 1,
+    presence_penalty: 0,
+    frequency_penalty: 0,
+    tts_voice: "alloy",
+    kid: '',
+    kName: ''
+}
     return v ;
 }
 export const gptConfigStore= reactive({
@@ -98,9 +93,6 @@ export interface gptServerType{
     MJ_API_SECRET:string
     UPLOADER_URL:string
     MJ_CDN_WSRV?:boolean //wsrv.nl
-    SUNO_SERVER:string
-    SUNO_KEY:string
-    IS_SET_SYNC?:boolean
 
 }
 
@@ -111,10 +103,7 @@ let v:gptServerType={
         MJ_SERVER:'',
         UPLOADER_URL:'',
         MJ_API_SECRET:'',
-        SUNO_KEY:'',
-        SUNO_SERVER:'',
         MJ_CDN_WSRV:false
-        ,IS_SET_SYNC:true
     }
     return v ;
 }

+ 2 - 1
src/store/modules/user/helper.ts

@@ -6,7 +6,8 @@ export interface UserInfo {
   avatar: string
   name: string
   userBalance: number
-  userGrade: string
+  userGrade: string,
+  userName: string
 }
 
 export interface UserState {

+ 4 - 0
src/store/modules/user/index.ts

@@ -28,6 +28,10 @@ export const useUserStore = defineStore('user-store', {
       }
       return Promise.reject(err);
     },
+    // 二维码登录的方法
+    async userQrLogin(token: string){
+      setToken(token);
+    },
 
     logout: async (): Promise<void> => {
       await loginOut();

+ 1 - 1
src/styles/lib/github-markdown.less

@@ -1099,4 +1099,4 @@ html {
 
 .markdown-body ::-webkit-calendar-picker-indicator {
   filter: invert(50%);
-}
+}

+ 1415 - 0
src/styles/lib/highlight.less

@@ -113,6 +113,478 @@ html.dark {
 	.hljs-link {
 		text-decoration: underline
 	}
+	.nav-bar{
+		&>div{
+			background-color: #141718;
+		}
+		.user-info{
+			box-shadow: 0px 20px 24px rgba(0, 0, 0, 0.5);
+			background-color: #232627;
+			.user-free{
+				color: #000;
+			}	
+			.top{
+				.avatar{
+					.circle{
+						border-color: #232627;
+					}
+				}
+			}
+		}
+		.user-bottom{
+			border: 2px solid #343839;
+		}
+		.user-footer{
+			box-shadow: 0px 20px 24px rgba(0, 0, 0, 0.5);
+			background-color: #232627;
+			svg{
+				margin: 0px 10px 0px 28px;
+				color: #6C7275;
+			}
+			.settings,
+			.log-out{
+				color: #6c7275;
+				&.active,
+				&:hover{
+					background-color: #141718;
+					color: #fff;
+					svg{
+						color: #fff;
+					}
+				}
+			}
+		}
+	}
+	.nemu-bar{
+		.nemu-item{
+			& .flex-row{
+				&:hover,
+				&.active{
+					background: linear-gradient(270deg, #323337 50%, rgba(70, 79, 111, 0.5) 100%);
+					box-shadow: 0px 4px 8px rgba(0, 0, 0, 0.1), inset 0px 1px 0px rgba(255, 255, 255, 0.05);
+				}
+			}
+		}
+	}
+	.n-layout-scroll-container{
+		background-color: #141718;
+		// margin-top: 20px;
+	}
+	.char-sider{
+		.top-new-button {
+			border-color: #2e3132;
+			button{
+				color: #fff;
+				&:hover{
+					color: #7fe7c4;
+				}
+			}
+		}
+		.chat-item{
+			&.check-chat-item,
+			&:hover{
+				background: linear-gradient(270deg, #323337 50%, rgba(80, 62, 110, 0.29) 100%);
+				border-radius: 8px;
+			}
+		}
+	}
+	.chat-content {
+		.footer-content{
+			background-color: #232627;
+			.chat-footer{
+				background-color: #232627;
+				border: 2px solid #343839;
+				.top-bar{
+					border-bottom: 2px solid #343839;
+					svg{
+						fill: #fff;
+						outline: none;
+					}
+				}
+				.chat-input{
+					.n-input-wrapper{
+						background-color: #232627;
+					}
+				}
+				.send{
+					background-color: #0084FF;
+				}
+			}
+		}
+		#scrollRef{
+			background-color: #232627;
+		}
+		#image-wrapper{
+			background-color: #232627;
+		}
+		.message-reply{
+			background-color: #000;
+		}
+		.message-request{
+			background-color: #0084FF;
+		}
+		.message-reply,
+		.message-request{
+			.markdown-body{
+				p{
+					color: #fff;
+				}
+			}
+		}
+	}
+	.change-dialog{
+		background-color: #141718;
+		border: none !important;
+		box-shadow: none !important;
+		.n-card-header{
+			border-color: #232627;
+		}
+	}
+	.model-button{
+		button{
+			position: static;
+			&:first-child{
+				outline: none;
+				border: 2px solid #075caa;
+				color: #0084ff;
+			}
+			&:last-child{
+				border: none;
+				box-shadow: none;
+				background-color: #0084ff;
+				border-color: #0084ff;
+				color: #fff;
+			}
+		}
+	}
+	.mydrawer{
+		.n-drawer-header{
+			background-color: #232627;
+		}
+		.store-content{
+			background-color: #232627;
+			.store-label-item{
+				button{
+					color: #fff;
+					background-color: #0084ff;
+					&.n-button--secondary{
+						background-color: #2b2f30;
+						color: #0084ff;
+					}
+				}
+			}
+			.store-info-item{
+				background-color: #2b2f30;
+				border: 1px solid #3e4040;
+			}
+		}
+	}
+	.genner-button{
+		background-color: #0084ff !important;
+		color: #fff;
+		margin-bottom: 6px;
+	}
+	.draw-tabs .n-tabs-tab--active{
+		color: #0084ff !important;
+	}
+	.draw-tabs{
+		.n-tabs-tab{
+			&:hover{
+				color: #0084ff !important;
+			}
+				
+		}
+	}
+	.draw-tabs .n-tabs-bar{
+		background-color: #0084ff;
+	}
+	.n-slider-rail{
+		.n-slider-rail__fill{
+			background-color: #0084ff;
+				
+		}
+	}
+	.voice-drawer{
+		.success-button{
+			background-color: #141718;
+		}
+	}
+	.add-role-draw{
+		.add-role-button{
+			height: 48px;
+			line-height: 48px;
+			border-radius: 12px;
+			padding: 0 40px;
+			background-color: #0084FF;
+			color: #fff;
+		}
+	}
+	.add-role-draw{
+		background-color: #141718;
+	}
+	.n-data-table{
+		.n-data-table-base-table-body{
+			.n-data-table-thead{
+				border: 1px solid red;
+				border-top-left-radius: 10px;
+				border-top-right-radius: 10px;
+				th{
+					border: 1px solid #343637;
+					&:nth-child(1) {
+						border-right: none;
+					}
+					&:nth-child(2) {
+						border-left: none;
+						border-right: none;
+	
+					}
+					&:nth-child(3) {
+						border-left: none;
+						border-right: none;
+					}
+					&:nth-child(4) {
+						padding-left: 40px;
+						border-left: none;
+					}
+				}
+			}
+			th{
+				background-color: #2b2f30;
+				&:first-child{
+					border-top-left-radius: 10px;
+				}
+			}
+			.n-data-table-tbody{
+				.n-data-table-tr{
+					&:nth-child(even) {
+						td{
+							background-color: #2b2f30;
+						}
+					}
+					&:nth-child(odd) {
+						td{
+							background-color: #232627;
+						}
+					}
+					td{
+						font-size: 14px;
+						padding: 12px 15px;
+					}
+				}
+			}
+		}
+		.table-button{
+			background: rgba(0, 132, 255, 0.2);
+			color: #0084FF !important;
+			&:last-child{
+				color: #D84C10 !important;
+				background: rgba(216, 76, 16, 0.2);
+			}
+		}
+	}
+	.draw-button{
+		background-color: #0084FF;
+		color: #fff;
+	}
+	.knowledge-draw{
+		background-color: #141718;
+	}
+	.top-header,
+	.chat-header{
+		border-bottom: 1px solid #343839;
+		background-color: #232627;
+		button{
+			background-color: #141718;
+		}
+	}
+	.annex-modal{
+		width: 540px;
+		background-color: #141718 !important;
+		.annex-upload{
+			width: 500px;
+		}
+	}
+	.success-button{
+		border: 2px solid #0084FF !important;
+		color: #0084FF;
+		background-color: #232627 !important;
+		&:hover{
+			opacity: .8;
+			color: #0084ff;
+		}
+	}
+	.sound-button-box{
+		button{
+			background-color: #232627;
+			&:hover{
+				opacity: .7;
+			}
+			&:first-child{
+				border: 2px solid #0084FF;
+				color: #0084FF;
+			}
+			&:last-child{
+				border: 2px solid #D84C10;
+				color: #D84C10;
+				margin-left: 10px;
+			}
+		}
+	}
+	.role-card{
+		background-color: #232627;
+		.card-container{
+			.card-item{
+				background-color: #2b2f30;
+				border: 1px solid #505050;
+				.ellipsis{
+					color: #aaacac;
+				}
+			}
+		}
+		.voice-pagination{
+			left: 19px;
+		}
+	}
+	.plan-draw{
+		background-color: #141718 !important;
+		.plan-item{
+			background-image: url(@/assets/Subtract.png);
+			.header{
+				border-bottom: 1px solid #343839;
+				.price{
+					color: #0084FF;
+				}
+				.title{
+					color: #babec1;
+				}
+			}
+			.content{
+				border-bottom: 1px solid #343839;
+				.option-item{
+					.quanquan{
+						background-color: #0084ff;
+						color: #fff;
+					}
+				}
+			}
+			.footer{
+				background-color: #0084ff;
+				color: #fff;
+			}
+			&:hover{
+				background-image: url(@/assets/Subtract-active.png);
+				.header{
+					border-bottom: 1px solid #339dff;
+					.title{
+						color: #fff;
+					}
+					.price{
+						color: #fff;
+					}
+				}
+				.content{
+					border-bottom: 1px solid #339dff;
+					.option-item{
+						.quanquan{
+							background-color: #fff ;
+							color: #0084ff;
+						}
+					}
+				}
+				.footer{
+					background-color: #fff;
+					color: #0084ff;
+				}
+			}
+		}
+	}
+	.input-button-container {
+		button{
+			background-color: #0084FF;
+			color: #fff;
+		}
+	}
+	.table-box{
+		background-color: #232627;
+	}
+	.know-header{
+		border-bottom: 1px solid #2e3132;
+		background-color: #232627;
+	}
+	.clear-chat{
+		background-color: #111415;
+		&:hover{
+			transform: scale(1.1);
+		}
+		svg{
+			fill: #fff;
+		}
+	}
+	.gpts-box{
+		color: #494747;
+		h1{
+			color: #fff;
+		}
+		.text{
+			background-color: #141718;
+			p{
+				color: #e2e2e2;
+			}
+			.title{
+				color: #fff;
+				font-size: 16px;
+			}
+		}
+		.gpts-list{
+			.refresh{
+				color: #fff;
+				&:hover{
+					color: #0084ff;
+				}
+			}
+			.gpts-item{
+				background-color: #232627;
+				border: 1px solid #3a3b3c;
+				.name{
+					color: #a7a8a9;
+					&:hover{
+						color: #0084ff;
+					}
+				}
+			}
+		}
+	}
+	.music-content{
+		background-color: #232627;
+		.music-list{
+			background-color: #232627;
+		}
+		.n-switch__button-placeholder{
+			background-color: #0084FF;
+		}
+		.n-tabs-nav-scroll-content{
+			.n-tabs-tab--active{
+				color: #0084ff ;
+			}
+			.n-tabs-bar{
+				background-color: #0084FF;
+			}
+			.n-tabs-tab-wrapper:hover{
+				.n-tabs-tab__label{
+					color: #0084FF;
+				}
+			}
+		}
+	}
+	.video-content{
+		background-color: #232627;
+		.video-list{
+			background-color: #232627;
+		}
+	}
+	.annex-main{
+		background-color: #232627;
+	}
 }
 
 html {
@@ -204,3 +676,946 @@ html {
 		text-decoration: underline
 	}
 }
+
+
+.n-layout-scroll-container{
+	background-color: #e8eaf1;
+	padding-right: 12px;
+}
+.is-mobile{.n-layout-scroll-container{
+	padding-right: 0;
+}
+}
+.text-[18px]{
+	font-size: 18px;
+}
+.nav-bar{
+width: 332px;
+position: relative;
+&>div{
+	// background-color: #131718;
+}
+.nemu-bar{
+	width: 332px;
+	.nemu-item{
+	margin-left: 16px;
+	width: 288px;
+	& .flex-row{
+		width: 288px;
+		height: 48px;
+		border-radius: 8px;
+		font-family: 'Inter';
+		font-style: normal;
+		font-weight: 600;
+		padding-left: 31px;
+		span{
+		// color: rgba(232, 236, 239, 0.75);
+			margin-left: 15px;
+		}
+		&:hover,
+		&.active{
+			background: linear-gradient(270deg, #323337 50%, rgba(70, 79, 111, 0.5) 100%);
+			background: linear-gradient(270deg, #fff 50%, #e8eaf1 100%);
+			box-shadow: 0px 4px 8px rgba(0, 0, 0, 0.1), inset 0px 1px 0px rgba(255, 255, 255, 0.05);
+		}
+
+	}
+	}
+}
+.user-info{
+	padding: 10px;
+	width: 288px;
+	left: 16px;
+	position: absolute;
+	height: 144px;
+	background-color: #e8eaf1;
+	box-shadow: 0px 4px 10px rgba(102, 100, 100, 0.5);
+	border-radius: 12px;
+	bottom: 84px;
+	.top{
+	padding: 10px;
+	padding-right: 0;
+	height: 66px;
+	position: relative;
+	.avatar{
+		width: 40px;
+		height: 40px;
+		border-radius: 50%;
+		position: relative;
+		float: left;
+		margin-right: 16px;
+		img{
+		width: 100%;
+		border-radius: 50%;
+		}
+		.circle{
+		position: absolute;
+		right: -4px;
+		bottom: -4px;
+		width: 20px;
+		height: 20px;
+		border: 5px solid #e8eaf1;
+		border-radius: 50%;
+		background-color: #3fdd78;
+		}
+	}
+	.user-name{
+		font-family: 'Inter';
+		font-style: normal;
+		font-size: 14px;
+		// color: #FEFEFE;
+		float: left;
+		min-width: 100px;
+	}
+	.user-free{
+		float: right;
+		height: 24px;
+		line-height: 24px;
+		padding: 0 12px;
+		background-color: #3fdd78;
+
+		border-radius: 6px;
+		font-size: 12px;
+		font-weight: 600;
+	}
+	.user-email{
+		font-family: 'Inter';
+		font-style: normal;
+		color: #86898b;
+		font-size: 12px;
+	}
+	}
+	.user-bottom{
+		width: 268px;
+		height: 48px;
+		border: 2px solid #e0e0e6;
+		border-radius: 12px;
+		text-align: center;
+		line-height: 48px;
+		font-size: 14px;
+		margin-top: 10px;
+		&:hover{
+			cursor: pointer;
+			border-color: #3fdd78;
+			color: #3fdd78;
+		}
+	}
+}
+.user-footer{
+	position: absolute;
+	bottom: 24px;
+	width: 288px;
+	height: 48px;
+	padding: 4px;
+	left: 16px;
+	border-radius: 12px;
+	// background: #232627;
+	box-shadow: 0px 4px 10px rgba(0, 0, 0, 0.5);
+	display: flex;
+	.settings,
+	.log-out{
+		cursor: pointer;
+		flex: 1;
+		height: 40px;
+		width: 138px;
+		line-height: 40px;
+		// color: #6C7275;
+		svg{
+			margin: 0px 10px 0px 28px;
+			color: #6C7275;
+		}
+		&.active,
+		&:hover{
+			background-color: #3fdd78;
+			border-radius: 12px;
+			color: #fff;
+			svg{
+				color: #fff;
+			}
+		}
+	}
+}
+}
+.char-sider{
+	.top-new-button {
+		height: 116px;
+		padding-top: 15px;
+		border: 1px solid #e0e0e6;
+		border-left: unset;
+		border-top: unset;
+		button{
+			width: 289px;
+			height: 48px;
+			line-height: 48px;
+			text-align: center;
+			// background-color: #e8eaf1;
+			border-radius: 12px;
+			font-size: 16px;
+			color: #000;
+			margin-left: 10px;
+			margin-top: 20px;
+		}
+	}
+}
+.chat-history{
+	padding: 23px 30px 0;
+	.history-title{
+		font-size: 18px;
+		margin-bottom: 18px;
+		margin-left: 18px;
+	}
+	.chat-item{
+		width: 288px;
+		height: 48px;
+		&.check-chat-item,
+		&:hover{
+			background: linear-gradient(270deg, #fff 50%, #e8eaf1 100%);
+			border-radius: 8px;
+		}
+	}
+}
+.change-select{
+	border-radius: 8px !important;
+	.n-base-selection{
+		border-radius: 8px !important;
+	}
+	.n-base-selection-label,
+	.n-input-wrapper,
+	.n-input{
+		height: 40px !important;
+		border-radius: 8px !important;
+	}
+}
+.change-slider{
+	.n-slider-rail__fill{
+		background-color: #0084ff !important;
+	}
+}
+
+.change-dialog{
+	border-radius: 10px !important;
+	.n-card-header{
+		border-bottom: 1px solid #e0e0e6;
+		margin-bottom: 15px;
+	}
+}
+
+.message-reply,
+.message-request{
+	.markdown-body{
+		p{
+			font-size: 17px;
+			// color: #fff;
+		}
+	}
+}
+.model-button{
+	button{
+		padding: 0 30px;
+		height: 48px;
+		line-height: 48px;
+		border-radius: 12px;
+		font-size: 16px;
+		margin-left: 20px !important;
+		&:last-child{
+			// border: 1px solid #d2f9d1;
+			// color: #d2f9d1;
+		}
+	}
+}
+.mydrawer{
+	.store-content{
+		.store-label-item{
+			button{
+				border: 0;
+				height: 48px;
+				line-height: 48px;
+				padding: 0 20px;
+				min-width: 100px;
+				text-align: center;
+				border-radius: 40px;
+				font-size: 17px;
+			}
+		}
+		.dianzan{
+			left: 110px;
+		}
+	}
+}
+.sound-button-box{
+	button{
+		border-radius: 12px;
+		height: 48px !important;
+		line-height: 48px !important;
+		font-size: 16px;
+		padding: 0 19px;
+		padding: 0 35px;
+		&:hover{
+			opacity: .7;
+		}
+	}
+}
+.role-card{
+	position: relative;
+	.card-container{
+		.card-item{
+			height: 152px;
+			border: 1px solid #e5e7eb;
+			h3{
+				font-size: 17px;
+				font-weight: 700;
+			}
+			.ellipsis{
+				font-size: 14px;
+			}
+			.card-avatar{
+				width: 48px;
+				height: 48px;
+				border-radius: 50%;
+			}
+			.n-divider{
+				margin-bottom: 10px;
+			}
+			.button-list{
+				justify-content: flex-end;
+				button{
+					height: 24px;
+					line-height: 24px;
+					text-align: center;
+					padding: 0 15px;
+					font-size: 12px;
+					margin-left: 10px;
+				}
+			}
+		}
+	}
+	.voice-pagination{
+		left: 19px;
+	}
+}
+.success-button{
+	color: #000;
+	border-radius: 12px !important;
+	padding: 0 25px !important;
+}
+.voice-drawer{
+	border-radius: 10px !important;
+	.addvoicebutton{
+		height: 48px;
+		line-height: 48px;
+		border-radius: 12px;
+		background-color: #0084FF;
+		color: #fff;
+		font-size: 16px;
+		font-weight: 700;
+	}
+}
+.n-input{
+	border-radius: 8px !important;
+}
+.n-pagination-item--active{
+	color: #0084FF !important;
+	border-color: #0084FF !important;
+}
+.add-role-draw{
+	.add-role-button{
+		height: 48px;
+		line-height: 48px;
+		border-radius: 12px;
+		padding: 0 40px;
+		float: right;
+	}
+	.add-role-upload{
+		.n-upload-dragger{
+			width: 500px;
+			height: 228px;
+			padding: 40px 63px;
+			font-size: 14px;
+			line-height: 24px;
+			text-align: center;
+			letter-spacing: -0.02em;
+		}
+	}
+	.role-avatar-upload{
+		.n-upload-trigger{
+			width: 120px;
+			height: 120px;
+			.n-upload-dragger{
+				width: 120px;
+				height: 120px;
+				display: block;
+				border-radius: 8px;
+			}
+		}
+	}
+}
+.n-data-table{
+	.n-data-table-base-table-body{
+		border-top-left-radius: 10px;
+		border-top-right-radius: 10px;
+		.n-data-table-tbody{
+			.n-data-table-tr{
+				td{
+					font-size: 14px;
+					padding: 12px 15px;
+					border: none;
+				}
+			}
+		}
+	}
+	.table-button{
+		height: 24px;
+		line-height: 24px;
+		border-radius: 12px;
+		background: rgba(0, 132, 255, 0.2);
+	}
+}
+.draw-button{
+	height: 48px !important;
+	line-height: 48px !important;
+	border-radius: 12px !important;
+	padding: 0 33px !important;
+	font-size: 16px;
+}
+.top-header{
+	height: 116px;
+	line-height: 116px;
+	align-items: center;
+	border-bottom: 1px solid #e5e7eb;
+	margin: 24px 0 0px !important;
+	padding: 0 20px;
+	background-color: #fff;
+	border-top-right-radius: 20px;
+	button{
+		height: 48px;
+		line-height: 48px;
+	}
+}
+.chat-header{
+	height: 116px;
+	line-height: 116px;
+	align-items: center;
+	border-bottom: 1px solid #e5e7eb;
+	padding: 0 20px;
+	background-color: #fff;
+	border-top-right-radius: 20px;
+	margin-top: 0;
+	button{
+		height: 48px;
+		line-height: 48px;
+	}
+}
+.know-header{
+	height: 116px;
+	line-height: 116px;
+	align-items: center;
+	border-bottom: 1px solid #e5e7eb;
+	margin: 2px 0 0px !important;
+	padding: 0 20px;
+	background-color: #fff;
+	border-top-right-radius: 20px;
+	border-top-left-radius: 20px;
+	button{
+		height: 48px;
+		line-height: 48px;
+	}
+}
+.n-data-table__pagination{
+	justify-content: start !important;
+}
+.annex-modal{
+	width: 540px;
+	.annex-upload{
+		width: 500px;
+	}
+}
+.plan-draw{
+	border-radius: 20px !important;
+	.n-card-header{
+		display: none !important;
+	}
+	.change-combo{
+		height: 50px;
+		display: flex;
+		justify-content: center;
+		align-items: center;
+		span{
+			font-size: 18px;
+			&.active-span{
+				color: #0084FF;
+			}
+		}
+		.n-switch{
+			margin: 0 12px;
+			.n-switch__rail{
+				width: 53px;
+			}
+			&.n-switch--active{
+				.n-switch__rail{
+					background-color: #0084ff;
+				}
+			}
+		}
+	}
+	.n-tab-pane{
+		padding: 0 !important;
+	}
+	.plan-item{
+		width: 320px;
+		height: 342px;
+		background-image: url(@/assets/Subtract-3.png);
+		margin: 20px 15px;
+		&:hover{
+			transform: scale(1.02);
+			background-image: url(@/assets/Subtract-2.png);
+			.header{
+				.title{
+					color: #fff;
+				}
+				.price{
+					color: #fff;
+				}
+				.date{
+					color: #fff;
+				}
+			}
+		}
+		.header{
+			height: 78px;
+			width: calc(100% - 40px);
+			margin: 0 20px;
+			border-bottom: 1px solid #fff;
+			line-height: 90px;
+			.title{
+				color: #000;
+				font-size: 20px;
+				font-weight: 700;
+			}
+			.price{
+				font-size: 32px;
+				font-weight: 700;
+			}
+			.date{
+				font-size: 14px;
+			}
+		}
+		.content{
+			width: calc(100% - 30px);
+			// padding: 15px 0;
+			margin: 0 20px;
+			border-bottom: 1px solid #fff;
+			height: 160px;
+			overflow-y: scroll;
+			.option-item{
+				font-size: 14px;
+				margin-bottom: 2px;
+				display: flex;
+				width: 280px;
+				.quanquan{
+					display: inline-block;
+					width: 14px;
+					height: 14px;
+					background-color: #0084ff;
+					color: #fff;
+					text-align: center;
+					line-height: 14px;
+					border-radius: 50%;
+					color: #fff;
+					position: relative;
+					margin-top: 3px;
+					margin-right: 10px;
+					svg{
+						position: absolute;
+						top: 1px;
+						left: 1px;
+						width: 12px;
+						height: 12px;
+					}
+				}
+				p{
+					white-space: nowrap;
+					overflow: hidden;
+					text-overflow: ellipsis;
+					max-width: 250px;
+				}
+			}
+		}
+		.footer{
+			width: calc(100% - 40px);
+			margin: 20px 20px 0;
+			background-color: #fff;
+			color: #000;
+			height: 48px;
+			line-height: 48px;
+			font-size: 16px;
+			text-align: center;
+			border-radius: 12px;
+			// border: 1px solid #e0e0e6;
+			&:hover{
+				transform: scale(1.02);
+			}
+			svg{
+				position: relative;
+				top: -2px;
+				margin-right: 15px;
+			}
+		}
+	}
+	&.mobile-plan-draw {
+		.plan-content{
+			max-height: 85vh;
+			overflow-y: scroll;
+			// flex-direction: column !important;
+			// align-items: center;
+			display: block !important;
+			.plan-item{
+				width: 320px;
+				margin: 0;
+				margin-bottom: 20px;
+				margin-left: calc((100vw - 320px) / 2 - 24px);
+			}
+		}
+	}
+	
+	.n-tabs-nav-scroll-content{
+		border: 0 !important;
+		margin-bottom: 20px;
+	}
+	.n-data-table{
+		margin-left: 0;
+	}
+}
+.plan-tabs{
+	.n-tabs-tab{
+		&:hover{
+			color: #0084FF !important;
+		}
+	}
+	.n-tabs-tab--active{
+		color: #0084FF !important;
+	}
+	.n-tabs-bar{
+		background-color: #0084FF !important;
+	}
+}
+.input-button-container {
+	button{
+		border-radius: 12px;
+		margin-left: 40px;
+		padding: 0 20px;
+		width: calc(20% - 10px);
+		// width: 20%;
+	}
+}
+.table-box{
+	background-color: #fff;
+	padding: 20px 46px 20px 20px;
+	height: calc(100% - 164px);
+	border-bottom-right-radius: 20px;
+}
+.draw-table-box{
+	.n-data-table-wrapper{
+		max-height: 60vh;
+		overflow-y: scroll;
+	}
+}
+
+.chat-content {
+	height: calc(100vh - 48px);
+	margin-top: 24px;
+	main{
+		border-top-right-radius: 20px;
+	}
+	.header-button{
+		margin-right: 20px;
+	}
+	.footer-content{
+		padding: 0;
+		background-color: #fff;
+		border-bottom-right-radius: 20px;
+		// height: 156px;
+		.chat-footer{
+			border-bottom-right-radius: 12px;
+			position: relative;
+			background-color: #e8eaf1;
+			padding: 0;
+			height: 155px;
+			width: calc(100% - 80px);
+			border: 2px solid #fff;
+			margin: 0 40px 40px;
+			border-radius: 16px;
+			.items-base {
+				// position: absolute;
+				// top: -70px;
+			}
+			.top-bar{
+				& .left{
+					display: flex;
+					position: relative;
+					float: left;
+					.chage-model-select{
+						float: left;
+						position: relative;
+						display: flex;
+						align-items: center;
+						font-size: 16px;
+						cursor: pointer;
+						box-shadow: 0px 3px 5px #000;
+						border-radius: 5px;
+						padding: 2px;
+						top: -5px;
+						&:hover{
+							color: #0084FF;
+						}
+						svg{
+							margin-right: 0 !important;
+						}
+						span{
+							margin: 0 5px;
+						}
+					}
+				}
+				height: 42px;
+				border-bottom: 2px solid #e0e0e6;
+				padding: 10px 26px 0;
+				svg{
+					margin-right: 25px;
+					cursor: pointer;
+					float: left;
+					fill: #000;
+					outline: none;
+					&:hover{
+						opacity: .8;
+						transform: scale(1.1);
+					}
+				}
+				.right{
+					float: right;
+					cursor: pointer;
+					&:hover{
+						transform: scale(1.1);
+					}
+				}
+			}
+			.chat-input{
+				float: left;
+				.n-input-wrapper{
+					background-color: #e8eaf1;
+					border: none;
+				}
+				width: 80%;
+				margin: 15px 0px 0 26px;
+			}
+			.send{
+				float: right;
+				cursor: pointer;
+				// width: 94px;
+				height: 24px;
+				border-radius: 12px;
+				background-color: #0084FF;
+				position: absolute;
+				right: 23px;
+				bottom: 22px;
+				line-height: 24px;
+				color: #fff;
+				padding: 0 10px;
+				svg{
+					fill: #fff;
+				}
+				&:hover{
+					transform: scale(1.02);
+				}
+			}
+		}
+	}
+	#scrollRef{
+		background-color: #fff;
+	}
+	#image-wrapper{
+		background-color: #fff;
+	}
+	.message-reply{
+		background-color: #d3e3fd;
+	}
+	.message-request{
+		background-color: #4b9e5f;
+	}
+	.message-reply,
+	.message-request{
+		border-radius: 16px;
+		.markdown-body{
+			p{
+				font-size: 17px;
+				color: #000;
+			}
+		}
+	}
+	.message-request{
+		.markdown-body p{
+			color: #fff;
+		}
+	}
+}
+.clear-chat{
+	border-radius: 20px;
+	background-color: #e8eaf1;
+	&:hover{
+		transform: scale(1.1);
+	}
+	svg{
+		fill: #000;
+	}
+}
+.gpts-box{
+	color: #494747;
+	h1{
+		font-weight: 700;
+		font-size: 40px;
+		text-align: center;
+		color: #000;
+		margin: 20px 0;
+	}
+	.ai-icon{
+		float: left;
+	}
+	.text{
+		background-color: #e8eaf1;
+		border-radius: 20px;
+		float: left;
+		width: calc(100% - 100px);
+		margin-left: 12px;
+		.title{
+			font-weight: 700;
+			color: #000;
+		}
+	}
+	&>div{
+		&::after{
+			content: '';
+			display: table;
+			clear: both;
+		}
+	}
+	.gpts-list{
+		margin-top: 10px;
+		position: relative;
+		.refresh{
+			position: absolute;
+			top: -35px;
+			right: 25px;
+			cursor: pointer;
+			&:hover{
+				transform: scale(1.05);
+				color: #0084FF;
+			}
+		}
+		.gpts-item{
+			background-color: #ffffff;
+			border-radius: 10px;
+			border: 1px solid #e8eaf1;
+			float: left;
+			.n-image{
+				float: left;
+				margin-top: 5px;
+			}
+			.info{
+				white-space: nowrap;
+				overflow: hidden;
+				text-overflow: ellipsis;
+			}
+			.name{
+				cursor: pointer;
+				color: #a7a8a9;
+				white-space: nowrap;
+				overflow: hidden;
+				text-overflow: ellipsis;
+				&:hover{
+					color: #0084ff;
+				}
+			}
+		}
+	}
+}
+#app{
+	.login-box{
+		background-color: #232627;
+		border: 1px solid #343839;
+		box-shadow: none;
+		color: #fff;
+		margin-top: 10px;
+		h2{
+			color: #b7babd;
+		}
+		.login-desc{
+			color: #B3B7B9;
+			a{
+				color: #0084ff;
+			}
+		}
+		form{
+			label{
+				color: #b7babd;
+				margin-bottom: 10px;
+			}
+			input{
+				background-color: #232627;
+				border: 2px solid #343839;
+				color: #fff !important;
+				height: 54px;
+				border-radius: 8px;
+				caret-color: #fff;
+				&::placeholder{
+					color: #696B6F;
+				}
+			}
+		}
+		.footer-login{
+			button{
+				height: 54px;
+				line-height: 54px;
+				border-radius: 8px;
+				background-color: #0084FF;
+				color: #fff;
+				font-size: 16px;
+				padding: 0;
+				margin-top: 10px;
+				width: 100%;
+				box-shadow: none;
+			}
+		}
+	}
+}
+.music-content{
+	background-color: #fff;
+	height: calc(100% - 48px);
+	margin-top: 24px;
+	border-radius: 20px;
+	.music-list{
+		background-color: #fff;
+		padding: 15px;
+	}
+	.p-2{
+		padding: 15px;
+		padding-top: 10px;
+	}
+}
+.video-content{
+	background-color: #fff;
+	height: calc(100% - 48px);
+	margin-top: 24px;
+	border-radius: 20px;
+	.p-2{
+		padding: 15px;
+		.upload-video{
+			margin: 15px 0;
+		}
+	}
+	.video-list{
+		background-color: #fff;
+		border-top-right-radius: 20px;
+		border-bottom-right-radius: 20px;
+	}
+}
+.annex-main{
+	background-color: #fff;
+}

+ 1 - 0
src/typings/user.d.ts

@@ -1,6 +1,7 @@
 export interface LoginFrom {
 	username: string // 用户名
 	password: string // 用户密码
+	type: string // 登录类型
 }
 
 export interface LoginUserInfo {

+ 2 - 1
src/utils/request/index.ts

@@ -16,7 +16,8 @@ export interface HttpOption {
 export interface Response<T = any> {
   data: T
   msg: string | undefined
-  code: number
+  code: number,
+  rows: []
 }
 
 function http<T = any>(

+ 84 - 0
src/views/chat/aichatmsg.vue

@@ -0,0 +1,84 @@
+<script setup lang="ts">
+import { ref, onMounted, reactive } from 'vue';
+import { NDataTable, useMessage,NPagination } from 'naive-ui';
+import { listByUser } from '@/api/chatmsg';
+import to from 'await-to-js';
+import { t } from '@/locales';
+
+const message = useMessage();
+const columns = ref([
+  // {
+  //   title: 'ID',
+  //   key: 'id',
+  //   width: 80,
+  //   ellipsis: true,
+  // },
+  { title: t('model.content'), key: 'content', ellipsis: { tooltip: true } },
+  { title: t('model.modelName'), key: 'modelName' , ellipsis: { tooltip: true } },
+  { title: t('model.totalTokens'), key: 'totalTokens'},
+  { title: t('model.deductCost'), key: 'deductCost'},
+  { title: t('model.msgtime'), key: 'createTime' },
+  // { title: t('model.msgremark'), key: 'remark'  , ellipsis: { tooltip: true } }
+]);
+const tableData = ref([]);
+const pagination = reactive({
+  page: 1,
+  pageSize: 10,
+  pageCount: 0, // 根据总条数和pageSize计算
+  itemCount: 0, // 总条数
+  showSizePicker: true,
+  pageSizes: [10, 20, 30, 40],
+  onChange: (page: number) => {
+    fetchData(page, pagination.pageSize);
+  },
+  onUpdatePageSize: (pageSize: number) => {
+    fetchData(1, pageSize);
+  }
+});
+
+const fetchData = async (pageNum: number, pageSize: number) => {
+  const params = { pageNum, pageSize };
+  const [err, result] = await to(listByUser(params));
+  if (err) {
+    message.error(err.message);
+    return;
+  }
+  tableData.value = result.data.rows;
+  pagination.itemCount = result.data.total;
+  pagination.pageCount = Math.ceil(result.data.total / pageSize);
+};
+
+onMounted(() => {
+  fetchData(pagination.page, pagination.pageSize);
+});
+</script>
+
+<template>
+  <div class="flex h-full">
+    <main class="flex-1 overflow-hidden h-full">
+      <n-data-table 
+        :columns="columns"
+        :data="tableData"
+      />
+      <div class="pagination-wrapper">
+        <n-pagination
+          v-model:page="pagination.page"
+          v-model:page-size="pagination.pageSize"
+          :item-count="pagination.itemCount"
+          :page-sizes="pagination.pageSizes"
+          :show-size-picker="pagination.showSizePicker"
+          @update:page="pagination.onChange"
+          @update:page-size="pagination.onUpdatePageSize"
+        />
+      </div>
+    </main>
+  </div>
+</template>
+<style scoped>
+.pagination-wrapper {
+  display: flex;
+  justify-content: flex-end; /* Aligns the pagination to the right */
+  padding: 10px; /* Adds space around the pagination */
+}
+
+</style>

+ 17 - 28
src/views/chat/components/Header/index.vue

@@ -8,9 +8,9 @@ import aiModel from "@/views/mj/aiModel.vue"
 import { chatSetting } from '@/api'
 
 const { isMobile } = useBasicLayout()
-
 interface Props {
-  usingContext: boolean
+  usingContext: boolean,
+  haveData: boolean
 }
 
 interface Emit {
@@ -19,7 +19,6 @@ interface Emit {
 }
 
 defineProps<Props>()
-
 const emit = defineEmits<Emit>()
 
 const appStore = useAppStore()
@@ -51,13 +50,14 @@ const nGptStore = ref( chatSet.getGptConfig())  ;
 const st = ref({isShow:false});
 watch(()=>gptConfigStore.myData,()=>nGptStore.value=  chatSet.getGptConfig() , {deep:true})
 watch(()=>homeStore.myData.act,(n)=> n=='saveChat' && (nGptStore.value=  chatSet.getGptConfig() ), {deep:true})
+
 </script>
 
 <template>
   <header
-    class="sticky top-0 left-0 right-0 z-30 border-b dark:border-neutral-800 bg-white/80 dark:bg-black/20 backdrop-blur"
+    class="sticky top-0 left-0 right-0 z-30 border-b dark:border-neutral-800 bg-white/80 dark:bg-black/20 backdrop-blur chat-header top-header"
   >
-    <div class="relative flex items-center justify-between min-w-0 overflow-hidden h-14">
+    <div class="relative flex items-center justify-between min-w-0 overflow-hidden h-14" style="height: 116px; line-height: 116px;">
       <div class="flex items-center">
         <button
           class="flex items-center justify-center w-11 h-11"
@@ -67,42 +67,31 @@ watch(()=>homeStore.myData.act,(n)=> n=='saveChat' && (nGptStore.value=  chatSet
           <SvgIcon v-else class="text-2xl" icon="ri:align-right" />
         </button>
       </div>
-      <h1
+      <p
         class="flex-1 px-4 pr-6 overflow-hidden cursor-pointer select-none text-ellipsis whitespace-nowrap"
         @dblclick="onScrollToTop"
       >
         {{ currentChatHistory?.title ?? '' }}
-      </h1>
-      <div class="flex items-center space-x-2">
-        <HoverButton @click="handleExport">
+      </p>
+      <div class="flex items-center space-x-2 header-button">
+        <!-- <HoverButton @click="handleExport">
           <span class="text-xl text-[#4f555e] dark:text-white">
             <SvgIcon icon="ri:download-2-line" />
           </span>
-        </HoverButton>
-        <HoverButton @click="handleClear">
-          <span class="text-xl text-[#4f555e] dark:text-white">
-            <SvgIcon icon="ri:delete-bin-line" />
+        </HoverButton> -->
+        <HoverButton @click="handleClear" class="clear-chat">
+          <span class="text-xl text-[#4f555e] dark:text-white" >
+            <IconSvg icon="clear" width="28px" height="22px"></IconSvg>
+            <!-- <SvgIcon icon="ri:delete-bin-line" /> -->
           </span>
         </HoverButton>
       </div>
     </div>
-    
-    <div @click="st.isShow=true" class="absolute left-1/2   top-full -translate-x-1/2 cursor-pointer select-none rounded-b-md border  bg-white px-2 dark:border-neutral-800 dark:bg-[#111114]">
-        <div class="flex items-center   justify-center space-x-1 cursor-pointer hover:text-[#4b9e5f]" v-if="homeStore.myData.local!='draw'">
-            <template   v-if="nGptStore.gpts">
-             <SvgIcon icon="ri:apps-fill" /> 
-             <span class="line-clamp-1 overflow-hidden">{{ nGptStore.gpts.name }}</span> 
-            </template>
-            <template v-else >
-            <SvgIcon icon="heroicons:sparkles" /> 
-            <span >{{ nGptStore.model }}</span> 
-            </template>
-            <SvgIcon icon="icon-park-outline:right" />
-        </div>
-    </div>
+
+
   </header>
 
-  <NModal v-model:show="st.isShow"   preset="card"  :title="$t('mjchat.modelChange')" class="!max-w-[620px]" @close="st.isShow=false" >  
+  <NModal v-model:show="st.isShow"   preset="card"  :title="$t('mjchat.modelChange')" class="!max-w-[540px] change-dialog" @close="st.isShow=false" >
         <aiModel @close="st.isShow=false"/>
   </NModal>
 </template>

+ 2 - 2
src/views/chat/components/Message/Text.vue

@@ -125,8 +125,8 @@ onUnmounted(() => {
         <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 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 style="font-size: 17px; font-family: 'Karla';" v-else class="whitespace-pre-wrap" v-text="text" />
         </template>
       </div>
       <whisperText v-else-if="text=='whisper' && chat.opt?.lkey "  :chat="chat" />

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

@@ -131,7 +131,7 @@ 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>{{ dateTime }}</span>
+        <span style="font-size: 14px; font-family: 'Karla';">{{ 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'">

+ 170 - 29
src/views/chat/index.vue

@@ -5,23 +5,24 @@ import type { Ref } from 'vue'
 import { computed, onMounted, onUnmounted, ref,watch,h } from 'vue'
 import { useRoute, useRouter } from 'vue-router'
 import { storeToRefs } from 'pinia'
-import { NAutoComplete, NButton, NInput, useDialog, useMessage,NAvatar } from 'naive-ui'
+import { NAutoComplete, NButton, NInput, useDialog, useMessage,NAvatar,NModal,NCard,NImage } from 'naive-ui'
 import html2canvas from 'html2canvas'
 import { Message } from './components'
 import { useScroll } from './hooks/useScroll'
 import { useChat } from './hooks/useChat'
 import { useUsingContext } from './hooks/useUsingContext'
-import HeaderComponent from './components/Header/index.vue'
+import { getGpts } from '@/api/chatmsg';
 import {  SvgIcon } from '@/components/common'
 import { useBasicLayout } from '@/hooks/useBasicLayout'
 import { gptConfigStore, gptsUlistStore, homeStore, useChatStore, usePromptStore } from '@/store'
-import { chatSetting, fetchChatAPIProcess, gptsType, mlog, myFetch } from '@/api'
+import { chatSetting, fetchChatAPIProcess, gptsType, mlog, myFetch, my2Fetch } from '@/api'
 import { t } from '@/locales'
 import drawListVue from '../mj/drawList.vue'
 import aiGPT from '../mj/aiGpt.vue'
 import AiSiderInput from '../mj/aiSiderInput.vue'
 import aiGptInput from '../mj/aiGptInput.vue'
-
+import { getNotice,readNotice, getInform } from '@/api/notice'
+import to from "await-to-js";
 
 let controller = new AbortController()
 
@@ -32,7 +33,9 @@ const dialog = useDialog()
 const ms = useMessage()
 const router = useRouter()
 const chatStore = useChatStore()
+// const href = window.location.href.split('#')[0]
 
+const href = window.location.hostname;
 const { isMobile } = useBasicLayout()
 const { addChat, updateChat, updateChatSome, getChatByUuidAndIndex } = useChat()
 const { scrollRef, scrollToBottom, scrollToBottomIfAtBottom } = useScroll()
@@ -423,6 +426,9 @@ function handleStop() {
   }
 }
 
+
+
+
 // 可优化部分
 // 搜索选项计算,这里使用value作为索引项,所以当出现重复value时渲染异常(多项同时出现选中效果)
 // 理想状态下其实应该是key作为索引项,但官方的renderOption会出现问题,所以就需要value反renderLabel实现
@@ -452,15 +458,15 @@ const searchOptions = computed(() => {
 
 const goUseGpts= async ( item: gptsType)=>{
     const saveObj= {model:  `${ item.gid }`   ,gpts:item}
-    gptConfigStore.setMyData(saveObj); 
+    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 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}});
 
@@ -473,14 +479,14 @@ 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:()=>{  
+    , 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  ) 
+    , 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) {
@@ -511,11 +517,15 @@ onMounted(() => {
   scrollToBottom()
   if (inputRef.value && !isMobile.value)
     inputRef.value?.focus()
+  // 查询公告信息
+  selectNotice()
+  // 查询通知信息
+  selectInform()
 })
 
 onUnmounted(() => {
 
-  if (loading.value)   controller.abort() 
+  if (loading.value)   controller.abort()
   homeStore.setMyData({isLoader:false});
 })
 
@@ -538,30 +548,114 @@ const ychat = computed( ()=>{
     scrollToBottomIfAtBottom();
   }
   return { text, dateTime: t('chat.preview')} as Chat.Chat;
-}) 
+})
+
+const showModal = ref(false);
+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
+  }
+}
+async function selectInform() {
+  const [err, result] = await to(getInform());
+  if (result?.rows) {
+    informContent.value = result.rows.length ? result.rows : []
+  }
+}
 
+async function handleClose(){
+  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 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 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]
+}
+load()
 
 </script>
 
 <template>
-  <div class="flex flex-col w-full h-full">
+  <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
-     
+    <!-- <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">
+        <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]"
@@ -569,14 +663,60 @@ const ychat = computed( ()=>{
         >
           <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="flex items-center justify-center mt-4 text-center text-neutral-300" v-else>
+            </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>
+            </div> -->
           </template>
-      
+
           <template v-else>
             <div>
               <Message
@@ -612,14 +752,14 @@ const ychat = computed( ()=>{
         </div>
       </div>
     </main>
-    <footer :class="footerClass" v-if="local!=='draw'">
+    <footer :class="footerClass" class="footer-content" v-if="local!=='draw'">
       <div class="w-full max-w-screen-xl m-auto">
-        <aiGptInput v-if="['gpt-4-vision-preview','gpt-3.5-turbo-16k'].indexOf(gptConfigStore.myData.model)>-1 || st.inputme "
-         v-model:modelValue="prompt" :disabled="buttonDisabled" 
+        <aiGptInput @handle-clear="handleClear" @export="handleExport" v-if="['gpt-4-vision-preview','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" />
@@ -665,7 +805,8 @@ const ychat = computed( ()=>{
     </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/chat/layout/Layout.vue

@@ -63,7 +63,7 @@ const getContainerClass = computed(() => {
 </script>
 
 <template>
-  <div class="  dark:bg-[#24272e] transition-all p-0"  :class="[isMobile ? 'h55' : 'h-full' ]">
+  <div class="  dark:bg-[#24272e] transition-all p-0 111"  :class="[isMobile ? 'h55' : 'h-full' ]">
     <div class="h-full overflow-hidden" :class="getMobileClass">
       <NLayout class="z-40 transition" :class="getContainerClass" has-sider>
         <aiSider v-if="!isMobile"/>

+ 7 - 6
src/views/chat/layout/sider/List.vue

@@ -14,7 +14,6 @@ const appStore = useAppStore()
 const chatStore = useChatStore()
 
 const dataSources = computed(() => chatStore.history)
-
 async function handleSelect({ uuid }: Chat.History) {
   if (isActive(uuid))
     return
@@ -70,8 +69,9 @@ watch(()=>gptConfigStore.myData , toMyuid , {deep:true})
 </script>
 
 <template>
-  <NScrollbar class="px-4">
+  <NScrollbar class="px-4 chat-history">
     <div class="flex flex-col gap-2 text-sm">
+      <p class="history-title">{{ $t('common.history')}}</p>
       <template v-if="!dataSources.length">
         <div class="flex flex-col items-center mt-4 text-center text-neutral-300">
           <SvgIcon icon="ri:inbox-line" class="mb-2 text-3xl" />
@@ -81,15 +81,16 @@ watch(()=>gptConfigStore.myData , toMyuid , {deep:true})
       <template v-else>
         <div v-for="(item, index) of dataSources" :key="index">
           <a
-            class="relative flex items-center gap-3 px-3 py-3 break-all border rounded-md cursor-pointer hover:bg-neutral-100 group dark:border-neutral-800 dark:hover:bg-[#24272e]"
-            :class="isActive(item.uuid) && ['border-[#4b9e5f]', 'bg-neutral-100', 'text-[#4b9e5f]', 'dark:bg-[#24272e]', 'dark:border-[#4b9e5f]', 'pr-14']"
+            class="relative flex items-center gap-3 px-3 py-3 break-all border rounded-md cursor-pointer hover:bg-neutral-100 group dark:border-neutral-800 dark:hover:bg-[#24272e] chat-item"
+            :class="isActive(item.uuid) && ['check-chat-item']"
             @click="handleSelect(item)"
           >
              
-             <AiListText   :myObj="isInObjs(item.uuid)" :myItem="item">
+             <AiListText   :myObj="isInObjs(item.uuid)" :myItem="item" :index="index">
                <NInput
+               style="width: 226px"
                 v-if="item.isEdit"
-                v-model:value="item.title" size="tiny"
+                v-model:value="item.title" size="small"
                 @keypress="handleEnter(item, false, $event)"
               />
              </AiListText>

+ 28 - 12
src/views/chat/layout/sider/index.vue

@@ -4,14 +4,13 @@ import { computed, ref, watch } from 'vue'
 import { NButton, NLayoutSider, useDialog } from 'naive-ui'
 import List from './List.vue'
 import Footer from './Footer.vue'
-import { useAppStore, useChatStore } from '@/store'
+import { useAppStore, useChatStore, homeStore } from '@/store'
 import { useBasicLayout } from '@/hooks/useBasicLayout'
-import { PromptStore, SvgIcon } from '@/components/common'
+import { IconSvg, PromptStore, SvgIcon } from '@/components/common'
 import { t } from '@/locales'
 
 const appStore = useAppStore()
 const chatStore = useChatStore()
-
 const dialog = useDialog()
 
 const { isMobile } = useBasicLayout()
@@ -48,10 +47,26 @@ const getMobileClass = computed<CSSProperties>(() => {
     return {
       position: 'fixed',
       zIndex: 50,
-      height: '100%',
+      height: '100%'
+    }
+  }
+  if(appStore.theme == 'dark') {
+    return {
+        height: 'calc(100% - 48px)',
+        marginTop: '24px',
+        borderTopLeftRadius: '20px',
+        borderBottomLeftRadius: '20px',
+        backgroundColor: '#232627'
+    }
+  }else{
+    return {
+      height: 'calc(100% - 48px)',
+      marginTop: '24px',
+      borderTopLeftRadius: '20px',
+      borderBottomLeftRadius: '20px',
+      backgroundColor: '#fff'
     }
   }
-  return {}
 })
 
 const mobileSafeArea = computed(() => {
@@ -79,25 +94,26 @@ watch(
   <NLayoutSider
     :collapsed="collapsed"
     :collapsed-width="0"
-    :width="260"
+    :width="348"
     :show-trigger="isMobile ? false : 'arrow-circle'"
     collapse-mode="transform"
-    
     bordered
+    v-if="homeStore.myData.local == 'Chat'"
     :style="getMobileClass"
     @update-collapsed="handleUpdateCollapsed"
   >
-    <div class="flex flex-col h-full" :style="mobileSafeArea">
+    <div class="flex flex-col h-full char-sider" :style="mobileSafeArea">
       <main class="flex flex-col flex-1 min-h-0">
-        <div class="p-4">
-          <NButton dashed block @click="handleAdd">
+        <div class="p-4 top-new-button">
+          <NButton block @click="handleAdd">
+            <IconSvg icon="add"></IconSvg>&nbsp;&nbsp;
             {{ $t('chat.newChatButton') }}
           </NButton>
         </div>
         <div class="flex-1 min-h-0 pb-4 overflow-hidden">
           <List />
         </div>
-        <div class="flex items-center p-4 space-x-4">
+        <div class="flex items-center p-4 space-x-4" v-if="isMobile">
           <div class="flex-1">
             <NButton block @click="show = true">
               {{ $t('store.siderButton') }}
@@ -108,7 +124,7 @@ watch(
           </NButton>
         </div>
       </main>
-      <Footer></Footer>
+      <Footer v-if="isMobile"></Footer>
     </div>
   </NLayoutSider>
   <template v-if="isMobile">

+ 110 - 69
src/views/knowledge/annex.vue

@@ -1,26 +1,24 @@
 <script setup lang="ts">
 
-import {  h, onMounted, ref , computed } from 'vue'
-import { NButton,NDataTable,DrawerPlacement,NDrawer,NDrawerContent,
-  NForm,NFormItem,NInput,NSpin,NSpace,UploadFileInfo,NUpload,NUploadDragger,
-  NText,NIcon,NP,useMessage,NModal
+import { h, onMounted, ref } from 'vue'
+import { NButton, NDataTable, DrawerPlacement, NFormItem, NSpin, NSpace, UploadFileInfo, NUpload, NUploadDragger,
+  NP, useMessage, NModal
 } from 'naive-ui'
+import { SvgIcon } from '@/components/common'
 import { getToken } from '@/store/modules/auth/helper'
-import { getKnowledgeDetail } from '@/api/knowledge'
-
+import { getKnowledgeDetail, delKnowledgeDetail } from '@/api/knowledge'
 import to from "await-to-js";
 import { useRouter } from 'vue-router'
+import { t } from '@/locales';
 
-const router = useRouter()
 
+const router = useRouter()
+const kid = ref<string>('');
 onMounted(() => { 
   kid.value = router.currentRoute.value.query.kid as string
-
   fetchData() 
 });
 
-const kid = ref<string>('');
-
 const token = getToken()
 
 const message = useMessage()
@@ -31,22 +29,19 @@ const headers = {
 
 const spinShow = ref(false);
 
-
-
-
-
-
-function handleFinish({event,file}: {
+function handleFinish({event, file}: {
   file: UploadFileInfo
   event?: ProgressEvent
 }) {
-  message.success('附件上传成功!')
+  message.success(t('annex.fileUploadSuccess'))
   showModal.value = false
   // 关闭加载条
   spinShow.value = false
+  // 重新获取数据,更新表格
+  fetchData();
 }
 
-function handleActionButtonClick(row: any,action1:string): void {
+function handleActionButtonClick(row: any, action1: string): void {
   // 跳转到知识片段页面
   router.push({ path: '/fragment/t', query: { docId: row.docId } });
 }
@@ -57,51 +52,82 @@ function handleBeforeUpload(){
     spinShow.value = true
 }
 
-
 const showModal = ref(false)
 // 定义一个激活抽屉的函数,接受一个 DrawerPlacement 类型的参数
 const activate = (place: DrawerPlacement) => {
     showModal.value = true
 }
 
+// 删除附件 
+async function delKnowledgeForm(docId: string) {
+  // 发起一个请求
+  const req ={
+	  kid: kid.value, // 附件id
+    docId: docId
+  }
+
+  const [err] = await to(delKnowledgeDetail(req));
+
+  if (err) {
+    message.error(t('annex.deletionFailed')) 
+  } else {
+    message.success(t('annex.attachmentDeletedSuccess'))
+  }
+  // 重新获取数据,更新表格
+  await fetchData();
+}
+
+const pagination = ref({
+  page: 1,
+  pageSize: 10,
+  showSizePicker: true,
+  pageSizes: [10, 20, 30, 40],
+  onChange: (page: number) => {
+    pagination.value.page = page;
+  },
+  onUpdatePageSize: (pageSize: number) => {
+    pagination.value.pageSize = pageSize;
+    pagination.value.page = 1;
+  }
+});
 
 const createColumns = () => {
   return [
   ...(false
       ? [{
-          title: '附件ID',
+          title: 'ID',
           key: 'id',
           width: 80,
           ellipsis: true,
         }]
       : []),
     {
-      title: '文档编号',
+      title: t('annex.docId'),
       key: 'docId'
     },
     {
-      title: '文档名称',
+      title: t('annex.docName'),
       key: 'docName'
     },
     {
-      title: '文档类型',
+      title: t('annex.docType'),
       key: 'docType'
     },
     {
-      title: '操作',
+      title: t('annex.action'),
       key: 'actions',
       render: (row: any) => {
         return [
      
           h(NButton, {
-            onClick: () => handleActionButtonClick(row, 'action2'),
+            onClick: () => delKnowledgeForm(row.docId),
             style: 'margin-left: 8px; color: #FF4500;',
-          }, { default: () => '删除' }),
+          }, { default: () => t('annex.deleteAttachment') }),
 
           h(NButton, {
             onClick: () => handleActionButtonClick(row, 'action4'),
             style: 'margin-left: 8px; color: #32CD32;', 
-          }, { default: () => '知识片段' })
+          }, { default: () => t('annex.knowledgeFragment') })
         ];
       }
     }
@@ -109,46 +135,50 @@ const createColumns = () => {
 }
 
 const tableData = ref([]);
-  const fetchData = async () => {
-    try {
-       // 发起一个请求
-      const [err, result] = await to(getKnowledgeDetail(kid.value));
-      console.log("result===", result)
-      if (err) {
-       message.error(err.message)
-      } else {
-       tableData.value = result;
-      }
-    } catch (error) {
-      console.error('Error fetching data:', error);
+const fetchData = async () => {
+  try {
+    // 发起一个请求
+    const [err, result] = await to(getKnowledgeDetail(kid.value));
+    console.log("result===", result)
+    if (err) {
+      message.error(err.message)
+    } else {
+      tableData.value = result;
     }
-  };
-
+  } catch (error) {
+    console.error('Error fetching data:', error);
+  }
+};
 
 const columns = ref(createColumns());
 
 </script>
 <template>
 <br>
-<div style="display: flex; justify-content: flex-start; margin:10px;">
-    <n-button @click="activate('right')" type="primary">
-       上传附件
+<div style="display: flex; justify-content: flex-start; margin:10px;border-top-left-radius: 20px;" class="know-header">
+    <n-button @click="activate('right')" type="primary" :bordered="false" class="success-button">
+       {{ $t('annex.uploadAttachment') }}
     </n-button>
 </div>
 
-<div class="flex h-full"> 
-    <main class="flex-1 overflow-hidden h-full">
-      <n-data-table :columns="columns" :data="tableData" />
+<div class="flex h-full table-box" style="border-bottom-left-radius: 20px;"> 
+    <main class="flex-1 overflow-hidden h-full annex-main">
+      <n-data-table 
+        :columns="columns"
+        :data="tableData"
+        :pagination="pagination"
+        :bordered="false"
+      />
     </main>
 </div>
 
-<n-modal v-model:show="showModal" title="上传附件" :auto-focus="false" preset="card"
-		style="width: 90%; max-width: 450px;">
+<n-modal class="annex-modal" v-model:show="showModal" :title="$t('annex.uploadAttachment')" :auto-focus="false" preset="card"
+         style="width: 100%; max-width: 540px; background-color: #f8f9fa;">
   <n-space vertical>
     <n-form-item>
       <n-spin :show="spinShow">
-         <!-- @before-upload="beforeUpload" -->
-         <n-upload
+        <n-upload
+          class="annex-upload"
           directory-dnd
           action="/api/knowledge/attach/upload"
           name="file"
@@ -157,26 +187,37 @@ const columns = ref(createColumns());
           :headers="headers"
           @finish="handleFinish"
           :max="1">
-              <n-upload-dragger>
-                <div style="margin-bottom: 12px">
-                  <n-icon size="48" :depth="3">
-                    <archive-icon />
-                  </n-icon>
-                </div>
-                <n-text style="font-size: 16px">
-                  请上传一个5MB以内的文件
-                </n-text>
-                <n-p depth="3" style="margin: 8px 0 0 0">
-                  已支持 md、pdf、docx、txt、csv 等文件格式
-                </n-p>
-              </n-upload-dragger>
+          <n-upload-dragger style="padding: 20px; border: 1px dashed #d9d9d9; border-radius: 8px; text-align: left;">
+            <div style="display: flex; justify-content: center; align-items: center; margin-bottom: 12px; height: 50px;">
+              <SvgIcon icon="mage:upload" class="text-3xl"></SvgIcon>
+            </div>
+     
+            <n-p style="font-size: 16px; color: #B3B7B9; text-align: center;">
+               {{ $t('annex.pleaseUploadFile') }}
+            </n-p>
+
+            <n-p style="font-size: 16px; color: #B3B7B9; text-align: center;">
+              {{ $t('annex.supportedFormats') }}
+            </n-p>
+
+            <n-p depth="3" style="margin: 8px 0 0 0; color: #fa541c;">
+              {{ $t('annex.friendlyReminder') }}
+            </n-p>
+            <n-p depth="3" style="margin: 8px 0 0 0; color: #fa541c;">
+              {{ $t('annex.largeFileWarning') }}
+            </n-p>
+            <n-p depth="3" style="margin: 8px 0 0 0; color: #fa541c;">
+              {{ $t('annex.utf8Warning') }}
+            </n-p>
+            <n-p depth="3" style="margin: 8px 0 0 0; color: #fa541c;">
+              {{ $t('annex.uploadCharacterLimit') }}
+            </n-p>
+          </n-upload-dragger>
         </n-upload>
-      </n-spin> 
-    </n-form-item> 
+      </n-spin>
+    </n-form-item>
   </n-space>
   <br>
-
 </n-modal>
-		
-</template>
 
+</template>

+ 34 - 108
src/views/knowledge/fragment.vue

@@ -1,16 +1,11 @@
 <script setup lang="ts">
 
-import { h, onMounted, ref, computed } from 'vue'
-import {
-    NButton, NDataTable, DrawerPlacement, NDrawer, NDrawerContent,
-    NForm, NFormItem, NInput, NDivider, NSpace, UploadFileInfo, NUpload, NUploadDragger,
-    NText, NIcon, NP, useMessage, NModal
-} from 'naive-ui'
-import { getToken } from '@/store/modules/auth/helper'
+import { onMounted, ref} from 'vue'
+import { NButton, NDataTable, useMessage } from 'naive-ui'
 import { getfragmentList } from '@/api/knowledge'
-
 import to from "await-to-js";
 import { useRouter } from 'vue-router'
+import { t } from '@/locales';
 
 const router = useRouter()
 
@@ -21,93 +16,54 @@ onMounted(() => {
     fetchData() 
 });
 
-
-
-
-
-const token = getToken()
-
 const message = useMessage()
 
-const headers = {
-    Authorization: `Bearer ${token}`
-}
+const goBack = () => {
+  router.go(-1);
+};
 
 
-// 初始化表单数据对象
-const formValue = ref({
-    id: '',// 知识库id
-    kid: '', // 附件id
-    uid: '',//  用户id  
-    kname: '', // 知识库名称
-    description: '',// 知识库描述 
+const pagination = ref({
+  page: 1,
+  pageSize: 10,
+  showSizePicker: true,
+  pageSizes: [10, 20, 30, 40],
+  onChange: (page: number) => {
+    pagination.value.page = page;
+  },
+  onUpdatePageSize: (pageSize: number) => {
+    pagination.value.pageSize = pageSize;
+    pagination.value.page = 1;
+  }
 });
 
-export interface KnowledgeReq {
-
-}
-
-function handleFinish({ event, file }: {
-    file: UploadFileInfo
-    event?: ProgressEvent
-}) {
-    const ext = (event?.target as XMLHttpRequest).response
-    // ext 转成json对象
-    const fileData = JSON.parse(ext);
-    formValue.value.prompt = fileData.data.url
-    console.log("ext===================" + fileData.data.url, file.file?.size)
-    message.success('上传成功!')
-}
-
-async function submitSimpleGenerate() {
-
-}
-
-const showModal = ref(false)
-// 定义一个激活抽屉的函数,接受一个 DrawerPlacement 类型的参数
-const activate = (place: DrawerPlacement) => {
-    showModal.value = true
-}
 
 
 const createColumns = () => {
     return [
         ...(false
             ? [{
-                title: '片段ID',
+                title: 'ID',
                 key: 'id',
                 width: 80,
                 ellipsis: true,
             }]
             : []),
         {
-            title: '片段编号',
+            title: t('knowledge.no'),
             key: 'fid'
         },
 
         {
-            title: '片段内容',
+            title: t('knowledge.content'),
             key: 'content',
-            width: 300,
+            width: 800,
             ellipsis: {
                 tooltip: {
                     contentStyle: 'max-width:300px'
                 }
             }
         },
-
-        {
-            title: '操作',
-            key: 'actions',
-            render: (row: any) => {
-                return [
-                    h(NButton, {
-                        onClick: () => handleActionButtonClick(row, 'action2'),
-                        style: 'margin-left: 8px; color: #FF4500;',
-                    }, { default: () => '删除' })
-                ];
-            }
-        }
     ]
 }
 
@@ -133,49 +89,19 @@ const columns = ref(createColumns());
 </script>
 <template>
     <br>
-    <div style="display: flex; justify-content: flex-start; margin:10px;">
-        <n-button @click="activate('right')" type="primary">
-            添加片段
+    <div style="display: flex; justify-content: flex-start; margin:10px;border-top-left-radius: 20px;" class="know-header">
+        <n-button @click="goBack" type="primary" :bordered="false" class="success-button">
+            {{ $t('knowledge.return') }}
         </n-button>
     </div>
-
-    <div class="flex h-full">
-        <main class="flex-1 overflow-hidden h-full">
-            <n-data-table :columns="columns" :data="tableData" />
-        </main>
-    </div>
-
-    <n-modal v-model:show="showModal" title="添加片段" :auto-focus="false" preset="card"
-        style="width: 95%; max-width: 500px;">
-        <n-space vertical>
-            <n-form-item>
-
-                <!-- @before-upload="beforeUpload" -->
-                <n-upload directory-dnd action="/api/knowledge/attach/upload" name="file" :data="{ kid: kid }"
-                    :headers="headers" @finish="handleFinish" :max="1">
-                    <n-upload-dragger>
-                        <div style="margin-bottom: 12px">
-                            <n-icon size="48" :depth="3">
-                                <archive-icon />
-                            </n-icon>
-                        </div>
-                        <n-text style="font-size: 16px">
-                            请上传一个5MB以内的文件
-                        </n-text>
-                        <n-p depth="3" style="margin: 8px 0 0 0">
-                            已支持 md、pdf、docx、txt、csv 等文件格式
-                        </n-p>
-                    </n-upload-dragger>
-                </n-upload>
-            </n-form-item>
-        </n-space>
-
-        <br>
-        <div style="display: flex; justify-content: flex-end">
-            <n-button @click="submitSimpleGenerate" type="primary">
-                添加到知识库
-            </n-button>
-        </div>
-    </n-modal>
+    <div class="flex h-full table-box" style="border-bottom-left-radius: 20px;"> 
+    <main class="flex-1 overflow-hidden h-full annex-main">
+      <n-data-table 
+        :columns="columns"
+        :data="tableData"
+        :pagination="pagination"
+      />
+    </main>
+  </div>
 
 </template>

+ 95 - 82
src/views/knowledge/index.vue

@@ -1,161 +1,176 @@
 <script setup lang="ts">
-
-import {  h, onMounted, ref } from 'vue'
-import { NButton,NDataTable,DrawerPlacement,NDrawer,NDrawerContent,NForm,NFormItem,NInput,NDivider,NSpace,useMessage
-} from 'naive-ui'
-import { createKnowledgeReq,getKnowledge } from '@/api/knowledge'
-import to from "await-to-js";
-import { useRouter } from 'vue-router'
+import { h, onMounted, ref } from 'vue';
+import { NButton, NDataTable, DrawerPlacement, NDrawer, NDrawerContent, NForm, NFormItem, NInput, NDivider, NSpace, useMessage } from 'naive-ui';
+import { createKnowledgeReq, getKnowledge, delKnowledge } from '@/api/knowledge';
+import to from 'await-to-js';
+import { useRouter } from 'vue-router';
+import { t } from '@/locales';
 
 onMounted(() => { fetchData() });
 
-const router = useRouter()
-const message = useMessage()
+const router = useRouter();
+const message = useMessage();
 
 // 初始化表单数据对象
 const formValue = ref({
-	id:'',// 知识库id
-	kid:'', // 附件id
-	uid:'',//  用户id  
-	kname:'', // 知识库名称
-	description:'',// 知识库描述 
+  id: '', // 知识库id
+  kid: '', // 附件id
+  uid: '', // 用户id  
+  kname: '', // 知识库名称
+  description: '', // 知识库描述 
 });
 
- async function submitForm() {
-  //关闭弹框
-  active.value = false
+async function submitForm() {
+  // 关闭弹框
+  active.value = false;
   // 发起一个请求
   const [err, result] = await to(createKnowledgeReq(formValue.value));
-	  console.log("result===", result)
-    if (err) {
-      message.error(err.message)
-    } else {
-      message.success('知识库创建成功!')
-    }
+  console.log("result===", result);
+  if (err) {
+    message.error(err.message);
+  } else {
+    message.success(t('knowledge.createKnowledgeBaseSuccess'));
+    // 重新获取数据,更新表格
+    await fetchData();
+  }
 }
 
-function handleActionButtonClick(row: any,action1:string): void {
+async function delKnowledgeForm(kid: string) {
+  // 发起一个请求
+  const req = {
+    kid: kid // 附件id
+  };
+  const [err] = await to(delKnowledge(req));
+  if (err) {
+    message.error(t('knowledge.deleteFailed'));
+  } else {
+    message.success(t('knowledge.deleteSuccess'));
+  }
+  // 重新获取数据,更新表格
+  await fetchData();
+}
+
+function handleActionButtonClick(row: any, action1: string): void {
   // 跳转到知识库附件页面
   router.push({ path: '/annex/t', query: { kid: row.id } });
 }
 
 // 定义一个激活抽屉的函数,接受一个 DrawerPlacement 类型的参数
 const activate = (place: DrawerPlacement) => {
-  active.value = true
-  placement.value = place
+  active.value = true;
+  placement.value = place;
 }
 
 // 使用 ref 来创建响应式变量
-const active = ref(false)
-
-const placement = ref<DrawerPlacement>('right')
+const active = ref(false);
+const placement = ref<DrawerPlacement>('right');
 
 const createColumns = () => {
   return [
-  ...(false
+    ...(false
       ? [{
-          title: '角色ID',
+          title: "ID",
           key: 'id',
           width: 80,
           ellipsis: true,
         }]
       : []),
-      {
-      title: '编号',
-      key: 'kid'
+    {
+      title: t('knowledge.number'),
+      key: 'kid',
+      width: 200
     },
     {
-      title: '知识名称',
-      key: 'kname'
+      title: t('knowledge.name'),
+      key: 'kname',
+      width: 200
     },
     {
-      title: '知识描述',
-      key: 'description'
+      title: t('knowledge.description'),
+      key: 'description',
+      width: 200
     },
-  
     {
-      title: '操作',
+      title: t('knowledge.actions'),
       key: 'actions',
+      width: 200,
       render: (row: any) => {
         return [
           h(NButton, {
-            onClick: () => handleActionButtonClick(row, 'action1'),
-            style: 'margin-left: 0px; color: #1E90FF;', 
-          }, { default: () => '编辑' }),
-
-          h(NButton, {
-            onClick: () => handleActionButtonClick(row, 'action2'),
+            onClick: () => delKnowledgeForm(row.kid),
             style: 'margin-left: 8px; color: #FF4500;',
-          }, { default: () => '删除' }),
+            class: 'table-button',
+            bordered: false,
+          }, { default: () => t('knowledge.delete') }),
 
           h(NButton, {
             onClick: () => handleActionButtonClick(row, 'action3'),
             style: 'margin-left: 8px; color: #32CD32;', 
-          }, { default: () => '附件' }),
-
+            class: 'table-button',
+            bordered: false,
+          }, { default: () => t('knowledge.attachment') }),
         ];
       }
     }
-  ]
+  ];
 }
 
 const tableData = ref([]);
 
-  const fetchData = async () => {
-    try {
-       // 发起一个请求
-      const [err, result] = await to(getKnowledge());
-      console.log("result===", result)
-      if (err) {
-       message.error(err.message)
-      } else {
-       tableData.value = result;
-      }
-    } catch (error) {
-      console.error('Error fetching data:', error);
+const fetchData = async () => {
+  try {
+    // 发起一个请求
+    const [err, result] = await to(getKnowledge());
+    console.log("result===", result);
+    if (err) {
+      message.error(err.message);
+    } else {
+      tableData.value = result;
     }
-  };
-
+  } catch (error) {
+    console.error('Error fetching data:', error);
+  }
+};
 
 const columns = ref(createColumns());
-
 </script>
+
 <template>
 <br>
-<div style="display: flex; justify-content: flex-start; margin:10px;">
-    <n-button @click="activate('right')" type="primary">
-       创建知识库
+<div style="display: flex; justify-content: flex-start; margin:10px;" class="know-header">
+    <n-button @click="activate('right')" type="primary" :bordered="false" class="success-button">
+       {{ $t('knowledge.createKnowledgeBase') }}
     </n-button>
 </div>
 
-<div class="flex h-full"> 
+<div class="flex h-full table-box" style="border-bottom-left-radius: 20px;"> 
     <main class="flex-1 overflow-hidden h-full">
-      <n-data-table :columns="columns" :data="tableData" />
+      <n-data-table striped :bordered="false" :columns="columns" :data="tableData" />
     </main>
 </div>
 
-<n-drawer v-model:show="active" :width="502" :placement="placement">
-      <n-drawer-content title="创建知识库">
-          在这里创建你的知识库
+<n-drawer class="knowledge-draw" v-model:show="active" :width="540" :placement="placement">
+      <n-drawer-content :title="$t('knowledge.createKnowledgeBase')">
+          {{ $t('knowledge.createYourKnowledgeBase') }}
           <n-divider />
           <n-space vertical>
-            <n-form ref="formRef" >
+            <n-form ref="formRef">
               <n-form-item
-                label="知识名称"  path="formValue.kname">
-                <n-input  v-model:value="formValue.kname" placeholder="输入知识名称" />
+                :label="$t('knowledge.knowledgeName')" path="formValue.kname">
+                <n-input v-model:value="formValue.kname" :placeholder="$t('knowledge.enterKnowledgeName')" />
               </n-form-item>
-              <n-form-item label="知识描述" path="formValue.description">
+              <n-form-item :label="$t('knowledge.knowledgeDescription')" path="formValue.description">
                 <n-input maxlength="1000"
                   type="textarea"
                   v-model:value="formValue.description"
-                  placeholder="输入知识描述" 
+                  :placeholder="$t('knowledge.enterKnowledgeDescription')" 
                 />
               </n-form-item>
 
               <n-col :span="24">
                 <div style="display: flex; justify-content: flex-end">
-                  <n-button @click="submitForm"  type="primary">
-                    添加
+                  <n-button @click="submitForm" :bordered="false" type="primary" class="draw-button">
+                    {{ $t('knowledge.add') }}
                   </n-button>
                 </div>
               </n-col>
@@ -164,6 +179,4 @@ const columns = ref(createColumns());
           </n-space>
       </n-drawer-content>
 </n-drawer>
-
 </template>
-

+ 61 - 0
src/views/knowledge/layout.vue

@@ -0,0 +1,61 @@
+<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: 'Knowledge', params: { uuid: chatStore.active } })
+homeStore.setMyData({local:'knowledge'});
+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/>
+</template>
+<style  >
+.h55{
+  height: calc(100% - 55px);
+}
+</style>

+ 242 - 123
src/views/login/index.vue

@@ -1,145 +1,262 @@
 <script setup lang='ts'>
-import { ref } from 'vue'
-import { useRouter } from 'vue-router'
-import { useMessage} from 'naive-ui'
-import { LoginFrom } from '@/typings/user'
-import defaultAvatar from '@/assets/avatar.jpg'
-import { useUserStore } from '@/store/modules/user'
+import { onMounted, ref, onUnmounted } from 'vue';
+import { useRouter } from 'vue-router';
+import { useMessage, NButton, NInput, NImage, NModal, NCard } from 'naive-ui';
+import { LoginFrom } from '@/typings/user';
+import { getConfigKey, getMpQrCode, getLoginType, authSystem } from '@/api/user';
+import { useUserStore } from '@/store/modules/user';
 import to from "await-to-js";
+import { useI18n } from 'vue-i18n';
+import { useBasicLayout } from '@/hooks/useBasicLayout'
+const { isMobile } = useBasicLayout()
+const { t } = useI18n();
 
-const userStore = useUserStore()
-const router = useRouter()
-const message = useMessage()
-const user = ref<LoginFrom>(Object.create(null))
-
-// const rules = {
-//   account: {
-//     required: true,
-//     message: '请输入用户名',
-//     trigger: ['input', 'blur'],
-//   },
-//   password: {
-//     required: true,
-//     message: '请输入密码',
-//     trigger: ['input', 'blur'],
-//   },
-// }
+const userStore = useUserStore();
+const router = useRouter();
+const message = useMessage();
+const user = ref<LoginFrom>(Object.create(null));
 
 // 点击登录
-async function handleValidateButtonClick(e: MouseEvent){
-	e.preventDefault()
-	const { username, password } = user.value
+let loginLoading = ref(false)
+async function handleValidateButtonClick(e: MouseEvent) {
+	e.preventDefault();
+	const { username, password } = user.value;
 	if (!validateAccount(username)) {
-		message.error("账号格式不正确!")
-		return
+		message.error(t('login.accountFormatError'));
+		return;
 	}
 	if (username && password) {
+		loginLoading.value = true
 		const [err] = await to(userStore.userLogin(user.value));
 		if (!err) {
-			message.success("登录成功!")
+			message.success(t('login.loginSuccess'));
 			await router.push('/');
-		}else{
-			message.error(err.message)
+			loginLoading.value = false
+		} else {
+			message.error(err.message);
+			loginLoading.value = false
 		}
 	} else {
-		message.error("用户名或者密码不能为空!")
+		message.error(t('login.usernameOrPasswordEmpty'));
 	}
 }
 
-function validateAccount(account:string) {
-    if(!account){
-        return false
-    }
-    const phoneRegex = /^1[3456789]\d{9}$/;
-    const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
-    return emailRegex.test(account) || phoneRegex.test(account);;
+function validateAccount(account: string) {
+	if (!account) {
+		return false;
+	}
+	const phoneRegex = /^1[3456789]\d{9}$/;
+	const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
+	return emailRegex.test(account) || phoneRegex.test(account);
 }
 
-
 const handleRegistBtnClick = async (e: MouseEvent) => {
-  router.push('/regist')
+	router.push('/regist');
+}
+
+const copyright = ref("");
+
+const logo = ref("");
+
+const activate = ref(false);
+
+const code = ref("");
+
+// 在组件挂载后执行异步操作
+onMounted(async () => {
+	try {
+		const [err, res] = await to(getConfigKey("copyright"));
+		if (err) {
+			console.error("获取配置失败", err.message);
+		} else {
+			copyright.value = res.msg;
+		}
+
+		const [err1, res1] = await to(getConfigKey("logoImage"));
+		if (err1) {
+			console.error("获取配置失败", err1.message);
+		} else {
+			logo.value = res1.msg;
+		}
+
+		const [err2, res2] = await to(getConfigKey("activate"));
+		if (err2) {
+			console.error("获取配置失败", err1.message);
+		} else {
+			activate.value = res2.msg === 'true';
+		}
+
+	} catch (error) {
+		console.error("获取配置失败", error);
+	}
+
+});
+
+const activeTab = ref('login');
+
+const showModal = ref(false);
+
+// 登录二维码
+const qrCode = ref("");
+const ticket = ref("");
+
+let intervalId: string | number | NodeJS.Timer | undefined;
+// 定义轮询间隔时间,例如每3秒轮询一次
+const POLLING_INTERVAL = 3000;
+
+async function handleBeforeLeave(tabName: string) {
+	activeTab.value = tabName;
+	if (tabName == "qr") {
+		//获取二维码信息
+		const [err1, res1] = await to(getMpQrCode());
+		if (err1) {
+			message.error("获取二维码失败: " + err1.message);
+		} else {
+			qrCode.value = res1.data.qrCodeUrl;
+			ticket.value = res1.data.ticket;
+			intervalId = setInterval(slectLoginType, POLLING_INTERVAL);
+		}
+	}
+}
+
+async function sysAuth() {
+	const [err, res] = await to(authSystem(code.value));
+	if (err) {
+		message.error(t('login.activationFailed'));
+	} else {
+		message.success(t('login.activationSuccess'));
+	}
 }
+
+// 1. 定时查询是否登录成功
+async function slectLoginType() {
+	const [err, res] = await to(getLoginType(ticket.value));
+	if (!err) {
+		console.log("res.token", res);
+		if (res.data.token) {
+			// 2. 登录成功,保存token
+			userStore.userQrLogin(res.data.token);
+			clearInterval(intervalId);
+			// 3. 跳转到主页
+			message.success(t('login.loginSuccess'));
+			await router.push('/');
+		}
+	}
+}
+
+onUnmounted(() => {
+	// 页面组件卸载前清除定时器,避免内存泄漏
+	if (intervalId !== undefined) {
+		clearInterval(intervalId);
+	}
+});
 </script>
 
 <template>
 	<div id="app">
-		<br><br>
-		<br><br><br>
-		<div class="flex justify-center" data-v-0ee1b774="">
-			<img :src="defaultAvatar" alt="Robot Icon" class="h-12 mt-8 w-fit hover:cursor-pointer md:mt-0 md:h-16"
-				data-v-0ee1b774="">
+		<br><br><br><br>
+		<div class="flex justify-center mt-8 md:mt-0">
+			<img style="border-radius: 60px; width: 120px; height: 120px;" :src="logo" alt="Robot Icon" class="h-12 w-fit hover:cursor-pointer md:h-16">
 		</div>
 		<br>
-		<div class="relative w-full mt-10 overflow-hidden bg-white shadow-xl ring-1 ring-gray-900/5 sm:mx-auto sm:h-min sm:max-w-4xl sm:rounded-lg lg:max-w-5xl 2xl:max-w-6xl"
-			data-v-0ee1b774="">
-			<div class="px-6 pt-4 pb-8 sm:px-10" data-v-0ee1b774="">
-				<main class="mx-auto sm:max-w-4xl lg:max-w-5xl 2xl:max-w-6xl" data-v-0ee1b774=""><!--[--><!--[--><!--[-->
-					<div class="nuxt-loading-indicator"
-						style="position: fixed; top: 0px; right: 0px; left: 0px; pointer-events: none; width: 0%; height: 3px; opacity: 0; background: rgb(45, 212, 191); transition: width 0.1s ease 0s, height 0.4s ease 0s, opacity 0.4s ease 0s; z-index: 999999;">
-					</div>
-					<div>
-						<div>
-							<div class="flex flex-col justify-center min-h-full my-4 space-y-8 sm:px-6 lg:px-8">
-								<div class="sm:mx-auto sm:w-full sm:max-w-md">
-									<h2 class="text-3xl font-bold tracking-tight text-center text-gray-900">登录</h2>
-									<p class="mt-2 text-sm text-center text-gray-600"> 或者
-										<a @click="handleRegistBtnClick" style="font-size: 18px"
-											class="font-semibold text-teal-500 hover:text-teal-600">注册</a> 并免费体验问答助手
-									</p>
-								</div>
-								<div class="sm:mx-auto sm:w-full sm:max-w-sm">
-									<form class="space-y-6">
-										<div>
-											<label for="email"
-												class="block text-sm font-medium text-gray-700">邮箱或者电话号码</label>
-											<div class="mt-1">
-
-												<input id="email" v-model="user.username"
-													:allow-input="(val: string) => { return !/[^A-Za-z0-9_@.]/g.test(val) }"
-													maxlength="32" placeholder="请输入邮箱或者电话号码" name="email" type="email"
-													autocomplete="email" required="true"
-													class="block w-full px-3 py-2 placeholder-gray-400 border border-gray-300 rounded-md shadow-sm appearance-none focus:border-teal-500 focus:outline-none focus:ring-teal-500 sm:text-sm">
-											</div>
-										</div>
-										<div>
-											<label for="password" class="block text-sm font-medium text-gray-700">密码</label>
-											<div class="mt-1">
-												<input id="password" maxLength="16" v-model="user.password"
-													placeholder="请输入密码" name="password" type="password" required="true"
-													class="block w-full px-3 py-2 placeholder-gray-400 border border-gray-300 rounded-md shadow-sm appearance-none focus:border-teal-500 focus:outline-none focus:ring-teal-500 sm:text-sm">
-											</div>
-
-											<a href="#/resetpassword"
-												style="font-size: 15px; float: right; margin-top: 10PX; margin-bottom: 10px;"
-												class="font-semibold text-teal-500 hover:text-teal-600">忘记密码? </a>
-										</div>
-										<div>
+		<div
+			class="relative w-full bg-white mt-10 overflow-hidden shadow-xl ring-1 sm:mx-auto sm:h-min sm:max-w-4xl sm:rounded-lg lg:max-w-5xl 2xl:max-w-6xl login-box" :style="{width: isMobile ? '100%' : '880px'}">
+			<div class="px-6 pt-4 pb-8 sm:px-10">
+				<main class="mx-auto sm:max-w-4xl lg:max-w-5xl 2xl:max-w-6xl">
+					<!-- <div class="flex justify-start mb-4">
+						<button @click="handleBeforeLeave('login')"
+							:class="{ 'bg-teal-500 text-white': activeTab === 'login', 'bg-white text-gray-700': activeTab !== 'login' }"
+							class="px-4 py-2 font-medium rounded-md hover:bg-teal-600 hover:text-white focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-teal-500 mr-2">
+							{{ $t('login.emailOrPhone') }}
+						</button>
+				
+				
+						<button @click="handleBeforeLeave('qr')"
+							:class="{ 'bg-teal-500 text-white': activeTab === 'qr', 'bg-white text-gray-700': activeTab !== 'qr' }"
+							class="px-4 py-2 font-medium rounded-md hover:bg-teal-600 hover:text-white focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-teal-500">
+							{{ $t('login.activateSystem') }}
+						</button>
 
+					</div> -->
+					<div v-if="activeTab === 'login'">
+						<!-- 登录表单 -->
+						<div class="flex flex-col justify-center my-4 space-y-8">
+							<div class="mx-auto w-full max-w-md">
+								<h2 class="text-3xl font-bold text-center text-gray-900">{{ $t('login.login') }}</h2>
+								<p class="mt-2 text-sm text-center text-gray-600 login-desc">
+									{{ $t('login.or') }} <a @click="handleRegistBtnClick"
+										class="font-semibold text-teal-500 hover:text-teal-600">{{ $t('login.register') }}</a> {{ $t('login.andExperience') }}
+								</p>
+							</div>
+							<div class="mx-auto w-full max-w-sm">
+								<form class="space-y-6" :style="{width: !isMobile ? '580px' : 'calc(100% - 20px)', marginLeft: isMobile ? '10px' : 'calc(50% - 290px)'}">
+									<div>
+										<label for="email"
+											class="block text-sm font-medium text-gray-700">{{ $t('login.emailOrPhone') }}</label>
+										<div class="mt-1">
+											<input id="email" v-model="user.username"
+												:allow-input="(val: string) => { return !/[^A-Za-z0-9_@.]/g.test(val) }"
+												maxlength="32" :placeholder="$t('login.enterEmailOrPhone')" name="email" type="email"
+												autocomplete="email" required
+												class="block w-full px-3 py-2 placeholder-gray-400 border border-gray-300 rounded-md focus:border-teal-500 focus:outline-none focus:ring-teal-500">
 										</div>
-										<div>
-											<button @click="handleValidateButtonClick"
-												class="flex inline-flex items-center justify-center w-full px-4 py-2 text-sm font-medium text-white transition bg-teal-500 bg-teal-600 border border-transparent rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-offset-2 disabled:bg-teal-800 hover:bg-teal-700 focus:ring-teal-500 hover:bg-teal-600 focus:ring-teal-400">
-												登录</button>
+									</div>
+									<div>
+										<label for="password" class="block text-sm font-medium text-gray-700">{{ $t('login.password') }}</label>
+										<div class="mt-1">
+											<input id="password" maxLength="16" v-model="user.password"
+												:placeholder="$t('login.enterPassword')" name="password" type="password" required
+												class="block w-full px-3 py-2 placeholder-gray-400 border border-gray-300 rounded-md focus:border-teal-500 focus:outline-none focus:ring-teal-500">
 										</div>
-									</form>
-								</div>
+										<a style="color: #0084ff; font-weight: 500" href="#/resetpassword"
+											class="float-right mt-2 text-sm font-semibold text-teal-500 hover:text-teal-600">{{ $t('login.forgotPassword') }}</a>
+									</div>
+									<div class="footer-login">
+										<n-button :loading="loginLoading" @click="handleValidateButtonClick"
+											class="w-full px-4 py-2 font-medium text-white bg-teal-500 rounded-md hover:bg-teal-600 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-teal-500">{{ $t('login.login') }}</n-button>
+									</div>
+								</form>
 							</div>
 						</div>
-				</div>
-			</main>
+					</div>
+					<div v-else-if="activeTab === 'qr'">
+						<!-- 扫码登录 -->
+						<div class="flex flex-col items-center justify-center">
+							<p class="text-sm text-center">{{ $t('login.scanQrCodeLogin') }}</p>
+							<n-image width="320" :src="qrCode" />
+						</div>
+					</div>
+				</main>
+			</div>
+		</div>
+		<div class="footer">
+			<a target="_blank" style="color: #999999; font-size: 14px;" href="https://beian.miit.gov.cn/">
+				&nbsp;{{ copyright }}
+			</a>
+		</div>
+		<div v-if="!activate" id="specialDiv">
+  			<p>{{ $t('login.systemNotActivated') }}
+				<n-button size="small" secondary strong @click="showModal = true">{{ $t('login.activate') }}</n-button>
+			</p>
 		</div>
 	</div>
-
-	<div class="footer">
-		<a target="_blank" style="color: #999999; font-size: 14px; display: inline-block; vertical-align: middle;"
-			href="https://beian.miit.gov.cn/">
-			<img src='http://cdn.beiruijk.com/0be25a8d779aee40433aaca76c5f6ce.jpg'
-				style="display: inline-block; vertical-align: middle;" />
-			&nbsp;Copyright © 本网站由:熊猫智能科技有限公司运营 粤ICP备202410086号
-		</a>
-	</div>
-
-</div>
+  <n-modal v-model:show="showModal">
+    <n-card
+      style="width: 600px"
+      :bordered="false"
+      size="huge"
+      role="dialog"
+      aria-modal="true"
+    >
+	      <p class="text-sm text-center" style="margin-right: 10px;">{{ $t('login.enterAuthCode') }}</p>
+			<br>
+			<div style="display: flex; align-items: center; justify-content: center;">
+				<input id="code" v-model="code" maxlength="32" :placeholder="$t('login.activationCode')" 
+				 class="block w-full px-3 py-2 placeholder-gray-400 border border-gray-300 rounded-md focus:border-teal-500 focus:outline-none focus:ring-teal-500"/>
+				<n-button style="margin-left: 10px;" @click="sysAuth()">{{ $t('login.activate') }}</n-button>
+		</div>
+    </n-card>
+  </n-modal>
 
 </template>
 
@@ -148,31 +265,33 @@ const handleRegistBtnClick = async (e: MouseEvent) => {
 	display: flex;
 	flex-direction: column;
 	min-height: 100vh;
-	/* 确保至少为视口的高度 */
-	background-image: url('@/assets/background.jpg');
+	/* background-image: url('@/assets/background.jpg'); */
+	background-color: #141718;
 	background-size: cover;
 	background-repeat: no-repeat;
 }
 
-
 .footer {
 	display: flex;
-	flex-direction: column;
 	justify-content: center;
-	/* 垂直居中 */
 	align-items: center;
-	/* 水平居中 */
 	text-align: center;
-	color: #999999;
 	width: 100%;
 	margin-top: auto;
-	/* 自动将页脚推到底部 */
 }
 
-.forgot {
-	top: 1px;
-	right: 6px;
-	font-size: 12px;
-	color: var(--font-gray);
+input {
+	color: black !important;
+}
+
+#specialDiv {
+  position: relative; /* 使内部的绝对定位元素相对定位 */
+}
+
+#specialDiv p {
+  position: absolute; /* 绝对定位 */
+  bottom: 0; /* 将元素对齐到底部 */
+  right: 0; /* 将元素对齐到右边 */
+  margin: 0; /* 去除默认的段落外边距 */
 }
-</style>
+</style>

+ 61 - 0
src/views/luma/layout.vue

@@ -0,0 +1,61 @@
+<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: 'video', params: { uuid: chatStore.active } })
+homeStore.setMyData({local:'video'});
+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/> 
+</template>
+<style  >
+.h55{
+  height: calc(100% - 55px);
+}
+</style>

+ 16 - 0
src/views/luma/video.vue

@@ -0,0 +1,16 @@
+ <script setup lang="ts">
+ import VoInput from './voInput.vue';
+ import VoList from './voList.vue';
+ </script>
+<template>
+
+<div class="flex w-full h-full   video-content">
+    <div class="w-[300px] h-full  overflow-y-auto vo-input">
+         <VoInput/>
+    </div>
+    <div class=" flex-1  h-full bg-[#fafbfc] pt-2 dark:bg-[#18181c] overflow-y-auto video-list" >
+        <VoList/>
+    </div>
+     
+</div>
+</template> 

+ 85 - 0
src/views/luma/voInput.vue

@@ -0,0 +1,85 @@
+<script setup lang="ts">
+import { ref, computed, onMounted } from 'vue';
+import { NInput,NButton, useMessage,NTag } from 'naive-ui';
+import {SvgIcon} from '@/components/common'
+import { FeedLumaTask, lumaFetch, mlog, upImg } from '@/api';
+import { homeStore } from '@/store';
+import { t } from '@/locales';
+
+const luma= ref({ "aspect_ratio": "16:9", "expand_prompt": true,  "image_url": "",  "user_prompt": "" });
+const st= ref({isDo:false})
+const ms = useMessage();
+const fsRef= ref() ;
+onMounted(() => {
+    homeStore.setMyData({ms:ms})
+});
+
+const canPost = computed(() => {
+    return luma.value.user_prompt!='' && !st.value.isDo
+})
+const generate= async ()=>{
+    mlog("generate", luma.value )
+    st.value.isDo= true
+    if(!canPost){
+        ms.error( t('video.plsInput') )
+        return ;
+    }
+    try{
+        const d:any=  await lumaFetch('/generations/', luma.value);
+        mlog("d", d )
+        if(d.id ) FeedLumaTask(d.id )
+        else FeedLumaTask(d[0].id )
+        ms.success( t('video.submitSuccess'))
+    }catch(e){
+        
+    }
+    st.value.isDo= false
+    //FeedLumaTask('33ace512-9a46-40ab-9d08-a05eff989831')
+}
+
+function selectFile(input:any){
+     
+    upImg(input.target.files[0]).then(d=>{
+        luma.value.image_url= d;
+        fsRef.value=''
+    }).catch(e=>ms.error(e));
+    
+}
+
+const clearInput = ()=>{
+    luma.value.user_prompt= ''
+    luma.value.image_url= ''
+}
+ 
+</script>
+
+<template>
+<div class="p-2"> 
+    <div>
+      <n-input v-model:value="luma.user_prompt" 
+                :placeholder="$t('video.descpls')"  type="textarea"  size="small"   
+                :autosize="{ minRows: 3, maxRows: 12  }"  />
+    </div>
+    
+    <div class="pt-1">
+        <div class="flex justify-between  items-end">
+            <div> 
+                <input type="file"  @change="selectFile"  ref="fsRef" style="display: none" accept="image/jpeg, image/jpg, image/png, image/gif"/>
+                <div class="h-[80px] w-[80px] overflow-hidden rounded-sm border border-gray-400/20 flex justify-center items-center cursor-pointer upload-video" @click=" fsRef.click()">
+                    <img :src="luma.image_url" v-if="luma.image_url" />
+                    <div class="text-center" v-else>{{ $t('video.selectimg') }}</div> 
+                </div>
+            </div>
+            <div class="pb-1 text-right">
+                <!-- v-if="luma.user_prompt!=''||luma.image_url!=''" -->
+                <NTag type="info" size="small" round  ><span class="cursor-pointer" @click="clearInput()" >{{$t('video.clear')}}</span></NTag>
+            </div>
+        </div>
+            <div>
+                <div>
+                    <NButton :loading="st.isDo" style="border-radius: 10px" class="genner-button" :bordered="false" type="success" :disabled="!canPost" @click="generate()"><SvgIcon icon="ri:video-add-line"  /> {{$t('video.generate')}}</NButton> 
+                </div>
+            </div>
+    </div>    
+</div>
+</template>

+ 58 - 0
src/views/luma/voList.vue

@@ -0,0 +1,58 @@
+<script setup lang="ts">
+import { LumaMedia, lumaStore } from '@/api/lumaStore';
+import { computed, ref, watch } from 'vue';
+import {NEmpty ,NButton,NPopover} from 'naive-ui'
+import {FeedLumaTask} from '@/api';
+import { homeStore } from '@/store';
+import {SvgIcon} from '@/components/common'
+
+const st= ref({pIndex:-1});
+const list= ref<LumaMedia[]>([]);
+const csuno= new lumaStore()
+const initLoad=()=>{
+    let arr = csuno.getObjs();
+    list.value= arr.reverse()
+}
+const nowTime= computed(()=>{
+    return new Date().getTime()
+})
+watch(()=>homeStore.myData.act, (n)=>{
+     if(n=='FeedLumaTask')  initLoad() 
+});
+
+initLoad();
+</script>
+<template>
+<div v-if="list.length>0" class="p-4">
+    <div  class="grid grid-cols-1 gap-5 md:grid-cols-2 lg:grid-cols-3">
+        <div v-for="(item, index) in list" :key="index" class="relative" @mousemove="st.pIndex=index" @mouseout="st.pIndex=-1">
+            <div class="relative flex items-center justify-center bg-white bg-opacity-10 rounded-[16px] overflow-hidden aspect-[16/8.85] ">
+                <video v-if="item.video?.url|| item.video?.download_url" :src="item.video?.download_url? item.video?.download_url:item.video?.url" loop  playsinline  :controls="st.pIndex==index" class="w-full h-full object-cover"></video>
+                <div class=" text-center" v-else>
+                    
+                    <NButton  size="small" type="primary" @click="FeedLumaTask( item.id )"   v-if="!item.last_feed|| ((new Date().getTime())-item.last_feed)>20*1000" >{{$t('video.repeat')}}</NButton>
+                    <div class="pt-2" v-else>{{$t('video.process')}}{{ new Date(item.last_feed).toLocaleString() }}</div>
+                   
+                </div>
+            </div>
+            <div class="flex justify-between items-center">
+                <div  >
+                <n-popover trigger="hover">
+                    <template #trigger>
+                    <div class="line-clamp-1">{{item.prompt}}</div>
+                    </template>
+                    <div class=" max-w-[300px]">{{item.prompt}}</div>
+                </n-popover>
+                
+                </div>
+                <div> 
+                     <a :href="item.video?.download_url? item.video?.download_url:item.video?.url" download  target="_blank" v-if="item.video?.url|| item.video?.download_url"  ><SvgIcon icon="mdi:download" class="cursor-pointer"/></a>
+                </div>
+            </div>
+        </div>
+    </div>
+</div>
+<div class="w-full h-full flex justify-center items-center" v-else>
+    <NEmpty :description="$t('video.nodata')"></NEmpty>
+</div>
+</template>

+ 20 - 6
src/views/mj/aiBlend.vue

@@ -1,5 +1,5 @@
 <script setup lang="ts">
-import { ref } from 'vue';
+import {computed, ref} from 'vue';
 import {useMessage, NButton,NImage,NSelect} from 'naive-ui';
 import {upImg} from '@/api'
 import { homeStore } from '@/store';
@@ -30,7 +30,7 @@ const send= ()=>{
         return ;
     }
     let obj={
-            action:'blend', 
+            action:'blend',
             data:{
                 base64Array:base64Array.value
                 ,"botType": "MID_JOURNEY",
@@ -40,19 +40,33 @@ const send= ()=>{
         homeStore.setMyData({act:'draw',actData:obj});
         st.value.isGo=false;
 }
+const drawlocalized = computed(() => {
+	let localizedConfig = {};
+	Object.keys(config).forEach((key) => {
+		localizedConfig[key] = config[key].map((option) => {
+			// 假设 labelKey 如 "draw.qualityList.general"
+			let path = option.labelKey; // 直接使用 labelKey 作为路径
+			return {
+				...option,
+				label: t(path), // 从 i18n 中获取本地化的标签
+			};
+		});
+	});
+	return localizedConfig;
+});
 </script>
 <template>
 
 <input type="file"  @change="selectFile"  ref="fsRef" style="display: none" accept="image/jpeg, image/jpg, image/png, image/gif"/>
 <section class="mb-4 flex justify-between items-center"  >
      <div>{{ $t('mjchat.size') }}</div>
-    <n-select v-model:value="st.dimensions" :options="config.dimensionsList" size="small"  class="!w-[70%]" :clearable="true" />
+    <n-select v-model:value="st.dimensions" :options="drawlocalized.dimensionsList" size="small"  class="!w-[70%]" :clearable="true" />
 </section>
 <div class="flex justify-start items-center flex-wrap myblend">
     <div class="w-[var(--my-blend-img-size)] h-[var(--my-blend-img-size)] mr-2 mt-2 bg-[#ddd] overflow-hidden rounded-sm relative group " v-for="item in base64Array">
         <NImage :src="item" object-fit="cover"></NImage>
-        <SvgIcon icon="fluent:delete-12-filled" 
-        class="absolute top-0 right-0 text-red-600 text-[20px] cursor-pointer hidden group-hover:block " 
+        <SvgIcon icon="fluent:delete-12-filled"
+        class="absolute top-0 right-0 text-red-600 text-[20px] cursor-pointer hidden group-hover:block "
         @click="base64Array.splice(base64Array.indexOf(item),1)"></SvgIcon>
     </div>
     <div class="w-[var(--my-blend-img-size)] h-[var(--my-blend-img-size)] mt-2 bg-[#999] overflow-hidden rounded-sm flex justify-center items-center cursor-pointer"
@@ -63,7 +77,7 @@ const send= ()=>{
 <div   class="flex justify-end pt-5"><NButton @click="send" type="primary" :disabled="!st.isGo">{{$t('mjchat.blendStart')}}</NButton> </div>
 
 <ul class="pt-4" v-html="$t('mjchat.blendInfo')">
-    
+
 </ul>
 
 </template>

+ 2 - 1
src/views/mj/aiCanvas.vue

@@ -41,6 +41,7 @@ const loadOk= (e:Event)=>{
 }
 </script>
 <template>
-    <iframe @load="loadOk" ref="ifdiv" :src="`https://mj.aibear.com.cn/draw/mitf/index.html?${st.q}`"  class=" h-[80vh] w-full" style="border-width: 0px; border-style: none; overflow: hidden;" v-if="st.q"></iframe>
+    <!-- <iframe @load="loadOk" ref="ifdiv" :src="`./mitf/index.html?${st.q}`"  class=" h-[80vh] w-full" style="border-width: 0px; border-style: none; overflow: hidden;" v-if="st.q"></iframe> -->
+    <iframe @load="loadOk" ref="ifdiv" :src="`https://static.aitutu.cc/res/mitf/index.html?${st.q}`"  class=" h-[80vh] w-full" style="border-width: 0px; border-style: none; overflow: hidden;" v-if="st.q"></iframe>
 
 </template>

+ 1 - 1
src/views/mj/aiDrawInput.vue

@@ -9,7 +9,7 @@ const $emit=defineEmits(['drawSent','close']);
 const drawSent=(d:any )=> $emit('drawSent',d);
 </script>
 <template>
-<div class="overflow-y-auto bg-[#fafbfc] pt-2 dark:bg-[#18181c] h-full ">
+<div class="overflow-y-auto bg-[#fafbfc] pt-2 dark:bg-[#18181c] h-full draw-tabs">
  
 <n-tabs type="line" animated default-value="draw">
     <n-tab-pane name="start" tab=""> 

+ 13 - 10
src/views/mj/aiDrawInputItem.vue

@@ -327,7 +327,7 @@ function blobToFile(blob: Blob, fileName: string): File {
 <input type="file"  @change="selectFile2" ref="fsRef2" style="display: none" accept="image/jpeg, image/jpg, image/png, image/gif"/>
 <input type="file"  @change="selectFile3" ref="fsRef3" style="display: none" accept="image/jpeg, image/jpg, image/png, image/gif"/>
 
-<div class="overflow-y-auto bg-[#fafbfc] px-4 dark:bg-[#18181c] h-full ">
+<div class="overflow-y-auto bg-[#fafbfc] px-4 dark:bg-[#18181c] h-full draw-form">
 
     <section class="mb-4">
         <div class="mr-1  mb-2 flex justify-between items-center">
@@ -360,12 +360,14 @@ function blobToFile(blob: Blob, fileName: string): File {
     <!-- <template  >  </template> -->
         <section class="mb-4 flex justify-between items-center"  >
         <div  >cw(0-100)</div>
-        <NInputNumber :min="0" :max="100" v-model:value="f.cw" class="!w-[60%]" size="small" clearable placeholder="0-100 角色参考程度" />
+        <!-- 0-100 角色参考程度 -->
+        <NInputNumber :min="0" :max="100" v-model:value="f.cw" class="!w-[60%]" size="small" clearable placeholder="" />
         </section >
     
         <section class="mb-4 flex justify-between items-center"  >
         <div class="w-[45px]">sref</div>
-            <NInput v-model:value="f.sref" size="small" placeholder="图片url 生成风格一致的图像" clearable >
+        <!-- 图片url 生成风格一致的图像 -->
+            <NInput v-model:value="f.sref" size="small" placeholder="" clearable > 
                  <template #suffix>
                     <SvgIcon icon="ri:upload-line"  class="cursor-pointer" @click="uploader('sref')"></SvgIcon>
                 </template>
@@ -373,7 +375,8 @@ function blobToFile(blob: Blob, fileName: string): File {
         </section>
         <section class="mb-4 flex justify-between items-center"  >
         <div class="w-[45px]">cref</div>
-            <NInput  v-model:value="f.cref" size="small" placeholder="图片url 生成角色一致的图像" clearable>
+         <!--图片url 生成角色一致的图像 -->
+            <NInput  v-model:value="f.cref" size="small" placeholder="" clearable>
                 <template #suffix>
                     <SvgIcon icon="ri:upload-line" class="cursor-pointer"  @click="uploader('cref')"></SvgIcon>
                 </template>
@@ -393,7 +396,7 @@ function blobToFile(blob: Blob, fileName: string): File {
                 <n-tag type="error" round size="small" style="cursor: pointer; " :bordered="false" @click="fsRef.click()"   v-if="st.fileBase64.length">
                 <div style="display: flex;">  <SvgIcon icon="mdi:file-chart-check-outline" /> {{ $t('mjchat.imgCYes') }} </div>
                 </n-tag>
-                <n-tag type="warning" round size="small" style="cursor: pointer; " :bordered="false" @click="fsRef.click()"   v-else="st.fileBase64">
+                <n-tag type="error" round size="small" style="cursor: pointer; " :bordered="false" @click="fsRef.click()"   v-else="st.fileBase64">
                 <div style="display: flex;">  <SvgIcon icon="mdi:file-document-plus-outline" />  {{ $t('mjchat.imgCUpload') }} </div>
                 </n-tag>
                 </template>
@@ -416,7 +419,7 @@ function blobToFile(blob: Blob, fileName: string): File {
              <div class="pr-1 pt-1">
                <NPopover trigger="hover">
                     <template #trigger>
-                        <n-tag type="warning" round size="small" style="cursor: pointer; " :bordered="false" @click="fsRef2.click()"    >
+                        <n-tag type="error" round size="small" style="cursor: pointer; " :bordered="false" @click="fsRef2.click()"    >
                             <div style="display: flex;">  <SvgIcon icon="fluent:image-edit-16-regular" />  {{$t('mjchat.img2text')}} </div>
                         </n-tag>
                     </template>
@@ -425,7 +428,7 @@ function blobToFile(blob: Blob, fileName: string): File {
                 </NPopover>
             </div>
             <div class="pt-1" >
-                <n-tag type="success" round size="small" style="cursor: pointer; " :bordered="false" @click="shorten()"   >
+                <n-tag type="info" round size="small" style="cursor: pointer; " :bordered="false" @click="shorten()"   >
                      <div style="display: flex;">  <SvgIcon icon="game-icons:bouncing-spring" /> Shorten </div>
                 </n-tag>
             </div>
@@ -449,7 +452,7 @@ function blobToFile(blob: Blob, fileName: string): File {
 
 
         <div class="flex">
-            <n-button type="primary" :block="true" :disabled="isDisabled"  @click="create()">
+            <n-button type="success" style="border-radius: 12px; height: 48px; line-height: 48px; font-size: 17px;" :bordered="false" class="genner-button" :block="true" :disabled="isDisabled"  @click="create()">
             <SvgIcon icon="mingcute:send-plane-fill" />
 
             <template v-if="st.isLoad">{{$t('mjchat.traning')}} </template>
@@ -459,7 +462,7 @@ function blobToFile(blob: Blob, fileName: string): File {
         <div class="flex justify-start items-center py-1">
 
             <div >
-                <n-tag type="success" round size="small" style="cursor: pointer; " :bordered="false" @click="clearAll()"   >
+                <n-tag type="info" round size="small" style="cursor: pointer; " :bordered="false" @click="clearAll()"   >
                      <div style="display: flex;">  <SvgIcon icon="ant-design:clear-outlined" />{{   $t('mj.clearAll')  }}  </div>
                 </n-tag>
             </div>
@@ -476,7 +479,7 @@ function blobToFile(blob: Blob, fileName: string): File {
         <div @click="copy2()"  >复制2</div>
     </div> -->
 
-   <ul class="pt-4"  v-if="!isMobile" v-html="$t('mjchat.imginfo')"></ul>
+   <ul class="pt-4" style="font-size: 12px;" v-if="!isMobile" v-html="$t('mjchat.imginfo')"></ul>
 
 
 </div>

+ 55 - 55
src/views/mj/aiGpt.vue

@@ -1,7 +1,7 @@
-<script setup lang='ts'> 
-import { computed,   ref,watch  } from 'vue' 
+<script setup lang='ts'>
+import { computed,   ref,watch  } from 'vue'
 import { useRoute } from 'vue-router'
-import { useChat } from '../chat/hooks/useChat' 
+import { useChat } from '../chat/hooks/useChat'
 import {  homeStore, useChatStore } from '@/store'
 import { getInitChat, mlog, subModel,getSystemMessage , localSaveAny, canVisionModel
     ,isTTS, subTTS, file2blob, whisperUpload, getHistoryMessage, checkDisableGpt4, chatSetting } from '@/api'
@@ -10,7 +10,7 @@ import { useMessage  } from "naive-ui";
 import { t } from "@/locales";
 
 const emit = defineEmits(['finished']);
-const { addChat , updateChatSome } = useChat() 
+const { addChat , updateChatSome } = useChat()
 const chatStore = useChatStore()
 const st=ref({uuid:'1002', index:-1 });
 const controller = ref<AbortController>( );;// new AbortController();
@@ -23,13 +23,13 @@ const goFinish= (  )=>{
     updateChatSome( +st.value.uuid,  st.value.index , { dateTime: new Date().toLocaleString(),loading: false })
     //scrollToBottom();
     emit('finished');
-  
+
     homeStore.setMyData({act:'scrollToBottomIfAtBottom'});
     mlog('🐞 goFinish2',st.value.uuid);
     // setTimeout(() => {
-        
+
     //    if(textRz.value.length>0 )  textRz.value = [];
-    // }, 200 ); 
+    // }, 200 );
 }
 
 const getMessage= async (start=1000,loadingCnt=3)=>{
@@ -42,18 +42,18 @@ watch( ()=>textRz.value, (n)=>{
     //scrollToBottom();
     homeStore.setMyData({act:'scrollToBottomIfAtBottom'})
     //homeStore.setMyData({act:'scrollToBottom'})
-},{deep:true}) 
+},{deep:true})
 const { uuid } = useRoute().params as { uuid: string }
 watch(()=>homeStore.myData.act, async (n)=>{
 
     if(n=='gpt.submit' ||  n=='gpt.whisper'  ){
-        
+
         const dd:any = homeStore.myData.actData;
-       
+
         let  uuid2 =  dd.uuid?? uuid;
         st.value.uuid =  uuid2 ;
         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
 
@@ -61,11 +61,11 @@ watch(()=>homeStore.myData.act, async (n)=>{
             ms.error( t('mj.disableGpt4') );
             return false;
         }
-        
+
         let promptMsg = getInitChat(dd.prompt );
-        if( dd.fileBase64 && dd.fileBase64.length>0 ){ 
+        if( dd.fileBase64 && dd.fileBase64.length>0 ){
             if( !canVisionModel(model)  ) model='gpt-4-vision-preview';
-        
+
             try{
                     let images= await localSaveAny( JSON.stringify( dd.fileBase64)  ) ;
                     mlog('key', images );
@@ -94,11 +94,11 @@ watch(()=>homeStore.myData.act, async (n)=>{
                 ms.error( t('mj.noSupperChrom') );
                 return ;
             }
-            
+
             try{
-                const formData = new FormData( ); 
+                const formData = new FormData( );
                 formData.append('file',dd.file );
-                formData.append('model', 'whisper-1'); 
+                formData.append('model', 'whisper-1');
                 const whisper=  await whisperUpload( formData);
                 mlog('whisper 内容>> ', whisper );
                 let opt={duration:0,...promptMsg.opt };
@@ -110,12 +110,12 @@ watch(()=>homeStore.myData.act, async (n)=>{
                 updateChatSome(  +uuid2, dataSources.value.length-1, {text:`${t('mj.fail')}:${e}` } );
                 return ;
             }
-            
+
         }else{
             addChat(  +uuid2, promptMsg );
             homeStore.setMyData({act:'scrollToBottom'});
         }
-       
+
         let outMsg: Chat.Chat={
             dateTime: new Date().toLocaleString(),
             text: t('mj.thinking') ,//'思考中...',
@@ -126,7 +126,7 @@ watch(()=>homeStore.myData.act, async (n)=>{
             requestOptions: { prompt: dd.prompt, options: {  } },
             uuid:+uuid2,
             model ,
-            myid: `${Date.now()}` 
+            myid: `${Date.now()}`
         }
         // if(gptConfigStore.myData.gpts){
         //     outMsg.logo= gptConfigStore.myData.gpts.logo ;
@@ -147,7 +147,7 @@ watch(()=>homeStore.myData.act, async (n)=>{
         //return ;
         let message= [ {  "role": "system", "content": getSystemMessage(  +uuid2) },
                 ...historyMesg ];
-        let imageContent = [];         
+        let imageContent = [];
         if( dd.fileBase64 && dd.fileBase64.length>0 ){
             if(  model=='gpt-4-vision-preview' ){
                 let obj={
@@ -169,7 +169,7 @@ watch(()=>homeStore.myData.act, async (n)=>{
                         }
                     )
                 });
-                message.push(obj); 
+                message.push(obj);
             }else{
                 let cc= dd.prompt;
                 //附件需要时远程的图片链接 或者文件 链接
@@ -186,9 +186,9 @@ watch(()=>homeStore.myData.act, async (n)=>{
                 file: dd.file
             }
         }
-    
+
         submit(model,message,imageContent,opt);
- 
+
     }else if(n=='abort'){
        controller.value && controller.value.abort();
     }else if(n=='gpt.resubmit'){
@@ -200,14 +200,14 @@ watch(()=>homeStore.myData.act, async (n)=>{
         let  uuid2 =  dd.uuid?? uuid;
         st.value.uuid =  uuid2 ;
         st.value.index = +dd.index;
-        
+
         mlog('gpt.resubmit', dd  ) ;
         let historyMesg= await  getMessage( (+dd.index)-1,1  ); //
         mlog('gpt.resubmit historyMesg', historyMesg );
         let nobj = dataSources.value[ dd.index ];
         //mlog('gpt.resubmit model', nobj.model  );
-        let model = nobj.model
-        if(!model) model= 'gpt-3.5-turbo';
+        let model = nobj.model as string
+
         if(checkDisableGpt4(  model )){
             ms.error( t('mj.disableGpt4') );
             return false;
@@ -215,63 +215,63 @@ watch(()=>homeStore.myData.act, async (n)=>{
         //return ;
         if(['whisper-1','midjourney'].indexOf(model)>-1){
             ms.error( t('mj.noSuppertModel') );
-            return; 
+            return;
         }
 
         controller.value = new AbortController();
         let message= [ {  "role": "system", "content": getSystemMessage(+st.value.uuid ) },
-                ...historyMesg ]; 
+                ...historyMesg ];
         textRz.value=[];
-      
+
         submit(model, message);
 
-    }else if(n=='gpt.ttsv2'){ 
+    }else if(n=='gpt.ttsv2'){
         const actData:any = homeStore.myData.actData;
         mlog('gpt.ttsv2',actData );
         st.value.index= actData.index;
         st.value.uuid= actData.uuid;
         ms.info( t('mj.ttsLoading'));
         const chatSet = new chatSetting(   +st.value.uuid  );
-        const nGptStore =   chatSet.getGptConfig()  ; 
+        const nGptStore =   chatSet.getGptConfig()  ;
 
         subTTS({model:'tts-1',input: actData.text , voice:nGptStore.tts_voice }).then(d=>{
                 ms.success( t('mj.ttsSuccess'));
                 mlog('subTTS',d );
-                //d.player.play(); 
+                //d.player.play();
                 //textRz.value.push('ok');
-                updateChatSome( +st.value.uuid,  st.value.index 
-                , { 
-                dateTime: new Date().toLocaleString(),loading: false 
-                
+                updateChatSome( +st.value.uuid,  st.value.index
+                , {
+                dateTime: new Date().toLocaleString(),loading: false
+
                 ,opt:{duration:d.duration,lkey:d.saveID }
                 });
                // goFinish();
-                setTimeout(() => { 
+                setTimeout(() => {
                     homeStore.setMyData({act:'playtts',actData:{ saveID:d.saveID} });
                 }, 100);
             }).catch(e=>{
-                let  emsg =   (JSON.stringify(  e.reason? JSON.parse( e.reason ):e,null,2)); 
+                let  emsg =   (JSON.stringify(  e.reason? JSON.parse( e.reason ):e,null,2));
                 if(e.message!='canceled' && emsg.indexOf('aborted')==-1 ) textRz.value.push("\n"+t('mjchat.failReason')+" \n```\n"+emsg+"\n```\n");
                 //goFinish();
             });
-    }  
+    }
 })
 
 const submit= (model:string, message:any[],imageContent?:any[],opt?:any)=>{
     mlog('提交Model', model  );
     const chatSet = new chatSetting(   +st.value.uuid  );
-    const nGptStore =   chatSet.getGptConfig()  ; 
+    const nGptStore =   chatSet.getGptConfig()  ;
     controller.value = new AbortController();
         if(model=='whisper-1'){
-            
-            //mlog('whisper-12323',opt  ); 
-            const formData = new FormData( ); 
+
+            //mlog('whisper-12323',opt  );
+            const formData = new FormData( );
             formData.append('file', opt.file );
-            formData.append('model', 'whisper-1'); 
+            formData.append('model', 'whisper-1');
 
             //GptUploader('/v1/audio/transcriptions',formData).then(r=>{
             whisperUpload( formData).then(r=>{
-                //mlog('语音识别成功', r ); 
+                //mlog('语音识别成功', r );
                 textRz.value.push(r.text);
                 goFinish();
             }).catch(e=>{
@@ -283,23 +283,23 @@ const submit= (model:string, message:any[],imageContent?:any[],opt?:any)=>{
         }
         else if( isTTS(model)){
             let text  = message[message.length-1].content;
-            mlog('whisper-tts',  message[message.length-1] , text  ); 
+            mlog('whisper-tts',  message[message.length-1] , text  );
             subTTS({model,input: text, voice:nGptStore.tts_voice }).then(d=>{
                 mlog('subTTS',d );
-                //d.player.play(); 
+                //d.player.play();
                 //textRz.value.push('ok');
-                updateChatSome( +st.value.uuid,  st.value.index 
-                , { 
-                dateTime: new Date().toLocaleString(),loading: false 
+                updateChatSome( +st.value.uuid,  st.value.index
+                , {
+                dateTime: new Date().toLocaleString(),loading: false
                 ,text:'ok'
                 ,opt:{duration:d.duration,lkey:d.saveID }
                 });
                 goFinish();
-                setTimeout(() => { 
+                setTimeout(() => {
                     homeStore.setMyData({act:'playtts',actData:{ saveID:d.saveID} });
                 }, 100);
             }).catch(e=>{
-                let  emsg =   (JSON.stringify(  e.reason? JSON.parse( e.reason ):e,null,2)); 
+                let  emsg =   (JSON.stringify(  e.reason? JSON.parse( e.reason ):e,null,2));
                 if(e.message!='canceled' && emsg.indexOf('aborted')==-1 ) textRz.value.push("\n"+t('mjchat.failReason')+" \n```\n"+emsg+"\n```\n");
                 goFinish();
             });
@@ -331,5 +331,5 @@ const submit= (model:string, message:any[],imageContent?:any[],opt?:any)=>{
 homeStore.setMyData({isLoader:false});
 </script>
 <template>
- 
-</template>
+
+</template>

+ 526 - 273
src/views/mj/aiGptInput.vue

@@ -1,329 +1,582 @@
 <script setup lang="ts">
-import { ref ,computed,watch } from 'vue';
-import { useBasicLayout } from '@/hooks/useBasicLayout'
-import { t } from '@/locales'
-import { NInput ,NButton,useMessage,NImage,NTooltip, NAutoComplete,NTag
-,NPopover,NModal, NDropdown  } from 'naive-ui'
-import { SvgIcon } from '@/components/common';
-import { canVisionModel, GptUploader, mlog, upImg,getFileFromClipboard,isFileMp3
-    ,countTokens, checkDisableGpt4, Recognition } from '@/api';
-import { gptConfigStore, homeStore,useChatStore } from '@/store';
-import { AutoCompleteOptions } from 'naive-ui/es/auto-complete/src/interface';
-import { RenderLabel } from 'naive-ui/es/_internal/select-menu/src/interface';
-import { useRoute } from 'vue-router' 
-import aiModel from "@/views/mj/aiModel.vue"
-import AiMic from './aiMic.vue';
-import { useIconRender } from '@/hooks/useIconRender'
-
-const { iconRender } = useIconRender()
+import { ref, computed, watch } from "vue";
+import { useBasicLayout } from "@/hooks/useBasicLayout";
+import { t } from "@/locales";
+import {
+  NInput,
+  NButton,
+  useMessage,
+  NImage,
+  NTooltip,
+  NAutoComplete,
+  NTag,
+  NPopover,
+  NModal,
+  NDropdown,
+} from "naive-ui";
+import { SvgIcon, PromptStore } from "@/components/common";
+import {
+  canVisionModel,
+  GptUploader,
+  mlog,
+  upImg,
+  getFileFromClipboard,
+  isFileMp3,
+  countTokens,
+  checkDisableGpt4,
+  Recognition,
+  chatSetting,
+} from "@/api";
+import { gptConfigStore, homeStore, useChatStore } from "@/store";
+import { AutoCompleteOptions } from "naive-ui/es/auto-complete/src/interface";
+import { RenderLabel } from "naive-ui/es/_internal/select-menu/src/interface";
+import { useRoute } from "vue-router";
+import aiModel from "@/views/mj/aiModel.vue";
+import AiMic from "./aiMic.vue";
+import { useIconRender } from "@/hooks/useIconRender";
+import { Console } from "console";
+const { iconRender } = useIconRender();
 //import FormData from 'form-data'
-const route = useRoute() 
-const chatStore = useChatStore()
-
-const emit = defineEmits(['update:modelValue'])
-const props = defineProps<{ modelValue:string,disabled?:boolean,searchOptions?:AutoCompleteOptions,renderOption?: RenderLabel }>();
-const fsRef = ref()
-const st = ref<{fileBase64:string[],isLoad:number,isShow:boolean,showMic:boolean,micStart:boolean}>({fileBase64:[],isLoad:0
-    ,isShow:false,showMic:false , micStart:false})
-const { isMobile } = useBasicLayout()
+const route = useRoute();
+const chatStore = useChatStore();
+const emit = defineEmits(["update:modelValue", "export", "handleClear"]);
+const props = defineProps<{
+  modelValue: string;
+  disabled?: boolean;
+  searchOptions?: AutoCompleteOptions;
+  renderOption?: RenderLabel;
+}>();
+const fsRef = ref();
+const st = ref<{
+  fileBase64: string[];
+  isLoad: number;
+  isShow: boolean;
+  showMic: boolean;
+  micStart: boolean;
+}>({
+  fileBase64: [],
+  isLoad: 0,
+  isShow: false,
+  showMic: false,
+  micStart: false,
+});
+const { isMobile } = useBasicLayout();
 const placeholder = computed(() => {
-  if (isMobile.value)
-    return t('chat.placeholderMobile')
-  return t('chat.placeholder');//可输入说点什么,也可贴截图或拖拽文件
-})
-
-
-
-const { uuid } = route.params as { uuid: string }
+  if (isMobile.value) return t("chat.placeholderMobile");
+  return t("chat.placeholder"); //可输入说点什么,也可贴截图或拖拽文件
+});
 
-const dataSources = computed(() => chatStore.getChatByUuid(+uuid))
+const { uuid } = route.params as { uuid: string };
+const uuid1 = chatStore.active;
+const chatSet = new chatSetting(uuid1 == null ? 1002 : uuid1);
+const nGptStore = ref(chatSet.getGptConfig());
+const dataSources = computed(() => chatStore.getChatByUuid(+uuid));
 
-const handleSubmit = ( ) => {
-    if( mvalue.value==''  ) return ;
-    if(checkDisableGpt4(gptConfigStore.myData.model)){
-        ms.error( t('mj.disableGpt4') );
-        return false;
-    }
-    if( homeStore.myData.isLoader  ) { 
-        return ;
-    }
-    let obj={
-        prompt: mvalue.value,
-        fileBase64:st.value.fileBase64
-    }
-    homeStore.setMyData({act:'gpt.submit', actData:obj });
-    mvalue.value='';
-    st.value.fileBase64=[];
+watch(
+  () => gptConfigStore.myData,
+  () => (nGptStore.value = chatSet.getGptConfig()),
+  { deep: true }
+);
+watch(
+  () => homeStore.myData.act,
+  (n) => n == "saveChat" && (nGptStore.value = chatSet.getGptConfig()),
+  { deep: true }
+);
+const handleSubmit = () => {
+  if (mvalue.value == "") return;
+  if (checkDisableGpt4(gptConfigStore.myData.model)) {
+    ms.error(t("mj.disableGpt4"));
     return false;
-}
-const ms= useMessage();
+  }
+  if (homeStore.myData.isLoader) {
+    return;
+  }
+  let obj = {
+    prompt: mvalue.value,
+    fileBase64: st.value.fileBase64,
+  };
+  homeStore.setMyData({ act: "gpt.submit", actData: obj });
+  mvalue.value = "";
+  st.value.fileBase64 = [];
+  return false;
+};
+const ms = useMessage();
 const mvalue = computed({
-  get() { return props.modelValue  },
-  set(value) {  emit('update:modelValue', value) }
-})
-function selectFile(input:any){
-
-   const file = input.target.files[0];
-   upFile( file );  
+  get() {
+    return props.modelValue;
+  },
+  set(value) {
+    emit("update:modelValue", value);
+  },
+});
+function selectFile(input: any) {
+  const file = input.target.files[0];
+  upFile(file);
 }
 
-const myToken =ref({remain:0,modelTokens:'4k'});
-const funt = async ()=>{
-    const d = await countTokens( dataSources.value, mvalue.value ,chatStore.active??1002 ) 
-    myToken.value=d ;
-    return d ;
-} 
-watch(()=>mvalue.value,   funt  )
-watch(()=> dataSources.value ,  funt )
-watch(()=> gptConfigStore.myData ,  funt,{deep:true} )
-watch(()=> homeStore.myData.isLoader ,  funt,{deep:true} )
-funt(); 
- 
- const upFile= (file:any )=>{
-    if(  !canVisionModel(gptConfigStore.myData.model )  ) {
-        if( isFileMp3(  file.name ) ){
-            mlog('mp3' , file); 
-            //  const formData = new FormData( ); 
-            // formData.append('file', file);
-            // formData.append('model', 'whisper-1'); 
+const myToken = ref({ remain: 0, modelTokens: "4k" });
+const funt = async () => {
+  const d = await countTokens(
+    dataSources.value,
+    mvalue.value,
+    chatStore.active ?? 1002
+  );
+  myToken.value = d;
+  return d;
+};
+watch(() => mvalue.value, funt);
+watch(() => dataSources.value, funt);
+watch(() => gptConfigStore.myData, funt, { deep: true });
+watch(() => homeStore.myData.isLoader, funt, { deep: true });
+funt();
 
-            // GptUploader('/v1/audio/transcriptions',formData).then(r=>{
-            //     mlog('语音识别成功', r ); 
-            // }).catch(e=>ms.error('上传失败:'+ ( e.message?? JSON.stringify(e)) ));
-            homeStore.setMyData({act:'gpt.whisper', actData:{ file , prompt:'whisper' } });
-            return ;
+const upFile = (file: any) => {
+  if (!canVisionModel(gptConfigStore.myData.model)) {
+    if (isFileMp3(file.name)) {
+      mlog("mp3", file);
+      //  const formData = new FormData( );
+      // formData.append('file', file);
+      // formData.append('model', 'whisper-1');
 
-        }else{ 
-            upImg( file).then(d=>{
-                fsRef.value.value='';
-                if(st.value.fileBase64.findIndex(v=>v==d)>-1) {
-                    ms.error(t('mj.noReUpload')) ;//'不能重复上传'
-                    return ;
-                }
-                st.value.fileBase64.push(d)  
-            } ).catch(e=>ms.error(e));
-        }
-    }else{
-        const formData = new FormData( );
-        //const file = input.target.files[0];
-        formData.append('file', file); 
-        ms.info( t('mj.uploading') );
-        st.value.isLoad=1;
-        GptUploader('/v1/upload',formData).then(r=>{
-            //mlog('上传成功', r);
-             st.value.isLoad= 0 ;
-            if(r.url ){
-                ms.info(t('mj.uploadSuccess'));
-                if(r.url.indexOf('http')>-1) {
-                    st.value.fileBase64.push(r.url)
-                }else{
-                    st.value.fileBase64.push(location.origin +r.url)
-                }
-            }else if(r.error) ms.error(r.error);
-        }).catch(e=>{
-            st.value.isLoad= 0 ;
-            ms.error( t('mj.uploadFail')+ ( e.message?? JSON.stringify(e)) )
-        });
+      // GptUploader('/v1/audio/transcriptions',formData).then(r=>{
+      //     mlog('语音识别成功', r );
+      // }).catch(e=>ms.error('上传失败:'+ ( e.message?? JSON.stringify(e)) ));
+      homeStore.setMyData({
+        act: "gpt.whisper",
+        actData: { file, prompt: "whisper" },
+      });
+      return;
+    } else {
+      upImg(file)
+        .then((d) => {
+          fsRef.value.value = "";
+          if (st.value.fileBase64.findIndex((v) => v == d) > -1) {
+            ms.error(t("mj.noReUpload")); //'不能重复上传'
+            return;
+          }
+          st.value.fileBase64.push(d);
+        })
+        .catch((e) => ms.error(e));
     }
- }
- 
+  } else {
+    const formData = new FormData();
+    //const file = input.target.files[0];
+    formData.append("file", file);
+    ms.info(t("mj.uploading"));
+    st.value.isLoad = 1;
+    GptUploader("/v1/upload", formData)
+      .then((r) => {
+        //mlog('上传成功', r);
+        st.value.isLoad = 0;
+        if (r.url) {
+          ms.info(t("mj.uploadSuccess"));
+          if (r.url.indexOf("http") > -1) {
+            st.value.fileBase64.push(r.url);
+          } else {
+            st.value.fileBase64.push(location.origin + r.url);
+          }
+        } else if (r.error) ms.error(r.error);
+      })
+      .catch((e) => {
+        st.value.isLoad = 0;
+        ms.error(t("mj.uploadFail") + (e.message ?? JSON.stringify(e)));
+      });
+  }
+};
 
 function handleEnter(event: KeyboardEvent) {
   if (!isMobile.value) {
-    if (event.key === 'Enter' && !event.shiftKey) {
-      event.preventDefault()
-      handleSubmit()
+    if (event.key === "Enter" && !event.shiftKey) {
+      event.preventDefault();
+      handleSubmit();
     }
-  }
-  else {
-    if (event.key === 'Enter' && event.ctrlKey) {
-      event.preventDefault()
-      handleSubmit()
+  } else {
+    if (event.key === "Enter" && event.ctrlKey) {
+      event.preventDefault();
+      handleSubmit();
     }
   }
 }
 
 const acceptData = computed(() => {
-  if(  canVisionModel(gptConfigStore.myData.model) ) return "*/*";
-  return  "image/jpeg, image/jpg, image/png, image/gif, .mp3, .mp4, .mpeg, .mpga, .m4a, .wav, .webm"
-})
+  if (canVisionModel(gptConfigStore.myData.model)) return "*/*";
+  return "image/jpeg, image/jpg, image/png, image/gif, .mp3, .mp4, .mpeg, .mpga, .m4a, .wav, .webm";
+});
 
 const drop = (e: DragEvent) => {
   e.preventDefault();
   e.stopPropagation();
-  if( !e.dataTransfer || e.dataTransfer.files.length==0 ) return;
-  const files =    e.dataTransfer.files;
+  if (!e.dataTransfer || e.dataTransfer.files.length == 0) return;
+  const files = e.dataTransfer.files;
   upFile(files[0]);
   //mlog('drop', files);
-}
-const paste=   (e: ClipboardEvent)=>{
-    let rz =   getFileFromClipboard(e); 
-    if(rz.length>0 ) upFile(rz[0]);
-}
- 
+};
+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;
-    let du = 'whisper.wav';// (e.stat && e.stat.duration)?(e.stat.duration.toFixed(2)+'s'):'whisper.wav';
-    const file = new File([e.blob], du, { type: 'audio/wav' });
-    homeStore.setMyData({act:'gpt.whisper', actData:{ file , prompt:'whisper',duration : e.stat?.duration } });
-}
+const sendMic = (e: any) => {
+  mlog("sendMic", e);
+  st.value.showMic = false;
+  let du = "whisper.wav"; // (e.stat && e.stat.duration)?(e.stat.duration.toFixed(2)+'s'):'whisper.wav';
+  const file = new File([e.blob], du, { type: "audio/wav" });
+  homeStore.setMyData({
+    act: "gpt.whisper",
+    actData: { file, prompt: "whisper", duration: e.stat?.duration },
+  });
+};
 
 //语音识别ASR
-const goASR=()=>{
-    const olod = mvalue.value;
-    const rec= new Recognition();
-    let rz= '';
-    rec.setListener( (r:string)=>{
-        //mlog('result ', r  );
-        rz= r ; 
-        mvalue.value= r;
-        st.value.micStart= true 
-    }).setOnEnd( ( )=>{
-        //mlog('rec end');
-        mvalue.value= olod+rz;
-        ms.info( t('mj.micRecEnd'));
-        st.value.micStart= false 
-    }).setOpt({
-        timeOut:2000,
-        onStart:()=>{ ms.info( t('mj.micRec')); st.value.micStart= true },
-    }).start();
-}
+const goASR = () => {
+  const olod = mvalue.value;
+  const rec = new Recognition();
+  let rz = "";
+  rec
+    .setListener((r: string) => {
+      //mlog('result ', r  );
+      rz = r;
+      mvalue.value = r;
+      st.value.micStart = true;
+    })
+    .setOnEnd(() => {
+      //mlog('rec end');
+      mvalue.value = olod + rz;
+      ms.info(t("mj.micRecEnd"));
+      st.value.micStart = false;
+    })
+    .setOpt({
+      timeOut: 2000,
+      onStart: () => {
+        ms.info(t("mj.micRec"));
+        st.value.micStart = true;
+      },
+    })
+    .start();
+};
 
-const drOption=[
-    {
-        label:  t('mj.micWhisper'),
-        key: "whisper",
-        icon:iconRender({ icon: 'ri:openai-fill' }),
-    },{
-        label:  t('mj.micAsr'),
-        icon:iconRender({ icon: 'ri:chrome-line' }),
-        key: "asr"
-    }
-]
-const handleSelectASR = ( key: string | number )=>{ 
-    if(key=='asr')    goASR(); 
-    if(key=='whisper')   st.value.showMic=true; 
+const drOption = [
+  {
+    label: t("mj.micWhisper"),
+    key: "whisper",
+    icon: iconRender({ icon: "ri:openai-fill" }),
+  },
+  {
+    label: t("mj.micAsr"),
+    icon: iconRender({ icon: "ri:chrome-line" }),
+    key: "asr",
+  },
+];
+const handleSelectASR = (key: string | number) => {
+  if (key == "asr") goASR();
+  if (key == "whisper") st.value.showMic = true;
+};
+const show = ref(false);
+function handleExport() {
+  emit("export");
+}
+function handleClear() {
+  emit("handleClear");
 }
-
 </script>
 <template>
-<div v-if="st.showMic" class="  myinputs flex justify-center items-center" >
-    <AiMic @cancel="st.showMic=false" @send="sendMic" />
-</div>
-<div class="  myinputs"  @drop="drop" @paste="paste" v-else>
-
-    <input type="file" id="fileInput"  @change="selectFile"  class="hidden" ref="fsRef"   :accept="acceptData"/>
-    <div class="w-full relative">
-        <div class="flex items-base justify-start pb-1 flex-wrap-reverse" v-if="st.fileBase64.length>0 "> 
-            <div class="w-[60px] h-[60px] rounded-sm bg-slate-50 mr-1 mt-1 text-red-300 relative group" v-for="(v,ii) in st.fileBase64">
-            <NImage :src="v" object-fit="cover" class="w-full h-full" >
-                <template #placeholder>
-                    <a class="w-full h-full flex items-center justify-center  text-neutral-500" :href="v" target="_blank" >
-                        <SvgIcon icon="mdi:download" />{{ $t('mj.attr1') }} {{ ii+1 }}
-                    </a>
-                </template>
-            </NImage> 
-            <SvgIcon icon="mdi:close" class="hidden group-hover:block absolute top-[-5px] right-[-5px] rounded-full bg-red-300 text-white cursor-pointer" @click="st.fileBase64.splice(st.fileBase64.indexOf(v),1)"></SvgIcon>
+  <div v-if="st.showMic" class="myinputs flex justify-center items-center">
+    <AiMic @cancel="st.showMic = false" @send="sendMic" />
+  </div>
+  <div v-else>
+      <div
+        class="flex items-base justify-start pb-1 flex-wrap-reverse"
+        v-if="st.fileBase64.length > 0"
+        style="margin: 0 40px;"
+      >
+        <div
+          class="w-[60px] h-[60px] rounded-sm bg-slate-50 mr-1 mt-1 text-red-300 relative group"
+          v-for="(v, ii) in st.fileBase64"
+        >
+          <NImage :src="v" object-fit="cover" class="w-full h-full">
+            <template #placeholder>
+              <a
+                class="w-full h-full flex items-center justify-center text-neutral-500"
+                :href="v"
+                target="_blank"
+              >
+                <SvgIcon icon="mdi:download" />{{ $t("mj.attr1") }} {{ ii + 1 }}
+              </a>
+            </template>
+          </NImage>
+          <SvgIcon
+            icon="mdi:close"
+            class="hidden group-hover:block absolute top-[-5px] right-[-5px] rounded-full bg-red-300 text-white cursor-pointer"
+            @click="st.fileBase64.splice(st.fileBase64.indexOf(v), 1)"
+          ></SvgIcon>
+        </div>
+      </div>
+    <div
+      class="myinputs"
+      :class="[!isMobile ? 'chat-footer' : '']"
+      @drop="drop"
+      @paste="paste"
+    >
+      <div class="top-bar" v-if="!isMobile">
+        <div class="left" v-if="st">
+          <div
+            v-if="homeStore.myData.local != 'draw'"
+            class="chage-model-select"
+            @click="st.isShow = true"
+          >
+            <template v-if="nGptStore.gpts">
+              <SvgIcon icon="ri:apps-fill" />
+              <span class="line-clamp-1 overflow-hidden">{{
+                nGptStore.gpts.name
+              }}</span>
+            </template>
+            <template v-else>
+              <SvgIcon icon="heroicons:sparkles" />
+              <span>{{
+                nGptStore.modelLabel ? nGptStore.modelLabel : "o1-mini-2024-09-12"
+              }}</span>
+            </template>
+            <SvgIcon icon="icon-park-outline:right" />
+          </div>
+          <n-dropdown
+            trigger="hover"
+            :options="drOption"
+            @select="handleSelectASR"
+          >
+            <div class="relative; w-[22px]" style="margin: 0 25px">
+              <div
+                class="absolute bottom-[14px] left-[31px]"
+                v-if="st.micStart"
+              >
+                <span class="relative flex h-3 w-3">
+                  <span
+                    class="animate-ping absolute inline-flex h-full w-full rounded-full bg-red-500 opacity-75"
+                  ></span>
+                  <span
+                    class="relative inline-flex rounded-full h-3 w-3 bg-red-400"
+                  ></span>
+                </span>
+              </div>
+              <!-- <SvgIcon icon="bi:mic"  class="absolute bottom-[10px] left-[55px] cursor-pointer" @click="goASR()"></SvgIcon> -->
+              <IconSvg icon="voice" width="22px" height="22px"></IconSvg>
             </div>
+          </n-dropdown>
+          <n-tooltip trigger="hover">
+            <template #trigger>
+              <SvgIcon
+                icon="line-md:uploading-loop"
+                class="absolute bottom-[10px] left-[8px] cursor-pointer"
+                v-if="st.isLoad == 1"
+              ></SvgIcon>
+              <IconSvg
+                icon="upload"
+                @click="fsRef.click()"
+                v-else
+                width="22px"
+                height="22px"
+              ></IconSvg>
+            </template>
+            <div
+              v-if="canVisionModel(gptConfigStore.myData.model)"
+              v-html="$t('mj.upPdf')"
+            ></div>
+            <div v-else v-html="$t('mj.upImg')"></div>
+          </n-tooltip>
+          <IconSvg
+            @click="handleExport"
+            icon="screenshot"
+            width="22px"
+            height="22px"
+          ></IconSvg>
         </div>
-        <div class="absolute bottom-0 right-0 z-1">
-            <NPopover trigger="hover">
-                <template #trigger>
-                    <NTag type="info" round size="small" style="cursor: pointer; " :bordered="false" >
-                        <div class="opacity-60 flex"  >  
-                        <SvgIcon icon="material-symbols:token-outline"  /> {{ $t('mj.remain') }}{{ myToken.remain }}/{{ myToken.modelTokens }}
-                        </div>
-                    </NTag>
-                </template>
-                <div class="w-[300px]">
-                {{ $t('mj.tokenInfo1') }}
-                <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>
-                </p>
+        <IconSvg
+          @click="handleClear"
+          class="right"
+          icon="clear"
+          width="28px"
+          height="22px"
+        ></IconSvg>
+        <!-- <div @click="show = true">
+            {{ $t('store.siderButton') }}
+        </div> -->
+      </div>
+      <input
+        type="file"
+        id="fileInput"
+        @change="selectFile"
+        class="hidden"
+        ref="fsRef"
+        :accept="acceptData"
+      />
+      <div class="w-full relative">
+        <div class="absolute bottom-0 right-0 z-1" v-if="isMobile">
+          <NPopover trigger="hover">
+            <template #trigger>
+              <NTag
+                type="info"
+                round
+                size="small"
+                style="cursor: pointer"
+                :bordered="false"
+              >
+                <div class="opacity-60 flex">
+                  <SvgIcon icon="material-symbols:token-outline" />
+                  {{ $t("mj.remain") }}{{ myToken.remain }}/{{
+                    myToken.modelTokens
+                  }}
                 </div>
-                  
-            </NPopover>
-          
+              </NTag>
+            </template>
+            <div class="w-[300px]">
+              {{ $t("mj.tokenInfo1") }}
+              <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>
+              </p>
+            </div>
+          </NPopover>
         </div>
-    </div>
-    <NAutoComplete v-model:value="mvalue" :options="searchOptions" :render-label="renderOption" >
+      </div>
+      <NAutoComplete
+        v-model:value="mvalue"
+        :options="searchOptions"
+        :render-label="renderOption"
+        :class="[!isMobile ? 'chat-input' : '']"
+      >
         <template #default="{ handleInput, handleBlur, handleFocus }">
-        <NInput ref="inputRef"  v-model:value="mvalue"    type="textarea"
-            :placeholder="placeholder"  :autosize="{ minRows: 1, maxRows: isMobile ? 4 : 8 }"
+          <NInput
+            ref="inputRef"
+            v-model:value="mvalue"
+            type="textarea"
+            :placeholder="placeholder"
+            rows="3"
+            :autosize="{ minRows: 3, maxRows: 3 }"
             @input="handleInput"
             @focus="handleFocus"
-            @blur="handleBlur" 
-            @keypress="handleEnter"    >
-            <template #prefix>
-                <div  class=" relative; w-[22px]">
-                    <n-tooltip trigger="hover">
-                    <template #trigger>
-                    <SvgIcon icon="line-md:uploading-loop" class="absolute bottom-[10px] left-[8px] cursor-pointer" v-if="st.isLoad==1"></SvgIcon>
-                    <SvgIcon icon="ri:attachment-line" class="absolute bottom-[10px] left-[8px] cursor-pointer" @click="fsRef.click()" v-else></SvgIcon>
-                    </template>
-                    <div v-if="canVisionModel(gptConfigStore.myData.model)" v-html="$t('mj.upPdf')">
-                        
-                    </div>
-                    <div v-else v-html="$t('mj.upImg')"> 
-                    </div>
-                    </n-tooltip>
-                </div>
-                <!-- <div  class=" relative; w-[22px]">
+            @blur="handleBlur"
+            @keypress="handleEnter"
+          >
+            <template #prefix v-if="isMobile">
+              <div class="relative; w-[22px]">
+                <n-tooltip trigger="hover">
+                  <template #trigger>
+                    <SvgIcon
+                      icon="line-md:uploading-loop"
+                      class="absolute bottom-[10px] left-[8px] cursor-pointer"
+                      v-if="st.isLoad == 1"
+                    ></SvgIcon>
+                    <SvgIcon
+                      icon="ri:attachment-line"
+                      class="absolute bottom-[10px] left-[8px] cursor-pointer"
+                      @click="fsRef.click()"
+                      v-else
+                    ></SvgIcon>
+                  </template>
+                  <div
+                    v-if="canVisionModel(gptConfigStore.myData.model)"
+                    v-html="$t('mj.upPdf')"
+                  ></div>
+                  <div v-else v-html="$t('mj.upImg')"></div>
+                </n-tooltip>
+              </div>
+              <!-- <div  class=" relative; w-[22px]">
                     <SvgIcon icon="bi:mic"  class="absolute bottom-[10px] left-[30px] cursor-pointer" @click="st.showMic=true"></SvgIcon>
                 </div> -->
-                <n-dropdown trigger="hover" :options="drOption" @select="handleSelectASR">
-                    <div  class=" relative; w-[22px]">
-                        <div class="absolute bottom-[14px] left-[31px]" v-if="st.micStart">
-                            <span class="relative flex h-3 w-3" >
-                                <span class="animate-ping absolute inline-flex h-full w-full rounded-full bg-red-500 opacity-75"></span>
-                                <span class="relative inline-flex rounded-full h-3 w-3 bg-red-400"></span>
-                            </span>
-                        </div>
-                        <!-- <SvgIcon icon="bi:mic"  class="absolute bottom-[10px] left-[55px] cursor-pointer" @click="goASR()"></SvgIcon> -->
-                        <SvgIcon icon="bi:mic"  class="absolute bottom-[10px] left-[30px] cursor-pointer"></SvgIcon>
-                    </div>
-                </n-dropdown>
-                
+              <n-dropdown
+                trigger="hover"
+                :options="drOption"
+                @select="handleSelectASR"
+              >
+                <div class="relative; w-[22px]">
+                  <div
+                    class="absolute bottom-[14px] left-[31px]"
+                    v-if="st.micStart"
+                  >
+                    <span class="relative flex h-3 w-3">
+                      <span
+                        class="animate-ping absolute inline-flex h-full w-full rounded-full bg-red-500 opacity-75"
+                      ></span>
+                      <span
+                        class="relative inline-flex rounded-full h-3 w-3 bg-red-400"
+                      ></span>
+                    </span>
+                  </div>
+                  <!-- <SvgIcon icon="bi:mic"  class="absolute bottom-[10px] left-[55px] cursor-pointer" @click="goASR()"></SvgIcon> -->
+                  <SvgIcon
+                    icon="bi:mic"
+                    class="absolute bottom-[10px] left-[30px] cursor-pointer"
+                  ></SvgIcon>
+                </div>
+              </n-dropdown>
             </template>
-            <template #suffix>
-                <div  class=" relative; w-[40px] ">
-                    <div class="absolute bottom-[-3px] right-[0px] ">
-                        <NButton type="primary" :disabled="disabled || homeStore.myData.isLoader "     @click="handleSubmit" >
-                         
-                            <template #icon>
-                            <span class="dark:text-black">
-                                <SvgIcon icon="ri:stop-circle-line" v-if="homeStore.myData.isLoader" /> 
-                                <SvgIcon icon="ri:send-plane-fill"   v-else/> 
-                            </span>
-                            </template>
-                            
-                        </NButton>
-                    </div>
+            <template #suffix v-if="isMobile">
+              <div class="relative; w-[40px]">
+                <div class="absolute bottom-[-3px] right-[0px]">
+                  <NButton
+                    type="primary"
+                    :disabled="disabled || homeStore.myData.isLoader"
+                    @click="handleSubmit"
+                  >
+                    <template #icon>
+                      <span class="dark:text-black">
+                        <SvgIcon
+                          icon="ri:stop-circle-line"
+                          v-if="homeStore.myData.isLoader"
+                        />
+                        <SvgIcon icon="ri:send-plane-fill" v-else />
+                      </span>
+                    </template>
+                  </NButton>
                 </div>
+              </div>
             </template>
-        </NInput>
+          </NInput>
         </template>
-    </NAutoComplete>
-         <!-- translate-y-[-8px]       -->
-</div>
+      </NAutoComplete>
+      <div class="send" @click="handleSubmit" v-if="!isMobile">
+        <IconSvg icon="send" width="16px" height="15px"></IconSvg>
+        |
+        <IconSvg icon="money" width="14px" height="24px"></IconSvg>
+        <NPopover trigger="hover">
+          <template #trigger>
+            {{ myToken.modelTokens }}
+          </template>
+          <div class="w-[300px]">
+            {{ $t("mj.tokenInfo1") }}
+            <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>
+            </p>
+          </div>
+        </NPopover>
+      </div>
+      <!-- translate-y-[-8px]       -->
+    </div>
+  </div>
 
-<NModal v-model:show="st.isShow"   preset="card"  :title="$t('mjchat.modelChange')" class="!max-w-[620px]" @close="st.isShow=false" >  
-        <aiModel @close="st.isShow=false"/>
-</NModal>
+  <NModal
+    v-model:show="st.isShow"
+    preset="card"
+    :title="$t('mjchat.modelChange')"
+    class="!max-w-[620px]"
+    @close="st.isShow = false"
+  >
+    <aiModel @close="st.isShow = false" />
+  </NModal>
 
-<!-- <n-drawer v-model:show="st.showMic" :width="420" :on-update:show="onShowFun">
+  <PromptStore v-model:visible="show"></PromptStore>
+  <!-- <n-drawer v-model:show="st.showMic" :width="420" :on-update:show="onShowFun">
     <n-drawer-content title="录音" closable>
         <AiMic />
     </n-drawer-content>
 </n-drawer> -->
-
 </template>
 <style    >
-.myinputs .n-input .n-input-wrapper{
-     @apply items-stretch; 
-    
+.myinputs .n-input .n-input-wrapper {
+  @apply items-stretch;
 }
-</style>
+</style>

+ 94 - 50
src/views/mj/aiGptsCom.vue

@@ -1,11 +1,13 @@
-<script setup lang="ts"> 
+<script setup lang="ts">
 import { myFetch, gptsType, mlog, chatSetting,my2Fetch } from '@/api';
 import { homeStore,gptConfigStore,useChatStore, gptsUlistStore } from '@/store';
 import { ref,computed ,watch  } from 'vue';
-import { useMessage ,NButton,NImage,NTag,NPopover} from 'naive-ui';
+import { useMessage ,NButton,NImage,NPopover} from 'naive-ui';
 import { SvgIcon } from '@/components/common';
 import { useRouter } from 'vue-router';
-import { t } from '@/locales'; 
+import { getGpts } from '@/api/chatmsg';
+import { t } from '@/locales';
+import to from 'await-to-js';
 
 const router = useRouter()
 const ms = useMessage();
@@ -16,30 +18,45 @@ const pp= defineProps<{q:string}>( );
 const gptsPageList = ref<gptsType[]>([]);
 const gptsInitList = ref<gptsType[]>([]);
 const gptsSearchList = ref<gptsType[]>([]);
+
+const pageNum = ref(1);
+const pageSize = ref(12);
+const total = ref(0);
+
+
 const st= ref({loadPage:false,q:'',tab:'',search:false});
-const tag= ref(['画图','文件','发票']);
+// const tag= ref(['画图','文件','发票']);
 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  );
+       d = await my2Fetch( homeStore.myData.session.gptUrl);
     }else {
+        const params = { pageNum: pageNum.value, pageSize: pageSize.value };
+        const [err, result] = await to(getGpts(params));
+        if(err){
+            console.log("err===",err)
+        }else{
+            total.value = result.total;
+            gptsInitList.value = result.rows as unknown as gptsType[];
+        }
+
         d = await myFetch('https://gpts.ddaiai.com/open/gpts');
+
     }
-    gptsInitList.value = d.gpts as gptsType[];
-    tag.value= d.tag as string[];
+
 }
 const go= async ( item: gptsType)=>{
     const saveObj= {model:  `${ item.gid }`   ,gpts:item}
-    gptConfigStore.setMyData(saveObj); 
+    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`; 
+    const gptUrl= `https://gpts.ddaiai.com/open/gptsapi/use`;
     myFetch(gptUrl,item );
     emit('close');
     mlog('go local ', homeStore.myData.local );
@@ -48,15 +65,42 @@ const go= async ( item: gptsType)=>{
     gptsUlistStore.setMyData( item );
 
 }
-const pageLoad= async ()=>{
-    st.value.loadPage= true;
-    const gptUrl= `https://gpts.ddaiai.com/open/gptsapi/list/${ gptsPageList.value.length}`; 
-    let d = await myFetch(gptUrl);
-    st.value.loadPage= false;
 
-    let rz = d.data.list  as gptsType[];
-    gptsPageList.value = gptsPageList.value.concat(rz) //rz.concat( gptsPageList.value  )
-}
+const pageLoad = async () => {
+    // 如果已经在加载中,或者已经加载完所有数据,直接返回,防止重复加载
+
+
+    if (st.value.loadPage || (pageNum.value * pageSize.value >= total.value)) return;
+
+    // 设置加载状态
+    st.value.loadPage = true;
+    pageNum.value += 1;
+
+    const params = { pageNum: pageNum.value, pageSize: pageSize.value };
+
+    try {
+        const result = await getGpts(params);
+        let rz = result.rows as unknown as gptsType[];
+        // 更新总数据量
+        total.value = result.total;
+
+        // 合并新数据到现有列表
+        gptsPageList.value = [...gptsPageList.value, ...rz];
+
+        // 检查是否已经加载完所有数据
+        if (pageNum.value * pageSize.value >= total.value) {
+            ms.success("All data loaded");
+        }
+    } catch (err) {
+        console.error("Error loading page:", err);
+    } finally {
+        // 无论成功与否,都要重置加载状态
+        st.value.loadPage = false;
+    }
+};
+
+
+
 const gptsList = computed(()=>{
     let rz:gptsType[]=[];
     if(st.value.tab=='search'){
@@ -69,10 +113,10 @@ const searchQ= async (q:string)=>{
     st.value.q= q;
     st.value.tab= 'search';
     st.value.search= true;
-    const gptUrl= `https://gpts.ddaiai.com/open/gptsapi/search?q=${ st.value.q }`; 
+    const gptUrl= `https://gpts.ddaiai.com/open/gptsapi/search?q=${ st.value.q }`;
     let d = await myFetch(gptUrl);
       st.value.search= false;
-    gptsSearchList.value = d.data.list  as gptsType[];
+    gptsSearchList.value = d.data.list as gptsType[];
 }
 const goSearch =(q:string)=>{
     emit('toq',{q});
@@ -82,7 +126,7 @@ const goSearch =(q:string)=>{
 const badgo=(item:gptsType ,e:Event )=>{
     e.stopPropagation();
     mlog('badgo', item );
-    const gptUrl= `https://gpts.ddaiai.com/open/gptsapi/bad`; 
+    const gptUrl= `https://gpts.ddaiai.com/open/gptsapi/bad`;
     myFetch(gptUrl,item );
     item.bad= item.bad?(+item.bad+1):1;
 }
@@ -95,27 +139,18 @@ defineExpose({ searchQ })
 </script>
 <template>
 
-<div class="w-full h-full p-4">
+<div class="w-full h-full p-4 store-content">
     <template v-if="gptsList.length>0">
-        <div class="flex items-center justify-start line-clamp-1 pb-4"  >
-            <div class="m-1 cursor-pointer" v-for="v in tag" @click="goSearch(v)">
-            <n-button strong   round size="small" type="success" v-if="v==pp.q">{{ v }}</n-button>
-            <n-button strong secondary round size="small" type="success" v-else>{{ v }}</n-button>
+        <!-- <div class="flex items-center justify-start line-clamp-1 pb-4"  >
+            <div class="m-1 cursor-pointer store-label-item" v-for="v in tag" @click="goSearch(v)" :key="v">
+            <n-button strong  :bordered="false"  round size="small" type="success" v-if="v==pp.q">{{ v }}</n-button>
+            <n-button strong secondary :bordered="false" round size="small" type="success" v-else>{{ v }}</n-button>
             </div>
-        </div>
+        </div> -->
         <div class="grid grid-cols-1 gap-5 md:grid-cols-2 lg:grid-cols-3"  >
-            
-            <div @click="go(v)" v-for="v in gptsList" class="group relative flex gap-3 rounded-2xl bg-[#e8eaf1] p-5 dark:bg-neutral-600 cursor-pointer ">
-            
-                <div class="min-w-0 flex-1 mt-[-10px]">
-                    <div class="flex justify-between items-center">
-                        <h3 class=" transition   text-lg font-semibold line-clamp-1"> {{ v.name }}</h3>
-                        
-                        
-                    </div>
-                    <div class="mt-0.5 text-zinc-400 text-md line-clamp-2">{{ v.info }}</div>
-                     
-                </div>
+
+            <div @click="go(v)" v-for="v in gptsList" class="group relative flex gap-3 rounded-2xl bg-[#e8eaf1] p-5 dark:bg-neutral-600 cursor-pointer store-info-item">
+
                 <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]">
                     <template #placeholder>
@@ -124,29 +159,36 @@ defineExpose({ searchQ })
                       </div>
                     </template>
                 </NImage>
+                <div class="min-w-0 flex-1 mt-[-10px]">
+                    <div class="flex justify-between items-center">
+                        <p style="font-size: 17px"> {{ v.name }}</p>
+                    </div>
+                    <div class="mt-0.5 text-zinc-400 text-md line-clamp-2">{{ v.info }}</div>
+
+                </div>
                 <!-- <img  class="group-hover:scale-[130%] duration-300 shrink-0 overflow-hidden bg-base object-cover rounded-full bc-avatar w-[80px] h-[80px]" :src="v.logo"/> -->
-                <div class="space-x-1 flex absolute bottom-2 left-4">
+                <div class="space-x-1 flex absolute bottom-2 left-4 dianzan">
                      <n-popover trigger="hover">
                         <template #trigger>
-                        <n-tag type="success" size="small" round>
-                        <div class="flex items-center"><SvgIcon icon="mdi:hot"  ></SvgIcon>{{ v.use_cnt }}</div>
-                        </n-tag>
+                        <!-- <n-tag type="success" size="small" round> -->
+                        <div style="color: #D84C10;" class="flex items-center"><SvgIcon icon="mdi:hot"  ></SvgIcon>{{ v.useCnt }}</div>
+                        <!-- </n-tag> -->
                         </template>
-                        <span>使用热度</span>
+                        <span>{{ $t('chat.like') }}</span>
                     </n-popover>
                      <n-popover trigger="hover" >
                         <template #trigger>
-                        <n-tag type="success" size="small" round >
-                        <div class="flex items-center cursor-pointer" @click="badgo(v, $event )"><SvgIcon icon="icon-park-outline:bad-two"  ></SvgIcon>
+                        <!-- <n-tag type="success" size="small" round > -->
+                        <div style="color: #0084FF; margin-left: 20px;" class="flex items-center cursor-pointer" @click="badgo(v, $event )"><SvgIcon icon="icon-park-outline:bad-two"  ></SvgIcon>
                         <span class="ml-[2px]" > {{ v.bad }}</span>
                         </div>
-                        </n-tag>
+                        <!-- </n-tag> -->
                         </template>
-                         <span>不好用或应用已不存在请点这个</span>
+                         <span>{{ $t('chat.bad') }}</span>
                     </n-popover>
                 </div>
             </div>
-            
+
         </div>
         <div class="flex items-center justify-center py-10" v-if="st.tab=='' ">
             <div @click="pageLoad()" v-if="st.loadPage">{{ $t('mjchat.loading2') }}</div>
@@ -155,12 +197,14 @@ defineExpose({ searchQ })
     </template>
     <div class="h-full flex items-center justify-center flex-col"  v-else-if="st.tab=='search' && !st.search">
         <div>{{ $t('mjchat.nofind') }}<b class=" text-green-400">{{st.q}}</b> {{$t('mjchat.nofind2')}}</div>
+
         <div class="flex items-center justify-center flex-wrap">
             <div class="m-1 cursor-pointer" v-for="v in tag" @click="goSearch(v)"><n-button strong secondary round size="small" type="success" >{{ v }}</n-button></div>
         </div>
+
     </div>
     <div class="h-full flex items-center justify-center"  v-else>
         {{ $t('mjchat.loading2') }}
     </div>
 </div>
-</template>
+</template>

部分文件因为文件数量过多而无法显示