mirror of
				https://github.com/xiaoyiweb/YiAi.git
				synced 2025-11-04 08:13:44 +08:00 
			
		
		
		
	2.5版本 增加dall-e 优化mj 对接mj-plus
This commit is contained in:
		
							
								
								
									
										3
									
								
								.vscode/extensions.json
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								.vscode/extensions.json
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,3 @@
 | 
			
		||||
{
 | 
			
		||||
  "recommendations": ["Vue.volar", "dbaeumer.vscode-eslint"]
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										78
									
								
								.vscode/settings.json
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										78
									
								
								.vscode/settings.json
									
									
									
									
										vendored
									
									
								
							@@ -1,20 +1,66 @@
 | 
			
		||||
{
 | 
			
		||||
  "eslint.validate": ["html", "vue", "javascript", "jsx"],
 | 
			
		||||
  "emmet.syntaxProfiles": {
 | 
			
		||||
    "vue-html": "html",
 | 
			
		||||
    "vue": "html"
 | 
			
		||||
  },
 | 
			
		||||
  "editor.tabSize": 2,
 | 
			
		||||
  "eslint.alwaysShowStatus": true,
 | 
			
		||||
  "eslint.quiet": true,
 | 
			
		||||
  "prettier.enable": true,
 | 
			
		||||
  "editor.formatOnSave": true,
 | 
			
		||||
  "editor.codeActionsOnSave": {
 | 
			
		||||
    "source.fixAll.eslint": true,
 | 
			
		||||
    "source.fixAll": true,
 | 
			
		||||
    "source.fixAll.stylelint": true
 | 
			
		||||
    "source.fixAll.eslint": "explicit"
 | 
			
		||||
  },
 | 
			
		||||
  "stylelint.customSyntax": "postcss-less",
 | 
			
		||||
  "stylelint.validate": [
 | 
			
		||||
    "css",
 | 
			
		||||
    "less"
 | 
			
		||||
  ]
 | 
			
		||||
  "eslint.validate": [
 | 
			
		||||
    "javascript",
 | 
			
		||||
    "javascriptreact",
 | 
			
		||||
    "typescript",
 | 
			
		||||
    "typescriptreact",
 | 
			
		||||
    "vue",
 | 
			
		||||
    "html",
 | 
			
		||||
    "json",
 | 
			
		||||
    "jsonc",
 | 
			
		||||
    "json5",
 | 
			
		||||
    "yaml",
 | 
			
		||||
    "yml",
 | 
			
		||||
    "markdown"
 | 
			
		||||
  ],
 | 
			
		||||
  "cSpell.words": [
 | 
			
		||||
    "antfu",
 | 
			
		||||
    "axios",
 | 
			
		||||
    "bumpp",
 | 
			
		||||
    "chatgpt",
 | 
			
		||||
    "chenzhaoyu",
 | 
			
		||||
    "commitlint",
 | 
			
		||||
    "davinci",
 | 
			
		||||
    "dockerhub",
 | 
			
		||||
    "esno",
 | 
			
		||||
    "GPTAPI",
 | 
			
		||||
    "highlightjs",
 | 
			
		||||
    "hljs",
 | 
			
		||||
    "iconify",
 | 
			
		||||
    "katex",
 | 
			
		||||
    "katexmath",
 | 
			
		||||
    "linkify",
 | 
			
		||||
    "logprobs",
 | 
			
		||||
    "mdhljs",
 | 
			
		||||
    "mila",
 | 
			
		||||
    "MODELSMAPLIST",
 | 
			
		||||
    "modelvalue",
 | 
			
		||||
    "newconfig",
 | 
			
		||||
    "nodata",
 | 
			
		||||
    "OPENAI",
 | 
			
		||||
    "pinia",
 | 
			
		||||
    "Popconfirm",
 | 
			
		||||
    "rushstack",
 | 
			
		||||
    "Sider",
 | 
			
		||||
    "tailwindcss",
 | 
			
		||||
    "traptitech",
 | 
			
		||||
    "tsup",
 | 
			
		||||
    "Typecheck",
 | 
			
		||||
    "unplugin",
 | 
			
		||||
    "VITE",
 | 
			
		||||
    "vueuse",
 | 
			
		||||
    "Zhao"
 | 
			
		||||
  ],
 | 
			
		||||
  "vue.codeActions.enabled": false,
 | 
			
		||||
  "volar.experimental.tsconfigPaths": {
 | 
			
		||||
    "./chat": ["./chat/tsconfig.json"],
 | 
			
		||||
    "./admin": ["./admin/tsconfig.json"],
 | 
			
		||||
    "./service": ["./service/tsconfig.json"]
 | 
			
		||||
    // 每个键是工作区的相对路径,值是一个数组,包含该路径下的 tsconfig.json 文件
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										125
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										125
									
								
								README.md
									
									
									
									
									
								
							@@ -1,3 +1,128 @@
 | 
			
		||||
# Yi - Ai 更新日志
 | 
			
		||||
 | 
			
		||||
## V2.5.0(20240203)
 | 
			
		||||
 | 
			
		||||
### 功能更新
 | 
			
		||||
 | 
			
		||||
1. **mj-proxy-plus 支持更新**:
 | 
			
		||||
 | 
			
		||||
   - 新增容错和重试机制,提高稳定性。
 | 
			
		||||
 | 
			
		||||
2. **模型新增排序功能**:
 | 
			
		||||
 | 
			
		||||
   - 优化模型排序逻辑,提升用户体验。
 | 
			
		||||
 | 
			
		||||
3. **精简 mj 模型配置**:
 | 
			
		||||
 | 
			
		||||
   - 后台配置现仅需地址和 key,简化操作流程。
 | 
			
		||||
 | 
			
		||||
4. **dall-e 绘画整合进 chat**:
 | 
			
		||||
 | 
			
		||||
   - dall-e-3 模型可在后台单独配置。
 | 
			
		||||
   - 保留 dall-e-3 页面,绘图功能将纳入 chat 组件。
 | 
			
		||||
   - 连续绘图功能开发中。
 | 
			
		||||
 | 
			
		||||
5. **文件类型支持扩展**:
 | 
			
		||||
   - all 模型除 pdf 外,增加多种文件类型支持。
 | 
			
		||||
 | 
			
		||||
### Bug 修复
 | 
			
		||||
 | 
			
		||||
1. **国产模型兼容性修复**:
 | 
			
		||||
 | 
			
		||||
   - 修复了国产模型添加后无法使用的 bug。
 | 
			
		||||
 | 
			
		||||
2. **界面显示优化**:
 | 
			
		||||
   - 修复后台及绘画广场的显示问题。
 | 
			
		||||
 | 
			
		||||
### 注意事项
 | 
			
		||||
 | 
			
		||||
- 由于本次 mj-proxy-plus 升级不向下兼容,建议删除数据库中旧的 mj 数据库。
 | 
			
		||||
- 新的 key 可以通过中转平台购买。
 | 
			
		||||
- 如果您之前订阅过,但不想自建 mj-proxy-plus,可以考虑共享账号给我们,以合组账号池。
 | 
			
		||||
 | 
			
		||||
## V2.4.5
 | 
			
		||||
 | 
			
		||||
1. 部分页面UI精简。
 | 
			
		||||
2. 管理端地址改为 `/admin`,默认密码均设为 `123456`。
 | 
			
		||||
3. 支持使用 GPT-4-All(第三方逆向)解析上传的文件、图片。
 | 
			
		||||
4. 增加模型关联 Token 计费(可选)。
 | 
			
		||||
5. MJ 版本默认调整为 v6.0。
 | 
			
		||||
 | 
			
		||||
# 页面效果展示
 | 
			
		||||
<img src="https://nineai-1313051656.cos.ap-guangzhou.myqcloud.com/haibao/chat.png" width="100%">
 | 
			
		||||
<img src="https://nineai-1313051656.cos.ap-guangzhou.myqcloud.com/haibao/dark.png" width="100%">
 | 
			
		||||
 | 
			
		||||
# 项目部署教程
 | 
			
		||||
## 环境准备
 | 
			
		||||
 | 
			
		||||
1. **安装Node.js环境**
 | 
			
		||||
   - 请根据您的操作系统下载并安装Node.js。
 | 
			
		||||
   - 可以从[Node.js官网](https://nodejs.org/)下载。
 | 
			
		||||
 | 
			
		||||
2. **安装PM2**
 | 
			
		||||
   - 使用npm安装PM2:`npm install pm2 -g`
 | 
			
		||||
   - PM2是一个带有负载均衡功能的Node应用的进程管理器。
 | 
			
		||||
 | 
			
		||||
3. **安装PNPM**
 | 
			
		||||
   - 使用npm安装PNPM:`npm install -g pnpm`
 | 
			
		||||
   - PNPM是一个快速、节省磁盘空间的包管理工具。
 | 
			
		||||
 | 
			
		||||
## 配置项目
 | 
			
		||||
 | 
			
		||||
1. **配置环境变量**
 | 
			
		||||
   - 复制`.env.example`文件为`.env`。
 | 
			
		||||
   - 根据需要修改`.env`文件中的配置项。
 | 
			
		||||
 | 
			
		||||
2. **安装项目依赖**
 | 
			
		||||
   - 运行命令:`pnpm install`(若安装失败可尝试使用国内源)
 | 
			
		||||
   - 这将根据`package.json`文件安装所有必需的依赖。
 | 
			
		||||
 | 
			
		||||
## 启动项目
 | 
			
		||||
 | 
			
		||||
1. **启动服务**
 | 
			
		||||
   - 使用命令:`pnpm start`
 | 
			
		||||
   - 这将启动项目,并默认在9520端口监听。
 | 
			
		||||
 | 
			
		||||
2. **访问项目**
 | 
			
		||||
   - 在浏览器中访问`http://localhost:9520`,或者如果配置了nginx反向代理,则通过配置的域名访问。
 | 
			
		||||
 | 
			
		||||
## 管理平台
 | 
			
		||||
 | 
			
		||||
- **管理端地址**:`/`
 | 
			
		||||
- **普通管理员账号**:`admin`
 | 
			
		||||
- **超级管理员账号**:`super`
 | 
			
		||||
- **密码**:`123456`
 | 
			
		||||
 | 
			
		||||
普通管理员,可以预览后台非敏感信息。登入后台后请及时修改管理员密码,或按需要禁用普通管理员。
 | 
			
		||||
 | 
			
		||||
请确保遵循上述步骤进行配置和启动,以保证系统的正确运行。
 | 
			
		||||
 | 
			
		||||
## 项目升级
 | 
			
		||||
 | 
			
		||||
1. **拉取更新**
 | 
			
		||||
 | 
			
		||||
   - 拉取新的整合包:`git pull`
 | 
			
		||||
 | 
			
		||||
2. **删除旧进程**
 | 
			
		||||
 | 
			
		||||
   - 删除旧的 PM2 进程。
 | 
			
		||||
 | 
			
		||||
3. **安装依赖**
 | 
			
		||||
 | 
			
		||||
   - 运行命令:`pnpm install` 以安装 `package.json` 中定义的必需依赖。
 | 
			
		||||
 | 
			
		||||
4. **启动服务**
 | 
			
		||||
   - 使用命令:`pnpm start` 来启动项目,它将默认在 9520 端口监听。
 | 
			
		||||
 | 
			
		||||
## 作者wx
 | 
			
		||||
 | 
			
		||||
<img src="https://photo-1313051656.cos.ap-guangzhou.myqcloud.com/WechatIMG65.jpeg" width="300">
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# NineAI 更新整合版
 | 
			
		||||
 | 
			
		||||
## 更新日志
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,5 @@
 | 
			
		||||
{
 | 
			
		||||
  "version": "2.4.0",
 | 
			
		||||
  "version": "2.5.0",
 | 
			
		||||
  "scripts": {
 | 
			
		||||
    "dev": "vite",
 | 
			
		||||
    "build:test": "vue-tsc --noEmit && vite build --mode test",
 | 
			
		||||
 
 | 
			
		||||
@@ -174,6 +174,7 @@ export const MODEL_LIST = [
 | 
			
		||||
  "gpt-4-vision-preview",
 | 
			
		||||
  "gpt-4-all",
 | 
			
		||||
  "gpt-4-0125-preview",
 | 
			
		||||
  'dall-e-3',
 | 
			
		||||
  // claude
 | 
			
		||||
  "claude-2.0",
 | 
			
		||||
  "claude-2.1",
 | 
			
		||||
@@ -293,6 +294,7 @@ export const MODELSMAPLIST = {
 | 
			
		||||
    "gpt-4-vision-preview",
 | 
			
		||||
    "gpt-4-all",
 | 
			
		||||
    "gpt-4-0125-preview",
 | 
			
		||||
    'dall-e-3',
 | 
			
		||||
    // claude
 | 
			
		||||
    "claude-2.0",
 | 
			
		||||
    "claude-2.1",
 | 
			
		||||
 
 | 
			
		||||
@@ -23,21 +23,21 @@ const routes: RouteRecordRaw = {
 | 
			
		||||
        icon: 'menu-history',
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      path: 'config',
 | 
			
		||||
      name: 'mjManage',
 | 
			
		||||
      component: () => import('@/views/mjDraw/index.vue'),
 | 
			
		||||
      meta: {
 | 
			
		||||
        title: '参数配置',
 | 
			
		||||
        icon: 'menu-params',
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
    // {
 | 
			
		||||
    //   path: 'config',
 | 
			
		||||
    //   name: 'mjManage',
 | 
			
		||||
    //   component: () => import('@/views/mjDraw/index.vue'),
 | 
			
		||||
    //   meta: {
 | 
			
		||||
    //     title: '参数配置',
 | 
			
		||||
    //     icon: 'menu-params',
 | 
			
		||||
    //   },
 | 
			
		||||
    // },
 | 
			
		||||
    {
 | 
			
		||||
      path: 'proxy',
 | 
			
		||||
      name: 'mjProxyManage',
 | 
			
		||||
      component: () => import('@/views/mjDraw/proxy.vue'),
 | 
			
		||||
      meta: {
 | 
			
		||||
        title: '更多设置',
 | 
			
		||||
        title: '参数配置',
 | 
			
		||||
        icon: 'menu-proxy',
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
 
 | 
			
		||||
@@ -1,20 +1,23 @@
 | 
			
		||||
<route lang="yaml">
 | 
			
		||||
meta:
 | 
			
		||||
  title: MJ绘画管理
 | 
			
		||||
  </route>
 | 
			
		||||
</route>
 | 
			
		||||
 | 
			
		||||
<script lang="ts" setup>
 | 
			
		||||
import { onMounted, reactive, ref } from 'vue'
 | 
			
		||||
import type { FormInstance } from 'element-plus'
 | 
			
		||||
import { ElMessage } from 'element-plus'
 | 
			
		||||
import ApiChat from '@/api/modules/chat'
 | 
			
		||||
import ApiUsre from '@/api/modules/user'
 | 
			
		||||
import { DRAW_MJ_STATUS_LIST, RECOMMEND_STATUS_OPTIONS } from '@/constants/index'
 | 
			
		||||
import { onMounted, reactive, ref } from 'vue';
 | 
			
		||||
import type { FormInstance } from 'element-plus';
 | 
			
		||||
import { ElMessage } from 'element-plus';
 | 
			
		||||
import ApiChat from '@/api/modules/chat';
 | 
			
		||||
import ApiUsre from '@/api/modules/user';
 | 
			
		||||
import {
 | 
			
		||||
  DRAW_MJ_STATUS_LIST,
 | 
			
		||||
  RECOMMEND_STATUS_OPTIONS,
 | 
			
		||||
} from '@/constants/index';
 | 
			
		||||
 | 
			
		||||
const loading = ref(false)
 | 
			
		||||
const formRef = ref<FormInstance>()
 | 
			
		||||
const total = ref(0)
 | 
			
		||||
const userList = ref([])
 | 
			
		||||
const loading = ref(false);
 | 
			
		||||
const formRef = ref<FormInstance>();
 | 
			
		||||
const total = ref(0);
 | 
			
		||||
const userList = ref([]);
 | 
			
		||||
 | 
			
		||||
const formInline = reactive({
 | 
			
		||||
  userId: '',
 | 
			
		||||
@@ -22,60 +25,60 @@ const formInline = reactive({
 | 
			
		||||
  status: 3,
 | 
			
		||||
  page: 1,
 | 
			
		||||
  size: 10,
 | 
			
		||||
})
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
interface Logitem {
 | 
			
		||||
  id: number
 | 
			
		||||
  userId: number
 | 
			
		||||
  answer: string
 | 
			
		||||
  thumbImg: string
 | 
			
		||||
  rec: number
 | 
			
		||||
  model: number
 | 
			
		||||
  createdAt: string
 | 
			
		||||
  updatedAt: string
 | 
			
		||||
  id: number;
 | 
			
		||||
  userId: number;
 | 
			
		||||
  answer: string;
 | 
			
		||||
  thumbImg: string;
 | 
			
		||||
  rec: number;
 | 
			
		||||
  model: number;
 | 
			
		||||
  createdAt: string;
 | 
			
		||||
  updatedAt: string;
 | 
			
		||||
  drawUrl: string;
 | 
			
		||||
  fileInfo: {
 | 
			
		||||
    width: number
 | 
			
		||||
    height: number
 | 
			
		||||
    thumbImg: string
 | 
			
		||||
    cosUrl: string
 | 
			
		||||
  }
 | 
			
		||||
    width: number;
 | 
			
		||||
    height: number;
 | 
			
		||||
    thumbImg: string;
 | 
			
		||||
    cosUrl: string;
 | 
			
		||||
  };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const tableData = ref<Logitem[]>([])
 | 
			
		||||
const tableData = ref<Logitem[]>([]);
 | 
			
		||||
 | 
			
		||||
async function queryAllDrawLog() {
 | 
			
		||||
  loading.value = true
 | 
			
		||||
  loading.value = true;
 | 
			
		||||
  try {
 | 
			
		||||
    const res = await ApiChat.queryMjDrawAll(formInline)
 | 
			
		||||
    const { rows, count } = res.data
 | 
			
		||||
    loading.value = false
 | 
			
		||||
    const res = await ApiChat.queryMjDrawAll(formInline);
 | 
			
		||||
    const { rows, count } = res.data;
 | 
			
		||||
    loading.value = false;
 | 
			
		||||
 | 
			
		||||
    total.value = count
 | 
			
		||||
    tableData.value = rows
 | 
			
		||||
  }
 | 
			
		||||
  catch (error) {
 | 
			
		||||
    loading.value = false
 | 
			
		||||
    total.value = count;
 | 
			
		||||
    tableData.value = rows;
 | 
			
		||||
  } catch (error) {
 | 
			
		||||
    loading.value = false;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
async function recommendDrawImg(id: number) {
 | 
			
		||||
  const res = await ApiChat.recMjDrawImg({ id })
 | 
			
		||||
  ElMessage.success(res.data)
 | 
			
		||||
  queryAllDrawLog()
 | 
			
		||||
  const res = await ApiChat.recMjDrawImg({ id });
 | 
			
		||||
  ElMessage.success(res.data);
 | 
			
		||||
  queryAllDrawLog();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
async function handlerSearchUser(val: string) {
 | 
			
		||||
  const res = await ApiUsre.queryAllUser({ size: 30, username: val })
 | 
			
		||||
  userList.value = res.data.rows
 | 
			
		||||
  const res = await ApiUsre.queryAllUser({ size: 30, username: val });
 | 
			
		||||
  userList.value = res.data.rows;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function handlerReset(formEl: FormInstance | undefined) {
 | 
			
		||||
  formEl?.resetFields()
 | 
			
		||||
  queryAllDrawLog()
 | 
			
		||||
  formEl?.resetFields();
 | 
			
		||||
  queryAllDrawLog();
 | 
			
		||||
}
 | 
			
		||||
onMounted(() => {
 | 
			
		||||
  queryAllDrawLog()
 | 
			
		||||
})
 | 
			
		||||
  queryAllDrawLog();
 | 
			
		||||
});
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<template>
 | 
			
		||||
@@ -102,37 +105,68 @@ onMounted(() => {
 | 
			
		||||
          </el-select>
 | 
			
		||||
        </el-form-item>
 | 
			
		||||
        <el-form-item label="推荐状态" prop="rec">
 | 
			
		||||
          <el-select v-model="formInline.rec" placeholder="请选择推荐状态" clearable>
 | 
			
		||||
            <el-option v-for="item in RECOMMEND_STATUS_OPTIONS" :key="item.value" :label="item.label" :value="item.value" />
 | 
			
		||||
          <el-select
 | 
			
		||||
            v-model="formInline.rec"
 | 
			
		||||
            placeholder="请选择推荐状态"
 | 
			
		||||
            clearable
 | 
			
		||||
          >
 | 
			
		||||
            <el-option
 | 
			
		||||
              v-for="item in RECOMMEND_STATUS_OPTIONS"
 | 
			
		||||
              :key="item.value"
 | 
			
		||||
              :label="item.label"
 | 
			
		||||
              :value="item.value"
 | 
			
		||||
            />
 | 
			
		||||
          </el-select>
 | 
			
		||||
        </el-form-item>
 | 
			
		||||
        <el-form-item label="绘制状态" prop="status">
 | 
			
		||||
          <el-select v-model="formInline.status" placeholder="请选择图片绘制状态" clearable>
 | 
			
		||||
            <el-option v-for="item in DRAW_MJ_STATUS_LIST" :key="item.value" :label="item.label" :value="item.value" />
 | 
			
		||||
          <el-select
 | 
			
		||||
            v-model="formInline.status"
 | 
			
		||||
            placeholder="请选择图片绘制状态"
 | 
			
		||||
            clearable
 | 
			
		||||
          >
 | 
			
		||||
            <el-option
 | 
			
		||||
              v-for="item in DRAW_MJ_STATUS_LIST"
 | 
			
		||||
              :key="item.value"
 | 
			
		||||
              :label="item.label"
 | 
			
		||||
              :value="item.value"
 | 
			
		||||
            />
 | 
			
		||||
          </el-select>
 | 
			
		||||
        </el-form-item>
 | 
			
		||||
        <el-form-item>
 | 
			
		||||
          <el-button type="primary" @click="queryAllDrawLog">
 | 
			
		||||
            查询
 | 
			
		||||
          </el-button>
 | 
			
		||||
          <el-button @click="handlerReset(formRef)">
 | 
			
		||||
            重置
 | 
			
		||||
          </el-button>
 | 
			
		||||
          <el-button type="primary" @click="queryAllDrawLog"> 查询 </el-button>
 | 
			
		||||
          <el-button @click="handlerReset(formRef)"> 重置 </el-button>
 | 
			
		||||
        </el-form-item>
 | 
			
		||||
      </el-form>
 | 
			
		||||
    </page-main>
 | 
			
		||||
 | 
			
		||||
    <page-main v-loading="loading" style="width: 100%;">
 | 
			
		||||
    <page-main v-loading="loading" style="width: 100%">
 | 
			
		||||
      <div class="flex draw_container">
 | 
			
		||||
        <div v-for="item in tableData" :key="item.id" style="height: 280px;" class="draw_img_container flex border">
 | 
			
		||||
        <div
 | 
			
		||||
          v-for="item in tableData"
 | 
			
		||||
          :key="item.id"
 | 
			
		||||
          style="height: 280px"
 | 
			
		||||
          class="draw_img_container flex border"
 | 
			
		||||
        >
 | 
			
		||||
          <div class="draw_head">
 | 
			
		||||
            <el-image fit="contain" :preview-src-list="[item.fileInfo.cosUrl]" :src="item.fileInfo.thumbImg" lazy class="draw_img" hide-on-click-modal />
 | 
			
		||||
            <el-image
 | 
			
		||||
              fit="contain"
 | 
			
		||||
              :preview-src-list="[item.drawUrl]"
 | 
			
		||||
              :src="item.drawUrl"
 | 
			
		||||
              lazy
 | 
			
		||||
              class="draw_img"
 | 
			
		||||
              hide-on-click-modal
 | 
			
		||||
            />
 | 
			
		||||
          </div>
 | 
			
		||||
          <div class="draw_footer flex mt-3 justify-between items-center">
 | 
			
		||||
            <el-tag class="ml-2" :type="item.rec ? 'success' : 'info'">
 | 
			
		||||
              {{ item.rec ? '已推荐' : '未推荐' }}
 | 
			
		||||
            </el-tag>
 | 
			
		||||
            <el-button type="warning" plain size="small" @click="recommendDrawImg(item.id)">
 | 
			
		||||
            <el-button
 | 
			
		||||
              type="warning"
 | 
			
		||||
              plain
 | 
			
		||||
              size="small"
 | 
			
		||||
              @click="recommendDrawImg(item.id)"
 | 
			
		||||
            >
 | 
			
		||||
              {{ item.rec ? '取消推荐' : '加入推荐' }}
 | 
			
		||||
              <el-icon v-if="!item.rec">
 | 
			
		||||
                <Plus />
 | 
			
		||||
@@ -161,20 +195,20 @@ onMounted(() => {
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
  <style lang="less">
 | 
			
		||||
  .draw_container {
 | 
			
		||||
<style lang="less">
 | 
			
		||||
.draw_container {
 | 
			
		||||
  flex-wrap: wrap;
 | 
			
		||||
  min-height: 400px;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
  .draw_img_container {
 | 
			
		||||
.draw_img_container {
 | 
			
		||||
  max-width: 18%;
 | 
			
		||||
  flex-direction: column;
 | 
			
		||||
  margin: 8px;
 | 
			
		||||
  box-sizing: border-box;
 | 
			
		||||
  border: 1px solid #ccc;
 | 
			
		||||
  border-radius: 5px;
 | 
			
		||||
    .draw_head{
 | 
			
		||||
  .draw_head {
 | 
			
		||||
    flex: 1;
 | 
			
		||||
    display: flex;
 | 
			
		||||
    justify-content: center;
 | 
			
		||||
@@ -188,5 +222,5 @@ onMounted(() => {
 | 
			
		||||
  .draw_footer {
 | 
			
		||||
    height: 25px;
 | 
			
		||||
  }
 | 
			
		||||
  }
 | 
			
		||||
  </style>
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
 
 | 
			
		||||
@@ -1,82 +1,85 @@
 | 
			
		||||
<route lang="yaml">
 | 
			
		||||
meta:
 | 
			
		||||
  title: key列表
 | 
			
		||||
  </route>
 | 
			
		||||
</route>
 | 
			
		||||
 | 
			
		||||
<script lang="ts" setup>
 | 
			
		||||
import { onMounted, reactive } from 'vue'
 | 
			
		||||
import { ElMessage, type FormInstance } from 'element-plus'
 | 
			
		||||
import ApiMj from '@/api/modules/mj'
 | 
			
		||||
import ApiChat from '@/api/modules/chat'
 | 
			
		||||
import { utcToShanghaiTime } from '@/utils/utcformatTime'
 | 
			
		||||
import { onMounted, reactive } from 'vue';
 | 
			
		||||
import { ElMessage, type FormInstance } from 'element-plus';
 | 
			
		||||
import ApiMj from '@/api/modules/mj';
 | 
			
		||||
import ApiChat from '@/api/modules/chat';
 | 
			
		||||
import { utcToShanghaiTime } from '@/utils/utcformatTime';
 | 
			
		||||
 | 
			
		||||
import { DRAW_STATUS_MAP, RECOMMEND_STATUS, WITHDRAW_STATUS_OPTIONS } from '@/constants/index'
 | 
			
		||||
import {
 | 
			
		||||
  DRAW_STATUS_MAP,
 | 
			
		||||
  RECOMMEND_STATUS,
 | 
			
		||||
  WITHDRAW_STATUS_OPTIONS,
 | 
			
		||||
} from '@/constants/index';
 | 
			
		||||
 | 
			
		||||
const formRef = ref<FormInstance>()
 | 
			
		||||
const total = ref(0)
 | 
			
		||||
const loading = ref(false)
 | 
			
		||||
const formRef = ref<FormInstance>();
 | 
			
		||||
const total = ref(0);
 | 
			
		||||
const loading = ref(false);
 | 
			
		||||
 | 
			
		||||
const formInline = reactive({
 | 
			
		||||
  rec: null,
 | 
			
		||||
  status: null,
 | 
			
		||||
  page: 1,
 | 
			
		||||
  size: 10,
 | 
			
		||||
})
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
interface OrderInfo {
 | 
			
		||||
  id: number
 | 
			
		||||
  status: number
 | 
			
		||||
  withdrawalAmount: number
 | 
			
		||||
  userId: number
 | 
			
		||||
  remark: string
 | 
			
		||||
  contactInformation: string
 | 
			
		||||
  auditStatus: number
 | 
			
		||||
  auditUserId: number
 | 
			
		||||
  createdAt: Date
 | 
			
		||||
  updatedAt: Date
 | 
			
		||||
  id: number;
 | 
			
		||||
  status: number;
 | 
			
		||||
  withdrawalAmount: number;
 | 
			
		||||
  userId: number;
 | 
			
		||||
  remark: string;
 | 
			
		||||
  contactInformation: string;
 | 
			
		||||
  auditStatus: number;
 | 
			
		||||
  auditUserId: number;
 | 
			
		||||
  createdAt: Date;
 | 
			
		||||
  updatedAt: Date;
 | 
			
		||||
  userInfo: {
 | 
			
		||||
    avatar: string
 | 
			
		||||
    username: string
 | 
			
		||||
    email: string
 | 
			
		||||
  }
 | 
			
		||||
    avatar: string;
 | 
			
		||||
    username: string;
 | 
			
		||||
    email: string;
 | 
			
		||||
  };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const tableData = ref<OrderInfo[]>([])
 | 
			
		||||
const tableData = ref<OrderInfo[]>([]);
 | 
			
		||||
 | 
			
		||||
async function queryDrawList() {
 | 
			
		||||
  try {
 | 
			
		||||
    loading.value = true
 | 
			
		||||
    const res = await ApiMj.queryAdminDrawList(formInline)
 | 
			
		||||
    loading.value = false
 | 
			
		||||
    const { rows, count } = res.data
 | 
			
		||||
    total.value = count
 | 
			
		||||
    tableData.value = rows
 | 
			
		||||
  }
 | 
			
		||||
  catch (error) {
 | 
			
		||||
    loading.value = false
 | 
			
		||||
    loading.value = true;
 | 
			
		||||
    const res = await ApiMj.queryAdminDrawList(formInline);
 | 
			
		||||
    loading.value = false;
 | 
			
		||||
    const { rows, count } = res.data;
 | 
			
		||||
    total.value = count;
 | 
			
		||||
    tableData.value = rows;
 | 
			
		||||
  } catch (error) {
 | 
			
		||||
    loading.value = false;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function handlerReset(formEl: FormInstance | undefined) {
 | 
			
		||||
  formEl?.resetFields()
 | 
			
		||||
  queryDrawList()
 | 
			
		||||
  formEl?.resetFields();
 | 
			
		||||
  queryDrawList();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
async function recommendDrawImg(id: number) {
 | 
			
		||||
  const res = await ApiChat.recMjDrawImg({ id })
 | 
			
		||||
  ElMessage.success(res.data)
 | 
			
		||||
  queryDrawList()
 | 
			
		||||
  const res = await ApiChat.recMjDrawImg({ id });
 | 
			
		||||
  ElMessage.success(res.data);
 | 
			
		||||
  queryDrawList();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
async function handleDelChatLog(id) {
 | 
			
		||||
  const res = await ApiChat.delChatLog({ id })
 | 
			
		||||
  ElMessage.success(res.data)
 | 
			
		||||
  queryDrawList()
 | 
			
		||||
  const res = await ApiChat.delChatLog({ id });
 | 
			
		||||
  ElMessage.success(res.data);
 | 
			
		||||
  queryDrawList();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
onMounted(() => {
 | 
			
		||||
  queryDrawList()
 | 
			
		||||
})
 | 
			
		||||
  queryDrawList();
 | 
			
		||||
});
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<template>
 | 
			
		||||
@@ -84,24 +87,38 @@ onMounted(() => {
 | 
			
		||||
    <page-main>
 | 
			
		||||
      <el-form ref="formRef" :inline="true" :model="formInline">
 | 
			
		||||
        <el-form-item label="推荐状态" prop="rec">
 | 
			
		||||
          <el-select v-model="formInline.rec" placeholder="请选择推荐状态" clearable>
 | 
			
		||||
            <el-option v-for="item in RECOMMEND_STATUS" :key="item.value" :label="item.label" :value="item.value" />
 | 
			
		||||
          <el-select
 | 
			
		||||
            v-model="formInline.rec"
 | 
			
		||||
            placeholder="请选择推荐状态"
 | 
			
		||||
            clearable
 | 
			
		||||
          >
 | 
			
		||||
            <el-option
 | 
			
		||||
              v-for="item in RECOMMEND_STATUS"
 | 
			
		||||
              :key="item.value"
 | 
			
		||||
              :label="item.label"
 | 
			
		||||
              :value="item.value"
 | 
			
		||||
            />
 | 
			
		||||
          </el-select>
 | 
			
		||||
        </el-form-item>
 | 
			
		||||
        <el-form-item label="绘制状态" prop="status">
 | 
			
		||||
          <el-select v-model="formInline.status" placeholder="请选择绘制状态" clearable>
 | 
			
		||||
            <el-option v-for="item in WITHDRAW_STATUS_OPTIONS" :key="item.value" :label="item.label" :value="item.value" />
 | 
			
		||||
          <el-select
 | 
			
		||||
            v-model="formInline.status"
 | 
			
		||||
            placeholder="请选择绘制状态"
 | 
			
		||||
            clearable
 | 
			
		||||
          >
 | 
			
		||||
            <el-option
 | 
			
		||||
              v-for="item in WITHDRAW_STATUS_OPTIONS"
 | 
			
		||||
              :key="item.value"
 | 
			
		||||
              :label="item.label"
 | 
			
		||||
              :value="item.value"
 | 
			
		||||
            />
 | 
			
		||||
          </el-select>
 | 
			
		||||
        </el-form-item>
 | 
			
		||||
        <el-form-item>
 | 
			
		||||
          <el-button type="primary" @click="queryDrawList">
 | 
			
		||||
            查询
 | 
			
		||||
          </el-button>
 | 
			
		||||
          <el-button @click="handlerReset(formRef)">
 | 
			
		||||
            重置
 | 
			
		||||
          </el-button>
 | 
			
		||||
          <el-button type="primary" @click="queryDrawList"> 查询 </el-button>
 | 
			
		||||
          <el-button @click="handlerReset(formRef)"> 重置 </el-button>
 | 
			
		||||
        </el-form-item>
 | 
			
		||||
        <span style="float: right;">
 | 
			
		||||
        <span style="float: right">
 | 
			
		||||
          <el-button type="success" @click="queryDrawList">
 | 
			
		||||
            刷新列表
 | 
			
		||||
          </el-button>
 | 
			
		||||
@@ -109,33 +126,77 @@ onMounted(() => {
 | 
			
		||||
      </el-form>
 | 
			
		||||
    </page-main>
 | 
			
		||||
    <page-main>
 | 
			
		||||
      <el-alert show-icon title="MJ绘图历史说明" description="点击推荐的图片将会出现在画廊当中!" type="success" />
 | 
			
		||||
      <el-alert
 | 
			
		||||
        show-icon
 | 
			
		||||
        title="MJ绘图历史说明"
 | 
			
		||||
        description="点击推荐的图片将会出现在画廊当中!"
 | 
			
		||||
        type="success"
 | 
			
		||||
      />
 | 
			
		||||
    </page-main>
 | 
			
		||||
    <page-main style="width: 100%;">
 | 
			
		||||
      <el-table v-loading="loading" border :data="tableData" style="width: 100%;" size="large">
 | 
			
		||||
    <page-main style="width: 100%">
 | 
			
		||||
      <el-table
 | 
			
		||||
        v-loading="loading"
 | 
			
		||||
        border
 | 
			
		||||
        :data="tableData"
 | 
			
		||||
        style="width: 100%"
 | 
			
		||||
        size="large"
 | 
			
		||||
      >
 | 
			
		||||
        <el-table-column prop="id" align="center" label="ID" width="70" />
 | 
			
		||||
        <el-table-column prop="fileInfo.thumbImg" align="center" label="绘图结果">
 | 
			
		||||
        <el-table-column prop="drawUrl" align="center" label="绘图结果">
 | 
			
		||||
          <template #default="scope">
 | 
			
		||||
            <el-image style="height: 120px;" preview-teleported fit="contain" :preview-src-list="[scope.row?.fileInfo?.cosUrl]" :src="scope.row?.fileInfo?.thumbImg" lazy hide-on-click-modal />
 | 
			
		||||
            <el-image
 | 
			
		||||
              style="height: 120px"
 | 
			
		||||
              preview-teleported
 | 
			
		||||
              fit="contain"
 | 
			
		||||
              :preview-src-list="[scope.row?.drawUrl]"
 | 
			
		||||
              :src="scope.row?.drawUrl"
 | 
			
		||||
              lazy
 | 
			
		||||
              hide-on-click-modal
 | 
			
		||||
            />
 | 
			
		||||
          </template>
 | 
			
		||||
        </el-table-column>
 | 
			
		||||
        <el-table-column prop="userInfo.username" align="center" label="用户名" width="120" />
 | 
			
		||||
        <el-table-column prop="fileInfo.thumbImg" align="center" label="推荐状态" width="90">
 | 
			
		||||
        <el-table-column
 | 
			
		||||
          prop="userInfo.username"
 | 
			
		||||
          align="center"
 | 
			
		||||
          label="用户名"
 | 
			
		||||
          width="120"
 | 
			
		||||
        />
 | 
			
		||||
        <el-table-column
 | 
			
		||||
          prop="fileInfo.thumbImg"
 | 
			
		||||
          align="center"
 | 
			
		||||
          label="推荐状态"
 | 
			
		||||
          width="90"
 | 
			
		||||
        >
 | 
			
		||||
          <template #default="scope">
 | 
			
		||||
            <el-tag :type="scope.row.rec === 1 ? 'success' : ''">
 | 
			
		||||
              {{ scope.row.rec === 1 ? '已推荐' : '未推荐' }}
 | 
			
		||||
            </el-tag>
 | 
			
		||||
          </template>
 | 
			
		||||
        </el-table-column>
 | 
			
		||||
        <el-table-column prop="userInfo.email" label="邮箱" width="180" align="center" />
 | 
			
		||||
        <el-table-column prop="status" align="center" label="绘图状态" width="105">
 | 
			
		||||
        <el-table-column
 | 
			
		||||
          prop="userInfo.email"
 | 
			
		||||
          label="邮箱"
 | 
			
		||||
          width="180"
 | 
			
		||||
          align="center"
 | 
			
		||||
        />
 | 
			
		||||
        <el-table-column
 | 
			
		||||
          prop="status"
 | 
			
		||||
          align="center"
 | 
			
		||||
          label="绘图状态"
 | 
			
		||||
          width="105"
 | 
			
		||||
        >
 | 
			
		||||
          <template #default="scope">
 | 
			
		||||
            <el-tag :type="scope.row.status === 100 ? 'success' : ''">
 | 
			
		||||
              {{ DRAW_STATUS_MAP[scope.row.status] }}
 | 
			
		||||
            </el-tag>
 | 
			
		||||
          </template>
 | 
			
		||||
        </el-table-column>
 | 
			
		||||
        <el-table-column prop="fullPrompt" label="绘图指令" align="center" width="200">
 | 
			
		||||
        <el-table-column
 | 
			
		||||
          prop="fullPrompt"
 | 
			
		||||
          label="绘图指令"
 | 
			
		||||
          align="center"
 | 
			
		||||
          width="200"
 | 
			
		||||
        >
 | 
			
		||||
          <template #default="scope">
 | 
			
		||||
            <el-popover placement="top" :width="400" trigger="click">
 | 
			
		||||
              <template #reference>
 | 
			
		||||
@@ -149,19 +210,38 @@ onMounted(() => {
 | 
			
		||||
            </el-popover>
 | 
			
		||||
          </template>
 | 
			
		||||
        </el-table-column>
 | 
			
		||||
        <el-table-column prop="progress" align="center" label="绘图进度" width="90" />
 | 
			
		||||
        <el-table-column
 | 
			
		||||
          prop="progress"
 | 
			
		||||
          align="center"
 | 
			
		||||
          label="绘图进度"
 | 
			
		||||
          width="90"
 | 
			
		||||
        />
 | 
			
		||||
 | 
			
		||||
        <el-table-column prop="fileInfo.thumbImg" align="center" label="绘图尺寸" width="120">
 | 
			
		||||
        <el-table-column
 | 
			
		||||
          prop="fileInfo.thumbImg"
 | 
			
		||||
          align="center"
 | 
			
		||||
          label="绘图尺寸"
 | 
			
		||||
          width="120"
 | 
			
		||||
        >
 | 
			
		||||
          <template #default="scope">
 | 
			
		||||
            {{ scope.row?.fileInfo ? `${scope.row?.fileInfo?.width}*${scope.row?.fileInfo?.height}` : '---' }}
 | 
			
		||||
            {{
 | 
			
		||||
              scope.row?.fileInfo
 | 
			
		||||
                ? `${scope.row?.fileInfo?.width}*${scope.row?.fileInfo?.height}`
 | 
			
		||||
                : '---'
 | 
			
		||||
            }}
 | 
			
		||||
          </template>
 | 
			
		||||
        </el-table-column>
 | 
			
		||||
        <el-table-column prop="userInfo.avatar" label="用户头像" width="90">
 | 
			
		||||
          <template #default="scope">
 | 
			
		||||
            <img :src="scope.row?.userInfo?.avatar" style="height: 50px;">
 | 
			
		||||
            <img :src="scope.row?.userInfo?.avatar" style="height: 50px" />
 | 
			
		||||
          </template>
 | 
			
		||||
        </el-table-column>
 | 
			
		||||
        <el-table-column prop="createdAt" label="提问时间" align="center" width="200">
 | 
			
		||||
        <el-table-column
 | 
			
		||||
          prop="createdAt"
 | 
			
		||||
          label="提问时间"
 | 
			
		||||
          align="center"
 | 
			
		||||
          width="200"
 | 
			
		||||
        >
 | 
			
		||||
          <template #default="scope">
 | 
			
		||||
            {{ utcToShanghaiTime(scope.row.createdAt, 'YYYY-MM-DD hh:mm:ss') }}
 | 
			
		||||
          </template>
 | 
			
		||||
@@ -169,18 +249,32 @@ onMounted(() => {
 | 
			
		||||
 | 
			
		||||
        <el-table-column fixed="right" label="操作" width="200" align="center">
 | 
			
		||||
          <template #default="scope">
 | 
			
		||||
            <el-popconfirm :title="`确认${scope.row.rec === 1 ? '取消推荐' : '推荐'}图片吗!`" width="260" icon-color="red" @confirm="recommendDrawImg(scope.row.id)">
 | 
			
		||||
            <el-popconfirm
 | 
			
		||||
              :title="`确认${
 | 
			
		||||
                scope.row.rec === 1 ? '取消推荐' : '推荐'
 | 
			
		||||
              }图片吗!`"
 | 
			
		||||
              width="260"
 | 
			
		||||
              icon-color="red"
 | 
			
		||||
              @confirm="recommendDrawImg(scope.row.id)"
 | 
			
		||||
            >
 | 
			
		||||
              <template #reference>
 | 
			
		||||
                <el-button link :type="scope.row.rec === 1 ? 'success' : '' " size="small">
 | 
			
		||||
                <el-button
 | 
			
		||||
                  link
 | 
			
		||||
                  :type="scope.row.rec === 1 ? 'success' : ''"
 | 
			
		||||
                  size="small"
 | 
			
		||||
                >
 | 
			
		||||
                  推荐图片
 | 
			
		||||
                </el-button>
 | 
			
		||||
              </template>
 | 
			
		||||
            </el-popconfirm>
 | 
			
		||||
            <el-popconfirm title="`确认删除此条记录么!" width="260" icon-color="red" @confirm="handleDelChatLog(scope.row.id)">
 | 
			
		||||
            <el-popconfirm
 | 
			
		||||
              title="`确认删除此条记录么!"
 | 
			
		||||
              width="260"
 | 
			
		||||
              icon-color="red"
 | 
			
		||||
              @confirm="handleDelChatLog(scope.row.id)"
 | 
			
		||||
            >
 | 
			
		||||
              <template #reference>
 | 
			
		||||
                <el-button type="warning" size="small">
 | 
			
		||||
                  删除记录
 | 
			
		||||
                </el-button>
 | 
			
		||||
                <el-button type="warning" size="small"> 删除记录 </el-button>
 | 
			
		||||
              </template>
 | 
			
		||||
            </el-popconfirm>
 | 
			
		||||
          </template>
 | 
			
		||||
 
 | 
			
		||||
@@ -1,49 +1,59 @@
 | 
			
		||||
<route lang="yaml">
 | 
			
		||||
meta:
 | 
			
		||||
  title: MJ设置
 | 
			
		||||
  </route>
 | 
			
		||||
</route>
 | 
			
		||||
 | 
			
		||||
<script lang="ts" setup>
 | 
			
		||||
import { onMounted, reactive, ref } from 'vue'
 | 
			
		||||
import { ElMessage } from 'element-plus'
 | 
			
		||||
import type { FormInstance, FormRules } from 'element-plus'
 | 
			
		||||
import apiConfig from '@/api/modules/config'
 | 
			
		||||
import { onMounted, reactive, ref } from 'vue';
 | 
			
		||||
import { ElMessage } from 'element-plus';
 | 
			
		||||
import type { FormInstance, FormRules } from 'element-plus';
 | 
			
		||||
import apiConfig from '@/api/modules/config';
 | 
			
		||||
 | 
			
		||||
const formInline = reactive({
 | 
			
		||||
  mjTimeoutMs: '', // 接口超时时间
 | 
			
		||||
  mjProxy: '0',
 | 
			
		||||
  mjTimeoutMs: '500000',
 | 
			
		||||
  mjProxyUrl: '',
 | 
			
		||||
  mjLimitCount: null,
 | 
			
		||||
  mjProxyImgUrl: '',
 | 
			
		||||
  mjKey: '',
 | 
			
		||||
  mjLimitCount: '2',
 | 
			
		||||
  // mjProxyImgUrl: '',
 | 
			
		||||
  mjNotSaveImg: '0',
 | 
			
		||||
  mjUseBaiduFy: '0',
 | 
			
		||||
  // mjUseBaiduFy: '0',
 | 
			
		||||
  mjHideNotBlock: '0',
 | 
			
		||||
  mjHideWorkIn: '0'
 | 
			
		||||
})
 | 
			
		||||
  mjHideWorkIn: '0',
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const rules = ref<FormRules>({})
 | 
			
		||||
const rules = ref<FormRules>({});
 | 
			
		||||
 | 
			
		||||
const formRef = ref<FormInstance>()
 | 
			
		||||
const formRef = ref<FormInstance>();
 | 
			
		||||
 | 
			
		||||
async function queryAllconfig() {
 | 
			
		||||
  const res = await apiConfig.queryConfig({ keys: ['mjTimeoutMs', 'mjProxy', 'mjProxyUrl','mjLimitCount','mjNotSaveImg','mjProxyImgUrl','mjUseBaiduFy','mjHideNotBlock','mjHideWorkIn'] })
 | 
			
		||||
  Object.assign(formInline, res.data)
 | 
			
		||||
  const res = await apiConfig.queryConfig({
 | 
			
		||||
    keys: [
 | 
			
		||||
      'mjTimeoutMs',
 | 
			
		||||
      'mjKey',
 | 
			
		||||
      'mjProxyUrl',
 | 
			
		||||
      'mjLimitCount',
 | 
			
		||||
      'mjNotSaveImg',
 | 
			
		||||
      // 'mjProxyImgUrl',
 | 
			
		||||
      // 'mjUseBaiduFy',
 | 
			
		||||
      'mjHideNotBlock',
 | 
			
		||||
      'mjHideWorkIn',
 | 
			
		||||
    ],
 | 
			
		||||
  });
 | 
			
		||||
  Object.assign(formInline, res.data);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function handlerUpdateConfig() {
 | 
			
		||||
  formRef.value?.validate(async (valid) => {
 | 
			
		||||
    if (valid) {
 | 
			
		||||
      try {
 | 
			
		||||
        await apiConfig.setConfig({ settings: fotmatSetting(formInline) })
 | 
			
		||||
        ElMessage.success('变更配置信息成功')
 | 
			
		||||
        await apiConfig.setConfig({ settings: fotmatSetting(formInline) });
 | 
			
		||||
        ElMessage.success('变更配置信息成功');
 | 
			
		||||
      } catch (error) {}
 | 
			
		||||
      queryAllconfig();
 | 
			
		||||
    } else {
 | 
			
		||||
      ElMessage.error('请填写完整信息');
 | 
			
		||||
    }
 | 
			
		||||
      catch (error) {}
 | 
			
		||||
      queryAllconfig()
 | 
			
		||||
    }
 | 
			
		||||
    else {
 | 
			
		||||
      ElMessage.error('请填写完整信息')
 | 
			
		||||
    }
 | 
			
		||||
  })
 | 
			
		||||
  });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function fotmatSetting(settings: any) {
 | 
			
		||||
@@ -51,21 +61,27 @@ function fotmatSetting(settings: any) {
 | 
			
		||||
    return {
 | 
			
		||||
      configKey: key,
 | 
			
		||||
      configVal: settings[key],
 | 
			
		||||
    }
 | 
			
		||||
  })
 | 
			
		||||
    };
 | 
			
		||||
  });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
onMounted(() => {
 | 
			
		||||
  queryAllconfig()
 | 
			
		||||
})
 | 
			
		||||
  queryAllconfig();
 | 
			
		||||
});
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<template>
 | 
			
		||||
  <div>
 | 
			
		||||
    <page-main>
 | 
			
		||||
      <el-alert :closable="false" show-icon title="MJ参数说明" description="如果您是海外服务器则不强制开启代理、反之则需要开启代理、代理为系统配套项目、非常规代理、如果您想自己搭建代理请查看教程、如果您想使用系统提供的默认代理、那么选择开启代理并且不填写代理地址即可使用默认地址、如果想获取默认地址请在售后群获取地址!" type="success" />
 | 
			
		||||
      <el-alert
 | 
			
		||||
        :closable="false"
 | 
			
		||||
        show-icon
 | 
			
		||||
        title="MJ参数说明"
 | 
			
		||||
        description="如果您是海外服务器则不强制开启代理、反之则需要开启代理、代理为系统配套项目、非常规代理、如果您想自己搭建代理请查看教程、如果您想使用系统提供的默认代理、那么选择开启代理并且不填写代理地址即可使用默认地址、如果想获取默认地址请在售后群获取地址!"
 | 
			
		||||
        type="success"
 | 
			
		||||
      />
 | 
			
		||||
    </page-main>
 | 
			
		||||
    <el-card style="margin: 20px;">
 | 
			
		||||
    <el-card style="margin: 20px">
 | 
			
		||||
      <template #header>
 | 
			
		||||
        <div class="flex justify-between">
 | 
			
		||||
          <b>MJ参数设置</b>
 | 
			
		||||
@@ -74,9 +90,14 @@ onMounted(() => {
 | 
			
		||||
          </el-button>
 | 
			
		||||
        </div>
 | 
			
		||||
      </template>
 | 
			
		||||
      <el-form ref="formRef" :rules="rules" :model="formInline" label-width="150px">
 | 
			
		||||
      <el-form
 | 
			
		||||
        ref="formRef"
 | 
			
		||||
        :rules="rules"
 | 
			
		||||
        :model="formInline"
 | 
			
		||||
        label-width="150px"
 | 
			
		||||
      >
 | 
			
		||||
        <h4>绘图代理设置</h4>
 | 
			
		||||
        <el-row>
 | 
			
		||||
        <!-- <el-row>
 | 
			
		||||
          <el-col :xs="24" :md="20" :lg="15" :xl="12">
 | 
			
		||||
            <el-form-item label="是否开启代理" prop="mjProxy">
 | 
			
		||||
              <el-switch
 | 
			
		||||
@@ -86,107 +107,143 @@ onMounted(() => {
 | 
			
		||||
              />
 | 
			
		||||
            </el-form-item>
 | 
			
		||||
          </el-col>
 | 
			
		||||
        </el-row>
 | 
			
		||||
        </el-row> -->
 | 
			
		||||
        <el-row>
 | 
			
		||||
          <el-col :xs="24" :md="20" :lg="15" :xl="12">
 | 
			
		||||
            <el-form-item label="请填写代理地址" prop="mjProxyUrl" label-width="150">
 | 
			
		||||
              <el-input v-model="formInline.mjProxyUrl" placeholder="请填写代理地址、详细使用请访问教程!" clearable />
 | 
			
		||||
            <el-form-item
 | 
			
		||||
              label="请填写代理地址"
 | 
			
		||||
              prop="mjProxyUrl"
 | 
			
		||||
              label-width="150"
 | 
			
		||||
            >
 | 
			
		||||
              <el-input
 | 
			
		||||
                v-model="formInline.mjProxyUrl"
 | 
			
		||||
                placeholder="请填写代理地址、详细使用请访问教程!"
 | 
			
		||||
                clearable
 | 
			
		||||
              />
 | 
			
		||||
            </el-form-item>
 | 
			
		||||
          </el-col>
 | 
			
		||||
        </el-row>
 | 
			
		||||
        <el-row>
 | 
			
		||||
          <el-col :xs="24" :md="20" :lg="15" :xl="12">
 | 
			
		||||
            <el-form-item label="绘画超时时间设置ms" prop="mjTimeoutMs" label-width="150">
 | 
			
		||||
              <el-input v-model="formInline.mjTimeoutMs" placeholder="请设置绘画超时时间、单位为ms、根据慢速快速定义(后续优化逻辑)" clearable />
 | 
			
		||||
            <el-form-item label="MJ 绘图 Key" prop="mjKey" label-width="150">
 | 
			
		||||
              <el-input
 | 
			
		||||
                v-model="formInline.mjKey"
 | 
			
		||||
                placeholder="请填 MJ 绘图使用的 Key"
 | 
			
		||||
                clearable
 | 
			
		||||
              />
 | 
			
		||||
            </el-form-item>
 | 
			
		||||
          </el-col>
 | 
			
		||||
        </el-row>
 | 
			
		||||
        <el-divider />
 | 
			
		||||
        <h4>绘图并发设置</h4>
 | 
			
		||||
        <el-row>
 | 
			
		||||
          <el-col :xs="24" :md="20" :lg="15" :xl="12">
 | 
			
		||||
            <el-form-item label="单人绘图并发限制" prop="mjLimitCount" label-width="150">
 | 
			
		||||
              <el-input v-model="formInline.mjLimitCount" placeholder="单人同时绘制限制数量、同一时间最多可以绘制几张!" clearable />
 | 
			
		||||
            <el-form-item
 | 
			
		||||
              label="绘画超时时间设置ms"
 | 
			
		||||
              prop="mjTimeoutMs"
 | 
			
		||||
              label-width="150"
 | 
			
		||||
            >
 | 
			
		||||
              <el-input
 | 
			
		||||
                v-model="formInline.mjTimeoutMs"
 | 
			
		||||
                placeholder="请设置绘画超时时间、单位为ms、根据慢速快速定义(后续优化逻辑)"
 | 
			
		||||
                clearable
 | 
			
		||||
              />
 | 
			
		||||
            </el-form-item>
 | 
			
		||||
          </el-col>
 | 
			
		||||
        </el-row>
 | 
			
		||||
        <el-row>
 | 
			
		||||
          <el-col :xs="24" :md="20" :lg="15" :xl="12">
 | 
			
		||||
            <el-form-item
 | 
			
		||||
              label="单人绘图并发限制"
 | 
			
		||||
              prop="mjLimitCount"
 | 
			
		||||
              label-width="150"
 | 
			
		||||
            >
 | 
			
		||||
              <el-input
 | 
			
		||||
                v-model="formInline.mjLimitCount"
 | 
			
		||||
                placeholder="单人同时绘制限制数量、同一时间最多可以绘制几张!"
 | 
			
		||||
                clearable
 | 
			
		||||
              />
 | 
			
		||||
            </el-form-item>
 | 
			
		||||
          </el-col>
 | 
			
		||||
        </el-row>
 | 
			
		||||
        <el-divider />
 | 
			
		||||
        <h4>绘图可选设置</h4>
 | 
			
		||||
 | 
			
		||||
        <el-row>
 | 
			
		||||
        <!-- <el-row>
 | 
			
		||||
          <el-col :xs="24" :md="20" :lg="15" :xl="12">
 | 
			
		||||
            <el-form-item label="描述词使用百度翻译" prop="mjUseBaiduFy" label-width="150" >
 | 
			
		||||
            <el-form-item
 | 
			
		||||
              label="描述词使用百度翻译"
 | 
			
		||||
              prop="mjUseBaiduFy"
 | 
			
		||||
              label-width="150"
 | 
			
		||||
            >
 | 
			
		||||
              <el-switch
 | 
			
		||||
                v-model="formInline.mjUseBaiduFy"
 | 
			
		||||
                active-value="1"
 | 
			
		||||
                inactive-value="0"
 | 
			
		||||
              />
 | 
			
		||||
              <el-tooltip
 | 
			
		||||
                class="box-item"
 | 
			
		||||
                effect="dark"
 | 
			
		||||
                placement="right"
 | 
			
		||||
              >
 | 
			
		||||
              <el-tooltip class="box-item" effect="dark" placement="right">
 | 
			
		||||
                <template #content>
 | 
			
		||||
                  <div style="width: 250px;">
 | 
			
		||||
                  <div style="width: 250px">
 | 
			
		||||
                    mj描述词的翻译默认设置为AI翻译、如果您想使用百度翻译请打开此选项并且在下面的百度翻译中配置上所需参数。
 | 
			
		||||
                  </div>
 | 
			
		||||
                </template>
 | 
			
		||||
                <el-icon class="ml-3 cursor-pointer"><QuestionFilled /></el-icon>
 | 
			
		||||
                <el-icon class="ml-3 cursor-pointer"
 | 
			
		||||
                  ><QuestionFilled
 | 
			
		||||
                /></el-icon>
 | 
			
		||||
              </el-tooltip>
 | 
			
		||||
            </el-form-item>
 | 
			
		||||
          </el-col>
 | 
			
		||||
        </el-row>
 | 
			
		||||
        </el-row> -->
 | 
			
		||||
 | 
			
		||||
        <el-row>
 | 
			
		||||
          <el-col :xs="24" :md="20" :lg="15" :xl="12">
 | 
			
		||||
              <el-form-item label="隐藏不需要元素模块" prop="mjHideNotBlock" label-width="150" >
 | 
			
		||||
            <el-form-item
 | 
			
		||||
              label="隐藏不需要元素模块"
 | 
			
		||||
              prop="mjHideNotBlock"
 | 
			
		||||
              label-width="150"
 | 
			
		||||
            >
 | 
			
		||||
              <el-switch
 | 
			
		||||
                v-model="formInline.mjHideNotBlock"
 | 
			
		||||
                active-value="1"
 | 
			
		||||
                inactive-value="0"
 | 
			
		||||
              />
 | 
			
		||||
                <el-tooltip
 | 
			
		||||
                  class="box-item"
 | 
			
		||||
                  effect="dark"
 | 
			
		||||
                  placement="right"
 | 
			
		||||
                >
 | 
			
		||||
              <el-tooltip class="box-item" effect="dark" placement="right">
 | 
			
		||||
                <template #content>
 | 
			
		||||
                    <div style="width: 250px;">
 | 
			
		||||
                  <div style="width: 250px">
 | 
			
		||||
                    隐藏客户端绘图页面的不需要的元素模块、隐藏后用户不可选择无法选中模块
 | 
			
		||||
                  </div>
 | 
			
		||||
                </template>
 | 
			
		||||
                  <el-icon class="ml-3 cursor-pointer"><QuestionFilled /></el-icon>
 | 
			
		||||
                <el-icon class="ml-3 cursor-pointer"
 | 
			
		||||
                  ><QuestionFilled
 | 
			
		||||
                /></el-icon>
 | 
			
		||||
              </el-tooltip>
 | 
			
		||||
            </el-form-item>
 | 
			
		||||
          </el-col>
 | 
			
		||||
        </el-row>
 | 
			
		||||
        <el-row>
 | 
			
		||||
 | 
			
		||||
          <el-col :xs="24" :md="20" :lg="15" :xl="12">
 | 
			
		||||
            <el-form-item label="隐藏工作中内容模块" prop="mjHideWorkIn" label-width="150" >
 | 
			
		||||
            <el-form-item
 | 
			
		||||
              label="隐藏工作中内容模块"
 | 
			
		||||
              prop="mjHideWorkIn"
 | 
			
		||||
              label-width="150"
 | 
			
		||||
            >
 | 
			
		||||
              <el-switch
 | 
			
		||||
                v-model="formInline.mjHideWorkIn"
 | 
			
		||||
                active-value="1"
 | 
			
		||||
                inactive-value="0"
 | 
			
		||||
              />
 | 
			
		||||
              <el-tooltip
 | 
			
		||||
                class="box-item"
 | 
			
		||||
                effect="dark"
 | 
			
		||||
                placement="right"
 | 
			
		||||
              >
 | 
			
		||||
              <el-tooltip class="box-item" effect="dark" placement="right">
 | 
			
		||||
                <template #content>
 | 
			
		||||
                  <div style="width: 250px;">
 | 
			
		||||
                  <div style="width: 250px">
 | 
			
		||||
                    客户端绘图页面隐藏掉工作中模块、将不再展示给用户此模块。
 | 
			
		||||
                  </div>
 | 
			
		||||
                </template>
 | 
			
		||||
                <el-icon class="ml-3 cursor-pointer"><QuestionFilled /></el-icon>
 | 
			
		||||
                <el-icon class="ml-3 cursor-pointer"
 | 
			
		||||
                  ><QuestionFilled
 | 
			
		||||
                /></el-icon>
 | 
			
		||||
              </el-tooltip>
 | 
			
		||||
            </el-form-item>
 | 
			
		||||
          </el-col>
 | 
			
		||||
        </el-row>
 | 
			
		||||
 | 
			
		||||
        <el-divider />
 | 
			
		||||
        <h4>图片存储设置</h4>
 | 
			
		||||
        <el-row>
 | 
			
		||||
          <el-col :xs="24" :md="20" :lg="15" :xl="12">
 | 
			
		||||
            <el-form-item label="不存储图片" prop="mjNotSaveImg">
 | 
			
		||||
@@ -195,28 +252,35 @@ onMounted(() => {
 | 
			
		||||
                active-value="1"
 | 
			
		||||
                inactive-value="0"
 | 
			
		||||
              />
 | 
			
		||||
              <el-tooltip
 | 
			
		||||
                class="box-item"
 | 
			
		||||
                effect="dark"
 | 
			
		||||
                placement="right"
 | 
			
		||||
              >
 | 
			
		||||
              <el-tooltip class="box-item" effect="dark" placement="right">
 | 
			
		||||
                <template #content>
 | 
			
		||||
                  <div style="width: 250px;">
 | 
			
		||||
                  <div style="width: 250px">
 | 
			
		||||
                    默认会存储图片到配置的存储中、如果开启此选择则表示不保存原图到我们配置的存储上、那么则必须配置一个图片反代地址、直接反代访问原始图片、这样可以进一步节省空间、需要您部署mj-proxy项目并填写基础地址即可!
 | 
			
		||||
                  </div>
 | 
			
		||||
                </template>
 | 
			
		||||
                <el-icon class="ml-3 cursor-pointer"><QuestionFilled /></el-icon>
 | 
			
		||||
                <el-icon class="ml-3 cursor-pointer"
 | 
			
		||||
                  ><QuestionFilled
 | 
			
		||||
                /></el-icon>
 | 
			
		||||
              </el-tooltip>
 | 
			
		||||
            </el-form-item>
 | 
			
		||||
          </el-col>
 | 
			
		||||
        </el-row>
 | 
			
		||||
        <el-row>
 | 
			
		||||
        <!-- <el-row>
 | 
			
		||||
          <el-col :xs="24" :md="20" :lg="15" :xl="12">
 | 
			
		||||
            <el-form-item label="图片反代地址" prop="mjProxyImgUrl" label-width="150">
 | 
			
		||||
              <el-input v-model="formInline.mjProxyImgUrl" placeholder="图片反代地址、用于代理访问图片、此项目请自行部署mj-proxy项目配置其中的地址即可!" clearable  style="width: 100%;" />
 | 
			
		||||
            <el-form-item
 | 
			
		||||
              label="图片反代地址"
 | 
			
		||||
              prop="mjProxyImgUrl"
 | 
			
		||||
              label-width="150"
 | 
			
		||||
            >
 | 
			
		||||
              <el-input
 | 
			
		||||
                v-model="formInline.mjProxyImgUrl"
 | 
			
		||||
                placeholder="图片反代地址、用于代理访问图片、此项目请自行部署mj-proxy项目配置其中的地址即可!"
 | 
			
		||||
                clearable
 | 
			
		||||
                style="width: 100%"
 | 
			
		||||
              />
 | 
			
		||||
            </el-form-item>
 | 
			
		||||
          </el-col>
 | 
			
		||||
        </el-row>
 | 
			
		||||
        </el-row> -->
 | 
			
		||||
      </el-form>
 | 
			
		||||
    </el-card>
 | 
			
		||||
  </div>
 | 
			
		||||
 
 | 
			
		||||
@@ -10,13 +10,13 @@ import ApiModels from '@/api/modules/models'
 | 
			
		||||
import { utcToShanghaiTime } from '@/utils/utcformatTime'
 | 
			
		||||
 | 
			
		||||
import {
 | 
			
		||||
  DEDUCTTYPELIST,
 | 
			
		||||
  MODELSMAP,
 | 
			
		||||
  MODELSMAPLIST,
 | 
			
		||||
  MODELTYPELIST,
 | 
			
		||||
  MODEL_LIST,
 | 
			
		||||
  ModelTypeLabelMap,
 | 
			
		||||
  QUESTION_STATUS_OPTIONS,
 | 
			
		||||
  MODELTYPELIST,
 | 
			
		||||
  MODELSMAP,
 | 
			
		||||
  DEDUCTTYPELIST,
 | 
			
		||||
  MODELSMAPLIST,
 | 
			
		||||
} from '@/constants/index'
 | 
			
		||||
 | 
			
		||||
const formBlukRef = ref<FormInstance>()
 | 
			
		||||
@@ -45,16 +45,17 @@ const formPackage = reactive({
 | 
			
		||||
  status: true,
 | 
			
		||||
  model: '',
 | 
			
		||||
  isDraw: false,
 | 
			
		||||
  isTokenBased: false,
 | 
			
		||||
  tokenFeeRatio: 1000,
 | 
			
		||||
  keyWeight: 1,
 | 
			
		||||
  maxModelTokens: 4096,
 | 
			
		||||
  modelOrder: 1,
 | 
			
		||||
  maxModelTokens: 4000,
 | 
			
		||||
  maxResponseTokens: 2000,
 | 
			
		||||
  proxyUrl: '',
 | 
			
		||||
  timeout: 300,
 | 
			
		||||
  deduct: 1,
 | 
			
		||||
  deductType: 1,
 | 
			
		||||
  maxRounds: 12,
 | 
			
		||||
  isTokenBased: false,
 | 
			
		||||
  tokenFeeRatio: 1000,
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
const rules = reactive<FormRules>({
 | 
			
		||||
@@ -164,8 +165,7 @@ async function queryModelsList() {
 | 
			
		||||
    const { rows, count } = res.data
 | 
			
		||||
    total.value = count
 | 
			
		||||
    tableData.value = rows
 | 
			
		||||
  }
 | 
			
		||||
  catch (error) {
 | 
			
		||||
  } catch (error) {
 | 
			
		||||
    loading.value = false
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@@ -187,6 +187,7 @@ function handleEditKey(row: any) {
 | 
			
		||||
    status,
 | 
			
		||||
    model,
 | 
			
		||||
    keyWeight,
 | 
			
		||||
    modelOrder,
 | 
			
		||||
    maxModelTokens,
 | 
			
		||||
    maxResponseTokens,
 | 
			
		||||
    proxyUrl,
 | 
			
		||||
@@ -196,7 +197,7 @@ function handleEditKey(row: any) {
 | 
			
		||||
    maxRounds,
 | 
			
		||||
    isDraw,
 | 
			
		||||
    isTokenBased,
 | 
			
		||||
    tokenFeeRatio
 | 
			
		||||
    tokenFeeRatio,
 | 
			
		||||
  } = row
 | 
			
		||||
  nextTick(() => {
 | 
			
		||||
    Object.assign(formPackage, {
 | 
			
		||||
@@ -216,7 +217,8 @@ function handleEditKey(row: any) {
 | 
			
		||||
      maxRounds,
 | 
			
		||||
      isDraw,
 | 
			
		||||
      isTokenBased,
 | 
			
		||||
      tokenFeeRatio
 | 
			
		||||
      tokenFeeRatio,
 | 
			
		||||
      modelOrder,
 | 
			
		||||
    })
 | 
			
		||||
  })
 | 
			
		||||
  visible.value = true
 | 
			
		||||
@@ -254,7 +256,7 @@ onMounted(() => {
 | 
			
		||||
 | 
			
		||||
<template>
 | 
			
		||||
  <div>
 | 
			
		||||
    <page-main>
 | 
			
		||||
    <page-main style="padding-bottom:0">
 | 
			
		||||
      <el-form ref="formRef" :inline="true" :model="formInline">
 | 
			
		||||
        <el-form-item label="模型类型" prop="model">
 | 
			
		||||
          <el-select
 | 
			
		||||
@@ -303,21 +305,9 @@ onMounted(() => {
 | 
			
		||||
          </el-select>
 | 
			
		||||
        </el-form-item>
 | 
			
		||||
        <el-form-item>
 | 
			
		||||
          <el-button type="primary" @click="queryModelsList">
 | 
			
		||||
            查询
 | 
			
		||||
          </el-button>
 | 
			
		||||
          <el-button @click="handlerReset(formRef)">
 | 
			
		||||
            重置
 | 
			
		||||
          </el-button>
 | 
			
		||||
          <el-button type="primary" @click="queryModelsList"> 查询 </el-button>
 | 
			
		||||
          <el-button @click="handlerReset(formRef)"> 重置 </el-button>
 | 
			
		||||
        </el-form-item>
 | 
			
		||||
        <span style="float: right">
 | 
			
		||||
          <el-button type="success" @click="visible = true">
 | 
			
		||||
            添加模型Key
 | 
			
		||||
            <el-icon class="ml-3">
 | 
			
		||||
              <Plus />
 | 
			
		||||
            </el-icon>
 | 
			
		||||
          </el-button>
 | 
			
		||||
        </span>
 | 
			
		||||
      </el-form>
 | 
			
		||||
    </page-main>
 | 
			
		||||
    <page-main>
 | 
			
		||||
@@ -329,6 +319,12 @@ onMounted(() => {
 | 
			
		||||
      />
 | 
			
		||||
    </page-main>
 | 
			
		||||
    <page-main style="width: 100%">
 | 
			
		||||
      <el-button type="success" @click="visible = true" style="margin-bottom:20px">
 | 
			
		||||
        添加模型Key
 | 
			
		||||
        <el-icon class="ml-3">
 | 
			
		||||
          <Plus />
 | 
			
		||||
        </el-icon>
 | 
			
		||||
      </el-button>
 | 
			
		||||
      <el-table
 | 
			
		||||
        v-loading="loading"
 | 
			
		||||
        border
 | 
			
		||||
@@ -336,13 +332,19 @@ onMounted(() => {
 | 
			
		||||
        style="width: 100%"
 | 
			
		||||
        size="large"
 | 
			
		||||
      >
 | 
			
		||||
        <el-table-column prop="keyType" label="模型类型" width="120">
 | 
			
		||||
        <!-- <el-table-column prop="keyType" label="模型类型" width="120">
 | 
			
		||||
          <template #default="scope">
 | 
			
		||||
            <el-tag type="success">
 | 
			
		||||
              {{ MODELSMAP[scope.row.keyType] }}
 | 
			
		||||
            </el-tag>
 | 
			
		||||
          </template>
 | 
			
		||||
        </el-table-column>
 | 
			
		||||
        </el-table-column> -->
 | 
			
		||||
        <el-table-column
 | 
			
		||||
          prop="modelOrder"
 | 
			
		||||
          label="模型排序"
 | 
			
		||||
          width="90"
 | 
			
		||||
          align="center"
 | 
			
		||||
        />
 | 
			
		||||
        <el-table-column prop="modelName" label="模型名称" width="180" />
 | 
			
		||||
        <el-table-column
 | 
			
		||||
          prop="status"
 | 
			
		||||
@@ -386,11 +388,10 @@ onMounted(() => {
 | 
			
		||||
            </el-tag>
 | 
			
		||||
          </template>
 | 
			
		||||
        </el-table-column>
 | 
			
		||||
 | 
			
		||||
        <el-table-column
 | 
			
		||||
          prop="isDraw"
 | 
			
		||||
          align="center"
 | 
			
		||||
          label="是否绘画KEY"
 | 
			
		||||
          label="绘画KEY"
 | 
			
		||||
          width="120"
 | 
			
		||||
        >
 | 
			
		||||
          <template #default="scope">
 | 
			
		||||
@@ -402,7 +403,7 @@ onMounted(() => {
 | 
			
		||||
        <el-table-column
 | 
			
		||||
          prop="isTokenBased"
 | 
			
		||||
          align="center"
 | 
			
		||||
          label="设为Token计费"
 | 
			
		||||
          label="Token计费"
 | 
			
		||||
          width="120"
 | 
			
		||||
        >
 | 
			
		||||
          <template #default="scope">
 | 
			
		||||
@@ -419,7 +420,7 @@ onMounted(() => {
 | 
			
		||||
        >
 | 
			
		||||
          <template #default="scope">
 | 
			
		||||
            <el-tag :type="scope.row.deductType === 1 ? 'success' : 'warning'">
 | 
			
		||||
              {{ scope.row.deductType === 1 ? '普通余额' : '高级余额' }}
 | 
			
		||||
              {{ scope.row.deductType === 1 ? '普通积分' : '高级积分' }}
 | 
			
		||||
            </el-tag>
 | 
			
		||||
          </template>
 | 
			
		||||
        </el-table-column>
 | 
			
		||||
@@ -431,7 +432,7 @@ onMounted(() => {
 | 
			
		||||
        >
 | 
			
		||||
          <template #default="scope">
 | 
			
		||||
            <el-tag :type="scope.row.deductType === 1 ? 'success' : 'warning'">
 | 
			
		||||
              {{ `${scope.row.deduct} 余额` }}
 | 
			
		||||
              {{ `${scope.row.deduct} 积分` }}
 | 
			
		||||
            </el-tag>
 | 
			
		||||
          </template>
 | 
			
		||||
        </el-table-column>
 | 
			
		||||
@@ -575,7 +576,7 @@ onMounted(() => {
 | 
			
		||||
        :model="formPackage"
 | 
			
		||||
        :rules="rules"
 | 
			
		||||
      >
 | 
			
		||||
        <el-form-item label="模型类型选择" prop="keyType">
 | 
			
		||||
        <!-- <el-form-item label="模型类型选择" prop="keyType">
 | 
			
		||||
          <el-select
 | 
			
		||||
            v-model="formPackage.keyType"
 | 
			
		||||
            placeholder="请选择模型类型"
 | 
			
		||||
@@ -588,6 +589,27 @@ onMounted(() => {
 | 
			
		||||
              :value="item.value"
 | 
			
		||||
            />
 | 
			
		||||
          </el-select>
 | 
			
		||||
        </el-form-item> -->
 | 
			
		||||
        <el-form-item label="模型中文名称" prop="modelName">
 | 
			
		||||
          <el-input
 | 
			
		||||
            v-model="formPackage.modelName"
 | 
			
		||||
            placeholder="请填写模型中文名称(用户选择的)"
 | 
			
		||||
          />
 | 
			
		||||
        </el-form-item>
 | 
			
		||||
        <el-form-item label="模型排序" prop="modelOrder">
 | 
			
		||||
          <el-input
 | 
			
		||||
            v-model.number="formPackage.modelOrder"
 | 
			
		||||
            placeholder="模型排序,越大越靠前。"
 | 
			
		||||
          />
 | 
			
		||||
          <!-- <el-tooltip class="box-item" effect="dark" placement="right">
 | 
			
		||||
            <template #content>
 | 
			
		||||
              <div style="width: 250px">
 | 
			
		||||
                填写此配置可以限制用户在选择模型时候的高级配置中的最大上下文轮次、可以通过限制此数量减少token的损耗、减低上下文的损耗量、
 | 
			
		||||
                如果设置了模型的最大token和返回量、那么两个限制会同时生效!
 | 
			
		||||
              </div>
 | 
			
		||||
            </template>
 | 
			
		||||
            <el-icon class="ml-3 cursor-pointer"><QuestionFilled /></el-icon>
 | 
			
		||||
          </el-tooltip> -->
 | 
			
		||||
        </el-form-item>
 | 
			
		||||
        <el-form-item label="模型启用状态" prop="status">
 | 
			
		||||
          <el-switch v-model="formPackage.status" />
 | 
			
		||||
@@ -597,23 +619,16 @@ onMounted(() => {
 | 
			
		||||
                账号启用状态、一旦锁定当前key将停止工作!
 | 
			
		||||
              </div>
 | 
			
		||||
            </template>
 | 
			
		||||
            <el-icon class="ml-3 cursor-pointer">
 | 
			
		||||
              <QuestionFilled />
 | 
			
		||||
            </el-icon>
 | 
			
		||||
            <el-icon class="ml-3 cursor-pointer"><QuestionFilled /></el-icon>
 | 
			
		||||
          </el-tooltip>
 | 
			
		||||
        </el-form-item>
 | 
			
		||||
        <el-form-item label="模型中文名称" prop="modelName">
 | 
			
		||||
          <el-input
 | 
			
		||||
            v-model="formPackage.modelName"
 | 
			
		||||
            placeholder="请填写模型中文名称(用户选择的)"
 | 
			
		||||
          />
 | 
			
		||||
        </el-form-item>
 | 
			
		||||
 | 
			
		||||
        <el-form-item :label="labelKeyName" prop="key">
 | 
			
		||||
          <el-input
 | 
			
		||||
            v-model="formPackage.key"
 | 
			
		||||
            :type="Number(formPackage.keyType) === 1 ? 'textarea' : 'text'"
 | 
			
		||||
            :rows="5"
 | 
			
		||||
            placeholder="请填写模型Key|clientId|AppId"
 | 
			
		||||
            placeholder="请填写模型Key"
 | 
			
		||||
            style="width: 95%"
 | 
			
		||||
          />
 | 
			
		||||
          <el-tooltip class="box-item" effect="dark" placement="right">
 | 
			
		||||
@@ -622,15 +637,13 @@ onMounted(() => {
 | 
			
		||||
                不同模型的设置不同、例如openai仅设置key即可、如果是百度大模型、则填写clientId以及同时需要填写secret。对于OPENAI模型、我们支持批量导入、如果您需要批量导入key、则一行一个key即可、多个key使用换行隔离、其余配置将共享、多个key可以重复选用默认模型。
 | 
			
		||||
              </div>
 | 
			
		||||
            </template>
 | 
			
		||||
            <el-icon class="ml-3 cursor-pointer">
 | 
			
		||||
              <QuestionFilled />
 | 
			
		||||
            </el-icon>
 | 
			
		||||
            <el-icon class="ml-3 cursor-pointer"><QuestionFilled /></el-icon>
 | 
			
		||||
          </el-tooltip>
 | 
			
		||||
        </el-form-item>
 | 
			
		||||
        <el-form-item
 | 
			
		||||
          v-if="[2].includes(Number(formPackage.keyType))"
 | 
			
		||||
          label="SecretKey"
 | 
			
		||||
          prop="secret"
 | 
			
		||||
          v-if="[2].includes(Number(formPackage.keyType))"
 | 
			
		||||
        >
 | 
			
		||||
          <el-input
 | 
			
		||||
            v-model="formPackage.secret"
 | 
			
		||||
@@ -643,9 +656,7 @@ onMounted(() => {
 | 
			
		||||
                不同账号填写的内容不同、但是都代表的是Secret秘钥
 | 
			
		||||
              </div>
 | 
			
		||||
            </template>
 | 
			
		||||
            <el-icon class="ml-3 cursor-pointer">
 | 
			
		||||
              <QuestionFilled />
 | 
			
		||||
            </el-icon>
 | 
			
		||||
            <el-icon class="ml-3 cursor-pointer"><QuestionFilled /></el-icon>
 | 
			
		||||
          </el-tooltip>
 | 
			
		||||
        </el-form-item>
 | 
			
		||||
        <el-form-item label="账号关联模型" prop="model">
 | 
			
		||||
@@ -670,9 +681,7 @@ onMounted(() => {
 | 
			
		||||
                给定了部分可选的模型列表、你可以可以手动填写您需要调用的模型、请确保填写的模型是当前key支持的类型、否则可能会在调用中出现不可预知错误!
 | 
			
		||||
              </div>
 | 
			
		||||
            </template>
 | 
			
		||||
            <el-icon class="ml-3 cursor-pointer">
 | 
			
		||||
              <QuestionFilled />
 | 
			
		||||
            </el-icon>
 | 
			
		||||
            <el-icon class="ml-3 cursor-pointer"><QuestionFilled /></el-icon>
 | 
			
		||||
          </el-tooltip>
 | 
			
		||||
        </el-form-item>
 | 
			
		||||
        <el-form-item label="模型扣费类型" prop="deductType">
 | 
			
		||||
@@ -694,12 +703,10 @@ onMounted(() => {
 | 
			
		||||
          <el-tooltip class="box-item" effect="dark" placement="right">
 | 
			
		||||
            <template #content>
 | 
			
		||||
              <div style="width: 250px">
 | 
			
		||||
                设置当前key的扣费类型、扣除普通余额或是高级余额。
 | 
			
		||||
                设置当前key的扣费类型、扣除普通积分或是高级积分。
 | 
			
		||||
              </div>
 | 
			
		||||
            </template>
 | 
			
		||||
            <el-icon class="ml-3 cursor-pointer">
 | 
			
		||||
              <QuestionFilled />
 | 
			
		||||
            </el-icon>
 | 
			
		||||
            <el-icon class="ml-3 cursor-pointer"><QuestionFilled /></el-icon>
 | 
			
		||||
          </el-tooltip>
 | 
			
		||||
        </el-form-item>
 | 
			
		||||
        <el-form-item label="单次扣除金额" prop="deduct">
 | 
			
		||||
@@ -711,12 +718,10 @@ onMounted(() => {
 | 
			
		||||
          <el-tooltip class="box-item" effect="dark" placement="right">
 | 
			
		||||
            <template #content>
 | 
			
		||||
              <div style="width: 250px">
 | 
			
		||||
                设置当前key的单次调用扣除余额、建议同模型或名称key设置相同的金额、避免扣费发生异常!
 | 
			
		||||
                设置当前key的单次调用扣除积分、建议同模型或名称key设置相同的金额、避免扣费发生异常!
 | 
			
		||||
              </div>
 | 
			
		||||
            </template>
 | 
			
		||||
            <el-icon class="ml-3 cursor-pointer">
 | 
			
		||||
              <QuestionFilled />
 | 
			
		||||
            </el-icon>
 | 
			
		||||
            <el-icon class="ml-3 cursor-pointer"><QuestionFilled /></el-icon>
 | 
			
		||||
          </el-tooltip>
 | 
			
		||||
        </el-form-item>
 | 
			
		||||
        <el-form-item label="上下文限制" prop="maxRounds">
 | 
			
		||||
@@ -732,9 +737,7 @@ onMounted(() => {
 | 
			
		||||
                如果设置了模型的最大token和返回量、那么两个限制会同时生效!
 | 
			
		||||
              </div>
 | 
			
		||||
            </template>
 | 
			
		||||
            <el-icon class="ml-3 cursor-pointer">
 | 
			
		||||
              <QuestionFilled />
 | 
			
		||||
            </el-icon>
 | 
			
		||||
            <el-icon class="ml-3 cursor-pointer"><QuestionFilled /></el-icon>
 | 
			
		||||
          </el-tooltip>
 | 
			
		||||
        </el-form-item>
 | 
			
		||||
        <el-form-item label="调用轮询权重" prop="keyWeight">
 | 
			
		||||
@@ -750,9 +753,7 @@ onMounted(() => {
 | 
			
		||||
                保证每个key的调用顺序以及限制每次调用的准确次数
 | 
			
		||||
              </div>
 | 
			
		||||
            </template>
 | 
			
		||||
            <el-icon class="ml-3 cursor-pointer">
 | 
			
		||||
              <QuestionFilled />
 | 
			
		||||
            </el-icon>
 | 
			
		||||
            <el-icon class="ml-3 cursor-pointer"><QuestionFilled /></el-icon>
 | 
			
		||||
          </el-tooltip>
 | 
			
		||||
        </el-form-item>
 | 
			
		||||
        <el-form-item label="模型最大Token" prop="maxModelTokens">
 | 
			
		||||
@@ -768,9 +769,9 @@ onMounted(() => {
 | 
			
		||||
          />
 | 
			
		||||
        </el-form-item>
 | 
			
		||||
        <el-form-item
 | 
			
		||||
          v-if="[1].includes(Number(formPackage.keyType))"
 | 
			
		||||
          label="调用超时时间"
 | 
			
		||||
          prop="timeout"
 | 
			
		||||
          v-if="[1].includes(Number(formPackage.keyType))"
 | 
			
		||||
        >
 | 
			
		||||
          <el-input
 | 
			
		||||
            v-model.number="formPackage.timeout"
 | 
			
		||||
@@ -778,9 +779,9 @@ onMounted(() => {
 | 
			
		||||
          />
 | 
			
		||||
        </el-form-item>
 | 
			
		||||
        <el-form-item
 | 
			
		||||
          v-if="[1].includes(Number(formPackage.keyType))"
 | 
			
		||||
          label="设为特殊key"
 | 
			
		||||
          prop="isDraw"
 | 
			
		||||
          v-if="[1].includes(Number(formPackage.keyType))"
 | 
			
		||||
        >
 | 
			
		||||
          <el-switch v-model="formPackage.isDraw" />
 | 
			
		||||
          <el-tooltip class="box-item" effect="dark" placement="right">
 | 
			
		||||
@@ -789,15 +790,13 @@ onMounted(() => {
 | 
			
		||||
                基础绘画来自于OPENAI的DALL-E模型、所以需要为官方的apiKey、请确定至少设置一张key为基础绘画key即可使用绘画功能!同时当前版本的mind思维导图和mj联想绘图等功能都会走当前设置的key,会后后续版本解除此限制!
 | 
			
		||||
              </div>
 | 
			
		||||
            </template>
 | 
			
		||||
            <el-icon class="ml-3 cursor-pointer">
 | 
			
		||||
              <QuestionFilled />
 | 
			
		||||
            </el-icon>
 | 
			
		||||
            <el-icon class="ml-3 cursor-pointer"><QuestionFilled /></el-icon>
 | 
			
		||||
          </el-tooltip>
 | 
			
		||||
        </el-form-item>
 | 
			
		||||
        <el-form-item
 | 
			
		||||
          v-if="[1].includes(Number(formPackage.keyType))"
 | 
			
		||||
          label="设为token计费"
 | 
			
		||||
          prop="isTokenBased"
 | 
			
		||||
          v-if="[1].includes(Number(formPackage.keyType))"
 | 
			
		||||
        >
 | 
			
		||||
          <el-switch v-model="formPackage.isTokenBased" />
 | 
			
		||||
          <el-tooltip class="box-item" effect="dark" placement="right">
 | 
			
		||||
@@ -806,9 +805,7 @@ onMounted(() => {
 | 
			
		||||
                基于 token 计费,计费方式为(基础消费 * token消耗)
 | 
			
		||||
              </div>
 | 
			
		||||
            </template>
 | 
			
		||||
            <el-icon class="ml-3 cursor-pointer">
 | 
			
		||||
              <QuestionFilled />
 | 
			
		||||
            </el-icon>
 | 
			
		||||
            <el-icon class="ml-3 cursor-pointer"><QuestionFilled /></el-icon>
 | 
			
		||||
          </el-tooltip>
 | 
			
		||||
        </el-form-item>
 | 
			
		||||
        <el-form-item label="token计费比例" prop="tokenFeeRatio">
 | 
			
		||||
@@ -827,9 +824,9 @@ onMounted(() => {
 | 
			
		||||
          </el-tooltip>
 | 
			
		||||
        </el-form-item>
 | 
			
		||||
        <el-form-item
 | 
			
		||||
          v-if="[1].includes(Number(formPackage.keyType))"
 | 
			
		||||
          label="指定代理地址"
 | 
			
		||||
          prop="proxyUrl"
 | 
			
		||||
          v-if="[1].includes(Number(formPackage.keyType))"
 | 
			
		||||
        >
 | 
			
		||||
          <el-input
 | 
			
		||||
            v-model.number="formPackage.proxyUrl"
 | 
			
		||||
@@ -848,9 +845,3 @@ onMounted(() => {
 | 
			
		||||
    </el-dialog>
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<style scoped>
 | 
			
		||||
.el-form--inline .el-form-item {
 | 
			
		||||
  margin-right: 15px;
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										33
									
								
								build.sh
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										33
									
								
								build.sh
									
									
									
									
									
										Executable file
									
								
							@@ -0,0 +1,33 @@
 | 
			
		||||
#!/bin/bash
 | 
			
		||||
 | 
			
		||||
set -e
 | 
			
		||||
 | 
			
		||||
cd admin/
 | 
			
		||||
pnpm build
 | 
			
		||||
cd ..
 | 
			
		||||
 | 
			
		||||
cd chat/
 | 
			
		||||
pnpm build
 | 
			
		||||
cd ..
 | 
			
		||||
 | 
			
		||||
cd service/
 | 
			
		||||
pnpm build
 | 
			
		||||
cd ..
 | 
			
		||||
 | 
			
		||||
rm -rf AIWebQuickDeploy/dist/* AIWebQuickDeploy/public/* AIWebQuickDeploy/templates/*
 | 
			
		||||
 | 
			
		||||
mkdir -p AIWebQuickDeploy/dist AIWebQuickDeploy/public/admin AIWebQuickDeploy/templates
 | 
			
		||||
 | 
			
		||||
cp service/pm2.conf.json AIWebQuickDeploy/pm2.conf.json
 | 
			
		||||
cp service/package.json AIWebQuickDeploy/package.json
 | 
			
		||||
cp service/README.md AIWebQuickDeploy/README.md
 | 
			
		||||
cp service/.env.example AIWebQuickDeploy/.env.example
 | 
			
		||||
cp service/Dockerfile AIWebQuickDeploy/Dockerfile
 | 
			
		||||
cp service/docker-compose.yml AIWebQuickDeploy/docker-compose.yml
 | 
			
		||||
 | 
			
		||||
cp -r service/templates/* AIWebQuickDeploy/templates
 | 
			
		||||
cp -r service/dist/* AIWebQuickDeploy/dist
 | 
			
		||||
cp -r admin/dist/* AIWebQuickDeploy/public/admin
 | 
			
		||||
cp -r chat/dist/* AIWebQuickDeploy/public
 | 
			
		||||
 | 
			
		||||
echo "打包完成"
 | 
			
		||||
@@ -5,11 +5,11 @@ const path = require('path')
 | 
			
		||||
function configureAppMenu(mainWindow) {
 | 
			
		||||
 | 
			
		||||
  let	tray = new Tray(path.join(__dirname, '../icons/16x16.png'));
 | 
			
		||||
  // tray.setToolTip('Nine Ai');
 | 
			
		||||
  // tray.setToolTip('YiAi Ai');
 | 
			
		||||
 | 
			
		||||
  const template = [
 | 
			
		||||
    {
 | 
			
		||||
      label: 'NineAi',
 | 
			
		||||
      label: 'YiAi',
 | 
			
		||||
      submenu: [
 | 
			
		||||
        {
 | 
			
		||||
          label: '退出应用',
 | 
			
		||||
 
 | 
			
		||||
@@ -26,7 +26,7 @@ function createMainWindow() {
 | 
			
		||||
 | 
			
		||||
	if (app.isPackaged) {
 | 
			
		||||
    // mainWindow.loadFile(filePath)
 | 
			
		||||
    mainWindow.loadURL('https://ai.jiangly.com')
 | 
			
		||||
    // mainWindow.loadURL('https://ai.jiangly.com')
 | 
			
		||||
  }
 | 
			
		||||
  else {
 | 
			
		||||
    mainWindow.loadURL('http://127.0.0.1:1002')
 | 
			
		||||
 
 | 
			
		||||
@@ -190,6 +190,11 @@
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			console.log(
 | 
			
		||||
        "%c本项目作者----小易,联系QQ:805239273",
 | 
			
		||||
        "background-color:rgb(30,30,30);border-radius:4px;font-size:12px;padding:4px;color:rgb(220,208,129);"
 | 
			
		||||
      )
 | 
			
		||||
		</script>
 | 
			
		||||
</body>
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,9 +1,9 @@
 | 
			
		||||
{
 | 
			
		||||
	"name": "chatgpt-cooper",
 | 
			
		||||
  "version": "2.3.0",
 | 
			
		||||
	"version": "2.5.0",
 | 
			
		||||
	"private": true,
 | 
			
		||||
	"description": "ChatGPT Cooper",
 | 
			
		||||
  "author": "Snine <J_longyan@163.com>",
 | 
			
		||||
	"author": "Yi <a8052@qq.com>",
 | 
			
		||||
	"keywords": [
 | 
			
		||||
		"chatgpt-cooper",
 | 
			
		||||
		"chatgpt",
 | 
			
		||||
@@ -55,7 +55,7 @@
 | 
			
		||||
		"markmap-common": "0.14.2",
 | 
			
		||||
		"markmap-lib": "0.14.4",
 | 
			
		||||
		"markmap-view": "0.14.4",
 | 
			
		||||
    "naive-ui": "^2.34.3",
 | 
			
		||||
		"naive-ui": "^2.37.3",
 | 
			
		||||
		"pinia": "^2.0.33",
 | 
			
		||||
		"qrcode": "^1.5.3",
 | 
			
		||||
		"v-viewer": "3.0.11",
 | 
			
		||||
@@ -100,7 +100,7 @@
 | 
			
		||||
		]
 | 
			
		||||
	},
 | 
			
		||||
	"build": {
 | 
			
		||||
		"productName": "NineAi",
 | 
			
		||||
		"productName": "Yiai",
 | 
			
		||||
		"appId": "ai.jiangly.com",
 | 
			
		||||
		"icon": "icons/icon.icns",
 | 
			
		||||
		"directories": {
 | 
			
		||||
 
 | 
			
		||||
@@ -1,165 +1,223 @@
 | 
			
		||||
import type { AxiosProgressEvent, GenericAbortSignal } from 'axios'
 | 
			
		||||
import { get, post } from '@/utils/request'
 | 
			
		||||
import { useSettingStore } from '@/store'
 | 
			
		||||
import type { AxiosProgressEvent, GenericAbortSignal } from "axios";
 | 
			
		||||
import { get, post } from "@/utils/request";
 | 
			
		||||
import { useSettingStore } from "@/store";
 | 
			
		||||
 | 
			
		||||
/* 流失对话聊天 */
 | 
			
		||||
export function fetchChatAPIProcess<T = any>(
 | 
			
		||||
  params: {
 | 
			
		||||
    prompt: string
 | 
			
		||||
    appId?: number
 | 
			
		||||
    options?: { conversationId?: string; parentMessageId?: string; temperature: number }
 | 
			
		||||
    imageUrl?:string
 | 
			
		||||
    model?:string
 | 
			
		||||
    signal?: GenericAbortSignal
 | 
			
		||||
    onDownloadProgress?: (progressEvent: AxiosProgressEvent) => void },
 | 
			
		||||
) {
 | 
			
		||||
export function fetchChatAPIProcess<T = any>(params: {
 | 
			
		||||
	prompt: string;
 | 
			
		||||
	appId?: number;
 | 
			
		||||
	options?: {
 | 
			
		||||
		conversationId?: string;
 | 
			
		||||
		parentMessageId?: string;
 | 
			
		||||
		temperature: number;
 | 
			
		||||
	};
 | 
			
		||||
	imageUrl?: string;
 | 
			
		||||
	model?: string;
 | 
			
		||||
	signal?: GenericAbortSignal;
 | 
			
		||||
	onDownloadProgress?: (progressEvent: AxiosProgressEvent) => void;
 | 
			
		||||
}) {
 | 
			
		||||
	return post<T>({
 | 
			
		||||
    url: '/chatgpt/chat-process',
 | 
			
		||||
    data: { prompt: params.prompt, appId: params?.appId, options: params.options,imageUrl: params.imageUrl,model: params.model},
 | 
			
		||||
		url: "/chatgpt/chat-process",
 | 
			
		||||
		data: {
 | 
			
		||||
			prompt: params.prompt,
 | 
			
		||||
			appId: params?.appId,
 | 
			
		||||
			options: params.options,
 | 
			
		||||
			imageUrl: params.imageUrl,
 | 
			
		||||
			model: params.model,
 | 
			
		||||
		},
 | 
			
		||||
		signal: params.signal,
 | 
			
		||||
		onDownloadProgress: params.onDownloadProgress,
 | 
			
		||||
  })
 | 
			
		||||
	});
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* 获取个人信息 */
 | 
			
		||||
export function fetchGetInfo<T>() {
 | 
			
		||||
  return get<T>({ url: '/auth/getInfo' })
 | 
			
		||||
	return get<T>({ url: "/auth/getInfo" });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* 注册 */
 | 
			
		||||
export function fetchRegisterAPI<T>(data: { username: string;password: string;email: string }): Promise<T> {
 | 
			
		||||
  return post<T>({ url: '/auth/register', data }) as Promise<T>
 | 
			
		||||
export function fetchRegisterAPI<T>(data: {
 | 
			
		||||
	username: string;
 | 
			
		||||
	password: string;
 | 
			
		||||
	email: string;
 | 
			
		||||
}): Promise<T> {
 | 
			
		||||
	return post<T>({ url: "/auth/register", data }) as Promise<T>;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* 注册 */
 | 
			
		||||
export function fetchRegisterByPhoneAPI<T>(data: { username: string;password: string; phone: string; phoneCode: string }): Promise<T> {
 | 
			
		||||
  return post<T>({ url: '/auth/registerByPhone', data }) as Promise<T>
 | 
			
		||||
export function fetchRegisterByPhoneAPI<T>(data: {
 | 
			
		||||
	username: string;
 | 
			
		||||
	password: string;
 | 
			
		||||
	phone: string;
 | 
			
		||||
	phoneCode: string;
 | 
			
		||||
}): Promise<T> {
 | 
			
		||||
	return post<T>({ url: "/auth/registerByPhone", data }) as Promise<T>;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* 登录 */
 | 
			
		||||
export function fetchLoginAPI<T>(data: { username: string; password: string }): Promise<T> {
 | 
			
		||||
  return post<T>({ url: '/auth/login', data }) as Promise<T>
 | 
			
		||||
export function fetchLoginAPI<T>(data: {
 | 
			
		||||
	username: string;
 | 
			
		||||
	password: string;
 | 
			
		||||
}): Promise<T> {
 | 
			
		||||
	return post<T>({ url: "/auth/login", data }) as Promise<T>;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* 手机号登录 */
 | 
			
		||||
export function fetchLoginByPhoneAPI<T>(data: { phone: string; password: string }): Promise<T> {
 | 
			
		||||
  return post<T>({ url: '/auth/loginByPhone', data }) as Promise<T>
 | 
			
		||||
export function fetchLoginByPhoneAPI<T>(data: {
 | 
			
		||||
	phone: string;
 | 
			
		||||
	password: string;
 | 
			
		||||
}): Promise<T> {
 | 
			
		||||
	return post<T>({ url: "/auth/loginByPhone", data }) as Promise<T>;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* 修改个人信息 */
 | 
			
		||||
export function fetchUpdateInfoAPI<T>(data: { username?: string; avatar?: string }): Promise<T> {
 | 
			
		||||
  return post<T>({ url: '/user/update', data }) as Promise<T>
 | 
			
		||||
export function fetchUpdateInfoAPI<T>(data: {
 | 
			
		||||
	username?: string;
 | 
			
		||||
	avatar?: string;
 | 
			
		||||
}): Promise<T> {
 | 
			
		||||
	return post<T>({ url: "/user/update", data }) as Promise<T>;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* 获取个人绘画记录 */
 | 
			
		||||
export function fetchGetChatLogDraw<T>(data: { model: string }): Promise<T> {
 | 
			
		||||
  return get<T>({ url: '/chatLog/draw', data }) as Promise<T>
 | 
			
		||||
	return get<T>({ url: "/chatLog/draw", data }) as Promise<T>;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* 获取所有绘画记录 */
 | 
			
		||||
export function fetchGetAllChatLogDraw<T>(data: { size: number; rec: number; model: string }): Promise<T> {
 | 
			
		||||
  return get<T>({ url: '/chatLog/drawAll', data }) as Promise<T>
 | 
			
		||||
export function fetchGetAllChatLogDraw<T>(data: {
 | 
			
		||||
	size: number;
 | 
			
		||||
	rec: number;
 | 
			
		||||
	model: string;
 | 
			
		||||
}): Promise<T> {
 | 
			
		||||
	return get<T>({ url: "/chatLog/drawAll", data }) as Promise<T>;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* chatgpt的dall-e2绘画 */
 | 
			
		||||
export function fetchChatDraw<T>(data: { prompt: string;n: number;size: string }): Promise<T> {
 | 
			
		||||
  return post<T>({ url: '/chatgpt/chat-draw', data }) as Promise<T>
 | 
			
		||||
export function fetchChatDraw<T>(data: {
 | 
			
		||||
	prompt: string;
 | 
			
		||||
	n: number;
 | 
			
		||||
	size: string;
 | 
			
		||||
}): Promise<T> {
 | 
			
		||||
	return post<T>({ url: "/chatgpt/chat-draw", data }) as Promise<T>;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* 修改密码 */
 | 
			
		||||
export function fetchUpdatePasswordAPI<T>(data: { oldPassword?: string;password?: string }): Promise<T> {
 | 
			
		||||
  return post<T>({ url: '/auth/updatePassword', data }) as Promise<T>
 | 
			
		||||
export function fetchUpdatePasswordAPI<T>(data: {
 | 
			
		||||
	oldPassword?: string;
 | 
			
		||||
	password?: string;
 | 
			
		||||
}): Promise<T> {
 | 
			
		||||
	return post<T>({ url: "/auth/updatePassword", data }) as Promise<T>;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* 同步对话 */
 | 
			
		||||
export function fetchGetchatSyncApi<T = any>(
 | 
			
		||||
  params: {
 | 
			
		||||
    prompt: string
 | 
			
		||||
    options?: { conversationId?: string; parentMessageId?: string; temperature: number }
 | 
			
		||||
    signal?: GenericAbortSignal
 | 
			
		||||
    onDownloadProgress?: (progressEvent: AxiosProgressEvent) => void },
 | 
			
		||||
) {
 | 
			
		||||
export function fetchGetchatSyncApi<T = any>(params: {
 | 
			
		||||
	prompt: string;
 | 
			
		||||
	options?: {
 | 
			
		||||
		conversationId?: string;
 | 
			
		||||
		parentMessageId?: string;
 | 
			
		||||
		temperature: number;
 | 
			
		||||
	};
 | 
			
		||||
	signal?: GenericAbortSignal;
 | 
			
		||||
	onDownloadProgress?: (progressEvent: AxiosProgressEvent) => void;
 | 
			
		||||
}) {
 | 
			
		||||
	return post<T>({
 | 
			
		||||
    url: '/chatgpt/chat-sync',
 | 
			
		||||
		url: "/chatgpt/chat-sync",
 | 
			
		||||
		data: { prompt: params.prompt, options: params.options },
 | 
			
		||||
		signal: params.signal,
 | 
			
		||||
		onDownloadProgress: params.onDownloadProgress,
 | 
			
		||||
  })
 | 
			
		||||
	});
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* 获取mind绘画联想词 */
 | 
			
		||||
export function fetchGetchatMindApi<T = any>(
 | 
			
		||||
  params: {
 | 
			
		||||
    prompt: string
 | 
			
		||||
    options?: { conversationId?: string; parentMessageId?: string; temperature: number }
 | 
			
		||||
    signal?: GenericAbortSignal
 | 
			
		||||
    onDownloadProgress?: (progressEvent: AxiosProgressEvent) => void },
 | 
			
		||||
) {
 | 
			
		||||
export function fetchGetchatMindApi<T = any>(params: {
 | 
			
		||||
	prompt: string;
 | 
			
		||||
	options?: {
 | 
			
		||||
		conversationId?: string;
 | 
			
		||||
		parentMessageId?: string;
 | 
			
		||||
		temperature: number;
 | 
			
		||||
	};
 | 
			
		||||
	signal?: GenericAbortSignal;
 | 
			
		||||
	onDownloadProgress?: (progressEvent: AxiosProgressEvent) => void;
 | 
			
		||||
}) {
 | 
			
		||||
	return post<T>({
 | 
			
		||||
    url: '/chatgpt/chat-mind',
 | 
			
		||||
		url: "/chatgpt/chat-mind",
 | 
			
		||||
		data: { prompt: params.prompt, options: params.options },
 | 
			
		||||
		signal: params.signal,
 | 
			
		||||
		onDownloadProgress: params.onDownloadProgress,
 | 
			
		||||
  })
 | 
			
		||||
	});
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* 获取MJ绘画联想词 */
 | 
			
		||||
export function fetchGetMjPromptAssociateApi<T>(data: { prompt: string }): Promise<T> {
 | 
			
		||||
  return post<T>({ url: '/chatgpt/mj-associate', data }) as Promise<T>
 | 
			
		||||
export function fetchGetMjPromptAssociateApi<T>(data: {
 | 
			
		||||
	prompt: string;
 | 
			
		||||
}): Promise<T> {
 | 
			
		||||
	return post<T>({ url: "/chatgpt/mj-associate", data }) as Promise<T>;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* 获取MJ绘画联想词 */
 | 
			
		||||
export function fetchGetMjPromptFanyiApi<T>(data: { prompt: string }): Promise<T> {
 | 
			
		||||
  return post<T>({ url: '/chatgpt/mj-fy', data }) as Promise<T>
 | 
			
		||||
export function fetchGetMjPromptFanyiApi<T>(data: {
 | 
			
		||||
	prompt: string;
 | 
			
		||||
}): Promise<T> {
 | 
			
		||||
	return post<T>({ url: "/chatgpt/mj-fy", data }) as Promise<T>;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* 获取我得绘制列表 */
 | 
			
		||||
export function fetchMidjourneyDrawList<T>(data: { page?: number; size?: number }): Promise<T> {
 | 
			
		||||
  return get<T>({ url: '/midjourney/drawList', data }) as Promise<T>
 | 
			
		||||
export function fetchMidjourneyDrawList<T>(data: {
 | 
			
		||||
	page?: number;
 | 
			
		||||
	size?: number;
 | 
			
		||||
}): Promise<T> {
 | 
			
		||||
	return get<T>({ url: "/midjourney/drawList", data }) as Promise<T>;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* 获取Mj提示词 */
 | 
			
		||||
export function fetchMidjourneyPromptList<T>(): Promise<T> {
 | 
			
		||||
  return get<T>({ url: '/midjourney/queryPrompts' }) as Promise<T>
 | 
			
		||||
	return get<T>({ url: "/midjourney/queryPrompts" }) as Promise<T>;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* 获取Mj完整提示词 */
 | 
			
		||||
export function fetchMidjourneyFullPrompt<T>(data: any): Promise<T> {
 | 
			
		||||
  return get<T>({ url: '/midjourney/getFullPrompt', data }) as Promise<T>
 | 
			
		||||
	return get<T>({ url: "/midjourney/getFullPrompt", data }) as Promise<T>;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* 删除MJ绘画记录 */
 | 
			
		||||
export function fetchDownloadImg<T>(data: { id: number }): Promise<T> {
 | 
			
		||||
  return post<T>({ url: '/midjourney/delete', data }) as Promise<T>
 | 
			
		||||
	return post<T>({ url: "/midjourney/delete", data }) as Promise<T>;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* 获取我得绘制列表 */
 | 
			
		||||
export function fetchMidjourneyGetList<T>(data: { page?: number; size?: number; rec: number }): Promise<T> {
 | 
			
		||||
  return get<T>({ url: '/midjourney/getList', data }) as Promise<T>
 | 
			
		||||
export function fetchMidjourneyGetList<T>(data: {
 | 
			
		||||
	page?: number;
 | 
			
		||||
	size?: number;
 | 
			
		||||
	rec: number;
 | 
			
		||||
}): Promise<T> {
 | 
			
		||||
	return get<T>({ url: "/midjourney/getList", data }) as Promise<T>;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* 推荐图片 */
 | 
			
		||||
export function fetchRecDraw<T>(data: { id: number }): Promise<T> {
 | 
			
		||||
  return post<T>({ url: '/midjourney/rec', data }) as Promise<T>
 | 
			
		||||
	return post<T>({ url: "/midjourney/rec", data }) as Promise<T>;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* 获取图片验证码 */
 | 
			
		||||
export function fetchCaptchaImg<T>(data: { color: string }): Promise<T> {
 | 
			
		||||
  return post<T>({ url: '/auth/captcha', data }) as Promise<T>
 | 
			
		||||
	return post<T>({ url: "/auth/captcha", data }) as Promise<T>;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* 发送手机验证码 */
 | 
			
		||||
export function fetchSendSms<T>(data: { phone: string; captchaId: string; captchaCode: string }): Promise<T> {
 | 
			
		||||
  return post<T>({ url: '/auth/sendPhoneCode', data }) as Promise<T>
 | 
			
		||||
export function fetchSendSms<T>(data: {
 | 
			
		||||
	phone: string;
 | 
			
		||||
	captchaId: string;
 | 
			
		||||
	captchaCode: string;
 | 
			
		||||
}): Promise<T> {
 | 
			
		||||
	return post<T>({ url: "/auth/sendPhoneCode", data }) as Promise<T>;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* 获取九宫格设置 */
 | 
			
		||||
export function fetchGetChatBoxList<T>() {
 | 
			
		||||
  return get<T>({ url: '/chatgpt/queryChatBoxFrontend' })
 | 
			
		||||
	return get<T>({ url: "/chatgpt/queryChatBoxFrontend" });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/* 获取快问设置 */
 | 
			
		||||
export function fetchGetChatPreList<T>() {
 | 
			
		||||
  return get<T>({ url: '/chatgpt/queryChatPreList' })
 | 
			
		||||
	return get<T>({ url: "/chatgpt/queryChatPreList" });
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -33,7 +33,7 @@ export function fetchTranslateAPI<T>(data: { text: string }): Promise<T> {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* 提交一个绘画任务 */
 | 
			
		||||
export function fetchDrawTaskAPI<T>(data: { prompt?: string; imgUrl?: string; extraParam?: string; drawId?: number; action?: number; orderId?: number }): Promise<T> {
 | 
			
		||||
export function fetchDrawTaskAPI<T>(data: { prompt?: string; imgUrl?: string; extraParam?: string; drawId?: number; action?: string; orderId?: number }): Promise<T> {
 | 
			
		||||
  return post<T>({
 | 
			
		||||
    url: '/queue/addMjDrawQueue',
 | 
			
		||||
    data,
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										
											BIN
										
									
								
								chat/src/assets/voice.gif
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								chat/src/assets/voice.gif
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| 
		 After Width: | Height: | Size: 12 KiB  | 
@@ -6,13 +6,16 @@ const props = withDefaults(defineProps<Props>(), {
 | 
			
		||||
  gap: 10,
 | 
			
		||||
  progress: 0,
 | 
			
		||||
  tips: '',
 | 
			
		||||
	words: ['L', 'O', 'A', 'D', 'I', 'N', 'G']
 | 
			
		||||
  // words: ['L', 'O', 'A', 'D', 'I', 'N', 'G']
 | 
			
		||||
  words: ['AI', '绘', '画', '中'],
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
const appStore = useAppStore()
 | 
			
		||||
 | 
			
		||||
const theme = computed(() => appStore.theme)
 | 
			
		||||
const loadingTextColor = computed(() => theme.value === 'dark' ? '#fff' : '#000')
 | 
			
		||||
const loadingTextColor = computed(() =>
 | 
			
		||||
  theme.value === 'dark' ? '#fff' : '#000'
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
interface Props {
 | 
			
		||||
  gap?: number
 | 
			
		||||
@@ -27,7 +30,13 @@ interface Props {
 | 
			
		||||
<template>
 | 
			
		||||
  <div class="loading" :style="{ background: props.bgColor }">
 | 
			
		||||
    <div class="loading-text">
 | 
			
		||||
      <span v-for="item in props.words" :key="item" :style="{ margin: `0 ${props.gap}px`, color: loadingTextColor }" class="loading-text-words">{{ item }}</span>
 | 
			
		||||
      <span
 | 
			
		||||
        v-for="item in props.words"
 | 
			
		||||
        :key="item"
 | 
			
		||||
        :style="{ margin: `0 ${props.gap}px`, color: loadingTextColor }"
 | 
			
		||||
        class="loading-text-words"
 | 
			
		||||
        >{{ item }}</span
 | 
			
		||||
      >
 | 
			
		||||
    </div>
 | 
			
		||||
    <div v-if="!tips && props.progress" class="progress">
 | 
			
		||||
      绘制进度: {{ props.progress }}%
 | 
			
		||||
@@ -49,7 +58,7 @@ interface Props {
 | 
			
		||||
  user-select: none;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.progress{
 | 
			
		||||
.progress {
 | 
			
		||||
  position: absolute;
 | 
			
		||||
  bottom: 0;
 | 
			
		||||
  left: 50%;
 | 
			
		||||
@@ -72,7 +81,7 @@ interface Props {
 | 
			
		||||
  display: inline-block;
 | 
			
		||||
  margin: 0 5px;
 | 
			
		||||
  color: #fff;
 | 
			
		||||
  font-family: "Quattrocento Sans", sans-serif;
 | 
			
		||||
  font-family: 'Quattrocento Sans', sans-serif;
 | 
			
		||||
}
 | 
			
		||||
.loading-text span:nth-child(1) {
 | 
			
		||||
  filter: blur(0px);
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										377
									
								
								chat/src/components/common/GridManager2/index.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										377
									
								
								chat/src/components/common/GridManager2/index.vue
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,377 @@
 | 
			
		||||
<script lang="ts" setup>
 | 
			
		||||
import {
 | 
			
		||||
  onMounted,
 | 
			
		||||
  ref,
 | 
			
		||||
  computed,
 | 
			
		||||
  onUnmounted,
 | 
			
		||||
  getCurrentInstance,
 | 
			
		||||
  watch,
 | 
			
		||||
  nextTick,
 | 
			
		||||
} from 'vue';
 | 
			
		||||
import { throttle } from '@/utils/functions/throttle';
 | 
			
		||||
import { SvgIcon } from '@/components/common';
 | 
			
		||||
import { copyText } from '@/utils/format';
 | 
			
		||||
import { useMessage, NPopover } from 'naive-ui';
 | 
			
		||||
import { useAuthStore } from '@/store';
 | 
			
		||||
import { useRouter } from 'vue-router';
 | 
			
		||||
const authStore = useAuthStore();
 | 
			
		||||
interface Props {
 | 
			
		||||
  dataList: FileItem[];
 | 
			
		||||
  scaleWidth?: number;
 | 
			
		||||
  isDrawLike?: boolean;
 | 
			
		||||
  usePropmpt?: boolean;
 | 
			
		||||
  copyPropmpt?: boolean;
 | 
			
		||||
  gap?: number;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
interface FileItem {
 | 
			
		||||
  id: number;
 | 
			
		||||
  drawUrl: string;
 | 
			
		||||
  fullPrompt?: string;
 | 
			
		||||
  drawRatio: string;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
interface Emit {
 | 
			
		||||
  (ev: 'loadMore'): void;
 | 
			
		||||
  (ev: 'usePropmptDraw', prompt: string): void;
 | 
			
		||||
}
 | 
			
		||||
const props = withDefaults(defineProps<Props>(), {
 | 
			
		||||
  gap: 5,
 | 
			
		||||
});
 | 
			
		||||
const emit = defineEmits<Emit>();
 | 
			
		||||
const $viewerApi =
 | 
			
		||||
  getCurrentInstance()?.appContext.config.globalProperties.$viewerApi;
 | 
			
		||||
const ms = useMessage();
 | 
			
		||||
const boxRefs = ref<any>({});
 | 
			
		||||
const otherInfoContainerHeight = ref(0);
 | 
			
		||||
const realWidth = ref(160);
 | 
			
		||||
const realColumn = ref(0);
 | 
			
		||||
const loadComplete = ref<number[]>([]);
 | 
			
		||||
const wapperRef = ref<HTMLDivElement | null>(null);
 | 
			
		||||
const wapperHeigth = ref(0);
 | 
			
		||||
const isLogin = computed(() => authStore.isLogin);
 | 
			
		||||
const width = computed(() => {
 | 
			
		||||
  return props.scaleWidth
 | 
			
		||||
    ? Number(props.scaleWidth) * 2 + props.gap + 150
 | 
			
		||||
    : 150;
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const router = useRouter();
 | 
			
		||||
/* 拿到图片高度 对定位top和right  新的一轮去插入最小值的那一列 贪心算法即可 */
 | 
			
		||||
function compilerContainer() {
 | 
			
		||||
  calcHeight();
 | 
			
		||||
  compilerColumn();
 | 
			
		||||
  const columns = realColumn.value;
 | 
			
		||||
  const itemWidth = realWidth.value;
 | 
			
		||||
  const cacheHeight = <any>[];
 | 
			
		||||
  props.dataList.forEach((item, index) => {
 | 
			
		||||
    const drawRatio = item.drawRatio; // 假设 drawRatio 是 "1632x2912"
 | 
			
		||||
    const dimensions = drawRatio.split('x'); // 使用 'x' 分割字符串
 | 
			
		||||
    const width = parseInt(dimensions[0], 10); // 宽度,转换为数字
 | 
			
		||||
    const height = parseInt(dimensions[1], 10); // 高度,转换为数字
 | 
			
		||||
    const bi = itemWidth / width;
 | 
			
		||||
    const boxheight = height * bi + props.gap + otherInfoContainerHeight.value;
 | 
			
		||||
    const currentBox = boxRefs.value[item.id];
 | 
			
		||||
    if (cacheHeight.length < columns) {
 | 
			
		||||
      currentBox.style.top = '0px';
 | 
			
		||||
      currentBox.style.left = `${(itemWidth + props.gap) * index}px`;
 | 
			
		||||
      cacheHeight.push(boxheight);
 | 
			
		||||
    } else {
 | 
			
		||||
      const minHeight = Math.min.apply(null, cacheHeight);
 | 
			
		||||
      const minIndex = cacheHeight.findIndex((t: number) => t === minHeight);
 | 
			
		||||
      currentBox.style.top = `${minHeight + 0}px`;
 | 
			
		||||
      currentBox.style.left = `${minIndex * (realWidth.value + props.gap)}px`;
 | 
			
		||||
      cacheHeight[minIndex] += boxheight;
 | 
			
		||||
    }
 | 
			
		||||
  });
 | 
			
		||||
  wapperHeigth.value = Math.max(...cacheHeight) + 100;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function setItemRefs(el: HTMLDivElement, item: FileItem) {
 | 
			
		||||
  if (el && item) {
 | 
			
		||||
    boxRefs.value[item.id] = el;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* 通过额外展示的信息计算有没有除了图片意外额外的高度 eg: 图片100px 额外显示其他信息30px cacheHeight的高度在图片的基础上需要+30 */
 | 
			
		||||
function calcHeight() {
 | 
			
		||||
  const { showName = 0, showOther = 0 } = {};
 | 
			
		||||
  otherInfoContainerHeight.value =
 | 
			
		||||
    [showName, showOther].filter((t) => t).length * 15;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
watch(
 | 
			
		||||
  () => props.scaleWidth,
 | 
			
		||||
  (val) => {
 | 
			
		||||
    handleResizeThrottled();
 | 
			
		||||
  }
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
watch(
 | 
			
		||||
  () => props.dataList,
 | 
			
		||||
  (val) => {
 | 
			
		||||
    if (!val) return;
 | 
			
		||||
    nextTick(() => {
 | 
			
		||||
      handleResizeThrottled();
 | 
			
		||||
    });
 | 
			
		||||
  },
 | 
			
		||||
  { immediate: true }
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
/* 计算放多少列比较合理,并计算最终单个图片的宽 */
 | 
			
		||||
function compilerColumn() {
 | 
			
		||||
  if (!wapperRef.value) return;
 | 
			
		||||
  const containerWidth = wapperRef.value.clientWidth;
 | 
			
		||||
 | 
			
		||||
  /* 计算按目前宽度最多可以是几列 */
 | 
			
		||||
  realColumn.value = Math.floor(containerWidth / width.value);
 | 
			
		||||
  const surplus = containerWidth - realColumn.value * width.value; // 剩下的多余空间
 | 
			
		||||
  /* 计算如果给了左右间距那么作业间距需要占多少宽度 */
 | 
			
		||||
  const positionWith = (realColumn.value - 1) * props.gap; // 设置的right 需要padding的值
 | 
			
		||||
  /* 总宽度减去right的宽度,如果是负数考虑要不要cloumn-1 那么图片真实宽度就会比传入的宽度大 */
 | 
			
		||||
  if (surplus - positionWith < 0) {
 | 
			
		||||
    realColumn.value -= 1;
 | 
			
		||||
  }
 | 
			
		||||
  /* 图片宽度*列 + right的间距 不管大于小于总宽  多的或者少的那部分都平分给列容器 保证总宽是100% */
 | 
			
		||||
  realWidth.value = Math.floor(
 | 
			
		||||
    (containerWidth - positionWith) / realColumn.value
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function imgLoadSuccess(e: any, item: FileItem) {
 | 
			
		||||
  loadComplete.value.push(item.id);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function imgLoadError(e: any, item: FileItem) {
 | 
			
		||||
  console.error('Image failed to load:', item);
 | 
			
		||||
  loadComplete.value.push(item.id);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function handleCopy(item: any) {
 | 
			
		||||
  if (!isLogin.value) {
 | 
			
		||||
    return authStore.setLoginDialog(true);
 | 
			
		||||
  }
 | 
			
		||||
  const { prompt } = item;
 | 
			
		||||
  copyText({ text: prompt });
 | 
			
		||||
  ms.success('复制prompt成功');
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function drawLike(item: any) {
 | 
			
		||||
  router.push(`/midjourney?mjId=${item.id}`);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function usePropmptDraw(item: FileItem) {
 | 
			
		||||
  const { fullPrompt } = item;
 | 
			
		||||
  emit('usePropmptDraw', fullPrompt);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function handlePreview(item: any) {
 | 
			
		||||
  const { drawUrl } = item;
 | 
			
		||||
  $viewerApi({ options: {}, images: [drawUrl] });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const realHeight = computed(() => (item) => {
 | 
			
		||||
  const ratio = item.drawRatio.split('x');
 | 
			
		||||
  const originalWidth = Number(ratio[0]);
 | 
			
		||||
  const originalHeight = Number(ratio[1]);
 | 
			
		||||
  return originalHeight / (originalWidth / realWidth.value);
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const handleResizeThrottled = throttle(function (this: any) {
 | 
			
		||||
  compilerContainer();
 | 
			
		||||
}, 200);
 | 
			
		||||
 | 
			
		||||
onMounted(async () => {
 | 
			
		||||
  window.addEventListener('resize', handleResizeThrottled);
 | 
			
		||||
  const container: any = document.getElementById('footer');
 | 
			
		||||
  const observer = new IntersectionObserver((entries, observer) => {
 | 
			
		||||
    entries.forEach((entry) => {
 | 
			
		||||
      if (entry.isIntersecting) {
 | 
			
		||||
        emit('loadMore');
 | 
			
		||||
      }
 | 
			
		||||
    });
 | 
			
		||||
  });
 | 
			
		||||
  observer.observe(container);
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
onUnmounted(() => {
 | 
			
		||||
  window.removeEventListener('resize', handleResizeThrottled);
 | 
			
		||||
});
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<template>
 | 
			
		||||
  <div class="min-h-full overflow-hidden flex flex-col">
 | 
			
		||||
    <div class="flex-1 min-h-full p-4 relative">
 | 
			
		||||
      <div
 | 
			
		||||
        id="wapper"
 | 
			
		||||
        ref="wapperRef"
 | 
			
		||||
        class="wapper"
 | 
			
		||||
        :style="{ height: `${wapperHeigth}px` }"
 | 
			
		||||
      >
 | 
			
		||||
        <div
 | 
			
		||||
          v-for="(item, index) in dataList"
 | 
			
		||||
          :id="item.id.toString()"
 | 
			
		||||
          :key="index"
 | 
			
		||||
          :ref="(el) => setItemRefs(el, item)"
 | 
			
		||||
          class="wapper-item"
 | 
			
		||||
          :style="{ width: `${realWidth}px` }"
 | 
			
		||||
        >
 | 
			
		||||
          <transition name="img" :css="true">
 | 
			
		||||
            <img
 | 
			
		||||
              :id="item.id.toString()"
 | 
			
		||||
              class="item-file rounded-sm"
 | 
			
		||||
              :style="{
 | 
			
		||||
                width: `${realWidth}px`,
 | 
			
		||||
                height: `${realHeight(item)}px`,
 | 
			
		||||
              }"
 | 
			
		||||
              :src="item.drawUrl"
 | 
			
		||||
              loading="lazy"
 | 
			
		||||
              @load="imgLoadSuccess($event, item)"
 | 
			
		||||
              @error="imgLoadError($event, item)"
 | 
			
		||||
              @click="handlePreview(item)"
 | 
			
		||||
            />
 | 
			
		||||
          </transition>
 | 
			
		||||
          <div class="menu p-2 text-[#cbd5e1]">
 | 
			
		||||
            <div class="prompt">
 | 
			
		||||
              {{ item.fullPrompt }}
 | 
			
		||||
            </div>
 | 
			
		||||
            <div class="flex justify-end items-end space-x-2">
 | 
			
		||||
              <n-popover trigger="hover" v-if="isDrawLike">
 | 
			
		||||
                <template #trigger>
 | 
			
		||||
                  <button
 | 
			
		||||
                    class="flex h-5 w-8 items-center justify-center rounded border transition hover:bg-[#666161] dark:border-neutral-700 dark:hover:bg-[#33373c]"
 | 
			
		||||
                    @click.stop="drawLike(item)"
 | 
			
		||||
                  >
 | 
			
		||||
                    <span class="text-sm dark:text-slate-400">
 | 
			
		||||
                      <SvgIcon
 | 
			
		||||
                        icon="fluent:draw-image-24-regular"
 | 
			
		||||
                        class="text-sm"
 | 
			
		||||
                      />
 | 
			
		||||
                    </span>
 | 
			
		||||
                  </button>
 | 
			
		||||
                </template>
 | 
			
		||||
                <span>画同款</span>
 | 
			
		||||
              </n-popover>
 | 
			
		||||
 | 
			
		||||
              <n-popover trigger="hover" v-if="usePropmpt">
 | 
			
		||||
                <template #trigger>
 | 
			
		||||
                  <button
 | 
			
		||||
                    class="flex h-5 w-8 items-center justify-center rounded border transition hover:bg-[#666161] dark:border-neutral-700 dark:hover:bg-[#33373c]"
 | 
			
		||||
                    @click.stop="usePropmptDraw(item)"
 | 
			
		||||
                  >
 | 
			
		||||
                    <span class="text-sm dark:text-slate-400">
 | 
			
		||||
                      <SvgIcon
 | 
			
		||||
                        icon="fluent:draw-image-24-regular"
 | 
			
		||||
                        class="text-sm"
 | 
			
		||||
                      />
 | 
			
		||||
                    </span>
 | 
			
		||||
                  </button>
 | 
			
		||||
                </template>
 | 
			
		||||
                <span>使用当前画同款</span>
 | 
			
		||||
              </n-popover>
 | 
			
		||||
 | 
			
		||||
              <n-popover trigger="hover" v-if="copyPropmpt">
 | 
			
		||||
                <template #trigger>
 | 
			
		||||
                  <button
 | 
			
		||||
                    class="flex h-5 w-8 items-center justify-center rounded border transition hover:bg-[#666161] dark:border-neutral-700 dark:hover:bg-[#33373c]"
 | 
			
		||||
                    @click.stop="handleCopy(item)"
 | 
			
		||||
                  >
 | 
			
		||||
                    <span class="text-sm dark:text-slate-400">
 | 
			
		||||
                      <SvgIcon icon="tabler:copy" class="text-sm" />
 | 
			
		||||
                    </span>
 | 
			
		||||
                  </button>
 | 
			
		||||
                </template>
 | 
			
		||||
                <span>复制提示词</span>
 | 
			
		||||
              </n-popover>
 | 
			
		||||
            </div>
 | 
			
		||||
          </div>
 | 
			
		||||
          <div
 | 
			
		||||
            class="item-loading"
 | 
			
		||||
            v-if="!loadComplete.includes(item.id)"
 | 
			
		||||
            :style="{
 | 
			
		||||
              width: `${realWidth}px`,
 | 
			
		||||
              height: `${realHeight(item)}px`,
 | 
			
		||||
            }"
 | 
			
		||||
          ></div>
 | 
			
		||||
        </div>
 | 
			
		||||
        <div id="footer" class="w-full absolute bottom-[350px]" />
 | 
			
		||||
      </div>
 | 
			
		||||
    </div>
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<style lang="less">
 | 
			
		||||
.wapper {
 | 
			
		||||
  width: 100%;
 | 
			
		||||
  position: relative;
 | 
			
		||||
  height: 100%;
 | 
			
		||||
  padding-bottom: 20px;
 | 
			
		||||
 | 
			
		||||
  &-item {
 | 
			
		||||
    z-index: 10;
 | 
			
		||||
    overflow: hidden;
 | 
			
		||||
    position: absolute;
 | 
			
		||||
    transition: all 0.5s;
 | 
			
		||||
    cursor: pointer;
 | 
			
		||||
 | 
			
		||||
    &:hover {
 | 
			
		||||
      .menu {
 | 
			
		||||
        transition: transform 0.3s ease-in-out;
 | 
			
		||||
        transform: translateY(-10px);
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      img {
 | 
			
		||||
        transform: scale(1.1);
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .menu {
 | 
			
		||||
      position: absolute;
 | 
			
		||||
      bottom: 0;
 | 
			
		||||
      width: 94%;
 | 
			
		||||
      left: 3%;
 | 
			
		||||
      max-height: 70%;
 | 
			
		||||
      height: 100px;
 | 
			
		||||
      transform: translateY(100%);
 | 
			
		||||
      background-color: #090b15;
 | 
			
		||||
      opacity: 0.8;
 | 
			
		||||
      transition: all 0.1s cubic-bezier(0.68, -0.55, 0.265, 1.55);
 | 
			
		||||
      border-radius: 10px;
 | 
			
		||||
      overflow: hidden;
 | 
			
		||||
      display: flex;
 | 
			
		||||
      flex-direction: column;
 | 
			
		||||
      justify-content: space-between;
 | 
			
		||||
 | 
			
		||||
      .prompt {
 | 
			
		||||
        height: 50px;
 | 
			
		||||
        overflow: hidden;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    img {
 | 
			
		||||
      user-select: none;
 | 
			
		||||
      cursor: pointer;
 | 
			
		||||
      transition: all 0.6s cubic-bezier(0.19, 1, 0.22, 1);
 | 
			
		||||
      border-radius: 6px;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .item-loading {
 | 
			
		||||
      background: url(@/assets/img-bg.png) no-repeat center center;
 | 
			
		||||
      filter: blur(20px);
 | 
			
		||||
      position: absolute;
 | 
			
		||||
      top: 0;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.img-enter-active,
 | 
			
		||||
.img-leave-active {
 | 
			
		||||
  transition: transform 0.3s;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.img-enter,
 | 
			
		||||
.img-leave-to {
 | 
			
		||||
  transform: scale(0.6);
 | 
			
		||||
  opacity: 0;
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
@@ -317,7 +317,7 @@ onUnmounted(() => {
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		.item-loading {
 | 
			
		||||
			background: url(../../assets/img-bg.png) no-repeat center center;
 | 
			
		||||
      background: url(@/assets/img-bg.png) no-repeat center center;
 | 
			
		||||
			filter: blur(20px);
 | 
			
		||||
			position: absolute;
 | 
			
		||||
			top: 0;
 | 
			
		||||
@@ -10,7 +10,8 @@ export function defaultState(): Chat.ChatState {
 | 
			
		||||
    groupList: [],
 | 
			
		||||
    chatList: [],
 | 
			
		||||
    groupKeyWord: '',
 | 
			
		||||
		baseConfig: null
 | 
			
		||||
		baseConfig: null,
 | 
			
		||||
		chatPreList: [],
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -15,7 +15,7 @@ export function defaultSetting(): UserState {
 | 
			
		||||
  return {
 | 
			
		||||
    userInfo: {
 | 
			
		||||
      avatar: 'https://public-1300678944.cos.ap-shanghai.myqcloud.com/blog/1681310872890image.png',
 | 
			
		||||
      name: 'Nine Ai',
 | 
			
		||||
      name: 'Yi Ai',
 | 
			
		||||
    },
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -4,13 +4,6 @@ body,
 | 
			
		||||
	height: 100%;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// @font-face {
 | 
			
		||||
//   font-family: "nineai";
 | 
			
		||||
//   src: url("//at.alicdn.com/wf/webfont/KDHmc7Mx03dG/NkbQEk5ZpA2z.woff2") format("woff2"),
 | 
			
		||||
//        url("//at.alicdn.com/wf/webfont/KDHmc7Mx03dG/MG5Fkb82nDmI.woff") format("woff");
 | 
			
		||||
//   font-display: swap;
 | 
			
		||||
// }
 | 
			
		||||
 | 
			
		||||
*{
 | 
			
		||||
  font-family: consolas, Menlo, "PingFang SC", "Microsoft YaHei", sans-serif;
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -13,6 +13,9 @@ import {
 | 
			
		||||
  useDialog,
 | 
			
		||||
  useMessage,
 | 
			
		||||
  NAlert,
 | 
			
		||||
  NSpace,
 | 
			
		||||
  NPopselect,
 | 
			
		||||
  NText,
 | 
			
		||||
} from 'naive-ui'
 | 
			
		||||
import type { MessageRenderMessage } from 'naive-ui'
 | 
			
		||||
 | 
			
		||||
@@ -40,7 +43,7 @@ import {
 | 
			
		||||
  useGlobalStoreWithOut,
 | 
			
		||||
} from '@/store'
 | 
			
		||||
import { fetchQueryOneCatAPI } from '@/api/appStore'
 | 
			
		||||
import { fetchChatAPIProcess } from '@/api'
 | 
			
		||||
import { fetchChatAPIProcess, fetchVideoAPIProcess } from '@/api'
 | 
			
		||||
import { t } from '@/locales'
 | 
			
		||||
import { router } from '@/router'
 | 
			
		||||
const uploadUrl = ref(`${import.meta.env.VITE_GLOB_API_URL}/upload/file`)
 | 
			
		||||
@@ -84,6 +87,38 @@ const themeOptions: {
 | 
			
		||||
    icon: 'noto-v1:last-quarter-moon-face',
 | 
			
		||||
  },
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
const videoOptions: {
 | 
			
		||||
  label: string
 | 
			
		||||
  value: string
 | 
			
		||||
}[] = [
 | 
			
		||||
  {
 | 
			
		||||
    value: 'alloy',
 | 
			
		||||
    label: 'alloy',
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    value: 'echo',
 | 
			
		||||
    label: 'echo',
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    value: 'fable',
 | 
			
		||||
    label: 'fable',
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    value: 'nova',
 | 
			
		||||
    label: 'nova',
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    value: 'onyx',
 | 
			
		||||
    label: 'onyx',
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    value: 'shimmer',
 | 
			
		||||
    label: 'shimmer',
 | 
			
		||||
  },
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
let currentVideo = ref('alloy')
 | 
			
		||||
const theme = computed(() => appStore.theme)
 | 
			
		||||
 | 
			
		||||
const globaelConfig = computed(() => authStore.globalConfig)
 | 
			
		||||
@@ -198,6 +233,48 @@ function handleSignIn() {
 | 
			
		||||
  useGlobalStore.updateSignInDialog(true)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const audioRef = ref(null)
 | 
			
		||||
const audioState = ref('Play')
 | 
			
		||||
function hendleVideo(item) {
 | 
			
		||||
  var data = JSON.stringify({
 | 
			
		||||
    model: 'tts-1',
 | 
			
		||||
    input: item.text,
 | 
			
		||||
    voice: 'alloy',
 | 
			
		||||
  })
 | 
			
		||||
  axios({
 | 
			
		||||
    method: 'post',
 | 
			
		||||
    url: 'https://api.oneapi.dwyu.cn/v1/audio/speech',
 | 
			
		||||
    headers: {
 | 
			
		||||
      Authorization:
 | 
			
		||||
        'Bearer sk-z726fTNvD1jzSBZ42e8dF919840b48A5820e4e5d9d4e70A4',
 | 
			
		||||
      'Content-Type': 'application/json',
 | 
			
		||||
    },
 | 
			
		||||
    data: data,
 | 
			
		||||
  })
 | 
			
		||||
    .then(function (response) {
 | 
			
		||||
      console.log('--response.data', response.data)
 | 
			
		||||
      const audio = audioRef.value
 | 
			
		||||
      const blob = new Blob([response.data], { type: 'audio/mpeg' })
 | 
			
		||||
      if (!audio) return
 | 
			
		||||
      audio.src = URL.createObjectURL(blob)
 | 
			
		||||
      console.log(audio)
 | 
			
		||||
      audio.load()
 | 
			
		||||
      audio.play()
 | 
			
		||||
 | 
			
		||||
      // if (audio.paused) {
 | 
			
		||||
      //   audio.play()
 | 
			
		||||
      //   audioState.value = 'Stop'
 | 
			
		||||
      // } else {
 | 
			
		||||
      //   audio.pause()
 | 
			
		||||
      //   audio.currentTime = 0
 | 
			
		||||
      //   audioState.value = 'Play'
 | 
			
		||||
      // }
 | 
			
		||||
    })
 | 
			
		||||
    .catch(function (error) {
 | 
			
		||||
      console.error('There was an error fetching the audio data', error)
 | 
			
		||||
    })
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 解析文件 gpt-4-all逆向
 | 
			
		||||
let curFile: File | null
 | 
			
		||||
 | 
			
		||||
@@ -279,7 +356,6 @@ async function uploadFile() {
 | 
			
		||||
  } finally {
 | 
			
		||||
    dataBase64.value = null
 | 
			
		||||
    curFile = null
 | 
			
		||||
    showProgressModal.value = false
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -426,6 +502,7 @@ async function onConversation(msg?: string) {
 | 
			
		||||
              usage: data?.detail?.usage,
 | 
			
		||||
              error: false,
 | 
			
		||||
              loading: true,
 | 
			
		||||
              imageUrl: data?.imageUrl,
 | 
			
		||||
              conversationOptions: {
 | 
			
		||||
                conversationId: data?.conversationId,
 | 
			
		||||
                parentMessageId: data?.id,
 | 
			
		||||
@@ -594,6 +671,7 @@ async function onConversation(msg?: string) {
 | 
			
		||||
      return
 | 
			
		||||
    }
 | 
			
		||||
    updateGroupChat(dataSources.value.length - 1, {
 | 
			
		||||
      imageUrl: data.imageUrl,
 | 
			
		||||
      dateTime: new Date().toLocaleString(),
 | 
			
		||||
      text: errorMessage,
 | 
			
		||||
      inversion: false,
 | 
			
		||||
@@ -607,6 +685,8 @@ async function onConversation(msg?: string) {
 | 
			
		||||
    loading.value = false
 | 
			
		||||
    isStreamIn.value = false
 | 
			
		||||
    imageUrl = null
 | 
			
		||||
    typingStatusEnd.value = true
 | 
			
		||||
    scrollToBottom()
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -818,6 +898,7 @@ onUnmounted(() => {
 | 
			
		||||
                :imageUrl="item.imageUrl"
 | 
			
		||||
                @regenerate="handleSubmit(index)"
 | 
			
		||||
                @delete="handleDelete(item)"
 | 
			
		||||
                @video="hendleVideo(item)"
 | 
			
		||||
              />
 | 
			
		||||
              <div class="sticky bottom-1 left-0 flex justify-center">
 | 
			
		||||
                <NButton v-if="loading" @click="handleStop">
 | 
			
		||||
@@ -844,6 +925,7 @@ onUnmounted(() => {
 | 
			
		||||
                @click="toggleUsingContext"
 | 
			
		||||
              >
 | 
			
		||||
                <span
 | 
			
		||||
                  class=""
 | 
			
		||||
                  :class="{
 | 
			
		||||
                    'text-[#3076fd]': usingContext,
 | 
			
		||||
                    'text-[#ffffff]': !usingContext,
 | 
			
		||||
@@ -971,7 +1053,6 @@ onUnmounted(() => {
 | 
			
		||||
          </NTooltip>
 | 
			
		||||
        </div>
 | 
			
		||||
      </div>
 | 
			
		||||
 | 
			
		||||
      <div
 | 
			
		||||
        class="m-auto max-w-screen-4xl"
 | 
			
		||||
        :class="[isMobile ? 'px-2 py-1' : 'px-4 py-2']"
 | 
			
		||||
@@ -1097,35 +1178,6 @@ onUnmounted(() => {
 | 
			
		||||
                  </div>
 | 
			
		||||
                </div>
 | 
			
		||||
                <div class="flex justify-between items-center">
 | 
			
		||||
                  <!-- <div
 | 
			
		||||
                    class="flex items-center text-neutral-400 cursor-pointer hover:text-[#3076fd]"
 | 
			
		||||
                  >
 | 
			
		||||
                    <span class="ml-2 mr-2 text-xs" @click="toggleUsingNetwork"
 | 
			
		||||
                      >{{ usingNetwork ? '关闭' : '开启' }}联网访问</span
 | 
			
		||||
                    >
 | 
			
		||||
                    <NTooltip trigger="hover" :disabled="isMobile">
 | 
			
		||||
                      <template #trigger>
 | 
			
		||||
                        <SvgIcon
 | 
			
		||||
                          icon="zondicons:network"
 | 
			
		||||
                          class="cursor-pointer mb-0.5"
 | 
			
		||||
                          :class="[
 | 
			
		||||
                            {
 | 
			
		||||
                              'text-[#3076fd]': usingNetwork,
 | 
			
		||||
                              '': !usingNetwork,
 | 
			
		||||
                            },
 | 
			
		||||
                          ]"
 | 
			
		||||
                          @click="toggleUsingNetwork"
 | 
			
		||||
                        />
 | 
			
		||||
                      </template>
 | 
			
		||||
                      {{ usingNetwork ? '关闭联网模式' : '开启联网模式' }}
 | 
			
		||||
                    </NTooltip>
 | 
			
		||||
                    <div
 | 
			
		||||
                      class="mx-4 h-full text-neutral-300 dark:text-neutral-600"
 | 
			
		||||
                    >
 | 
			
		||||
                      |
 | 
			
		||||
                    </div>
 | 
			
		||||
                  </div> -->
 | 
			
		||||
 | 
			
		||||
                  <NButton
 | 
			
		||||
                    type="primary"
 | 
			
		||||
                    size="small"
 | 
			
		||||
@@ -1181,6 +1233,10 @@ onUnmounted(() => {
 | 
			
		||||
        />
 | 
			
		||||
      </NCard>
 | 
			
		||||
    </NModal>
 | 
			
		||||
 | 
			
		||||
    <!-- <audio ref="audioRef" controls>
 | 
			
		||||
      <source type="audio/mpeg" />
 | 
			
		||||
    </audio> -->
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -61,11 +61,9 @@ const themeOptions: {
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
const modelName = computed(() => {
 | 
			
		||||
  if (!chatStore.activeConfig)
 | 
			
		||||
    return
 | 
			
		||||
  if (!chatStore.activeConfig) return
 | 
			
		||||
  const { modelTypeInfo, modelInfo } = chatStore.activeConfig
 | 
			
		||||
  if (!modelTypeInfo || !modelInfo)
 | 
			
		||||
    return
 | 
			
		||||
  if (!modelTypeInfo || !modelInfo) return
 | 
			
		||||
  return `${modelTypeInfo?.label} / ${modelInfo.modelName}`
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
@@ -85,8 +83,7 @@ function handleUpdateCollapsed() {
 | 
			
		||||
 | 
			
		||||
function onScrollToTop() {
 | 
			
		||||
  const scrollRef = document.querySelector('#scrollRef')
 | 
			
		||||
  if (scrollRef)
 | 
			
		||||
    nextTick(() => (scrollRef.scrollTop = 0))
 | 
			
		||||
  if (scrollRef) nextTick(() => (scrollRef.scrollTop = 0))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function handleExport() {
 | 
			
		||||
@@ -216,7 +213,9 @@ function handleSignIn() {
 | 
			
		||||
                  class="flex h-8 w-8 items-center justify-center rounded border transition hover:bg-[#eef0f3] dark:border-neutral-700 dark:hover:bg-[#33373c]"
 | 
			
		||||
                  @click="handleClear"
 | 
			
		||||
                >
 | 
			
		||||
                  <span class="text-base text-slate-500 dark:text-slate-400"><SvgIcon icon="material-symbols:delete-outline"/></span>
 | 
			
		||||
                  <span class="text-base text-slate-500 dark:text-slate-400"
 | 
			
		||||
                    ><SvgIcon icon="material-symbols:delete-outline"
 | 
			
		||||
                  /></span>
 | 
			
		||||
                </button>
 | 
			
		||||
              </template>
 | 
			
		||||
              删除本页内容
 | 
			
		||||
@@ -227,7 +226,9 @@ function handleSignIn() {
 | 
			
		||||
                  class="flex h-8 w-8 items-center justify-center rounded border transition hover:bg-[#eef0f3] dark:border-neutral-700 dark:hover:bg-[#33373c]"
 | 
			
		||||
                  @click="handleScrollBtm"
 | 
			
		||||
                >
 | 
			
		||||
                  <span class="text-base text-slate-500 dark:text-slate-400"><SvgIcon icon="material-symbols:keyboard-arrow-down" /></span>
 | 
			
		||||
                  <span class="text-base text-slate-500 dark:text-slate-400"
 | 
			
		||||
                    ><SvgIcon icon="material-symbols:keyboard-arrow-down"
 | 
			
		||||
                  /></span>
 | 
			
		||||
                </button>
 | 
			
		||||
              </template>
 | 
			
		||||
              滚动到底部
 | 
			
		||||
 
 | 
			
		||||
@@ -4,11 +4,13 @@ import MarkdownIt from 'markdown-it'
 | 
			
		||||
import mdKatex from '@traptitech/markdown-it-katex'
 | 
			
		||||
import mila from 'markdown-it-link-attributes'
 | 
			
		||||
import hljs from 'highlight.js'
 | 
			
		||||
import { NButton, NIcon } from 'naive-ui'
 | 
			
		||||
import { NButton, NIcon, NImage } from 'naive-ui'
 | 
			
		||||
import { Copy, Delete } from '@icon-park/vue-next'
 | 
			
		||||
import { useAppStore } from '@/store'
 | 
			
		||||
import { useBasicLayout } from '@/hooks/useBasicLayout'
 | 
			
		||||
import { t } from '@/locales'
 | 
			
		||||
import { SvgIcon } from '@/components/common'
 | 
			
		||||
 | 
			
		||||
interface Props {
 | 
			
		||||
  inversion?: boolean
 | 
			
		||||
  error?: boolean
 | 
			
		||||
@@ -22,6 +24,7 @@ interface Emit {
 | 
			
		||||
  (ev: 'regenerate'): void
 | 
			
		||||
  (ev: 'delete'): void
 | 
			
		||||
  (ev: 'copy'): void
 | 
			
		||||
  (ev: 'video'): void
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const props = defineProps<Props>()
 | 
			
		||||
@@ -106,6 +109,10 @@ function handleRegenerate() {
 | 
			
		||||
  emit('regenerate')
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function hendleVideo() {
 | 
			
		||||
  emit('video')
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function handleCopy() {
 | 
			
		||||
  emit('copy')
 | 
			
		||||
}
 | 
			
		||||
@@ -118,6 +125,7 @@ defineExpose({ textRef })
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<template>
 | 
			
		||||
  <div class="flex flex-col group max-w-full" style="width: auto">
 | 
			
		||||
    <div :class="wrapClass" class="w-full" style="width: auto">
 | 
			
		||||
      <div ref="textRef" class="leading-relaxed break-words">
 | 
			
		||||
        <div v-if="!inversion" class="flex flex-col items-start">
 | 
			
		||||
@@ -129,95 +137,125 @@ defineExpose({ textRef })
 | 
			
		||||
              v-html="text"
 | 
			
		||||
            />
 | 
			
		||||
            <div v-else class="w-full whitespace-pre-wrap" v-text="text" />
 | 
			
		||||
          <span
 | 
			
		||||
            <!-- <span
 | 
			
		||||
            v-if="loading"
 | 
			
		||||
            class="dark:text-white w-[4px] h-[20px] block animate-blink"
 | 
			
		||||
            style="display: none"
 | 
			
		||||
            class="dark:text-white w-[4px] h-[10px] block animate-blink"
 | 
			
		||||
          /> -->
 | 
			
		||||
            <NImage
 | 
			
		||||
              v-if="imageUrl && isImageUrl"
 | 
			
		||||
              :src="imageUrl"
 | 
			
		||||
              :preview-src="imageUrl"
 | 
			
		||||
              alt="图片"
 | 
			
		||||
              class="h-md rounded-md m-1"
 | 
			
		||||
              :style="{ 'max-width': isMobile ? '100%' : '20vw' }"
 | 
			
		||||
              style="margin-top: 0.5rem"
 | 
			
		||||
            />
 | 
			
		||||
          </div>
 | 
			
		||||
          <!-- 小易改动:注册掉底部的内容 -->
 | 
			
		||||
        <!-- <div style="margin-top: 0.5rem"> -->
 | 
			
		||||
        <!-- <NButton class="ml-2" text type="primary" @click="handleCopy">
 | 
			
		||||
            <template #icon>
 | 
			
		||||
              <NIcon :size="10" :component="Copy" />
 | 
			
		||||
            </template>
 | 
			
		||||
            <span class="text-xs">复制</span>
 | 
			
		||||
          </NButton>
 | 
			
		||||
        </div>
 | 
			
		||||
        <div v-else>
 | 
			
		||||
          <div class="whitespace-pre-wrap" v-text="text" />
 | 
			
		||||
          <NImage
 | 
			
		||||
            :src="imageUrl"
 | 
			
		||||
            :preview-src="imageUrl"
 | 
			
		||||
            alt="图片"
 | 
			
		||||
            class="h-md rounded-md m-1"
 | 
			
		||||
            :style="{ 'max-width': isMobile ? '100%' : '20vw' }"
 | 
			
		||||
            style="margin-top: 0.5rem"
 | 
			
		||||
            v-if="imageUrl && isImageUrl"
 | 
			
		||||
          />
 | 
			
		||||
        </div>
 | 
			
		||||
      </div>
 | 
			
		||||
    </div>
 | 
			
		||||
    <div
 | 
			
		||||
      class="flex opacity-0 transition-opacity duration-300 group-hover:opacity-100 text-gray-700"
 | 
			
		||||
    >
 | 
			
		||||
      <div v-if="!inversion">
 | 
			
		||||
        <div class="mt-1 flex">
 | 
			
		||||
          <button
 | 
			
		||||
            class="flex ml-0 items-center text-gray-400 hover:text-gray-700 dark:text-gray-600 dark:hover:text-gray-400 mx-1"
 | 
			
		||||
            text
 | 
			
		||||
            type="primary"
 | 
			
		||||
            @click="handleCopy"
 | 
			
		||||
          >
 | 
			
		||||
            <SvgIcon class="flex h-3 w-3 mx-1" icon="tabler:copy" />
 | 
			
		||||
            <span class="flex text-xs">复制</span>
 | 
			
		||||
          </button>
 | 
			
		||||
 | 
			
		||||
          <span style="margin-left: 0.5rem" />
 | 
			
		||||
          <NButton
 | 
			
		||||
            class="ml-2"
 | 
			
		||||
          <button
 | 
			
		||||
            class="flex ml-0 items-center text-gray-400 hover:text-gray-700 dark:text-gray-600 dark:hover:text-gray-400 mx-1"
 | 
			
		||||
            text
 | 
			
		||||
            type="primary"
 | 
			
		||||
            @click="handleRegenerate"
 | 
			
		||||
          >
 | 
			
		||||
            <SvgIcon class="flex h-3 w-3 mx-1" icon="clarity:refresh-line" />
 | 
			
		||||
            <span class="flex text-xs">重新生成</span>
 | 
			
		||||
          </button>
 | 
			
		||||
 | 
			
		||||
          <button
 | 
			
		||||
            class="flex ml-0 items-center text-gray-400 hover:text-gray-700 dark:text-gray-600 dark:hover:text-gray-400 mx-1"
 | 
			
		||||
            text
 | 
			
		||||
            type="primary"
 | 
			
		||||
            @click="handleDelete"
 | 
			
		||||
          >
 | 
			
		||||
            <SvgIcon
 | 
			
		||||
              class="flex h-3 w-3 mx-1"
 | 
			
		||||
              icon="fluent:delete-48-regular"
 | 
			
		||||
            />
 | 
			
		||||
            <span class="flex text-xs">删除</span>
 | 
			
		||||
          </button>
 | 
			
		||||
 | 
			
		||||
          <button
 | 
			
		||||
            class="flex ml-0 items-center text-gray-400 hover:text-gray-700 dark:text-gray-600 dark:hover:text-gray-400 mx-1"
 | 
			
		||||
            text
 | 
			
		||||
            type="primary"
 | 
			
		||||
            @click="asRawText = !asRawText"
 | 
			
		||||
          >
 | 
			
		||||
            <template #icon>
 | 
			
		||||
            <SvgIcon
 | 
			
		||||
                class="text-xs"
 | 
			
		||||
              class="flex h-3 w-3 mx-1"
 | 
			
		||||
              :icon="asRawText ? 'ic:outline-code-off' : 'ic:outline-code'"
 | 
			
		||||
            />
 | 
			
		||||
            </template>
 | 
			
		||||
            <span class="text-xs">{{
 | 
			
		||||
            <span class="flex text-xs">{{
 | 
			
		||||
              asRawText ? t('chat.preview') : t('chat.showRawText')
 | 
			
		||||
            }}</span>
 | 
			
		||||
          </NButton> -->
 | 
			
		||||
        <!-- 删除BUG -->
 | 
			
		||||
        <!-- <span style="margin-left: 0.5rem" />
 | 
			
		||||
          <NButton text type="primary" @click="handleDelete">
 | 
			
		||||
            <template #icon>
 | 
			
		||||
              <NIcon :size="10" :component="Delete" />
 | 
			
		||||
            </template>
 | 
			
		||||
            <span class="text-xs">删除</span>
 | 
			
		||||
          </NButton> -->
 | 
			
		||||
          </button>
 | 
			
		||||
 | 
			
		||||
        <!-- <span style="margin-left: 0.5rem" />
 | 
			
		||||
          <NButton text type="primary" @click="handleRegenerate">
 | 
			
		||||
            <template #icon>
 | 
			
		||||
              <NIcon :size="10" :component="Refresh" />
 | 
			
		||||
            </template>
 | 
			
		||||
            <span class="text-xs">重新回答</span>
 | 
			
		||||
          </NButton> -->
 | 
			
		||||
        <!-- </div> -->
 | 
			
		||||
      </div>
 | 
			
		||||
      <div v-else>
 | 
			
		||||
        <div class="whitespace-pre-wrap" v-text="text" />
 | 
			
		||||
        <a v-if="imageUrl && isImageUrl" :href="imageUrl" target="_blank">
 | 
			
		||||
          <img
 | 
			
		||||
            :src="imageUrl"
 | 
			
		||||
            alt="图片"
 | 
			
		||||
            class="h-auto rounded-md mb-1"
 | 
			
		||||
            :class="{ 'max-w-full': isMobile, 'max-w-sm': !isMobile }"
 | 
			
		||||
            style="margin-top: 0.5rem"
 | 
			
		||||
          />
 | 
			
		||||
        </a>
 | 
			
		||||
        <a
 | 
			
		||||
          :href="imageUrl"
 | 
			
		||||
          target="_blank"
 | 
			
		||||
          :class="{ 'file-2': isMobile, 'file-1': !isMobile }"
 | 
			
		||||
          <!-- <button
 | 
			
		||||
            class="flex ml-0 items-center text-gray-400 hover:text-gray-700 dark:text-gray-600 dark:hover:text-gray-400 mx-1"
 | 
			
		||||
            text
 | 
			
		||||
            type="primary"
 | 
			
		||||
            @click="hendleVideo"
 | 
			
		||||
          >
 | 
			
		||||
          <img
 | 
			
		||||
            src="@/assets/file.jpeg"
 | 
			
		||||
            alt="文件"
 | 
			
		||||
            class="h-auto rounded-md mb-1"
 | 
			
		||||
            :class="{ 'file-2': isMobile, 'file-1': !isMobile }"
 | 
			
		||||
            v-if="imageUrl && !isImageUrl"
 | 
			
		||||
          />
 | 
			
		||||
        </a>
 | 
			
		||||
        <div v-if="false" style="margin-left: 0.5rem">
 | 
			
		||||
          <NButton class="ml-2" text color="#FFF" @click="handleCopy">
 | 
			
		||||
            <template #icon>
 | 
			
		||||
              <NIcon :size="10" :component="Copy" />
 | 
			
		||||
            </template>
 | 
			
		||||
            <img src="@/assets/voice.gif" class="flex h-3 w-3 mx-1" />
 | 
			
		||||
            <SvgIcon class="flex h-3 w-3 mx-1" icon="ep:video-play" />
 | 
			
		||||
            <span class="flex text-xs">播放</span>
 | 
			
		||||
          </button> -->
 | 
			
		||||
        </div>
 | 
			
		||||
      </div>
 | 
			
		||||
 | 
			
		||||
            <span class="text-xs">复制</span>
 | 
			
		||||
          </NButton>
 | 
			
		||||
          <span class="ml-3" />
 | 
			
		||||
          <NButton text color="#FFF" @click="handleDelete">
 | 
			
		||||
            <template #icon>
 | 
			
		||||
              <NIcon :size="10" :component="Delete" />
 | 
			
		||||
            </template>
 | 
			
		||||
            <span class="text-xs">删除</span>
 | 
			
		||||
          </NButton>
 | 
			
		||||
      <div v-else>
 | 
			
		||||
        <div class="mt-1 flex">
 | 
			
		||||
          <button
 | 
			
		||||
            class="flex ml-0 items-center text-gray-400 hover:text-gray-700 dark:text-gray-600 dark:hover:text-gray-400 mx-1"
 | 
			
		||||
            text
 | 
			
		||||
            type="primary"
 | 
			
		||||
            @click="handleCopy"
 | 
			
		||||
          >
 | 
			
		||||
            <SvgIcon class="flex h-3 w-3 mx-1" icon="tabler:copy" />
 | 
			
		||||
            <span class="flex text-xs">复制</span>
 | 
			
		||||
          </button>
 | 
			
		||||
          <button
 | 
			
		||||
            class="flex ml-0 items-center text-gray-400 hover:text-gray-700 dark:text-gray-600 dark:hover:text-gray-400 mx-1"
 | 
			
		||||
            text
 | 
			
		||||
            type="primary"
 | 
			
		||||
            @click="handleDelete"
 | 
			
		||||
          >
 | 
			
		||||
            <SvgIcon
 | 
			
		||||
              class="flex h-3 w-3 mx-1"
 | 
			
		||||
              icon="fluent:delete-48-regular"
 | 
			
		||||
            />
 | 
			
		||||
            <span class="flex text-xs">删除</span>
 | 
			
		||||
          </button>
 | 
			
		||||
        </div>
 | 
			
		||||
      </div>
 | 
			
		||||
    </div>
 | 
			
		||||
 
 | 
			
		||||
@@ -21,6 +21,7 @@ interface Props {
 | 
			
		||||
interface Emit {
 | 
			
		||||
  (ev: 'regenerate'): void
 | 
			
		||||
  (ev: 'delete'): void
 | 
			
		||||
  (ev: 'video'): void
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const props = defineProps<Props>()
 | 
			
		||||
@@ -81,6 +82,10 @@ function handleDetele() {
 | 
			
		||||
  emit('delete')
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function hendleVideo() {
 | 
			
		||||
  emit('video')
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function handleCopy() {
 | 
			
		||||
  copyText({ text: props.text ?? '' })
 | 
			
		||||
  props.text && ms.success('复制成功!')
 | 
			
		||||
@@ -128,9 +133,10 @@ function handleRegenerate() {
 | 
			
		||||
          @regenerate="handleRegenerate"
 | 
			
		||||
          @copy="handleCopy"
 | 
			
		||||
          @delete="handleDetele"
 | 
			
		||||
          @video="hendleVideo"
 | 
			
		||||
          :imageUrl="imageUrl"
 | 
			
		||||
        />
 | 
			
		||||
        <div class="flex flex-col">
 | 
			
		||||
        <!-- <div class="flex flex-col">
 | 
			
		||||
          <button
 | 
			
		||||
            v-if="!inversion"
 | 
			
		||||
            class="flex mb-2 transition text-neutral-300 hover:text-neutral-800 dark:hover:text-neutral-300"
 | 
			
		||||
@@ -150,7 +156,7 @@ function handleRegenerate() {
 | 
			
		||||
              <SvgIcon icon="ri:more-2-fill" />
 | 
			
		||||
            </button>
 | 
			
		||||
          </NDropdown>
 | 
			
		||||
        </div>
 | 
			
		||||
        </div> -->
 | 
			
		||||
      </div>
 | 
			
		||||
    </div>
 | 
			
		||||
  </div>
 | 
			
		||||
 
 | 
			
		||||
@@ -6,7 +6,7 @@ import { fetchChatDraw, fetchGetAllChatLogDraw, fetchGetChatLogDraw } from '@/ap
 | 
			
		||||
import { useBasicLayout } from '@/hooks/useBasicLayout'
 | 
			
		||||
import { TitleBar } from '@/components/base'
 | 
			
		||||
import { useAppStore, useAuthStore } from '@/store'
 | 
			
		||||
import GridManager from '@/components/common/GridManager/index.vue'
 | 
			
		||||
import OldGridManager from '@/components/common/OldGridManager/index.vue'
 | 
			
		||||
 | 
			
		||||
import Loading from '@/components/base/Loading.vue'
 | 
			
		||||
const theme = computed(() => appStore.theme)
 | 
			
		||||
@@ -73,14 +73,14 @@ function updateEx() {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
async function queryMyDrawList() {
 | 
			
		||||
  const res: ResData = await fetchGetChatLogDraw({ model: 'DALL-E2' })
 | 
			
		||||
  const res: ResData = await fetchGetChatLogDraw({ model: 'dall-e-3' })
 | 
			
		||||
  if (!res.success)
 | 
			
		||||
    return
 | 
			
		||||
	mineDrawList.value = formatFileInfo(res.data)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
async function queryAllDrawList() {
 | 
			
		||||
  const res: ResData = await fetchGetAllChatLogDraw({ size: 999, rec: 1, model: 'DALL-E2' })
 | 
			
		||||
  const res: ResData = await fetchGetAllChatLogDraw({ size: 999, rec: 1, model: 'dall-e-3' })
 | 
			
		||||
  if (!res.success)
 | 
			
		||||
    return ms.error(res.message)
 | 
			
		||||
  allDrawList.value = formatFileInfo(res.data.rows)
 | 
			
		||||
@@ -184,13 +184,13 @@ onMounted(() => {
 | 
			
		||||
          参数设置
 | 
			
		||||
        </h4>
 | 
			
		||||
        <div class="flex items-center mt-5">
 | 
			
		||||
          <span class="mr-2 inline-block w-16 flex-shrink-0">图片尺寸:</span>
 | 
			
		||||
          <span class="mr-2 inline-block w-20 flex-shrink-0">图片尺寸:</span>
 | 
			
		||||
          <div>
 | 
			
		||||
            <span v-for="item in imageSizeList" :key="item.value"  class="rounded ml-2 select-none cursor-pointer inline-block mb-2" :class="[item.value === form.size ? ['text-primary', 'bg-[#0d6efd1c]'] : ['bg-[#bfc4d033]'], isMobile ? 'px-1.5 py-0.5' : 'px-3 py-1']" @click="form.size = item.value">{{ item.label }}</span>
 | 
			
		||||
          </div>
 | 
			
		||||
        </div>
 | 
			
		||||
				<div class="flex items-center mt-5">
 | 
			
		||||
          <span class="mr-2 inline-block w-16 flex-shrink-0">图片质量:</span>
 | 
			
		||||
          <span class="mr-2 inline-block w-20 flex-shrink-0">图片质量:</span>
 | 
			
		||||
          <div>
 | 
			
		||||
            <span v-for="item in qualityList" :key="item.value" class=" py-0.5 px-2.5 rounded ml-2 select-none cursor-pointer inline-block mb-2" :class="item.value === form.quality ? ['text-primary', 'bg-[#0d6efd1c]'] : ['bg-[#bfc4d033]']" @click="form.quality = item.value">{{ item.label }}</span>
 | 
			
		||||
          </div>
 | 
			
		||||
@@ -232,13 +232,13 @@ onMounted(() => {
 | 
			
		||||
      <NTabs type="line" animated class="mt-5" @update:value="updateTabs">
 | 
			
		||||
        <NTabPane name="all" tab="公共生成">
 | 
			
		||||
					<div v-if="allDrawList.length" class="min-h-screen">
 | 
			
		||||
						<GridManager @loadMore="loadMore" usePropmpt :gap="8" preOrigin  @usePropmptDraw="usePropmptDraw" :dataList="allDrawList" :scaleWidth="50" />
 | 
			
		||||
						<OldGridManager @loadMore="loadMore" usePropmpt :gap="8" preOrigin  @usePropmptDraw="usePropmptDraw" :dataList="allDrawList" :scaleWidth="50" />
 | 
			
		||||
					</div>
 | 
			
		||||
          <NEmpty v-else size="huge" class="mt-20" description="暂无数据哟~" />
 | 
			
		||||
        </NTabPane>
 | 
			
		||||
        <NTabPane name="mine" tab="我的生成">
 | 
			
		||||
					<div v-if="mineDrawList.length" class="min-h-screen">
 | 
			
		||||
						<GridManager @loadMore="loadMore" usePropmpt :gap="8" preOrigin @usePropmptDraw="usePropmptDraw" :dataList="mineDrawList" :scaleWidth="50" />
 | 
			
		||||
						<OldGridManager @loadMore="loadMore" usePropmpt :gap="8" preOrigin @usePropmptDraw="usePropmptDraw" :dataList="mineDrawList" :scaleWidth="50" />
 | 
			
		||||
					</div>
 | 
			
		||||
          <NEmpty v-else size="huge" class="mt-20" description="暂无数据哟~" />
 | 
			
		||||
        </NTabPane>
 | 
			
		||||
 
 | 
			
		||||
@@ -7,8 +7,8 @@ import { NButton } from 'naive-ui'
 | 
			
		||||
 | 
			
		||||
const proxyImgBase = ref('')
 | 
			
		||||
 | 
			
		||||
const mode1Url = 'https://chevereto.jiangly.com/images/2023/11/21/61888bd4ede4.png'
 | 
			
		||||
const mode2Url = 'https://chevereto.jiangly.com/images/2023/11/19/shoes.jpg'
 | 
			
		||||
const mode1Url = ''
 | 
			
		||||
const mode2Url = ''
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
const canvasRef = ref<any>(null)
 | 
			
		||||
 
 | 
			
		||||
@@ -1,56 +1,61 @@
 | 
			
		||||
<script lang="ts" setup>
 | 
			
		||||
	import { onMounted, ref, computed } from 'vue'
 | 
			
		||||
	import { fetchMidjourneyGetList } from '@/api'
 | 
			
		||||
	import type { ResData } from '@/api/types'
 | 
			
		||||
	import { NSlider, NInput, NIcon } from 'naive-ui'
 | 
			
		||||
	import GridManager from '@/components/common/GridManager/index.vue'
 | 
			
		||||
	import { FlashOutline } from '@vicons/ionicons5'
 | 
			
		||||
import { onMounted, ref, computed } from 'vue';
 | 
			
		||||
import { fetchMidjourneyGetList } from '@/api';
 | 
			
		||||
import type { ResData } from '@/api/types';
 | 
			
		||||
import { NSlider, NInput, NIcon } from 'naive-ui';
 | 
			
		||||
import GridManager2 from '@/components/common/GridManager2/index.vue';
 | 
			
		||||
import { FlashOutline } from '@vicons/ionicons5';
 | 
			
		||||
 | 
			
		||||
	const imageList = ref<any>([])
 | 
			
		||||
	const wapperRef = ref<HTMLDivElement | null>(null)
 | 
			
		||||
	const scaleWidth = ref(50)
 | 
			
		||||
	const keyword = ref('')
 | 
			
		||||
	const page = ref(1)
 | 
			
		||||
	const size = ref(20)
 | 
			
		||||
	const loading = ref(false)
 | 
			
		||||
	const isMore = ref(true)
 | 
			
		||||
const imageList = ref<any>([]);
 | 
			
		||||
const wapperRef = ref<HTMLDivElement | null>(null);
 | 
			
		||||
const scaleWidth = ref(50);
 | 
			
		||||
const keyword = ref('');
 | 
			
		||||
const page = ref(1);
 | 
			
		||||
const size = ref(20);
 | 
			
		||||
const loading = ref(false);
 | 
			
		||||
const isMore = ref(true);
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
	const dataList = computed(() => {
 | 
			
		||||
		if(!keyword.value){
 | 
			
		||||
			return imageList.value
 | 
			
		||||
		}else{
 | 
			
		||||
const dataList = computed(() => {
 | 
			
		||||
  if (!keyword.value) {
 | 
			
		||||
    return imageList.value;
 | 
			
		||||
  } else {
 | 
			
		||||
    return imageList.value.filter((item: any) => {
 | 
			
		||||
				const { prompt } = item
 | 
			
		||||
				return prompt.includes(keyword.value)
 | 
			
		||||
			})
 | 
			
		||||
      const { prompt } = item;
 | 
			
		||||
      return prompt.includes(keyword.value);
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
	})
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
	async function queryDrawImg() {
 | 
			
		||||
		loading.value = true
 | 
			
		||||
		const res: ResData = await fetchMidjourneyGetList({ page: page.value ,size: size.value, rec: 1 })
 | 
			
		||||
		loading.value = false
 | 
			
		||||
		isMore.value = size.value === res.data.rows.length
 | 
			
		||||
		imageList.value = [...imageList.value, ...res.data.rows]
 | 
			
		||||
	}
 | 
			
		||||
async function queryDrawImg() {
 | 
			
		||||
  loading.value = true;
 | 
			
		||||
  const res: ResData = await fetchMidjourneyGetList({
 | 
			
		||||
    page: page.value,
 | 
			
		||||
    size: size.value,
 | 
			
		||||
    rec: 1,
 | 
			
		||||
  });
 | 
			
		||||
  loading.value = false;
 | 
			
		||||
  isMore.value = size.value === res.data.rows.length;
 | 
			
		||||
  imageList.value = [...imageList.value, ...res.data.rows];
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
	onMounted(async () => {
 | 
			
		||||
		await queryDrawImg()
 | 
			
		||||
	})
 | 
			
		||||
onMounted(async () => {
 | 
			
		||||
  await queryDrawImg();
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
	function loadMore(){
 | 
			
		||||
		page.value = page.value + 1
 | 
			
		||||
		queryDrawImg()
 | 
			
		||||
	}
 | 
			
		||||
	</script>
 | 
			
		||||
function loadMore() {
 | 
			
		||||
  page.value = page.value + 1;
 | 
			
		||||
  queryDrawImg();
 | 
			
		||||
}
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
	<template>
 | 
			
		||||
		<div class="bg-[#fff] h-[100vh] overflow-hidden p-4 pr-0 dark:bg-[#18181c] flex flex-col">
 | 
			
		||||
<template>
 | 
			
		||||
  <div
 | 
			
		||||
    class="bg-[#fff] h-[100vh] overflow-hidden p-4 pr-0 dark:bg-[#18181c] flex flex-col"
 | 
			
		||||
  >
 | 
			
		||||
    <div class="p-4 flex pr-6 justify-between items-center">
 | 
			
		||||
      <div class="font-bold text-xl">AI绘画广场</div>
 | 
			
		||||
      <div class="w-[200px] sm:w-[300px] flex justify-between">
 | 
			
		||||
					<span class="hidden sm:block"  >尺寸调整</span>
 | 
			
		||||
        <span class="hidden sm:block">尺寸调整</span>
 | 
			
		||||
        <div class="flex-1 ml-5">
 | 
			
		||||
          <n-slider v-model:value="scaleWidth" :step="10" />
 | 
			
		||||
        </div>
 | 
			
		||||
@@ -63,45 +68,51 @@
 | 
			
		||||
        </template>
 | 
			
		||||
      </n-input>
 | 
			
		||||
    </div>
 | 
			
		||||
			<div class="market  overflow-y-scroll flex-1 min-h-screen  p-4 dark:bg-[#18181c] relative ">
 | 
			
		||||
    <div
 | 
			
		||||
      class="market overflow-y-scroll flex-1 min-h-screen p-4 dark:bg-[#18181c] relative"
 | 
			
		||||
    >
 | 
			
		||||
      <div id="wapper" ref="wapperRef" class="wapper">
 | 
			
		||||
					<GridManager @loadMore="loadMore" copyPropmpt isDrawLike :dataList="dataList" :scaleWidth="scaleWidth" />
 | 
			
		||||
        <GridManager2
 | 
			
		||||
          @loadMore="loadMore"
 | 
			
		||||
          copyPropmpt
 | 
			
		||||
          isDrawLike
 | 
			
		||||
          :dataList="dataList"
 | 
			
		||||
          :scaleWidth="scaleWidth"
 | 
			
		||||
        />
 | 
			
		||||
      </div>
 | 
			
		||||
    </div>
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
	</template>
 | 
			
		||||
 | 
			
		||||
	<style lang="less">
 | 
			
		||||
	.market{
 | 
			
		||||
<style lang="less">
 | 
			
		||||
.market {
 | 
			
		||||
  padding: 15px;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
	.wapper{
 | 
			
		||||
.wapper {
 | 
			
		||||
  width: 100%;
 | 
			
		||||
  position: relative;
 | 
			
		||||
  height: 100%;
 | 
			
		||||
  padding-bottom: 20px;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
		&-item{
 | 
			
		||||
  &-item {
 | 
			
		||||
    z-index: 10;
 | 
			
		||||
    overflow: hidden;
 | 
			
		||||
    position: absolute;
 | 
			
		||||
    transition: all 0.5s;
 | 
			
		||||
    cursor: pointer;
 | 
			
		||||
 | 
			
		||||
			&:hover{
 | 
			
		||||
				.menu{
 | 
			
		||||
    &:hover {
 | 
			
		||||
      .menu {
 | 
			
		||||
        transition: transform 0.3s ease-in-out;
 | 
			
		||||
        transform: translateY(-10px);
 | 
			
		||||
      }
 | 
			
		||||
				img{
 | 
			
		||||
      img {
 | 
			
		||||
        transform: scale(1.1);
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
			.menu{
 | 
			
		||||
    .menu {
 | 
			
		||||
      position: absolute;
 | 
			
		||||
      bottom: 0;
 | 
			
		||||
      width: 94%;
 | 
			
		||||
@@ -111,43 +122,42 @@
 | 
			
		||||
      transform: translateY(100%);
 | 
			
		||||
      background-color: #090b15;
 | 
			
		||||
      opacity: 0.8;
 | 
			
		||||
				transition: all .1s cubic-bezier(0.68, -0.55, 0.265, 1.55);
 | 
			
		||||
      transition: all 0.1s cubic-bezier(0.68, -0.55, 0.265, 1.55);
 | 
			
		||||
      border-radius: 10px;
 | 
			
		||||
      overflow: hidden;
 | 
			
		||||
      display: flex;
 | 
			
		||||
      flex-direction: column;
 | 
			
		||||
      justify-content: space-between;
 | 
			
		||||
 | 
			
		||||
				.prompt{
 | 
			
		||||
      .prompt {
 | 
			
		||||
        height: 50px;
 | 
			
		||||
        overflow: hidden;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
			img{
 | 
			
		||||
    img {
 | 
			
		||||
      user-select: none;
 | 
			
		||||
      cursor: pointer;
 | 
			
		||||
				transition: all .6s cubic-bezier(0.19, 1, 0.22, 1);
 | 
			
		||||
      transition: all 0.6s cubic-bezier(0.19, 1, 0.22, 1);
 | 
			
		||||
      border-radius: 6px;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
			.item-loading{
 | 
			
		||||
    .item-loading {
 | 
			
		||||
      background: url(../../assets/img-bg.png) no-repeat center center;
 | 
			
		||||
      filter: blur(20px);
 | 
			
		||||
      position: absolute;
 | 
			
		||||
      top: 0;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
	.img-enter-active, .img-leave-active {
 | 
			
		||||
		transition: transform .3s;
 | 
			
		||||
	}
 | 
			
		||||
	.img-enter, .img-leave-to{
 | 
			
		||||
		transform: scale(.6);
 | 
			
		||||
.img-enter-active,
 | 
			
		||||
.img-leave-active {
 | 
			
		||||
  transition: transform 0.3s;
 | 
			
		||||
}
 | 
			
		||||
.img-enter,
 | 
			
		||||
.img-leave-to {
 | 
			
		||||
  transform: scale(0.6);
 | 
			
		||||
  opacity: 0;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
	</style>
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
 
 | 
			
		||||
@@ -1,67 +1,71 @@
 | 
			
		||||
<script lang='ts' setup>
 | 
			
		||||
import { NButton, NImage, NInput, NInputNumber, NSelect, NSpace, NSwitch, NTag, NTooltip, useDialog, useMessage, NScrollbar } from 'naive-ui'
 | 
			
		||||
import { onMounted, reactive, ref, toRefs, watch, computed } from 'vue'
 | 
			
		||||
import axios from 'axios'
 | 
			
		||||
import { fetchDownloadImg } from '@/api'
 | 
			
		||||
import type { ResData } from '@/api/types'
 | 
			
		||||
import failImg from '@/assets/fail.png'
 | 
			
		||||
import drawSvg from '@/assets/icons/draw.svg'
 | 
			
		||||
import zoomSvg from '@/assets/icons/zoom.svg'
 | 
			
		||||
import { useAppStore, useAuthStore } from '@/store'
 | 
			
		||||
import { fetchDrawTaskAPI, fetchTranslateAPI } from '@/api/mjDraw'
 | 
			
		||||
import { SvgIcon } from '@/components/common'
 | 
			
		||||
import Loading from '@/components/base/Loading.vue'
 | 
			
		||||
<script lang="ts" setup>
 | 
			
		||||
import {
 | 
			
		||||
  NButton,
 | 
			
		||||
  NImage,
 | 
			
		||||
  NInput,
 | 
			
		||||
  NInputNumber,
 | 
			
		||||
  NSelect,
 | 
			
		||||
  NSpace,
 | 
			
		||||
  NSwitch,
 | 
			
		||||
  NTag,
 | 
			
		||||
  NTooltip,
 | 
			
		||||
  useDialog,
 | 
			
		||||
  useMessage,
 | 
			
		||||
  NScrollbar,
 | 
			
		||||
} from 'naive-ui';
 | 
			
		||||
import { onMounted, reactive, ref, toRefs, watch, computed } from 'vue';
 | 
			
		||||
import axios from 'axios';
 | 
			
		||||
import { fetchDownloadImg } from '@/api';
 | 
			
		||||
import type { ResData } from '@/api/types';
 | 
			
		||||
import failImg from '@/assets/fail.png';
 | 
			
		||||
import drawSvg from '@/assets/icons/draw.svg';
 | 
			
		||||
import zoomSvg from '@/assets/icons/zoom.svg';
 | 
			
		||||
import { useAppStore, useAuthStore } from '@/store';
 | 
			
		||||
import { fetchDrawTaskAPI, fetchTranslateAPI } from '@/api/mjDraw';
 | 
			
		||||
import { SvgIcon } from '@/components/common';
 | 
			
		||||
import Loading from '@/components/base/Loading.vue';
 | 
			
		||||
 | 
			
		||||
interface Emits {
 | 
			
		||||
	(e: 'usePrompt', val: any): void
 | 
			
		||||
	(e: 'queryData'): void
 | 
			
		||||
  (e: 'usePrompt', val: any): void;
 | 
			
		||||
  (e: 'queryData'): void;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
interface Props {
 | 
			
		||||
	drawItemInfo: any
 | 
			
		||||
  drawItemInfo: any;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const emit = defineEmits<Emits>()
 | 
			
		||||
const appStore = useAppStore()
 | 
			
		||||
const authStore = useAuthStore()
 | 
			
		||||
const theme = computed(() => appStore.theme)
 | 
			
		||||
const loadingTextColor = computed(() => theme.value === 'dark' ? '#fff' : '#000')
 | 
			
		||||
const props = defineProps<Props>()
 | 
			
		||||
const dialog = useDialog()
 | 
			
		||||
const ms = useMessage()
 | 
			
		||||
const downloadUrl = `${import.meta.env.VITE_GLOB_API_URL}/midjourney/download`
 | 
			
		||||
const refreshLoading = ref(false)
 | 
			
		||||
const emit = defineEmits<Emits>();
 | 
			
		||||
const appStore = useAppStore();
 | 
			
		||||
const authStore = useAuthStore();
 | 
			
		||||
const theme = computed(() => appStore.theme);
 | 
			
		||||
const loadingTextColor = computed(() =>
 | 
			
		||||
  theme.value === 'dark' ? '#fff' : '#000'
 | 
			
		||||
);
 | 
			
		||||
const props = defineProps<Props>();
 | 
			
		||||
const dialog = useDialog();
 | 
			
		||||
const ms = useMessage();
 | 
			
		||||
const downloadUrl = `${import.meta.env.VITE_GLOB_API_URL}/midjourney/download`;
 | 
			
		||||
const refreshLoading = ref(false);
 | 
			
		||||
 | 
			
		||||
const statusType: any = computed(() => {
 | 
			
		||||
	const  { status } = props.drawItemInfo
 | 
			
		||||
	if (status === 1)
 | 
			
		||||
    return ''
 | 
			
		||||
  if (status === 2)
 | 
			
		||||
    return 'info'
 | 
			
		||||
  if (status === 3)
 | 
			
		||||
    return 'primary'
 | 
			
		||||
  if (status === 4)
 | 
			
		||||
    return 'error'
 | 
			
		||||
  if (status === 5)
 | 
			
		||||
    return 'error'
 | 
			
		||||
})
 | 
			
		||||
  const { status } = props.drawItemInfo;
 | 
			
		||||
  if (status === 1) return '';
 | 
			
		||||
  if (status === 2) return 'info';
 | 
			
		||||
  if (status === 3) return 'primary';
 | 
			
		||||
  if (status === 4) return 'error';
 | 
			
		||||
  if (status === 5) return 'error';
 | 
			
		||||
});
 | 
			
		||||
const statusMsg = computed(() => {
 | 
			
		||||
	const  { status } = props.drawItemInfo
 | 
			
		||||
  if (status === 1)
 | 
			
		||||
    return '等待中'
 | 
			
		||||
  if (status === 2)
 | 
			
		||||
    return '绘制中'
 | 
			
		||||
  if (status === 3)
 | 
			
		||||
    return '成功'
 | 
			
		||||
  if (status === 4)
 | 
			
		||||
    return '失败'
 | 
			
		||||
  if (status === 5)
 | 
			
		||||
    return '超时'
 | 
			
		||||
})
 | 
			
		||||
  const { status } = props.drawItemInfo;
 | 
			
		||||
  if (status === 1) return '等待中';
 | 
			
		||||
  if (status === 2) return '绘制中';
 | 
			
		||||
  if (status === 3) return '成功';
 | 
			
		||||
  if (status === 4) return '失败';
 | 
			
		||||
  if (status === 5) return '超时';
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
function usePrompt(){
 | 
			
		||||
	emit('usePrompt')
 | 
			
		||||
function usePrompt() {
 | 
			
		||||
  emit('usePrompt');
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* 下载图片 */
 | 
			
		||||
@@ -72,21 +76,27 @@ async function handleDownloadImg(item: any) {
 | 
			
		||||
    positiveText: '下载',
 | 
			
		||||
    negativeText: '取消',
 | 
			
		||||
    onPositiveClick: async () => {
 | 
			
		||||
			d.loading = true
 | 
			
		||||
      d.loading = true;
 | 
			
		||||
      return new Promise(async (resolve) => {
 | 
			
		||||
				const { fileInfo } = item
 | 
			
		||||
				const { filename, cosUrl } = fileInfo
 | 
			
		||||
				const response = await axios.post(downloadUrl, { url: cosUrl }, { responseType: 'blob' })
 | 
			
		||||
				const blob = new Blob([response.data], { type: response.headers['content-type'] })
 | 
			
		||||
				const urlObject = window.URL.createObjectURL(blob)
 | 
			
		||||
				const link = document.createElement('a')
 | 
			
		||||
				link.href = urlObject
 | 
			
		||||
				link.download = filename
 | 
			
		||||
				link.click()
 | 
			
		||||
				resolve(true)
 | 
			
		||||
			})
 | 
			
		||||
    }
 | 
			
		||||
  })
 | 
			
		||||
        const { fileInfo } = item;
 | 
			
		||||
        const { filename, cosUrl } = fileInfo;
 | 
			
		||||
        const response = await axios.post(
 | 
			
		||||
          downloadUrl,
 | 
			
		||||
          { url: cosUrl },
 | 
			
		||||
          { responseType: 'blob' }
 | 
			
		||||
        );
 | 
			
		||||
        const blob = new Blob([response.data], {
 | 
			
		||||
          type: response.headers['content-type'],
 | 
			
		||||
        });
 | 
			
		||||
        const urlObject = window.URL.createObjectURL(blob);
 | 
			
		||||
        const link = document.createElement('a');
 | 
			
		||||
        link.href = urlObject;
 | 
			
		||||
        link.download = filename;
 | 
			
		||||
        link.click();
 | 
			
		||||
        resolve(true);
 | 
			
		||||
      });
 | 
			
		||||
    },
 | 
			
		||||
  });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* 删除图片 */
 | 
			
		||||
@@ -97,100 +107,94 @@ async function handleDeleteDraw(item: any) {
 | 
			
		||||
    positiveText: '删除',
 | 
			
		||||
    negativeText: '取消',
 | 
			
		||||
    onPositiveClick: async () => {
 | 
			
		||||
      const { id } = item
 | 
			
		||||
      const res: ResData = await fetchDownloadImg({ id })
 | 
			
		||||
      if (!res.success)
 | 
			
		||||
        return ms.error(res.message)
 | 
			
		||||
      ms.success('删除绘制记录成功!')
 | 
			
		||||
			emit('queryData')
 | 
			
		||||
      const { id } = item;
 | 
			
		||||
      const res: ResData = await fetchDownloadImg({ id });
 | 
			
		||||
      if (!res.success) return ms.error(res.message);
 | 
			
		||||
      ms.success('删除绘制记录成功!');
 | 
			
		||||
      emit('queryData');
 | 
			
		||||
    },
 | 
			
		||||
  })
 | 
			
		||||
  });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* 提交放大绘制任务 */
 | 
			
		||||
async function handleUpscale(item: any, orderId: number) {
 | 
			
		||||
  const { id } = item
 | 
			
		||||
  await fetchDrawTaskAPI({ drawId: id, action: 2, orderId })
 | 
			
		||||
  ms.success('提交放大绘制任务成功、请等待绘制结束!')
 | 
			
		||||
	if(authStore.token){
 | 
			
		||||
		await refreshUserInfo()
 | 
			
		||||
  const { drawId } = item;
 | 
			
		||||
  await fetchDrawTaskAPI({ drawId: drawId, action: 'UPSCALE', orderId });
 | 
			
		||||
  ms.success('提交放大绘制任务成功、请等待绘制结束!');
 | 
			
		||||
  if (authStore.token) {
 | 
			
		||||
    await refreshUserInfo();
 | 
			
		||||
  }
 | 
			
		||||
 emit('queryData')
 | 
			
		||||
  emit('queryData');
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* 提交重新生成任务 */
 | 
			
		||||
async function handleReGenerate(item: any, orderId: number) {
 | 
			
		||||
  const { id } = item
 | 
			
		||||
  await fetchDrawTaskAPI({ drawId: id, action: 5, orderId })
 | 
			
		||||
  ms.success('提交重新生成绘制任务成功、请等待绘制结束!')
 | 
			
		||||
	if(authStore.token){
 | 
			
		||||
		await refreshUserInfo()
 | 
			
		||||
  const { drawId } = item;
 | 
			
		||||
  await fetchDrawTaskAPI({ drawId: drawId, action: 'REGENERATE', orderId });
 | 
			
		||||
  ms.success('提交重新生成绘制任务成功、请等待绘制结束!');
 | 
			
		||||
  if (authStore.token) {
 | 
			
		||||
    await refreshUserInfo();
 | 
			
		||||
  }
 | 
			
		||||
 emit('queryData')
 | 
			
		||||
  emit('queryData');
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* 提交变体任务 */
 | 
			
		||||
async function handleVariation(item: any, orderId: number) {
 | 
			
		||||
  const { id } = item
 | 
			
		||||
  await fetchDrawTaskAPI({ drawId: id, action: 3, orderId })
 | 
			
		||||
  ms.success('提交图片变换绘制任务成功、请等待绘制结束!')
 | 
			
		||||
	if(authStore.token){
 | 
			
		||||
		await refreshUserInfo()
 | 
			
		||||
  const { drawId } = item;
 | 
			
		||||
  await fetchDrawTaskAPI({ drawId: drawId, action: 'VARIATION', orderId });
 | 
			
		||||
  ms.success('提交图片变换绘制任务成功、请等待绘制结束!');
 | 
			
		||||
  if (authStore.token) {
 | 
			
		||||
    await refreshUserInfo();
 | 
			
		||||
  }
 | 
			
		||||
 emit('queryData')
 | 
			
		||||
  emit('queryData');
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
async function refreshUserInfo() {
 | 
			
		||||
  refreshLoading.value = true
 | 
			
		||||
  refreshLoading.value = true;
 | 
			
		||||
  try {
 | 
			
		||||
    await authStore.getUserInfo()
 | 
			
		||||
    refreshLoading.value = false
 | 
			
		||||
  }
 | 
			
		||||
  catch (error) {
 | 
			
		||||
    refreshLoading.value = false
 | 
			
		||||
    await authStore.getUserInfo();
 | 
			
		||||
    refreshLoading.value = false;
 | 
			
		||||
  } catch (error) {
 | 
			
		||||
    refreshLoading.value = false;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const calcTips = computed(() => {
 | 
			
		||||
  const { progress, status } = props.drawItemInfo
 | 
			
		||||
  if (status === 1)
 | 
			
		||||
    return '正在排队中...'
 | 
			
		||||
  if (status === 2 && !progress)
 | 
			
		||||
    return '正在绘制中...'
 | 
			
		||||
  if (status === 2 && progress === 100)
 | 
			
		||||
    return '正在存储图片中...'
 | 
			
		||||
})
 | 
			
		||||
  const { progress, status } = props.drawItemInfo;
 | 
			
		||||
  if (status === 1) return '正在排队中...';
 | 
			
		||||
  if (status === 2 && !progress) return '正在绘制中...';
 | 
			
		||||
  if (status === 2 && progress === 100) return '正在存储图片中...';
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
/* 提交对单张图片调整任务 */
 | 
			
		||||
async function handleVary(item: any, orderId: number) {
 | 
			
		||||
  const { id } = item
 | 
			
		||||
  await fetchDrawTaskAPI({ drawId: id, action: 7, orderId })
 | 
			
		||||
  ms.success('提交图片调整绘制任务成功、请等待绘制结束!')
 | 
			
		||||
	if(authStore.token){
 | 
			
		||||
		await refreshUserInfo()
 | 
			
		||||
  const { drawId } = item;
 | 
			
		||||
  await fetchDrawTaskAPI({ drawId: drawId, action: 'VARIATION', orderId });
 | 
			
		||||
  ms.success('提交图片调整绘制任务成功、请等待绘制结束!');
 | 
			
		||||
  if (authStore.token) {
 | 
			
		||||
    await refreshUserInfo();
 | 
			
		||||
  }
 | 
			
		||||
 emit('queryData')
 | 
			
		||||
  emit('queryData');
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* 提交对单张图片缩放任务 */
 | 
			
		||||
async function handleZoom(item: any, orderId: number) {
 | 
			
		||||
  const { id } = item
 | 
			
		||||
  await fetchDrawTaskAPI({ drawId: id, action: 6, orderId })
 | 
			
		||||
  ms.success('提交图片调整绘制任务成功、请等待绘制结束!')
 | 
			
		||||
	if(authStore.token){
 | 
			
		||||
		await refreshUserInfo()
 | 
			
		||||
  const { drawId } = item;
 | 
			
		||||
  await fetchDrawTaskAPI({ drawId: drawId, action: 'UPSCALE', orderId });
 | 
			
		||||
  ms.success('提交图片调整绘制任务成功、请等待绘制结束!');
 | 
			
		||||
  if (authStore.token) {
 | 
			
		||||
    await refreshUserInfo();
 | 
			
		||||
  }
 | 
			
		||||
 emit('queryData')
 | 
			
		||||
  emit('queryData');
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function handleRegion(file){}
 | 
			
		||||
 | 
			
		||||
function handleRegion(file) {}
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
<template>
 | 
			
		||||
	 <div  class="relative overflow-hidden rounded-md border p-4 transition-all hover:shadow dark:border-neutral-700">
 | 
			
		||||
  <div
 | 
			
		||||
    class="relative overflow-hidden rounded-md border p-4 transition-all hover:shadow dark:border-neutral-700"
 | 
			
		||||
  >
 | 
			
		||||
    <div class="flex items-center justify-between">
 | 
			
		||||
      <span>
 | 
			
		||||
        <NTag size="small" :type="statusType">
 | 
			
		||||
@@ -199,7 +203,11 @@ function handleRegion(file){}
 | 
			
		||||
      </span>
 | 
			
		||||
 | 
			
		||||
      <NSpace>
 | 
			
		||||
				<NTooltip v-if="drawItemInfo.isGroup" placement="top" trigger="hover">
 | 
			
		||||
        <NTooltip
 | 
			
		||||
          v-if="drawItemInfo.action === 'IMAGINE'"
 | 
			
		||||
          placement="top"
 | 
			
		||||
          trigger="hover"
 | 
			
		||||
        >
 | 
			
		||||
          <template #trigger>
 | 
			
		||||
            <NButton size="tiny" ghost @click="usePrompt">
 | 
			
		||||
              <template #icon>
 | 
			
		||||
@@ -229,26 +237,49 @@ function handleRegion(file){}
 | 
			
		||||
    </div>
 | 
			
		||||
    <!-- content -->
 | 
			
		||||
    <div class="my-4 h-[280px]">
 | 
			
		||||
			<div v-if="drawItemInfo.status === 3" class="flex h-full w-full items-center justify-center overflow-hidden rounded-md">
 | 
			
		||||
      <div
 | 
			
		||||
        v-if="drawItemInfo.status === 3"
 | 
			
		||||
        class="flex h-full w-full items-center justify-center overflow-hidden rounded-md"
 | 
			
		||||
      >
 | 
			
		||||
        <NImage
 | 
			
		||||
					style="object-fit: contain;"
 | 
			
		||||
					:src="drawItemInfo.fileInfo.thumbImg"
 | 
			
		||||
					:preview-src="drawItemInfo.fileInfo.cosUrl"
 | 
			
		||||
          style="object-fit: contain"
 | 
			
		||||
          :src="drawItemInfo.drawUrl"
 | 
			
		||||
          :preview-src="drawItemInfo.drawUrl"
 | 
			
		||||
          object-fit="contain"
 | 
			
		||||
        />
 | 
			
		||||
      </div>
 | 
			
		||||
			<div v-if="[4, 5, 6].includes(drawItemInfo.status)" class="flex flex-col h-full w-full items-center justify-center overflow-hidden rounded-md">
 | 
			
		||||
				<img class="w-[75px]" :src="failImg">
 | 
			
		||||
      <div
 | 
			
		||||
        v-if="[4, 5, 6].includes(drawItemInfo.status)"
 | 
			
		||||
        class="flex flex-col h-full w-full items-center justify-center overflow-hidden rounded-md"
 | 
			
		||||
      >
 | 
			
		||||
        <img class="w-[75px]" :src="failImg" />
 | 
			
		||||
        <span class="mt-3 text-base">绘制失败</span>
 | 
			
		||||
        <span class="mt-1">已退还余额至您的账户!</span>
 | 
			
		||||
      </div>
 | 
			
		||||
			<div v-if="[1, 2].includes(drawItemInfo.status)" class="my-4 h-[280px] relative">
 | 
			
		||||
				<Loading :text-color="loadingTextColor" :progress="drawItemInfo.progress" :tips="calcTips" />
 | 
			
		||||
      <div
 | 
			
		||||
        v-if="[1, 2].includes(drawItemInfo.status)"
 | 
			
		||||
        class="my-4 h-[280px] relative"
 | 
			
		||||
      >
 | 
			
		||||
        <Loading
 | 
			
		||||
          :text-color="loadingTextColor"
 | 
			
		||||
          :progress="drawItemInfo.progress"
 | 
			
		||||
          :tips="calcTips"
 | 
			
		||||
        />
 | 
			
		||||
      </div>
 | 
			
		||||
    </div>
 | 
			
		||||
    <!-- footer -->
 | 
			
		||||
    <div class="-mx-4 -mb-4 bg-[#fafafc] px-4 py-2 dark:bg-[#262629]">
 | 
			
		||||
			<div v-if="drawItemInfo.isGroup" class="w-full">
 | 
			
		||||
      <div
 | 
			
		||||
        v-if="
 | 
			
		||||
          (drawItemInfo.action === 'IMAGINE' ||
 | 
			
		||||
            drawItemInfo.action === 'VARIATION' ||
 | 
			
		||||
            drawItemInfo.action === 'ZOOM' ||
 | 
			
		||||
            drawItemInfo.action === 'OUTPAINT' ||
 | 
			
		||||
            drawItemInfo.action === 'REROLL') &&
 | 
			
		||||
          drawItemInfo.status === 3
 | 
			
		||||
        "
 | 
			
		||||
        class="w-full"
 | 
			
		||||
      >
 | 
			
		||||
        <div class="mb-2 flex items-center justify-between">
 | 
			
		||||
          <span>放大:</span>
 | 
			
		||||
          <span class="text-base text-neutral-400">
 | 
			
		||||
@@ -277,7 +308,10 @@ function handleRegion(file){}
 | 
			
		||||
              </NButton>
 | 
			
		||||
              <NTooltip placement="top" trigger="hover">
 | 
			
		||||
                <template #trigger>
 | 
			
		||||
									<NButton size="tiny" @click="handleReGenerate(drawItemInfo, 5)">
 | 
			
		||||
                  <NButton
 | 
			
		||||
                    size="tiny"
 | 
			
		||||
                    @click="handleReGenerate(drawItemInfo, 5)"
 | 
			
		||||
                  >
 | 
			
		||||
                    <SvgIcon icon="solar:refresh-outline" class="text-base" />
 | 
			
		||||
                  </NButton>
 | 
			
		||||
                </template>
 | 
			
		||||
@@ -289,7 +323,17 @@ function handleRegion(file){}
 | 
			
		||||
      </div>
 | 
			
		||||
 | 
			
		||||
      <!-- 套图 新生成 变体图 重新生成 三种类型 -->
 | 
			
		||||
			<div v-if="drawItemInfo.isGroup" class="w-full">
 | 
			
		||||
      <div
 | 
			
		||||
        v-if="
 | 
			
		||||
          (drawItemInfo.action === 'IMAGINE' ||
 | 
			
		||||
            drawItemInfo.action === 'VARIATION' ||
 | 
			
		||||
            drawItemInfo.action === 'ZOOM' ||
 | 
			
		||||
            drawItemInfo.action === 'OUTPAINT' ||
 | 
			
		||||
            drawItemInfo.action === 'REROLL') &&
 | 
			
		||||
          drawItemInfo.status === 3
 | 
			
		||||
        "
 | 
			
		||||
        class="w-full"
 | 
			
		||||
      >
 | 
			
		||||
        <div class="mb-2 flex items-center justify-between">
 | 
			
		||||
          <span>变换:</span>
 | 
			
		||||
          <span class="text-base text-neutral-400">
 | 
			
		||||
@@ -298,8 +342,10 @@ function handleRegion(file){}
 | 
			
		||||
                <SvgIcon icon="ri:error-warning-line" class="text-base" />
 | 
			
		||||
              </template>
 | 
			
		||||
              <div style="width: 240px">
 | 
			
		||||
								<p>参数释义:以某张图片为基准重新生成
 | 
			
		||||
									如 V1 则变换第一张图片,以此类推</p>
 | 
			
		||||
                <p>
 | 
			
		||||
                  参数释义:以某张图片为基准重新生成 如 V1
 | 
			
		||||
                  则变换第一张图片,以此类推
 | 
			
		||||
                </p>
 | 
			
		||||
              </div>
 | 
			
		||||
            </NTooltip>
 | 
			
		||||
          </span>
 | 
			
		||||
@@ -317,70 +363,27 @@ function handleRegion(file){}
 | 
			
		||||
              <NButton size="tiny" @click="handleVariation(drawItemInfo, 4)">
 | 
			
		||||
                V4
 | 
			
		||||
              </NButton>
 | 
			
		||||
							<NButton size="tiny" style="opacity:0">
 | 
			
		||||
								V5
 | 
			
		||||
							</NButton>
 | 
			
		||||
              <NButton size="tiny" style="opacity: 0"> V5 </NButton>
 | 
			
		||||
            </div>
 | 
			
		||||
          </div>
 | 
			
		||||
        </div>
 | 
			
		||||
      </div>
 | 
			
		||||
 | 
			
		||||
      <!-- 对老图片增强 单张图或生成中的图 -->
 | 
			
		||||
			<div v-if="!drawItemInfo.isGroup && drawItemInfo.orderId" class="w-full mb-2 flex items-center justify-between">
 | 
			
		||||
      <div
 | 
			
		||||
        v-if="drawItemInfo.progress !== 100 && drawItemInfo.status !== 3"
 | 
			
		||||
        class="w-full mb-2 flex items-center justify-between"
 | 
			
		||||
      >
 | 
			
		||||
        <!-- 图片放大或变体 并且图片还未生成成功的时候没有message_id -->
 | 
			
		||||
				<div v-if="drawItemInfo.orderId !== 5 && !drawItemInfo.extend">
 | 
			
		||||
					<span v-if="drawItemInfo.action === 2">
 | 
			
		||||
        <div v-if="drawItemInfo.orderId !== 5">
 | 
			
		||||
          <span v-if="drawItemInfo.action === 'UPSCALE'">
 | 
			
		||||
            操作:{{ `选中套图第${drawItemInfo.orderId || 'x'}张图片进行放大` }}
 | 
			
		||||
          </span>
 | 
			
		||||
					<span v-if="drawItemInfo.action === 3">
 | 
			
		||||
          <span v-if="drawItemInfo.action === 'VARIATION'">
 | 
			
		||||
            操作:{{ `选中套图第${drawItemInfo.orderId || 'x'}张图片进行变换` }}
 | 
			
		||||
          </span>
 | 
			
		||||
        </div>
 | 
			
		||||
        <!-- 已经生成成功的单张图 可以zoom和vary -->
 | 
			
		||||
				<div v-if="drawItemInfo.orderId !== 5 && drawItemInfo.extend" class="flex w-full">
 | 
			
		||||
					<div class="mb-2 flex flex-1 items-center justify-between">
 | 
			
		||||
						<span>调整:</span>
 | 
			
		||||
						<span class="text-base text-neutral-400">
 | 
			
		||||
							<NTooltip placement="top" trigger="hover">
 | 
			
		||||
								<template #trigger>
 | 
			
		||||
									<SvgIcon icon="ri:error-warning-line" class="text-base" />
 | 
			
		||||
								</template>
 | 
			
		||||
								<div style="width: 275px">
 | 
			
		||||
									<p>参数释义:Vary 以当前图片为基础调整图片</p>
 | 
			
		||||
								</div>
 | 
			
		||||
							</NTooltip>
 | 
			
		||||
						</span>
 | 
			
		||||
						<div class="flex-1">
 | 
			
		||||
							<div class="flex items-center  pl-2">
 | 
			
		||||
								<NSpace>
 | 
			
		||||
									<NTooltip placement="top" trigger="hover">
 | 
			
		||||
										<template #trigger>
 | 
			
		||||
											<NButton size="tiny" @click="handleVary(drawItemInfo, 1)">
 | 
			
		||||
												<template #icon>
 | 
			
		||||
													<img :src="drawSvg" class="w-4" alt="">
 | 
			
		||||
												</template>
 | 
			
		||||
												V(Strong)
 | 
			
		||||
											</NButton>
 | 
			
		||||
										</template>
 | 
			
		||||
										<p>以当前图片为基础大幅增强</p>
 | 
			
		||||
									</NTooltip>
 | 
			
		||||
 | 
			
		||||
									<NTooltip placement="top" trigger="hover">
 | 
			
		||||
										<template #trigger>
 | 
			
		||||
											<NButton size="tiny" @click="handleVary(drawItemInfo, 2)">
 | 
			
		||||
												<template #icon>
 | 
			
		||||
													<img :src="drawSvg" class="w-4" alt="">
 | 
			
		||||
												</template>
 | 
			
		||||
												V(Subtle)
 | 
			
		||||
											</NButton>
 | 
			
		||||
										</template>
 | 
			
		||||
										<p>以当前图片为基础细微调整</p>
 | 
			
		||||
									</NTooltip>
 | 
			
		||||
								</NSpace>
 | 
			
		||||
							</div>
 | 
			
		||||
						</div>
 | 
			
		||||
					</div>
 | 
			
		||||
				</div>
 | 
			
		||||
 | 
			
		||||
        <!-- 重新绘制套图【只在生成中显示 生成完毕即会进入group套图】 -->
 | 
			
		||||
        <span v-if="drawItemInfo.orderId === 5">
 | 
			
		||||
@@ -389,21 +392,40 @@ function handleRegion(file){}
 | 
			
		||||
      </div>
 | 
			
		||||
 | 
			
		||||
      <!-- 新图绘制中 -->
 | 
			
		||||
			<div v-if="!drawItemInfo.isGroup && !drawItemInfo.orderId && drawItemInfo.status === 2" class="w-full mb-2 flex items-center justify-between">
 | 
			
		||||
      <div
 | 
			
		||||
        v-if="
 | 
			
		||||
          drawItemInfo.action === 'IMAGINE' &&
 | 
			
		||||
          !drawItemInfo.orderId &&
 | 
			
		||||
          drawItemInfo.status === 'UPSCALE'
 | 
			
		||||
        "
 | 
			
		||||
        class="w-full mb-2 flex items-center justify-between"
 | 
			
		||||
      >
 | 
			
		||||
        操作:正在火速绘制中...
 | 
			
		||||
      </div>
 | 
			
		||||
 | 
			
		||||
      <!-- 绘制失败了 -->
 | 
			
		||||
			<div v-if="!drawItemInfo.isGroup && !drawItemInfo.orderId && [4, 5, 6].includes(drawItemInfo.status) " class="w-full mb-2 flex items-center justify-between">
 | 
			
		||||
      <div
 | 
			
		||||
        v-if="!drawItemInfo.orderId && [4, 5, 6].includes(drawItemInfo.status)"
 | 
			
		||||
        class="w-full mb-2 flex items-center justify-between"
 | 
			
		||||
      >
 | 
			
		||||
        执行: 换个提示词重新试试吧!
 | 
			
		||||
      </div>
 | 
			
		||||
      <!-- 加载失败 -->
 | 
			
		||||
			<div v-if="!drawItemInfo.isGroup && !drawItemInfo.extend" class="w-full mb-2 flex items-center justify-between">
 | 
			
		||||
      <div
 | 
			
		||||
        v-if="!drawItemInfo.action && !drawItemInfo.extend"
 | 
			
		||||
        class="w-full mb-2 flex items-center justify-between"
 | 
			
		||||
      >
 | 
			
		||||
        上级: {{ drawItemInfo.message_id || '正在加载中...' }}
 | 
			
		||||
      </div>
 | 
			
		||||
 | 
			
		||||
			<!--  -->
 | 
			
		||||
			<div v-if="!drawItemInfo.isGroup && drawItemInfo.orderId !== 5 && drawItemInfo.extend">
 | 
			
		||||
      <!-- 2 -->
 | 
			
		||||
      <div
 | 
			
		||||
        v-if="
 | 
			
		||||
          (drawItemInfo.action === 'UPSCALE' ||
 | 
			
		||||
            drawItemInfo.action === 'ACTION') &&
 | 
			
		||||
          drawItemInfo.status === 3
 | 
			
		||||
        "
 | 
			
		||||
      >
 | 
			
		||||
        <div class="mb-2 flex flex-1 items-center justify-between">
 | 
			
		||||
          <span>缩放:</span>
 | 
			
		||||
          <span class="text-base text-neutral-400">
 | 
			
		||||
@@ -423,49 +445,87 @@ function handleRegion(file){}
 | 
			
		||||
                  <template #trigger>
 | 
			
		||||
                    <NButton size="tiny" @click="handleZoom(drawItemInfo, 1)">
 | 
			
		||||
                      <template #icon>
 | 
			
		||||
												<img :src="zoomSvg" class="w-4" alt="">
 | 
			
		||||
                        <img :src="zoomSvg" class="w-4" alt="" />
 | 
			
		||||
                      </template>
 | 
			
		||||
											Zoom 2
 | 
			
		||||
                      U(Subtle)
 | 
			
		||||
                    </NButton>
 | 
			
		||||
                  </template>
 | 
			
		||||
									<p>缩放2倍</p>
 | 
			
		||||
                  <p>放大</p>
 | 
			
		||||
                </NTooltip>
 | 
			
		||||
 | 
			
		||||
                <NTooltip placement="top" trigger="hover">
 | 
			
		||||
                  <template #trigger>
 | 
			
		||||
                    <NButton size="tiny" @click="handleZoom(drawItemInfo, 2)">
 | 
			
		||||
                      <template #icon>
 | 
			
		||||
												<img :src="zoomSvg" class="w-4" alt="">
 | 
			
		||||
                        <img :src="zoomSvg" class="w-4" alt="" />
 | 
			
		||||
                      </template>
 | 
			
		||||
											Zoom 1.5
 | 
			
		||||
                      U(Creative)
 | 
			
		||||
                    </NButton>
 | 
			
		||||
                  </template>
 | 
			
		||||
									<p>缩放1.5倍</p>
 | 
			
		||||
                  <p>放大</p>
 | 
			
		||||
                </NTooltip>
 | 
			
		||||
 | 
			
		||||
								<!-- <NTooltip placement="top" trigger="hover">
 | 
			
		||||
									<template #trigger>
 | 
			
		||||
										<NButton size="tiny" @click="handleRegion(drawItemInfo)">
 | 
			
		||||
											<template #icon>
 | 
			
		||||
												<img :src="zoomSvg" class="w-4" alt="">
 | 
			
		||||
											</template>
 | 
			
		||||
											Region
 | 
			
		||||
										</NButton>
 | 
			
		||||
									</template>
 | 
			
		||||
									<p>缩放2倍</p>
 | 
			
		||||
								</NTooltip> -->
 | 
			
		||||
              </NSpace>
 | 
			
		||||
            </div>
 | 
			
		||||
          </div>
 | 
			
		||||
        </div>
 | 
			
		||||
      </div>
 | 
			
		||||
			<div class="w-full flex">
 | 
			
		||||
				<span class="text-[#64748b]">时间:{{ drawItemInfo.createdAt }}</span>
 | 
			
		||||
      <div
 | 
			
		||||
        v-if="
 | 
			
		||||
          (drawItemInfo.action === 'UPSCALE' ||
 | 
			
		||||
            drawItemInfo.action === 'ACTION') &&
 | 
			
		||||
          drawItemInfo.status === 3
 | 
			
		||||
        "
 | 
			
		||||
        class="flex w-full"
 | 
			
		||||
      >
 | 
			
		||||
        <div class="mb-2 flex flex-1 items-center justify-between">
 | 
			
		||||
          <span>调整:</span>
 | 
			
		||||
          <span class="text-base text-neutral-400">
 | 
			
		||||
            <NTooltip placement="top" trigger="hover">
 | 
			
		||||
              <template #trigger>
 | 
			
		||||
                <SvgIcon icon="ri:error-warning-line" class="text-base" />
 | 
			
		||||
              </template>
 | 
			
		||||
              <div style="width: 275px">
 | 
			
		||||
                <p>参数释义:Vary 以当前图片为基础调整图片</p>
 | 
			
		||||
              </div>
 | 
			
		||||
            </NTooltip>
 | 
			
		||||
          </span>
 | 
			
		||||
          <div class="flex-1">
 | 
			
		||||
            <div class="flex items-center pl-2">
 | 
			
		||||
              <NSpace>
 | 
			
		||||
                <NTooltip placement="top" trigger="hover">
 | 
			
		||||
                  <template #trigger>
 | 
			
		||||
                    <NButton size="tiny" @click="handleVary(drawItemInfo, 1)">
 | 
			
		||||
                      <template #icon>
 | 
			
		||||
                        <img :src="drawSvg" class="w-4" alt="" />
 | 
			
		||||
                      </template>
 | 
			
		||||
                      V(Strong)
 | 
			
		||||
                    </NButton>
 | 
			
		||||
                  </template>
 | 
			
		||||
                  <p>以当前图片为基础大幅增强</p>
 | 
			
		||||
                </NTooltip>
 | 
			
		||||
 | 
			
		||||
                <NTooltip placement="top" trigger="hover">
 | 
			
		||||
                  <template #trigger>
 | 
			
		||||
                    <NButton size="tiny" @click="handleVary(drawItemInfo, 2)">
 | 
			
		||||
                      <template #icon>
 | 
			
		||||
                        <img :src="drawSvg" class="w-4" alt="" />
 | 
			
		||||
                      </template>
 | 
			
		||||
                      V(Subtle)
 | 
			
		||||
                    </NButton>
 | 
			
		||||
                  </template>
 | 
			
		||||
                  <p>以当前图片为基础细微调整</p>
 | 
			
		||||
                </NTooltip>
 | 
			
		||||
              </NSpace>
 | 
			
		||||
            </div>
 | 
			
		||||
          </div>
 | 
			
		||||
        </div>
 | 
			
		||||
      </div>
 | 
			
		||||
 | 
			
		||||
      <!-- <div class="w-full flex">
 | 
			
		||||
        <span class="text-[#64748b]">时间:{{ drawItemInfo.createdAt }}</span>
 | 
			
		||||
      </div> -->
 | 
			
		||||
    </div>
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<style lang='scss' scoped>
 | 
			
		||||
 | 
			
		||||
</style>
 | 
			
		||||
<style lang="scss" scoped></style>
 | 
			
		||||
 
 | 
			
		||||
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							@@ -59,7 +59,7 @@ const demoData = `
 | 
			
		||||
`
 | 
			
		||||
 | 
			
		||||
const prompt = ref('')
 | 
			
		||||
const initValue = `# NineAi
 | 
			
		||||
const initValue = `# YiAi
 | 
			
		||||
## 基础功能
 | 
			
		||||
- 支持AI聊天
 | 
			
		||||
- 支持GPT4
 | 
			
		||||
 
 | 
			
		||||
@@ -10,8 +10,8 @@ function setupPlugins(env: ImportMetaEnv): PluginOption[] {
 | 
			
		||||
    env.VITE_GLOB_APP_PWA === 'true' && VitePWA({
 | 
			
		||||
      injectRegister: 'auto',
 | 
			
		||||
      manifest: {
 | 
			
		||||
        name: 'Nine AI',
 | 
			
		||||
        short_name: 'Nine AI',
 | 
			
		||||
        name: 'YI AI',
 | 
			
		||||
        short_name: 'YI AI',
 | 
			
		||||
        icons: [
 | 
			
		||||
          { src: 'pwa-192x192.png', sizes: '192x192', type: 'image/png' },
 | 
			
		||||
          { src: 'pwa-512x512.png', sizes: '512x512', type: 'image/png' },
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										11
									
								
								package.json
									
									
									
									
									
								
							
							
						
						
									
										11
									
								
								package.json
									
									
									
									
									
								
							@@ -1,6 +1,6 @@
 | 
			
		||||
{
 | 
			
		||||
  "name": "nine-ai",
 | 
			
		||||
  "version": "2.4.5",
 | 
			
		||||
  "name": "yi-ai",
 | 
			
		||||
  "version": "2.5.0",
 | 
			
		||||
  "description": "使用 Nestjs 和 Vue3 搭建的 AIGC 生态社区 持续集成AI能力到社区之中",
 | 
			
		||||
  "main": "index.js",
 | 
			
		||||
  "author": "longyanjiang",
 | 
			
		||||
@@ -39,11 +39,8 @@
 | 
			
		||||
  "devDependencies": {},
 | 
			
		||||
  "repository": {
 | 
			
		||||
    "type": "git",
 | 
			
		||||
    "url": "git+https://github.com/longyanjiang/Nine-Ai.git"
 | 
			
		||||
    "url": ""
 | 
			
		||||
  },
 | 
			
		||||
  "license": "MID",
 | 
			
		||||
  "bugs": {
 | 
			
		||||
    "url": "https://github.com/longyanjiang/Nine-Ai/issues"
 | 
			
		||||
  },
 | 
			
		||||
  "homepage": "https://github.com/longyanjiang/Nine-Ai#readme"
 | 
			
		||||
  "homepage": ""
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										13696
									
								
								pnpm-lock.yaml
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										13696
									
								
								pnpm-lock.yaml
									
									
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								service/.DS_Store
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										
											BIN
										
									
								
								service/.DS_Store
									
									
									
									
										vendored
									
									
								
							
										
											Binary file not shown.
										
									
								
							@@ -1,6 +1,6 @@
 | 
			
		||||
{
 | 
			
		||||
  "name": "service",
 | 
			
		||||
  "version": "2.4.5",
 | 
			
		||||
  "version": "2.5.0",
 | 
			
		||||
  "description": "",
 | 
			
		||||
  "author": "",
 | 
			
		||||
  "private": true,
 | 
			
		||||
@@ -49,7 +49,7 @@
 | 
			
		||||
    "body-parser": "^1.20.2",
 | 
			
		||||
    "bull": "^4.10.4",
 | 
			
		||||
    "cache-manager-redis-store": "^3.0.1",
 | 
			
		||||
    "chatgpt-nine-ai": "^1.0.2",
 | 
			
		||||
    "chatgpt-ai-web": "^1.0.0",
 | 
			
		||||
    "class-transformer": "^0.5.1",
 | 
			
		||||
    "class-validator": "^0.14.0",
 | 
			
		||||
    "compression": "^1.7.4",
 | 
			
		||||
@@ -64,6 +64,7 @@
 | 
			
		||||
    "form-data": "^4.0.0",
 | 
			
		||||
    "guid-typescript": "^1.0.9",
 | 
			
		||||
    "hbs": "^4.2.0",
 | 
			
		||||
    "image-size": "^1.1.1",
 | 
			
		||||
    "ioredis": "^5.3.2",
 | 
			
		||||
    "isomorphic-fetch": "^3.0.0",
 | 
			
		||||
    "javascript-obfuscator": "^4.0.2",
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,6 @@
 | 
			
		||||
{
 | 
			
		||||
  "apps": {
 | 
			
		||||
    "name": "nineai-v2.4.5",
 | 
			
		||||
    "name": "yiai-v2.5.0",
 | 
			
		||||
    "script": "./dist/main.js",
 | 
			
		||||
    "watch": true,
 | 
			
		||||
    "ignore_watch": [
 | 
			
		||||
 
 | 
			
		||||
@@ -2,7 +2,7 @@
 | 
			
		||||
<html lang="en">
 | 
			
		||||
  <head>
 | 
			
		||||
    <meta charset="UTF-8" />
 | 
			
		||||
    <title>Nine Ai</title>
 | 
			
		||||
    <title>Yi Ai</title>
 | 
			
		||||
    <style>
 | 
			
		||||
      .loading-container {
 | 
			
		||||
        position: fixed;
 | 
			
		||||
@@ -45,6 +45,13 @@
 | 
			
		||||
      <div class="loading"></div>
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
    <h1>Welcome Use Nine Ai</h1>
 | 
			
		||||
    <h1>Welcome Use Yi Ai</h1>
 | 
			
		||||
  </body>
 | 
			
		||||
 | 
			
		||||
  <script>
 | 
			
		||||
    console.log(
 | 
			
		||||
        "%c本项目作者----小易,联系QQ:805239273",
 | 
			
		||||
        "background-color:rgb(30,30,30);border-radius:4px;font-size:12px;padding:4px;color:rgb(220,208,129);"
 | 
			
		||||
      )
 | 
			
		||||
  </script>
 | 
			
		||||
</html>
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										
											BIN
										
									
								
								service/src/.DS_Store
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										
											BIN
										
									
								
								service/src/.DS_Store
									
									
									
									
										vendored
									
									
								
							
										
											Binary file not shown.
										
									
								
							@@ -2,8 +2,8 @@ import { UploadService } from './../upload/upload.service';
 | 
			
		||||
import { UserService } from './../user/user.service';
 | 
			
		||||
import { ConfigService } from 'nestjs-config';
 | 
			
		||||
import { HttpException, HttpStatus, Injectable, OnModuleInit, Logger } from '@nestjs/common';
 | 
			
		||||
import type { ChatGPTAPIOptions, ChatMessage, SendMessageOptions } from 'chatgpt-nine-ai';
 | 
			
		||||
import { Request, Response } from 'express';
 | 
			
		||||
import type { ChatGPTAPIOptions, ChatMessage, SendMessageOptions } from 'chatgpt-ai-web';
 | 
			
		||||
import e, { Request, Response } from 'express';
 | 
			
		||||
import { OpenAiErrorCodeMessage } from '@/common/constants/errorMessage.constant';
 | 
			
		||||
import {
 | 
			
		||||
  compileNetwork,
 | 
			
		||||
@@ -97,7 +97,7 @@ export class ChatgptService implements OnModuleInit {
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  async onModuleInit() {
 | 
			
		||||
    let chatgpt = await importDynamic('chatgpt-nine-ai');
 | 
			
		||||
    let chatgpt = await importDynamic('chatgpt-ai-web');
 | 
			
		||||
    let KeyvRedis = await importDynamic('@keyv/redis');
 | 
			
		||||
    let Keyv = await importDynamic('keyv');
 | 
			
		||||
    chatgpt = chatgpt?.default ? chatgpt.default : chatgpt;
 | 
			
		||||
@@ -165,7 +165,7 @@ export class ChatgptService implements OnModuleInit {
 | 
			
		||||
    /* 不同场景会变更其信息 */
 | 
			
		||||
    let setSystemMessage = systemMessage;
 | 
			
		||||
    const { parentMessageId } = options;
 | 
			
		||||
    const { prompt ,imageUrl,model:activeModel} = body;
 | 
			
		||||
    const { prompt, imageUrl, model: activeModel } = body;
 | 
			
		||||
    const { groupId, usingNetwork } = options;
 | 
			
		||||
    // const { model = 3 } = options;
 | 
			
		||||
    /* 获取当前对话组的详细配置信息 */
 | 
			
		||||
@@ -184,7 +184,7 @@ export class ChatgptService implements OnModuleInit {
 | 
			
		||||
      throw new HttpException('当前流程所需要的模型已被管理员下架、请联系管理员上架专属模型!', HttpStatus.BAD_REQUEST);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const { deduct, isTokenBased, deductType, key: modelKey, secret, modelName, id: keyId, accessToken } = currentRequestModelKey;
 | 
			
		||||
    const { deduct, isTokenBased, tokenFeeRatio, deductType, key: modelKey, secret, modelName, id: keyId, accessToken } = currentRequestModelKey;
 | 
			
		||||
    /* 用户状态检测 */
 | 
			
		||||
    await this.userService.checkUserStatus(req.user);
 | 
			
		||||
    /* 用户余额检测 */
 | 
			
		||||
@@ -260,7 +260,7 @@ export class ChatgptService implements OnModuleInit {
 | 
			
		||||
            userId: req.user.id,
 | 
			
		||||
            type: DeductionKey.CHAT_TYPE,
 | 
			
		||||
            prompt,
 | 
			
		||||
            imageUrl,
 | 
			
		||||
            imageUrl:response?.imageUrl,
 | 
			
		||||
            activeModel,
 | 
			
		||||
            answer: '',
 | 
			
		||||
            promptTokens: prompt_tokens,
 | 
			
		||||
@@ -307,7 +307,7 @@ export class ChatgptService implements OnModuleInit {
 | 
			
		||||
          /* 当用户回答一般停止时 也需要扣费 */
 | 
			
		||||
          let charge = deduct;
 | 
			
		||||
          if (isTokenBased === true) {
 | 
			
		||||
            charge = deduct * total_tokens;
 | 
			
		||||
            charge = Math.ceil((deduct * total_tokens) / tokenFeeRatio);
 | 
			
		||||
          }
 | 
			
		||||
          await this.userBalanceService.deductFromBalance(req.user.id, `model${deductType === 1 ? 3 : 4}`, charge, total_tokens);
 | 
			
		||||
        });
 | 
			
		||||
@@ -320,11 +320,11 @@ export class ChatgptService implements OnModuleInit {
 | 
			
		||||
          const { context: messagesHistory } = await this.nineStore.buildMessageFromParentMessageId(usingNetwork ? netWorkPrompt : prompt, {
 | 
			
		||||
            parentMessageId,
 | 
			
		||||
            systemMessage,
 | 
			
		||||
            imageUrl,
 | 
			
		||||
            activeModel,
 | 
			
		||||
            maxModelToken: maxToken,
 | 
			
		||||
            maxResponseTokens: maxTokenRes,
 | 
			
		||||
            maxRounds: addOneIfOdd(rounds),
 | 
			
		||||
            imageUrl,
 | 
			
		||||
            activeModel,
 | 
			
		||||
          });
 | 
			
		||||
          let firstChunk = true;
 | 
			
		||||
          response = await sendMessageFromOpenAi(messagesHistory, {
 | 
			
		||||
@@ -332,8 +332,9 @@ export class ChatgptService implements OnModuleInit {
 | 
			
		||||
            maxTokenRes,
 | 
			
		||||
            apiKey: modelKey,
 | 
			
		||||
            model,
 | 
			
		||||
            imageUrl,
 | 
			
		||||
            prompt,
 | 
			
		||||
            activeModel,
 | 
			
		||||
            imageUrl,
 | 
			
		||||
            temperature,
 | 
			
		||||
            proxyUrl: proxyResUrl,
 | 
			
		||||
            onProgress: (chat) => {
 | 
			
		||||
@@ -341,7 +342,7 @@ export class ChatgptService implements OnModuleInit {
 | 
			
		||||
              lastChat = chat;
 | 
			
		||||
              firstChunk = false;
 | 
			
		||||
            },
 | 
			
		||||
          });
 | 
			
		||||
          },this.uploadService);
 | 
			
		||||
          isSuccess = true;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
@@ -385,7 +386,6 @@ export class ChatgptService implements OnModuleInit {
 | 
			
		||||
          isSuccess = true;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /* 分别将本次用户输入的 和 机器人返回的分两次存入到 store */
 | 
			
		||||
        const userMessageData: MessageInfo = {
 | 
			
		||||
          id: this.nineStore.getUuid(),
 | 
			
		||||
          text: prompt,
 | 
			
		||||
@@ -407,7 +407,8 @@ export class ChatgptService implements OnModuleInit {
 | 
			
		||||
          text: response.text,
 | 
			
		||||
          role: 'assistant',
 | 
			
		||||
          name: undefined,
 | 
			
		||||
          usage: response.usage,
 | 
			
		||||
          usage: response?.usage,
 | 
			
		||||
          imageUrl: response?.imageUrl,
 | 
			
		||||
          parentMessageId: userMessageData.id,
 | 
			
		||||
          conversationId: response?.conversationId,
 | 
			
		||||
        };
 | 
			
		||||
@@ -415,7 +416,6 @@ export class ChatgptService implements OnModuleInit {
 | 
			
		||||
        await this.nineStore.setData(assistantMessageData);
 | 
			
		||||
 | 
			
		||||
        othersInfo = { model, parentMessageId: userMessageData.id };
 | 
			
		||||
        /* 回答完毕 */
 | 
			
		||||
      } else {
 | 
			
		||||
        const { key, maxToken, maxTokenRes, proxyResUrl } = await this.formatModelToken(currentRequestModelKey);
 | 
			
		||||
        const { parentMessageId, completionParams, systemMessage } = mergedOptions;
 | 
			
		||||
@@ -431,17 +431,22 @@ export class ChatgptService implements OnModuleInit {
 | 
			
		||||
          temperature,
 | 
			
		||||
          proxyUrl: proxyResUrl,
 | 
			
		||||
          onProgress: null,
 | 
			
		||||
          prompt,
 | 
			
		||||
        });
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      /* 统一最终输出格式 */
 | 
			
		||||
      const formatResponse = await unifiedFormattingResponse(keyType, response, othersInfo);
 | 
			
		||||
      const { prompt_tokens = 0, completion_tokens = 0, total_tokens = 0 } = formatResponse.usage;
 | 
			
		||||
 | 
			
		||||
      let usage = null;
 | 
			
		||||
      let formatResponse = null;
 | 
			
		||||
      if (model.includes('dall')) {
 | 
			
		||||
        usage = response.detail?.usage || { prompt_tokens: 1, completion_tokens: 1, total_tokens: 2 };
 | 
			
		||||
      } else {
 | 
			
		||||
        formatResponse = await unifiedFormattingResponse(keyType, response, othersInfo);
 | 
			
		||||
      }
 | 
			
		||||
      const { prompt_tokens, completion_tokens, total_tokens } = model.includes('dall') ? usage : formatResponse.usage;
 | 
			
		||||
      /* 区分扣除普通还是高级余额  model3: 普通余额  model4: 高级余额 */
 | 
			
		||||
      let charge = deduct;
 | 
			
		||||
      if (isTokenBased === true) {
 | 
			
		||||
        charge = deduct * total_tokens;
 | 
			
		||||
        charge = Math.ceil((deduct * total_tokens) / tokenFeeRatio);
 | 
			
		||||
      }
 | 
			
		||||
      await this.userBalanceService.deductFromBalance(req.user.id, `model${deductType === 1 ? 3 : 4}`, charge, total_tokens);
 | 
			
		||||
 | 
			
		||||
@@ -457,13 +462,13 @@ export class ChatgptService implements OnModuleInit {
 | 
			
		||||
        userId: req.user.id,
 | 
			
		||||
        type: DeductionKey.CHAT_TYPE,
 | 
			
		||||
        prompt,
 | 
			
		||||
        imageUrl,
 | 
			
		||||
        imageUrl: response?.imageUrl,
 | 
			
		||||
        activeModel,
 | 
			
		||||
        answer: '',
 | 
			
		||||
        promptTokens: prompt_tokens,
 | 
			
		||||
        completionTokens: 0,
 | 
			
		||||
        totalTokens: total_tokens,
 | 
			
		||||
        model: formatResponse.model,
 | 
			
		||||
        model: model,
 | 
			
		||||
        role: 'user',
 | 
			
		||||
        groupId,
 | 
			
		||||
        requestOptions: JSON.stringify({
 | 
			
		||||
@@ -479,7 +484,8 @@ export class ChatgptService implements OnModuleInit {
 | 
			
		||||
        userId: req.user.id,
 | 
			
		||||
        type: DeductionKey.CHAT_TYPE,
 | 
			
		||||
        prompt: prompt,
 | 
			
		||||
        answer: formatResponse?.text,
 | 
			
		||||
        imageUrl: response?.imageUrl,
 | 
			
		||||
        answer: response.text,
 | 
			
		||||
        promptTokens: prompt_tokens,
 | 
			
		||||
        completionTokens: completion_tokens,
 | 
			
		||||
        totalTokens: total_tokens,
 | 
			
		||||
@@ -501,7 +507,7 @@ export class ChatgptService implements OnModuleInit {
 | 
			
		||||
        }),
 | 
			
		||||
      });
 | 
			
		||||
      Logger.debug(
 | 
			
		||||
        `本次调用: ${req.user.id} model: ${model} key -> ${key}, 模型名称: ${modelName}, 最大回复token: ${maxResponseTokens}`,
 | 
			
		||||
        `用户ID: ${req.user.id} 模型名称: ${modelName}-${activeModel}, 消耗token: ${total_tokens}, 消耗积分: ${charge}`,
 | 
			
		||||
        'ChatgptService',
 | 
			
		||||
      );
 | 
			
		||||
      const userBalance = await this.userBalanceService.queryUserBalance(req.user.id);
 | 
			
		||||
@@ -599,7 +605,7 @@ export class ChatgptService implements OnModuleInit {
 | 
			
		||||
    await this.userBalanceService.validateBalance(req, 'mjDraw', money);
 | 
			
		||||
    let images = [];
 | 
			
		||||
    /* 从3的卡池随机拿一个key */
 | 
			
		||||
    const detailKeyInfo = await this.modelsService.getRandomDrawKey();
 | 
			
		||||
    const detailKeyInfo = await this.modelsService.getCurrentModelKeyInfo('dall-e-3');
 | 
			
		||||
    const keyId = detailKeyInfo?.id;
 | 
			
		||||
    const { key, proxyResUrl } = await this.formatModelToken(detailKeyInfo);
 | 
			
		||||
    Logger.log(`draw paompt info <==**==> ${body.prompt}, key ===> ${key}`, 'DrawService');
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,9 @@
 | 
			
		||||
import axios, { AxiosRequestConfig, AxiosResponse } from 'axios';
 | 
			
		||||
import { get_encoding } from '@dqbd/tiktoken'
 | 
			
		||||
import { removeSpecialCharacters } from '@/common/utils';
 | 
			
		||||
import { ConsoleLogger, HttpException, HttpStatus, Logger } from '@nestjs/common';
 | 
			
		||||
import * as uuid from 'uuid';
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
const tokenizer = get_encoding('cl100k_base')
 | 
			
		||||
 | 
			
		||||
@@ -11,38 +14,103 @@ interface SendMessageResult {
 | 
			
		||||
  detail?: any;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function getFullUrl(proxyUrl){
 | 
			
		||||
function getFullUrl(proxyUrl) {
 | 
			
		||||
  const processedUrl = proxyUrl.endsWith('/') ? proxyUrl.slice(0, -1) : proxyUrl;
 | 
			
		||||
  const baseUrl = processedUrl || 'https://api.openai.com'
 | 
			
		||||
  return `${baseUrl}/v1/chat/completions`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export  function sendMessageFromOpenAi(messagesHistory,  inputs ){
 | 
			
		||||
  const { onProgress, maxToken, apiKey, model, temperature = 0.95, proxyUrl } = inputs
 | 
			
		||||
  console.log('current request options: ',apiKey, model, maxToken, proxyUrl );
 | 
			
		||||
  const max_tokens = compilerToken(model,  maxToken)
 | 
			
		||||
export async function sendMessageFromOpenAi(messagesHistory, inputs, uploadService?) {
 | 
			
		||||
  const { onProgress, maxToken, apiKey, model, temperature = 0.8, proxyUrl, prompt } = inputs
 | 
			
		||||
  if (model.includes('dall')) {
 | 
			
		||||
    let result: any = { text: '', imageUrl: '' };
 | 
			
		||||
    try {
 | 
			
		||||
      const options: AxiosRequestConfig = {
 | 
			
		||||
        method: 'POST',
 | 
			
		||||
        url: `${proxyUrl}/v1/images/generations`,
 | 
			
		||||
        headers: {
 | 
			
		||||
          'Content-Type': 'application/json',
 | 
			
		||||
          Authorization: `Bearer ${apiKey}`,
 | 
			
		||||
        },
 | 
			
		||||
        data: {
 | 
			
		||||
          prompt: prompt,
 | 
			
		||||
          model: model,
 | 
			
		||||
          response_format: 'b64_json'
 | 
			
		||||
        },
 | 
			
		||||
      }
 | 
			
		||||
      const response: any = await axios(options);
 | 
			
		||||
      const { b64_json, revised_prompt } = response.data.data[0]
 | 
			
		||||
      const buffer = Buffer.from(b64_json, 'base64');
 | 
			
		||||
      let imgUrl = '';
 | 
			
		||||
      try {
 | 
			
		||||
        const filename = uuid.v4().slice(0, 10) + '.png';
 | 
			
		||||
        Logger.debug(`------> 开始上传图片!!!`, 'MidjourneyService');
 | 
			
		||||
        const buffer = Buffer.from(b64_json, 'base64');
 | 
			
		||||
        // imgUrl = await uploadService.uploadFileFromUrl({ filename, url })
 | 
			
		||||
        imgUrl = await uploadService.uploadFile({ filename, buffer });
 | 
			
		||||
        Logger.debug(`图片上传成功,URL: ${imgUrl}`, 'MidjourneyService');
 | 
			
		||||
      } catch (error) {
 | 
			
		||||
        Logger.error(`上传图片过程中出现错误: ${error}`, 'MidjourneyService');
 | 
			
		||||
      }
 | 
			
		||||
      result.imageUrl = imgUrl
 | 
			
		||||
      result.text = revised_prompt;
 | 
			
		||||
      onProgress && onProgress({ text: result.text })
 | 
			
		||||
      return result;
 | 
			
		||||
    } catch (error) {
 | 
			
		||||
      const status = error?.response?.status || 500;
 | 
			
		||||
      console.log('openai-draw error: ', JSON.stringify(error), status);
 | 
			
		||||
      const message = error?.response?.data?.error?.message;
 | 
			
		||||
      if (status === 429) {
 | 
			
		||||
        result.text = '当前请求已过载、请稍等会儿再试试吧!';
 | 
			
		||||
        return result;
 | 
			
		||||
      }
 | 
			
		||||
      if (status === 400 && message.includes('This request has been blocked by our content filters')) {
 | 
			
		||||
        result.text = '您的请求已被系统拒绝。您的提示可能存在一些非法的文本。';
 | 
			
		||||
        return result;
 | 
			
		||||
      }
 | 
			
		||||
      if (status === 400 && message.includes('Billing hard limit has been reached')) {
 | 
			
		||||
        result.text = '当前模型key已被封禁、已冻结当前调用Key、尝试重新对话试试吧!';
 | 
			
		||||
        return result;
 | 
			
		||||
      }
 | 
			
		||||
      if (status === 500) {
 | 
			
		||||
        result.text = '绘制图片失败,请检查你的提示词是否有非法描述!';
 | 
			
		||||
        return result;
 | 
			
		||||
      }
 | 
			
		||||
      if (status === 401) {
 | 
			
		||||
        result.text = '绘制图片失败,此次绘画被拒绝了!';
 | 
			
		||||
        return result;
 | 
			
		||||
      }
 | 
			
		||||
      result.text = '绘制图片失败,请稍后试试吧!';
 | 
			
		||||
      return result;
 | 
			
		||||
    }
 | 
			
		||||
  } else {
 | 
			
		||||
    let result: any = { text: '' };
 | 
			
		||||
    const options: AxiosRequestConfig = {
 | 
			
		||||
      method: 'POST',
 | 
			
		||||
      url: getFullUrl(proxyUrl),
 | 
			
		||||
      responseType: 'stream',
 | 
			
		||||
      headers: {
 | 
			
		||||
        'Content-Type': 'application/json',
 | 
			
		||||
      Authorization: `Bearer ${removeSpecialCharacters(apiKey)}`,
 | 
			
		||||
        Accept: "application/json",
 | 
			
		||||
        Authorization: `Bearer ${apiKey}`,
 | 
			
		||||
      },
 | 
			
		||||
      data: {
 | 
			
		||||
      max_tokens,
 | 
			
		||||
        stream: true,
 | 
			
		||||
        temperature,
 | 
			
		||||
        model,
 | 
			
		||||
      messages: messagesHistory
 | 
			
		||||
        messages: messagesHistory,
 | 
			
		||||
      },
 | 
			
		||||
    };
 | 
			
		||||
  const prompt = messagesHistory[messagesHistory.length-1]?.content
 | 
			
		||||
  return new Promise(async (resolve, reject) =>{
 | 
			
		||||
 | 
			
		||||
    if (model === 'gpt-4-vision-preview') {
 | 
			
		||||
      options.data.max_tokens = 2048;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return new Promise(async (resolve, reject) => {
 | 
			
		||||
      try {
 | 
			
		||||
        const response: any = await axios(options);
 | 
			
		||||
        const stream = response.data;
 | 
			
		||||
      let result: any = { text: '' };
 | 
			
		||||
 | 
			
		||||
        stream.on('data', (chunk) => {
 | 
			
		||||
          const splitArr = chunk.toString().split('\n\n').filter((line) => line.trim() !== '');
 | 
			
		||||
          for (const line of splitArr) {
 | 
			
		||||
@@ -54,11 +122,12 @@ export  function sendMessageFromOpenAi(messagesHistory,  inputs ){
 | 
			
		||||
              ISEND = false;
 | 
			
		||||
            }
 | 
			
		||||
            /* 如果结束 返回所有 */
 | 
			
		||||
          if (data === '[DONE]' || ISEND) {
 | 
			
		||||
            if (ISEND) {
 | 
			
		||||
              result.text = result.text.trim();
 | 
			
		||||
              return result;
 | 
			
		||||
            }
 | 
			
		||||
            try {
 | 
			
		||||
              if (data !== " [DONE]" && data !== "[DONE]" && data != "[DONE] ") {
 | 
			
		||||
                const parsedData = JSON.parse(data);
 | 
			
		||||
                if (parsedData.id) {
 | 
			
		||||
                  result.id = parsedData.id;
 | 
			
		||||
@@ -72,21 +141,26 @@ export  function sendMessageFromOpenAi(messagesHistory,  inputs ){
 | 
			
		||||
                  }
 | 
			
		||||
                  result.detail = parsedData;
 | 
			
		||||
                }
 | 
			
		||||
            onProgress && onProgress({text:result.text})
 | 
			
		||||
                onProgress && onProgress({ text: result.text })
 | 
			
		||||
              }
 | 
			
		||||
            } catch (error) {
 | 
			
		||||
            console.log('parse Error', data )
 | 
			
		||||
              console.log('parse Error', data)
 | 
			
		||||
            }
 | 
			
		||||
          }
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        let totalText = '';
 | 
			
		||||
        messagesHistory.forEach(message => {
 | 
			
		||||
          totalText += message.content + ' ';
 | 
			
		||||
        });
 | 
			
		||||
        stream.on('end', () => {
 | 
			
		||||
          // 手动计算token
 | 
			
		||||
        if(result.detail && result.text){
 | 
			
		||||
          const promptTokens = getTokenCount(prompt)
 | 
			
		||||
          if (result.detail && result.text) {
 | 
			
		||||
            const promptTokens = getTokenCount(totalText)
 | 
			
		||||
            const completionTokens = getTokenCount(result.text)
 | 
			
		||||
            result.detail.usage = {
 | 
			
		||||
              prompt_tokens: promptTokens,
 | 
			
		||||
            completion_tokens: completionTokens ,
 | 
			
		||||
              completion_tokens: completionTokens,
 | 
			
		||||
              total_tokens: promptTokens + completionTokens,
 | 
			
		||||
              estimated: true
 | 
			
		||||
            }
 | 
			
		||||
@@ -97,10 +171,9 @@ export  function sendMessageFromOpenAi(messagesHistory,  inputs ){
 | 
			
		||||
        reject(error)
 | 
			
		||||
      }
 | 
			
		||||
    })
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
export function getTokenCount(text: string) {
 | 
			
		||||
  if (!text) return 0;
 | 
			
		||||
  // 确保text是字符串类型
 | 
			
		||||
@@ -111,28 +184,4 @@ export function getTokenCount(text: string) {
 | 
			
		||||
  return tokenizer.encode(text).length
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function compilerToken(model, maxToken){
 | 
			
		||||
  let max = 0
 | 
			
		||||
 | 
			
		||||
  /* 3.5 */
 | 
			
		||||
  if(model.includes(3.5)){
 | 
			
		||||
    max = maxToken > 4096 ? 4096 : maxToken
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /* 4.0 */
 | 
			
		||||
  if(model.includes('gpt-4')){
 | 
			
		||||
    max = maxToken > 8192 ? 8192 : maxToken
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /* 4.0 preview */
 | 
			
		||||
  if(model.includes('preview')){
 | 
			
		||||
    max = maxToken > 4096 ? 4096 : maxToken
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /* 4.0 32k */
 | 
			
		||||
  if(model.includes('32k')){
 | 
			
		||||
    max = maxToken > 32768 ? 32768 : maxToken
 | 
			
		||||
  }
 | 
			
		||||
 
 | 
			
		||||
  return max
 | 
			
		||||
}
 | 
			
		||||
@@ -2,6 +2,7 @@ import Keyv from 'keyv';
 | 
			
		||||
import { v4 as uuidv4 } from 'uuid';
 | 
			
		||||
import { get_encoding } from '@dqbd/tiktoken';
 | 
			
		||||
import { Logger } from '@nestjs/common';
 | 
			
		||||
import { includes } from 'lodash';
 | 
			
		||||
 | 
			
		||||
const tokenizer = get_encoding('cl100k_base');
 | 
			
		||||
 | 
			
		||||
@@ -96,7 +97,7 @@ export class NineStore implements NineStoreInterface {
 | 
			
		||||
    let nextNumTokensEstimate = 0;
 | 
			
		||||
    // messages.push({ role: 'system', content: systemMessage, name })
 | 
			
		||||
    if (systemMessage) {
 | 
			
		||||
      const specialModels = ['gemini-pro', 'ERNIE', 'qwen', 'SparkDesk', 'hunyuan'];
 | 
			
		||||
      const specialModels = ['gemini-pro', 'ERNIE','hunyuan'];
 | 
			
		||||
      const isSpecialModel = activeModel && specialModels.some((specialModel) => activeModel.includes(specialModel));
 | 
			
		||||
      if (isSpecialModel) {
 | 
			
		||||
        messages.push({ role: 'user', content: systemMessage, name });
 | 
			
		||||
@@ -146,7 +147,7 @@ export class NineStore implements NineStoreInterface {
 | 
			
		||||
      let content = text; // 默认情况下使用text作为content
 | 
			
		||||
 | 
			
		||||
      // 特别处理包含 imageUrl 的消息
 | 
			
		||||
      if (role === 'user' && imageUrl) {
 | 
			
		||||
      if (imageUrl) {
 | 
			
		||||
        if (activeModel === 'gpt-4-vision-preview') {
 | 
			
		||||
          content = [
 | 
			
		||||
            { type: 'text', text: text },
 | 
			
		||||
 
 | 
			
		||||
@@ -13,7 +13,7 @@ interface UserInfo {
 | 
			
		||||
 | 
			
		||||
@Injectable()
 | 
			
		||||
export class DatabaseService implements OnModuleInit {
 | 
			
		||||
  constructor(private connection: Connection) {}
 | 
			
		||||
  constructor(private connection: Connection) { }
 | 
			
		||||
  async onModuleInit() {
 | 
			
		||||
    await this.checkSuperAdmin();
 | 
			
		||||
    await this.checkSiteBaseConfig();
 | 
			
		||||
@@ -23,7 +23,7 @@ export class DatabaseService implements OnModuleInit {
 | 
			
		||||
  async checkSuperAdmin() {
 | 
			
		||||
    const user = await this.connection.query(`SELECT * FROM users WHERE role = 'super'`);
 | 
			
		||||
    if (!user || user.length === 0) {
 | 
			
		||||
      const superPassword = bcrypt.hashSync('123456', 10);
 | 
			
		||||
      const superPassword = bcrypt.hashSync('123456', 10); //初始密码
 | 
			
		||||
      const adminPassword = bcrypt.hashSync('123456', 10);
 | 
			
		||||
      const superEmail = 'default@cooper.com';
 | 
			
		||||
      const adminEmail = 'defaultAdmin@cooper.com';
 | 
			
		||||
@@ -44,7 +44,7 @@ export class DatabaseService implements OnModuleInit {
 | 
			
		||||
      const userId = user.insertId;
 | 
			
		||||
      const balance = await this.connection.query(`INSERT INTO balance (userId, balance, usesLeft, paintCount) VALUES ('${userId}', 0, 1000, 100)`);
 | 
			
		||||
      Logger.log(
 | 
			
		||||
        `初始化创建${role}用户成功、用户名为[${username}]、初始密码为[${username === 'super' ? '123456' : '123456'}] ==============> 请注意查阅`,
 | 
			
		||||
        `初始化创建${role}用户成功、用户名为[${username}]、初始密码为[${username === 'super' ? 'nine-super' : '123456'}] ==============> 请注意查阅`,
 | 
			
		||||
        'DatabaseService',
 | 
			
		||||
      );
 | 
			
		||||
    } catch (error) {
 | 
			
		||||
@@ -68,17 +68,7 @@ export class DatabaseService implements OnModuleInit {
 | 
			
		||||
  /* 创建基础的网站数据 */
 | 
			
		||||
  async createBaseSiteConfig() {
 | 
			
		||||
    try {
 | 
			
		||||
      const code = `
 | 
			
		||||
  <script>
 | 
			
		||||
  var _hmt = _hmt || [];
 | 
			
		||||
  (function() {
 | 
			
		||||
    var hm = document.createElement("script");
 | 
			
		||||
    hm.src = "https://hm.baidu.com/hm.js?cb8c9a3bcadbc200e950b05f9c61a385";
 | 
			
		||||
    var s = document.getElementsByTagName("script")[0];
 | 
			
		||||
    s.parentNode.insertBefore(hm, s);
 | 
			
		||||
  })();
 | 
			
		||||
  </script>
 | 
			
		||||
`;
 | 
			
		||||
      const code = ``;
 | 
			
		||||
 | 
			
		||||
      const noticeInfo = `
 | 
			
		||||
#### YiAi 欢迎您
 | 
			
		||||
@@ -88,21 +78,21 @@ export class DatabaseService implements OnModuleInit {
 | 
			
		||||
`;
 | 
			
		||||
 | 
			
		||||
      const defaultConfig = [
 | 
			
		||||
        { configKey: 'siteName', configVal: 'Nine Ai', public: 1, encry: 0 },
 | 
			
		||||
        { configKey: 'qqNumber', configVal: '840814166', public: 1, encry: 0 },
 | 
			
		||||
        { configKey: 'vxNumber', configVal: 'wangpanzhu321', public: 1, encry: 0 },
 | 
			
		||||
        { configKey: 'siteName', configVal: 'Yi Ai', public: 1, encry: 0 },
 | 
			
		||||
        { configKey: 'qqNumber', configVal: '805239273', public: 1, encry: 0 },
 | 
			
		||||
        { configKey: 'vxNumber', configVal: 'HelloWordYi819', public: 1, encry: 0 },
 | 
			
		||||
        { configKey: 'robotAvatar', configVal: '', public: 1, encry: 0 },
 | 
			
		||||
        {
 | 
			
		||||
          configKey: 'userDefautlAvatar',
 | 
			
		||||
          configVal: 'https://public-1300678944.cos.ap-shanghai.myqcloud.com/blog/1682571295452image.png',
 | 
			
		||||
          configVal: '',
 | 
			
		||||
          public: 0,
 | 
			
		||||
          encry: 0,
 | 
			
		||||
        },
 | 
			
		||||
        { configKey: 'baiduCode', configVal: code, public: 1, encry: 0 },
 | 
			
		||||
        { configKey: 'baiduSiteId', configVal: '19024441', public: 0, encry: 0 },
 | 
			
		||||
        { configKey: 'baiduSiteId', configVal: '', public: 0, encry: 0 },
 | 
			
		||||
        {
 | 
			
		||||
          configKey: 'baiduToken',
 | 
			
		||||
          configVal: '121.a1600b9b60910feea2ef627ea9776a6f.YGP_CWCOA2lNcIGJ27BwXGxa6nZhBQyLUS4XVaD.TWt9TA',
 | 
			
		||||
          configVal: '',
 | 
			
		||||
          public: 0,
 | 
			
		||||
          encry: 0,
 | 
			
		||||
        },
 | 
			
		||||
@@ -110,25 +100,25 @@ export class DatabaseService implements OnModuleInit {
 | 
			
		||||
        { configKey: 'openaiBaseUrl', configVal: 'https://api.openai.com', public: 0, encry: 0 },
 | 
			
		||||
        { configKey: 'noticeInfo', configVal: noticeInfo, public: 1, encry: 0 },
 | 
			
		||||
 | 
			
		||||
        { configKey: 'registerVerifyEmailTitle', configVal: 'NineTeam团队账号验证', public: 0, encry: 0 },
 | 
			
		||||
        { configKey: 'registerVerifyEmailTitle', configVal: 'Yi Ai团队账号验证', public: 0, encry: 0 },
 | 
			
		||||
        {
 | 
			
		||||
          configKey: 'registerVerifyEmailDesc',
 | 
			
		||||
          configVal: '欢迎使用Nine Team团队的产品服务,请在五分钟内完成你的账号激活,点击以下按钮激活您的账号,',
 | 
			
		||||
          configVal: '欢迎使用Yi Ai团队的产品服务,请在五分钟内完成你的账号激活,点击以下按钮激活您的账号,',
 | 
			
		||||
          public: 0,
 | 
			
		||||
          encry: 0,
 | 
			
		||||
        },
 | 
			
		||||
        { configKey: 'registerVerifyEmailFrom', configVal: 'NineTeam团队', public: 0, encry: 0 },
 | 
			
		||||
        { configKey: 'registerVerifyEmailFrom', configVal: 'Yi Ai团队', public: 0, encry: 0 },
 | 
			
		||||
        { configKey: 'registerVerifyExpir', configVal: '1800', public: 0, encry: 0 },
 | 
			
		||||
        { configKey: 'registerSuccessEmailTitle', configVal: 'NineTeam团队账号激活成功', public: 0, encry: 0 },
 | 
			
		||||
        { configKey: 'registerSuccessEmailTeamName', configVal: 'NineTeam团队', public: 0, encry: 0 },
 | 
			
		||||
        { configKey: 'registerSuccessEmailTitle', configVal: 'Yi Ai账号激活成功', public: 0, encry: 0 },
 | 
			
		||||
        { configKey: 'registerSuccessEmailTeamName', configVal: 'Yi Ai', public: 0, encry: 0 },
 | 
			
		||||
        {
 | 
			
		||||
          configKey: 'registerSuccessEmaileAppend',
 | 
			
		||||
          configVal: ',请妥善保管您的账号,我们将为您赠送50次对话额度和5次绘画额度、祝您使用愉快',
 | 
			
		||||
          configVal: ',请妥善保管您的账号,祝您使用愉快',
 | 
			
		||||
          public: 0,
 | 
			
		||||
          encry: 0,
 | 
			
		||||
        },
 | 
			
		||||
        { configKey: 'registerFailEmailTitle', configVal: 'NineTeam账号激活失败', public: 0, encry: 0 },
 | 
			
		||||
        { configKey: 'registerFailEmailTeamName', configVal: 'NineTeam团队', public: 0, encry: 0 },
 | 
			
		||||
        { configKey: 'registerFailEmailTitle', configVal: 'Yi Ai账号激活失败', public: 0, encry: 0 },
 | 
			
		||||
        { configKey: 'registerFailEmailTeamName', configVal: 'Yi Ai团队', public: 0, encry: 0 },
 | 
			
		||||
        /* 注册默认设置 */
 | 
			
		||||
        { configKey: 'registerSendStatus', configVal: '1', public: 1, encry: 0 },
 | 
			
		||||
        { configKey: 'registerSendModel3Count', configVal: '30', public: 1, encry: 0 },
 | 
			
		||||
@@ -136,16 +126,16 @@ export class DatabaseService implements OnModuleInit {
 | 
			
		||||
        { configKey: 'registerSendDrawMjCount', configVal: '3', public: 1, encry: 0 },
 | 
			
		||||
        { configKey: 'firstRegisterSendStatus', configVal: '1', public: 1, encry: 0 },
 | 
			
		||||
        { configKey: 'firstRegisterSendRank', configVal: '500', public: 1, encry: 0 },
 | 
			
		||||
        { configKey: 'firstRregisterSendModel3Count', configVal: '20', public: 1, encry: 0 },
 | 
			
		||||
        { configKey: 'firstRregisterSendModel4Count', configVal: '2', public: 1, encry: 0 },
 | 
			
		||||
        { configKey: 'firstRregisterSendDrawMjCount', configVal: '3', public: 1, encry: 0 },
 | 
			
		||||
        { configKey: 'firstRregisterSendModel3Count', configVal: '10', public: 1, encry: 0 },
 | 
			
		||||
        { configKey: 'firstRregisterSendModel4Count', configVal: '10', public: 1, encry: 0 },
 | 
			
		||||
        { configKey: 'firstRregisterSendDrawMjCount', configVal: '10', public: 1, encry: 0 },
 | 
			
		||||
        { configKey: 'inviteSendStatus', configVal: '1', public: 1, encry: 0 },
 | 
			
		||||
        { configKey: 'inviteGiveSendModel3Count', configVal: '30', public: 1, encry: 0 },
 | 
			
		||||
        { configKey: 'inviteGiveSendModel4Count', configVal: '3', public: 1, encry: 0 },
 | 
			
		||||
        { configKey: 'inviteGiveSendDrawMjCount', configVal: '1', public: 1, encry: 0 },
 | 
			
		||||
        { configKey: 'inviteGiveSendModel3Count', configVal: '0', public: 1, encry: 0 },
 | 
			
		||||
        { configKey: 'inviteGiveSendModel4Count', configVal: '0', public: 1, encry: 0 },
 | 
			
		||||
        { configKey: 'inviteGiveSendDrawMjCount', configVal: '0', public: 1, encry: 0 },
 | 
			
		||||
        { configKey: 'invitedGuestSendModel3Count', configVal: '10', public: 1, encry: 0 },
 | 
			
		||||
        { configKey: 'invitedGuestSendModel4Count', configVal: '1', public: 1, encry: 0 },
 | 
			
		||||
        { configKey: 'invitedGuestSendDrawMjCount', configVal: '1', public: 1, encry: 0 },
 | 
			
		||||
        { configKey: 'invitedGuestSendModel4Count', configVal: '10', public: 1, encry: 0 },
 | 
			
		||||
        { configKey: 'invitedGuestSendDrawMjCount', configVal: '10', public: 1, encry: 0 },
 | 
			
		||||
        { configKey: 'isVerifyEmail', configVal: '1', public: 1, encry: 0 },
 | 
			
		||||
      ];
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -22,9 +22,6 @@ export class MidjourneyEntity extends BaseEntity {
 | 
			
		||||
  @Column({ comment: '垫图图片 + 绘画描述词 + 额外参数 = 完整的prompt', type: 'text' })
 | 
			
		||||
  fullPrompt: string;
 | 
			
		||||
 | 
			
		||||
  @Column({ comment: '随机产生的绘画ID用于拿取比对结果' })
 | 
			
		||||
  randomDrawId: string;
 | 
			
		||||
 | 
			
		||||
  @Column({ comment: '当前绘制任务的进度', nullable: true })
 | 
			
		||||
  progress: number;
 | 
			
		||||
 | 
			
		||||
@@ -35,7 +32,7 @@ export class MidjourneyEntity extends BaseEntity {
 | 
			
		||||
  status: number;
 | 
			
		||||
 | 
			
		||||
  @Column({ comment: 'mj绘画的动作、绘图、放大、变换、图生图' })
 | 
			
		||||
  action: number;
 | 
			
		||||
  action: string;
 | 
			
		||||
 | 
			
		||||
  @Column({ comment: '一组图片的第几张、放大或者变换的时候需要使用', nullable: true })
 | 
			
		||||
  orderId: number;
 | 
			
		||||
@@ -43,14 +40,17 @@ export class MidjourneyEntity extends BaseEntity {
 | 
			
		||||
  @Column({ comment: '是否推荐0: 默认不推荐 1: 推荐', nullable: true, default: 0 })
 | 
			
		||||
  rec: number;
 | 
			
		||||
 | 
			
		||||
  @Column({ comment: '对图片操作的', nullable: true })
 | 
			
		||||
  customId: string;
 | 
			
		||||
 | 
			
		||||
  @Column({ comment: '绘画的ID每条不一样', nullable: true })
 | 
			
		||||
  message_id: string;
 | 
			
		||||
  drawId: string;
 | 
			
		||||
 | 
			
		||||
  @Column({ comment: '对图片放大或者变体的ID', nullable: true })
 | 
			
		||||
  custom_id: string;
 | 
			
		||||
  @Column({ comment: '图片链接', nullable: true, type: 'text' })
 | 
			
		||||
  drawUrl: string;
 | 
			
		||||
 | 
			
		||||
  @Column({ comment: '图片信息尺寸', nullable: true, type: 'text' })
 | 
			
		||||
  fileInfo: string;
 | 
			
		||||
  @Column({ comment: '图片比例', nullable: true, type: 'text' })
 | 
			
		||||
  drawRatio: string;
 | 
			
		||||
 | 
			
		||||
  @Column({ comment: '扩展参数', nullable: true, type: 'text' })
 | 
			
		||||
  extend: string;
 | 
			
		||||
@@ -60,4 +60,5 @@ export class MidjourneyEntity extends BaseEntity {
 | 
			
		||||
 | 
			
		||||
  @Column({ comment: '是否存入了图片到配置的储存项 配置了则存储 不配置地址则是源地址', default: true })
 | 
			
		||||
  isSaveImg: boolean;
 | 
			
		||||
  messageId: any;
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							@@ -1,4 +1,4 @@
 | 
			
		||||
import { AddBadWordDto } from '../../badwords/dto/addBadWords.dto';
 | 
			
		||||
import { AddBadWordDto } from './../../badwords/dto/addBadWords.dto';
 | 
			
		||||
import { IsNotEmpty, MinLength, MaxLength, IsEmail, IsOptional, IsNumber } from 'class-validator';
 | 
			
		||||
import { ApiProperty } from '@nestjs/swagger';
 | 
			
		||||
 | 
			
		||||
@@ -27,6 +27,9 @@ export class SetModelDto {
 | 
			
		||||
  @ApiProperty({ example: 1, description: 'key的权重' })
 | 
			
		||||
  keyWeight: number;
 | 
			
		||||
 | 
			
		||||
  @ApiProperty({ example: 1, description: '模型排序' })
 | 
			
		||||
  modelOrder: number;
 | 
			
		||||
 | 
			
		||||
  @ApiProperty({ example: 4096, description: '模型支持的最大TOken数量', required: true })
 | 
			
		||||
  maxModelTokens: number;
 | 
			
		||||
 | 
			
		||||
@@ -53,9 +56,10 @@ export class SetModelDto {
 | 
			
		||||
 | 
			
		||||
  @ApiProperty({ example: true, description: '是否设置为绘画Key', required: false })
 | 
			
		||||
  isDraw: boolean;
 | 
			
		||||
  //设置token计费
 | 
			
		||||
 | 
			
		||||
  @ApiProperty({ example: true, description: '是否使用token计费', required: false })
 | 
			
		||||
  isTokenBased: boolean;
 | 
			
		||||
 | 
			
		||||
  @ApiProperty({ example: true, description: 'token计费比例', required: false })
 | 
			
		||||
  tokenFeeRatio: number;
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -42,4 +42,7 @@ export class ModelsTypeEntity extends BaseEntity {
 | 
			
		||||
 | 
			
		||||
  @Column({ comment: '是否为特殊模型、可以提供联想翻译、思维导图等特殊操作', default: 0 })
 | 
			
		||||
  isUseTool: boolean;
 | 
			
		||||
 | 
			
		||||
  @Column({ comment: '模型排序', default: 1 })
 | 
			
		||||
  modelOrder: number;
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -69,4 +69,8 @@ export class ModelsEntity extends BaseEntity {
 | 
			
		||||
 | 
			
		||||
  @Column({ comment: 'token计费比例', default: 0 })
 | 
			
		||||
  tokenFeeRatio: number;
 | 
			
		||||
 | 
			
		||||
  @Column({ comment: 'key权重', default: 1 })
 | 
			
		||||
  modelOrder: number;
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -5,7 +5,7 @@ import { ModelsEntity } from './models.entity';
 | 
			
		||||
import { SetModelDto } from './dto/setModel.dto';
 | 
			
		||||
import { QueryModelDto } from './dto/queryModel.dto';
 | 
			
		||||
import { ModelsMapCn } from '@/common/constants/status.constant';
 | 
			
		||||
import { getAccessToken } from '../chatgpt/baidu';
 | 
			
		||||
// import { getAccessToken } from '../chatgpt/baidu';
 | 
			
		||||
import { getRandomItemFromArray, hideString } from '@/common/utils';
 | 
			
		||||
import { ModelsTypeEntity } from './modelType.entity';
 | 
			
		||||
import { SetModelTypeDto } from './dto/setModelType.dto';
 | 
			
		||||
@@ -19,7 +19,7 @@ export class ModelsService {
 | 
			
		||||
    private readonly modelsEntity: Repository<ModelsEntity>,
 | 
			
		||||
    @InjectRepository(ModelsTypeEntity)
 | 
			
		||||
    private readonly modelsTypeEntity: Repository<ModelsTypeEntity>,
 | 
			
		||||
  ){}
 | 
			
		||||
  ) { }
 | 
			
		||||
 | 
			
		||||
  private modelTypes = []
 | 
			
		||||
  private modelMaps = {}
 | 
			
		||||
@@ -28,73 +28,73 @@ export class ModelsService {
 | 
			
		||||
  private keyPoolMap = {} // 记录每个模型的所有key 并且记录顺序
 | 
			
		||||
  private keyPoolIndexMap = {} // 记录每个模型的当前调用的下标
 | 
			
		||||
 | 
			
		||||
  async onModuleInit(){
 | 
			
		||||
  async onModuleInit() {
 | 
			
		||||
    await this.initCalcKey()
 | 
			
		||||
    this.refreshBaiduAccesstoken()
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /* 初始化整理所有key 进行分类并且默认一个初始模型配置 默认是配置的第一个分类的第一个key为准 */
 | 
			
		||||
  async initCalcKey(){
 | 
			
		||||
  async initCalcKey() {
 | 
			
		||||
    this.keyPoolMap = {}
 | 
			
		||||
    this.keyPoolIndexMap = {}
 | 
			
		||||
    this.keyList = {}
 | 
			
		||||
    this.modelMaps = {}
 | 
			
		||||
    this.modelTypes = []
 | 
			
		||||
    const allKeys = await this.modelsEntity.find({where: { status: true }})
 | 
			
		||||
    const keyTypes = allKeys.reduce( (pre: any, cur ) => {
 | 
			
		||||
      if(!pre[cur.keyType]){
 | 
			
		||||
    const allKeys = await this.modelsEntity.find({ where: { status: true } })
 | 
			
		||||
    const keyTypes = allKeys.reduce((pre: any, cur) => {
 | 
			
		||||
      if (!pre[cur.keyType]) {
 | 
			
		||||
        pre[cur.keyType] = [cur]
 | 
			
		||||
      }else{
 | 
			
		||||
      } else {
 | 
			
		||||
        pre[cur.keyType].push(cur)
 | 
			
		||||
      }
 | 
			
		||||
      return pre
 | 
			
		||||
    }, {})
 | 
			
		||||
    this.modelTypes = Object.keys(keyTypes).map( keyType => {
 | 
			
		||||
      return { label: ModelsMapCn[keyType] , val: keyType}
 | 
			
		||||
    this.modelTypes = Object.keys(keyTypes).map(keyType => {
 | 
			
		||||
      return { label: ModelsMapCn[keyType], val: keyType }
 | 
			
		||||
    })
 | 
			
		||||
    this.modelMaps = keyTypes
 | 
			
		||||
    this.keyList = {}
 | 
			
		||||
    allKeys.forEach( keyDetail => {
 | 
			
		||||
 | 
			
		||||
    allKeys.forEach(keyDetail => {
 | 
			
		||||
      const { keyType, model, keyWeight } = keyDetail
 | 
			
		||||
      if(!this.keyPoolMap[model]) this.keyPoolMap[model] = []
 | 
			
		||||
      if (!this.keyPoolMap[model]) this.keyPoolMap[model] = []
 | 
			
		||||
      for (let index = 0; index < keyWeight; index++) {
 | 
			
		||||
        this.keyPoolMap[model].push(keyDetail)
 | 
			
		||||
      }
 | 
			
		||||
      if(!this.keyPoolIndexMap[model]) this.keyPoolIndexMap[model] = 0
 | 
			
		||||
      if(!this.keyList[keyType]) this.keyList[keyType] = {}
 | 
			
		||||
      if(!this.keyList[keyType][model]) this.keyList[keyType][model] = []
 | 
			
		||||
      if (!this.keyPoolIndexMap[model]) this.keyPoolIndexMap[model] = 0
 | 
			
		||||
      if (!this.keyList[keyType]) this.keyList[keyType] = {}
 | 
			
		||||
      if (!this.keyList[keyType][model]) this.keyList[keyType][model] = []
 | 
			
		||||
      this.keyList[keyType][model].push(keyDetail)
 | 
			
		||||
    })
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /* lock key 自动锁定key */
 | 
			
		||||
  async lockKey(keyId, remark, keyStatus = -1){
 | 
			
		||||
  async lockKey(keyId, remark, keyStatus = -1) {
 | 
			
		||||
    const res = await this.modelsEntity.update({ id: keyId }, { status: false, keyStatus, remark });
 | 
			
		||||
    Logger.error(`key: ${keyId} 欠费或被官方封禁导致不可用,已被系统自动锁定`);
 | 
			
		||||
    this.initCalcKey()
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /* 获取本次调用key的详细信息 */
 | 
			
		||||
  async getCurrentModelKeyInfo(model){
 | 
			
		||||
    if(!this.keyPoolMap[model]){
 | 
			
		||||
  async getCurrentModelKeyInfo(model) {
 | 
			
		||||
    if (!this.keyPoolMap[model]) {
 | 
			
		||||
      throw new HttpException('当前调用模型已经被移除、请重新选择模型!', HttpStatus.BAD_REQUEST)
 | 
			
		||||
    }
 | 
			
		||||
    /* 调用下标+1 */
 | 
			
		||||
    this.keyPoolIndexMap[model]++
 | 
			
		||||
    /* 判断下标超出边界没有 */
 | 
			
		||||
    const index = this.keyPoolIndexMap[model]
 | 
			
		||||
    if(index >= this.keyPoolMap[model].length) this.keyPoolIndexMap[model] = 0
 | 
			
		||||
    if (index >= this.keyPoolMap[model].length) this.keyPoolIndexMap[model] = 0
 | 
			
		||||
    const key = this.keyPoolMap[model][this.keyPoolIndexMap[model]]
 | 
			
		||||
    return key
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /* 通过现有配置的key和分类给到默认的配置信息 默认给到第一个分类的第一个key的配置 */
 | 
			
		||||
  async getBaseConfig(appId?: number): Promise<any>{
 | 
			
		||||
    if(!this.modelTypes.length || !Object.keys(this.modelMaps).length) return;
 | 
			
		||||
  async getBaseConfig(appId?: number): Promise<any> {
 | 
			
		||||
    if (!this.modelTypes.length || !Object.keys(this.modelMaps).length) return;
 | 
			
		||||
    /* 有appid只可以使用openai 的 模型 */
 | 
			
		||||
    const modelTypeInfo = appId ? this.modelTypes.find( item => Number(item.val) === 1) : this.modelTypes[0]
 | 
			
		||||
    const modelTypeInfo = appId ? this.modelTypes.find(item => Number(item.val) === 1) : this.modelTypes[0]
 | 
			
		||||
    // TODO 第0个会有问题  先添加的4默认就是模型4了  后面优化下
 | 
			
		||||
    if(!modelTypeInfo) return;
 | 
			
		||||
    if (!modelTypeInfo) return;
 | 
			
		||||
    const { keyType, modelName, model, maxModelTokens, maxResponseTokens, deductType, deduct, maxRounds } = this.modelMaps[modelTypeInfo.val][0] // 取到第一个默认的配置项信息
 | 
			
		||||
    return {
 | 
			
		||||
      modelTypeInfo,
 | 
			
		||||
@@ -102,25 +102,22 @@ export class ModelsService {
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  async setModel(params: SetModelDto){
 | 
			
		||||
  async setModel(params: SetModelDto) {
 | 
			
		||||
    try {
 | 
			
		||||
      const { id } = params
 | 
			
		||||
      params.status && (params.keyStatus = 1)
 | 
			
		||||
    if(id){
 | 
			
		||||
      const res = await this.modelsEntity.update({id}, params)
 | 
			
		||||
      if (id) {
 | 
			
		||||
        const res = await this.modelsEntity.update({ id }, params)
 | 
			
		||||
        await this.initCalcKey()
 | 
			
		||||
        return res.affected > 0
 | 
			
		||||
    }else{
 | 
			
		||||
      } else {
 | 
			
		||||
        const { keyType, key } = params
 | 
			
		||||
      if(Number(keyType !== 1)){
 | 
			
		||||
        if (Number(keyType !== 1)) {
 | 
			
		||||
          const res = await this.modelsEntity.save(params)
 | 
			
		||||
          await this.initCalcKey()
 | 
			
		||||
        if(keyType === 2){ //百度的需要刷新token
 | 
			
		||||
          this.refreshBaiduAccesstoken()
 | 
			
		||||
        }
 | 
			
		||||
          return res
 | 
			
		||||
      }else{
 | 
			
		||||
        const data = key.map( k => {
 | 
			
		||||
        } else {
 | 
			
		||||
          const data = key.map(k => {
 | 
			
		||||
            try {
 | 
			
		||||
              const data = JSON.parse(JSON.stringify(params))
 | 
			
		||||
              data.key = k
 | 
			
		||||
@@ -139,34 +136,37 @@ export class ModelsService {
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  async delModel({id}){
 | 
			
		||||
    if(!id) {
 | 
			
		||||
  async delModel({ id }) {
 | 
			
		||||
    if (!id) {
 | 
			
		||||
      throw new HttpException('缺失必要参数!', HttpStatus.BAD_REQUEST)
 | 
			
		||||
    }
 | 
			
		||||
    const m = await this.modelsEntity.findOne({where: {id}})
 | 
			
		||||
    if(!m){
 | 
			
		||||
    const m = await this.modelsEntity.findOne({ where: { id } })
 | 
			
		||||
    if (!m) {
 | 
			
		||||
      throw new HttpException('当前账号不存在!', HttpStatus.BAD_REQUEST)
 | 
			
		||||
    }
 | 
			
		||||
    const res = await this.modelsEntity.delete({id})
 | 
			
		||||
    const res = await this.modelsEntity.delete({ id })
 | 
			
		||||
    await this.initCalcKey()
 | 
			
		||||
    return res;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  async queryModels(req, params: QueryModelDto){
 | 
			
		||||
  async queryModels(req, params: QueryModelDto) {
 | 
			
		||||
    const { role } = req.user
 | 
			
		||||
    const { keyType, key, status, model, page = 1, size = 10 } = params
 | 
			
		||||
    let where: any = {}
 | 
			
		||||
    keyType && (where.keyType = keyType)
 | 
			
		||||
    model && (where.model = model)
 | 
			
		||||
    status && (where.status = Number(status) === 1 ? true : false)
 | 
			
		||||
    key && ( where.key = Like(`%${key}%`))
 | 
			
		||||
    key && (where.key = Like(`%${key}%`))
 | 
			
		||||
    const [rows, count] = await this.modelsEntity.findAndCount({
 | 
			
		||||
      where: where,
 | 
			
		||||
      order: {
 | 
			
		||||
        modelOrder: 'ASC'
 | 
			
		||||
      },
 | 
			
		||||
      skip: (page - 1) * size,
 | 
			
		||||
      take: size,
 | 
			
		||||
    })
 | 
			
		||||
    if(role !== 'super'){
 | 
			
		||||
      rows.forEach( item => {
 | 
			
		||||
    if (role !== 'super') {
 | 
			
		||||
      rows.forEach(item => {
 | 
			
		||||
        item.key && (item.key = hideString(item.key))
 | 
			
		||||
        item.secret && (item.secret = hideString(item.secret))
 | 
			
		||||
      })
 | 
			
		||||
@@ -176,24 +176,29 @@ export class ModelsService {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /* 客户端查询到的所有的配置的模型类别 以及类别下自定义的多少中文模型名称 */
 | 
			
		||||
  async modelsList(){
 | 
			
		||||
    const cloneModelMaps = JSON.parse(JSON.stringify(this.modelMaps))
 | 
			
		||||
    Object.keys(cloneModelMaps).forEach( key => {
 | 
			
		||||
  async modelsList() {
 | 
			
		||||
    const cloneModelMaps = JSON.parse(JSON.stringify(this.modelMaps));
 | 
			
		||||
    Object.keys(cloneModelMaps).forEach(key => {
 | 
			
		||||
      // 对每个模型进行排序
 | 
			
		||||
      cloneModelMaps[key] = cloneModelMaps[key].sort((a, b) => a.modelOrder - b.modelOrder);
 | 
			
		||||
      cloneModelMaps[key] = Array.from(
 | 
			
		||||
        cloneModelMaps[key].map( t => {
 | 
			
		||||
          const { modelName, model, deduct, deductType, maxRounds } = t
 | 
			
		||||
          return { modelName, model, deduct, deductType, maxRounds }
 | 
			
		||||
        }).reduce((map, obj) => map.set(obj.modelName, obj), new Map()).values()
 | 
			
		||||
      );
 | 
			
		||||
        cloneModelMaps[key]
 | 
			
		||||
          .map(t => {
 | 
			
		||||
            const { modelName, model, deduct, deductType, maxRounds } = t;
 | 
			
		||||
            return { modelName, model, deduct, deductType, maxRounds };
 | 
			
		||||
          })
 | 
			
		||||
          .reduce((map, obj) => map.set(obj.modelName, obj), new Map()).values()
 | 
			
		||||
      );
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    return {
 | 
			
		||||
      modelTypeList: this.modelTypes,
 | 
			
		||||
      modelMaps: cloneModelMaps
 | 
			
		||||
    }
 | 
			
		||||
    };
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /* 记录使用次数和使用的token数量 */
 | 
			
		||||
  async saveUseLog(id, useToken){
 | 
			
		||||
  async saveUseLog(id, useToken) {
 | 
			
		||||
    await this.modelsEntity
 | 
			
		||||
      .createQueryBuilder()
 | 
			
		||||
      .update(ModelsEntity)
 | 
			
		||||
@@ -202,54 +207,32 @@ export class ModelsService {
 | 
			
		||||
      .execute();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  async refreshBaiduAccesstoken(){
 | 
			
		||||
    const allKeys = await this.modelsEntity.find({ where: { keyType: 2 } })
 | 
			
		||||
    const keysMap: any = {}
 | 
			
		||||
    allKeys.forEach( keyInfo => {
 | 
			
		||||
      const { key, secret } = keyInfo
 | 
			
		||||
      if(!keysMap.key){
 | 
			
		||||
        keysMap[key] = [{ keyInfo }]
 | 
			
		||||
      }else{
 | 
			
		||||
        keysMap[key].push(keyInfo)
 | 
			
		||||
      }
 | 
			
		||||
    })
 | 
			
		||||
    Object.keys(keysMap).forEach( async key => {
 | 
			
		||||
      const {secret, id } = keysMap[key][0]['keyInfo']
 | 
			
		||||
      const accessToken: any = await getAccessToken(key, secret)
 | 
			
		||||
      await this.modelsEntity.update({ key }, { accessToken })
 | 
			
		||||
    })
 | 
			
		||||
 | 
			
		||||
    setTimeout(() => {
 | 
			
		||||
      this.initCalcKey()
 | 
			
		||||
    }, 1000)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /* 获取一张绘画key */
 | 
			
		||||
  async getRandomDrawKey(){
 | 
			
		||||
    const drawkeys = await this.modelsEntity.find({where: { isDraw: true, status: true }})
 | 
			
		||||
    if(!drawkeys.length){
 | 
			
		||||
  async getRandomDrawKey() {
 | 
			
		||||
    const drawkeys = await this.modelsEntity.find({ where: { isDraw: true, status: true } })
 | 
			
		||||
    if (!drawkeys.length) {
 | 
			
		||||
      throw new HttpException('当前未指定特殊模型KEY、前往后台模型池设置吧!', HttpStatus.BAD_REQUEST)
 | 
			
		||||
    }
 | 
			
		||||
    return getRandomItemFromArray(drawkeys)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /* 获取所有key */
 | 
			
		||||
  async getAllKey(){
 | 
			
		||||
  async getAllKey() {
 | 
			
		||||
    return await this.modelsEntity.find()
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /* 查询模型类型 */
 | 
			
		||||
  async queryModelType(params: QueryModelTypeDto){
 | 
			
		||||
  async queryModelType(params: QueryModelTypeDto) {
 | 
			
		||||
    return 1
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /* 创建修改模型类型 */
 | 
			
		||||
  async setModelType(params: SetModelTypeDto){
 | 
			
		||||
  async setModelType(params: SetModelTypeDto) {
 | 
			
		||||
    return 1
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /* 删除模型类型 */
 | 
			
		||||
  async delModelType(params){
 | 
			
		||||
  async delModelType(params) {
 | 
			
		||||
    return 1
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -20,9 +20,9 @@ export class MjDrawDto {
 | 
			
		||||
  @IsOptional()
 | 
			
		||||
  imgUrl?: string;
 | 
			
		||||
 | 
			
		||||
  @ApiProperty({ example: 1, description: '绘画动作 绘图、放大、变换、图生图' })
 | 
			
		||||
  @ApiProperty({ example: 'IMAGINE', description: '任务类型,可用值:IMAGINE,UPSCALE,VARIATION,ZOOM,PAN,DESCRIBE,BLEND,SHORTEN,SWAP_FACE' })
 | 
			
		||||
  @IsOptional()
 | 
			
		||||
  action: number;
 | 
			
		||||
  action: string;
 | 
			
		||||
 | 
			
		||||
  @ApiProperty({ example: 1, description: '变体或者放大的序号' })
 | 
			
		||||
  @IsOptional()
 | 
			
		||||
@@ -31,4 +31,8 @@ export class MjDrawDto {
 | 
			
		||||
  @ApiProperty({ example: 1, description: '绘画的DBID' })
 | 
			
		||||
  @IsOptional()
 | 
			
		||||
  drawId: number;
 | 
			
		||||
 | 
			
		||||
  @ApiProperty({ example: 1, description: '任务ID' })
 | 
			
		||||
  @IsOptional()
 | 
			
		||||
  taskId: number;
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -15,7 +15,7 @@ export class QueueService implements OnApplicationBootstrap {
 | 
			
		||||
    private readonly midjourneyService: MidjourneyService,
 | 
			
		||||
    private readonly userBalanceService: UserBalanceService,
 | 
			
		||||
    private readonly globalConfigService: GlobalConfigService,
 | 
			
		||||
  ) {}
 | 
			
		||||
  ) { }
 | 
			
		||||
  private readonly jobIds: any[] = [];
 | 
			
		||||
 | 
			
		||||
  async onApplicationBootstrap() {
 | 
			
		||||
@@ -27,14 +27,13 @@ export class QueueService implements OnApplicationBootstrap {
 | 
			
		||||
 | 
			
		||||
  /* 提交绘画任务 */
 | 
			
		||||
  async addMjDrawQueue(body: MjDrawDto, req: Request) {
 | 
			
		||||
    const { prompt, imgUrl, extraParam, orderId, action = 1, drawId } = body;
 | 
			
		||||
    const { imgUrl, orderId, action, drawId } = body;
 | 
			
		||||
    /* 限制普通用户队列最多可以有两个任务在排队或者等待中 */
 | 
			
		||||
    await this.midjourneyService.checkLimit(req);
 | 
			
		||||
    /* 检测余额 */
 | 
			
		||||
    await this.userBalanceService.validateBalance(req, 'mjDraw', action === 2 ? 1 : 4);
 | 
			
		||||
 | 
			
		||||
    await this.userBalanceService.validateBalance(req, 'mjDraw', action === 'UPSCALE' ? 1 : 4);
 | 
			
		||||
    /* 绘图或者图生图 */
 | 
			
		||||
    if (action === MidjourneyActionEnum.DRAW || action === MidjourneyActionEnum.GENERATE) {
 | 
			
		||||
    if (action === 'IMAGINE') {
 | 
			
		||||
      /* 绘图或者图生图是相同的 区分一个action即可 */
 | 
			
		||||
      const randomDrawId = `${createRandomUid()}`;
 | 
			
		||||
      const params = { ...body, userId: req.user.id, randomDrawId };
 | 
			
		||||
@@ -44,84 +43,28 @@ export class QueueService implements OnApplicationBootstrap {
 | 
			
		||||
      /* 添加任务到队列 通过imgUrl判断是不是图生图 */
 | 
			
		||||
      const job = await this.mjDrawQueue.add(
 | 
			
		||||
        'mjDraw',
 | 
			
		||||
        { id: res.id, action: imgUrl ? 4 : 1, userId: req.user.id },
 | 
			
		||||
        { id: res.id, action: action, userId: req.user.id },
 | 
			
		||||
        { delay: 1000, timeout: +timeout },
 | 
			
		||||
      );
 | 
			
		||||
      /* 绘图和图生图扣除余额4 */
 | 
			
		||||
      this.jobIds.push(job.id);
 | 
			
		||||
      /* 扣费 */
 | 
			
		||||
      // await this.userBalanceService.deductFromBalance(req.user.id, 'mjDraw', 4, 4);
 | 
			
		||||
      return true;
 | 
			
		||||
    } else {
 | 
			
		||||
      const { orderId, action, drawId } = body;
 | 
			
		||||
      const actionDetail = await this.midjourneyService.getDrawActionDetail(action, drawId, orderId);
 | 
			
		||||
      const params = { ...body, userId: req.user.id, ...actionDetail };
 | 
			
		||||
      const res = await this.midjourneyService.addDrawQueue(params);
 | 
			
		||||
      const timeout = (await this.globalConfigService.getConfigs(['mjTimeoutMs'])) || 200000;
 | 
			
		||||
      const job = await this.mjDrawQueue.add('mjDraw', { id: res.id, action, userId: req.user.id }, { delay: 1000, timeout: +timeout });
 | 
			
		||||
      this.jobIds.push(job.id);
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (!drawId || !orderId) {
 | 
			
		||||
      throw new HttpException('缺少必要参数!', HttpStatus.BAD_REQUEST);
 | 
			
		||||
    }
 | 
			
		||||
    /* 图片操作 */
 | 
			
		||||
 | 
			
		||||
    /* 图片放大 */
 | 
			
		||||
    if (action === MidjourneyActionEnum.UPSCALE) {
 | 
			
		||||
      const actionDetail: any = await this.midjourneyService.getDrawActionDetail(action, drawId, orderId);
 | 
			
		||||
      const { custom_id } = actionDetail;
 | 
			
		||||
      /* 检测当前图片是不是已经放大过了 */
 | 
			
		||||
      await this.midjourneyService.checkIsUpscale(custom_id);
 | 
			
		||||
      const params = { ...body, userId: req.user.id, ...actionDetail };
 | 
			
		||||
      const res = await this.midjourneyService.addDrawQueue(params);
 | 
			
		||||
      const timeout = (await this.globalConfigService.getConfigs(['mjTimeoutMs'])) || 200000;
 | 
			
		||||
      const job = await this.mjDrawQueue.add('mjDraw', { id: res.id, action, userId: req.user.id }, { delay: 1000, timeout: +timeout });
 | 
			
		||||
      /* 扣费 */
 | 
			
		||||
      // await this.userBalanceService.deductFromBalance(req.user.id, 'mjDraw', 1, 1);
 | 
			
		||||
      this.jobIds.push(job.id);
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /* 图片变体 */
 | 
			
		||||
    if (action === MidjourneyActionEnum.VARIATION) {
 | 
			
		||||
      const actionDetail: any = await this.midjourneyService.getDrawActionDetail(action, drawId, orderId);
 | 
			
		||||
      const params = { ...body, userId: req.user.id, ...actionDetail };
 | 
			
		||||
      const res = await this.midjourneyService.addDrawQueue(params);
 | 
			
		||||
      const timeout = (await this.globalConfigService.getConfigs(['mjTimeoutMs'])) || 200000;
 | 
			
		||||
      const job = await this.mjDrawQueue.add('mjDraw', { id: res.id, action, userId: req.user.id }, { delay: 1000, timeout: +timeout });
 | 
			
		||||
      this.jobIds.push(job.id);
 | 
			
		||||
      /* 扣费 */
 | 
			
		||||
      // await this.userBalanceService.deductFromBalance(req.user.id, 'mjDraw', 4, 4);
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /* 重新生成 */
 | 
			
		||||
    if (action === MidjourneyActionEnum.REGENERATE) {
 | 
			
		||||
      const actionDetail: any = await this.midjourneyService.getDrawActionDetail(action, drawId, orderId);
 | 
			
		||||
      const params = { ...body, userId: req.user.id, ...actionDetail };
 | 
			
		||||
      const res = await this.midjourneyService.addDrawQueue(params);
 | 
			
		||||
      const timeout = (await this.globalConfigService.getConfigs(['mjTimeoutMs'])) || 200000;
 | 
			
		||||
      const job = await this.mjDrawQueue.add('mjDraw', { id: res.id, action, userId: req.user.id }, { delay: 1000, timeout: +timeout });
 | 
			
		||||
      this.jobIds.push(job.id);
 | 
			
		||||
      // await this.userBalanceService.deductFromBalance(req.user.id, 'mjDraw', 4, 4);
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /* 对图片增强 Vary */
 | 
			
		||||
    if (action === MidjourneyActionEnum.VARY) {
 | 
			
		||||
      const actionDetail: any = await this.midjourneyService.getDrawActionDetail(action, drawId, orderId);
 | 
			
		||||
      const params = { ...body, userId: req.user.id, ...actionDetail };
 | 
			
		||||
      const res = await this.midjourneyService.addDrawQueue(params);
 | 
			
		||||
      const timeout = (await this.globalConfigService.getConfigs(['mjTimeoutMs'])) || 200000;
 | 
			
		||||
      const job = await this.mjDrawQueue.add('mjDraw', { id: res.id, action, userId: req.user.id }, { delay: 1000, timeout: +timeout });
 | 
			
		||||
      this.jobIds.push(job.id);
 | 
			
		||||
      // await this.userBalanceService.deductFromBalance(req.user.id, 'mjDraw', 4, 4);
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /* 对图片缩放 Zoom */
 | 
			
		||||
    if (action === MidjourneyActionEnum.ZOOM) {
 | 
			
		||||
      const actionDetail: any = await this.midjourneyService.getDrawActionDetail(action, drawId, orderId);
 | 
			
		||||
      const params = { ...body, userId: req.user.id, ...actionDetail };
 | 
			
		||||
      const res = await this.midjourneyService.addDrawQueue(params);
 | 
			
		||||
      const timeout = (await this.globalConfigService.getConfigs(['mjTimeoutMs'])) || 200000;
 | 
			
		||||
      const job = await this.mjDrawQueue.add('mjDraw', { id: res.id, action, userId: req.user.id }, { delay: 1000, timeout: +timeout });
 | 
			
		||||
      this.jobIds.push(job.id);
 | 
			
		||||
      // await this.userBalanceService.deductFromBalance(req.user.id, 'mjDraw', 4, 4);
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /* 查询队列 */
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
import { GlobalConfigService } from '../globalConfig/globalConfig.service';
 | 
			
		||||
import { GlobalConfigService } from './../globalConfig/globalConfig.service';
 | 
			
		||||
import { Injectable, Logger, OnModuleInit } from '@nestjs/common';
 | 
			
		||||
import { Cron, CronExpression } from '@nestjs/schedule';
 | 
			
		||||
import { UserBalanceEntity } from '../userBalance/userBalance.entity';
 | 
			
		||||
@@ -13,7 +13,7 @@ export class TaskService {
 | 
			
		||||
    private readonly userBalanceEntity: Repository<UserBalanceEntity>,
 | 
			
		||||
    private readonly globalConfigService: GlobalConfigService,
 | 
			
		||||
    private readonly modelsService: ModelsService,
 | 
			
		||||
  ) {}
 | 
			
		||||
  ) { }
 | 
			
		||||
 | 
			
		||||
  /* 每小时刷新一次微信的token */
 | 
			
		||||
  @Cron(CronExpression.EVERY_HOUR)
 | 
			
		||||
@@ -40,8 +40,8 @@ export class TaskService {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /* 每小时检测一次授权 */
 | 
			
		||||
  @Cron('0 0 */5 * *')
 | 
			
		||||
  refreshBaiduAccesstoken() {
 | 
			
		||||
    this.modelsService.refreshBaiduAccesstoken();
 | 
			
		||||
  }
 | 
			
		||||
  // @Cron('0 0 */5 * *')
 | 
			
		||||
  // refreshBaiduAccesstoken() {
 | 
			
		||||
  //   this.modelsService.refreshBaiduAccesstoken();
 | 
			
		||||
  // }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -10,34 +10,47 @@ import * as FormData from 'form-data';
 | 
			
		||||
 | 
			
		||||
@Injectable()
 | 
			
		||||
export class UploadService implements OnModuleInit {
 | 
			
		||||
  constructor(private readonly globalConfigService: GlobalConfigService) {}
 | 
			
		||||
  constructor(private readonly globalConfigService: GlobalConfigService) { }
 | 
			
		||||
  private tencentCos: any;
 | 
			
		||||
 | 
			
		||||
  onModuleInit() {}
 | 
			
		||||
  onModuleInit() { }
 | 
			
		||||
 | 
			
		||||
  async uploadFile(file) {
 | 
			
		||||
    const { filename: name, originalname, buffer, dir = 'ai', mimetype } = file;
 | 
			
		||||
    const fileTyle = mimetype ? mimetype.split('/')[1] : '';
 | 
			
		||||
    const filename = originalname || name
 | 
			
		||||
    Logger.debug(`准备上传文件: ${filename}, 类型: ${fileTyle}`, 'UploadService');
 | 
			
		||||
 | 
			
		||||
    const {
 | 
			
		||||
      tencentCosStatus = 0,
 | 
			
		||||
      aliOssStatus = 0,
 | 
			
		||||
      cheveretoStatus = 0,
 | 
			
		||||
    } = await this.globalConfigService.getConfigs(['tencentCosStatus', 'aliOssStatus', 'cheveretoStatus']);
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    Logger.debug(`上传配置状态 - 腾讯云: ${tencentCosStatus}, 阿里云: ${aliOssStatus}, Chevereto: ${cheveretoStatus}`, 'UploadService');
 | 
			
		||||
 | 
			
		||||
    if (!Number(tencentCosStatus) && !Number(aliOssStatus) && !Number(cheveretoStatus)) {
 | 
			
		||||
      throw new HttpException('请先前往后台配置上传图片的方式', HttpStatus.BAD_REQUEST);
 | 
			
		||||
    }
 | 
			
		||||
    try {
 | 
			
		||||
      if (Number(tencentCosStatus)) {
 | 
			
		||||
      return this.uploadFileByTencentCos({ filename, buffer, dir, fileTyle });
 | 
			
		||||
        Logger.debug(`使用腾讯云COS上传`, 'UploadService');
 | 
			
		||||
        return await this.uploadFileByTencentCos({ filename, buffer, dir, fileTyle });
 | 
			
		||||
      }
 | 
			
		||||
      if (Number(aliOssStatus)) {
 | 
			
		||||
        Logger.debug(`使用阿里云OSS上传`, 'UploadService');
 | 
			
		||||
        return await this.uploadFileByAliOss({ filename, buffer, dir, fileTyle });
 | 
			
		||||
      }
 | 
			
		||||
      if (Number(cheveretoStatus)) {
 | 
			
		||||
        Logger.debug(`使用Chevereto上传`, 'UploadService');
 | 
			
		||||
        const { filename, buffer: fromBuffer, dir } = file;
 | 
			
		||||
        return await this.uploadFileByChevereto({ filename, buffer: fromBuffer.toString('base64'), dir, fileTyle });
 | 
			
		||||
      }
 | 
			
		||||
    } catch (error) {
 | 
			
		||||
      Logger.error(`上传失败: ${error.message}`, 'UploadService');
 | 
			
		||||
      throw error; // 重新抛出异常,以便调用方可以处理
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  async getUploadType() {
 | 
			
		||||
@@ -122,23 +135,10 @@ export class UploadService implements OnModuleInit {
 | 
			
		||||
    this.tencentCos = new TENCENTCOS({ SecretId, SecretKey, FileParallelLimit: 10 });
 | 
			
		||||
    try {
 | 
			
		||||
      const proxyMj = (await this.globalConfigService.getConfigs(['mjProxy'])) || 0;
 | 
			
		||||
      /* 开启代理 */
 | 
			
		||||
      if (Number(proxyMj) === 1) {
 | 
			
		||||
        const data = { cosType: 'tencent', url, cosParams: { Bucket, Region, SecretId, SecretKey } };
 | 
			
		||||
        const mjProxyUrl = (await this.globalConfigService.getConfigs(['mjProxyUrl'])) || 'http://172.247.48.137:8000';
 | 
			
		||||
        const res = await axios.post(`${mjProxyUrl}/mj/replaceUpload`, data);
 | 
			
		||||
        if (!res.data) throw new HttpException('上传图片失败[ten][url]', HttpStatus.BAD_REQUEST);
 | 
			
		||||
        let locationUrl = res.data.replace(/^(http:\/\/|https:\/\/|\/\/|)(.*)/, 'https://$2');
 | 
			
		||||
        const { acceleratedDomain } = await this.getUploadConfig('tencent');
 | 
			
		||||
        if (acceleratedDomain) {
 | 
			
		||||
          locationUrl = locationUrl.replace(/^(https:\/\/[^/]+)(\/.*)$/, `https://${acceleratedDomain}$2`);
 | 
			
		||||
          console.log('当前已开启全球加速----------------->');
 | 
			
		||||
        }
 | 
			
		||||
        return locationUrl;
 | 
			
		||||
      } else {
 | 
			
		||||
 | 
			
		||||
      const buffer = await this.getBufferFromUrl(url);
 | 
			
		||||
      return await this.uploadFileByTencentCos({ filename, buffer, dir, fileTyle: '' });
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
    } catch (error) {
 | 
			
		||||
      console.log('TODO->error:  ', error);
 | 
			
		||||
      throw new HttpException('上传图片失败[ten][url]', HttpStatus.BAD_REQUEST);
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user