2.5版本 增加dall-e 优化mj 对接mj-plus

This commit is contained in:
小易
2024-02-04 18:51:37 +08:00
parent 822ff0a51d
commit 2ca3c164a0
57 changed files with 12239 additions and 7693 deletions

View File

@@ -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: '退出应用',

View File

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

View File

@@ -190,6 +190,11 @@
}
}
}
console.log(
"%c本项目作者----小易联系QQ805239273",
"background-color:rgb(30,30,30);border-radius:4px;font-size:12px;padding:4px;color:rgb(220,208,129);"
)
</script>
</body>

View File

@@ -1,106 +1,106 @@
{
"name": "chatgpt-cooper",
"version": "2.3.0",
"private": true,
"description": "ChatGPT Cooper",
"author": "Snine <J_longyan@163.com>",
"keywords": [
"chatgpt-cooper",
"chatgpt",
"chatbot",
"vue",
"nestjs"
],
"main": "electron/main.js",
"scripts": {
"start:h": "pnpm run -C service dev",
"start:f": "vite",
"all": "npm-run-all --parallel start:h start:f",
"dev": "vite",
"build-check": "run-p type-check build-only",
"preview": "vite preview",
"build": "vite build --mode=production",
"type-check": "vue-tsc --noEmit",
"lint": "eslint .",
"lint:fix": "eslint . --fix",
"bootstrap": "pnpm install && pnpm run common:prepare",
"start": "pnpm dev && electron .",
"ele": "electron .",
"common:cleanup": "rimraf node_modules && rimraf pnpm-lock.yaml",
"pack:mac": "NPM_CONFIG_ELECTRON_MIRROR=http://npm.taobao.org/mirrors/electron/ electron-builder build --mac",
"pack:win": "NPM_CONFIG_ELECTRON_MIRROR=http://npm.taobao.org/mirrors/electron/ electron-builder build --win --ia32"
},
"dependencies": {
"@electron/remote": "^2.1.0",
"@icon-park/vue-next": "^1.4.2",
"@traptitech/markdown-it-katex": "^3.6.0",
"@types/dom-to-image": "^2.6.4",
"@types/file-saver": "^2.0.5",
"@vicons/ionicons5": "^0.12.0",
"@vueuse/core": "^9.13.0",
"@vueuse/electron": "^10.2.1",
"@vueuse/integrations": "^10.2.0",
"@vueuse/motion": "^2.0.0",
"add": "^2.0.6",
"clientjs": "^0.2.1",
"date-fns": "^2.30.0",
"dom-to-image": "^2.6.0",
"file-saver": "^2.0.5",
"highlight.js": "^11.7.0",
"html-to-image": "^1.11.11",
"html2canvas": "^1.4.1",
"katex": "^0.16.4",
"markdown-it": "^13.0.1",
"marked": "^4.3.0",
"markmap-common": "0.14.2",
"markmap-lib": "0.14.4",
"markmap-view": "0.14.4",
"naive-ui": "^2.34.3",
"pinia": "^2.0.33",
"qrcode": "^1.5.3",
"v-viewer": "3.0.11",
"vue": "^3.2.47",
"vue-clipboard3": "^2.0.0",
"vue-i18n": "^9.2.2",
"vue-router": "^4.1.6"
},
"devDependencies": {
"@antfu/eslint-config": "^0.35.3",
"@commitlint/cli": "^17.4.4",
"@commitlint/config-conventional": "^17.4.4",
"@iconify/vue": "^4.1.0",
"@types/crypto-js": "^4.1.1",
"@types/katex": "^0.16.0",
"@types/markdown-it": "^12.2.3",
"@types/markdown-it-link-attributes": "^3.0.1",
"@types/node": "^18.14.6",
"@vitejs/plugin-vue": "^4.0.0",
"autoprefixer": "^10.4.13",
"axios": "^1.3.4",
"crypto-js": "^4.1.1",
"electron": "^25.3.1",
"electron-builder": "^24.4.0",
"eslint": "^8.35.0",
"husky": "^8.0.3",
"less": "^4.1.3",
"lint-staged": "^13.1.2",
"markdown-it-link-attributes": "^4.0.1",
"npm-run-all": "^4.1.5",
"postcss": "^8.4.21",
"rimraf": "^4.2.0",
"tailwindcss": "^3.2.7",
"typescript": "~4.9.5",
"vite": "^4.2.0",
"vite-plugin-pwa": "^0.14.4",
"vue-tsc": "^1.2.0"
},
"lint-staged": {
"*.{ts,tsx,vue}": [
"pnpm lint:fix"
]
},
"build": {
"productName": "NineAi",
"name": "chatgpt-cooper",
"version": "2.5.0",
"private": true,
"description": "ChatGPT Cooper",
"author": "Yi <a8052@qq.com>",
"keywords": [
"chatgpt-cooper",
"chatgpt",
"chatbot",
"vue",
"nestjs"
],
"main": "electron/main.js",
"scripts": {
"start:h": "pnpm run -C service dev",
"start:f": "vite",
"all": "npm-run-all --parallel start:h start:f",
"dev": "vite",
"build-check": "run-p type-check build-only",
"preview": "vite preview",
"build": "vite build --mode=production",
"type-check": "vue-tsc --noEmit",
"lint": "eslint .",
"lint:fix": "eslint . --fix",
"bootstrap": "pnpm install && pnpm run common:prepare",
"start": "pnpm dev && electron .",
"ele": "electron .",
"common:cleanup": "rimraf node_modules && rimraf pnpm-lock.yaml",
"pack:mac": "NPM_CONFIG_ELECTRON_MIRROR=http://npm.taobao.org/mirrors/electron/ electron-builder build --mac",
"pack:win": "NPM_CONFIG_ELECTRON_MIRROR=http://npm.taobao.org/mirrors/electron/ electron-builder build --win --ia32"
},
"dependencies": {
"@electron/remote": "^2.1.0",
"@icon-park/vue-next": "^1.4.2",
"@traptitech/markdown-it-katex": "^3.6.0",
"@types/dom-to-image": "^2.6.4",
"@types/file-saver": "^2.0.5",
"@vicons/ionicons5": "^0.12.0",
"@vueuse/core": "^9.13.0",
"@vueuse/electron": "^10.2.1",
"@vueuse/integrations": "^10.2.0",
"@vueuse/motion": "^2.0.0",
"add": "^2.0.6",
"clientjs": "^0.2.1",
"date-fns": "^2.30.0",
"dom-to-image": "^2.6.0",
"file-saver": "^2.0.5",
"highlight.js": "^11.7.0",
"html-to-image": "^1.11.11",
"html2canvas": "^1.4.1",
"katex": "^0.16.4",
"markdown-it": "^13.0.1",
"marked": "^4.3.0",
"markmap-common": "0.14.2",
"markmap-lib": "0.14.4",
"markmap-view": "0.14.4",
"naive-ui": "^2.37.3",
"pinia": "^2.0.33",
"qrcode": "^1.5.3",
"v-viewer": "3.0.11",
"vue": "^3.2.47",
"vue-clipboard3": "^2.0.0",
"vue-i18n": "^9.2.2",
"vue-router": "^4.1.6"
},
"devDependencies": {
"@antfu/eslint-config": "^0.35.3",
"@commitlint/cli": "^17.4.4",
"@commitlint/config-conventional": "^17.4.4",
"@iconify/vue": "^4.1.0",
"@types/crypto-js": "^4.1.1",
"@types/katex": "^0.16.0",
"@types/markdown-it": "^12.2.3",
"@types/markdown-it-link-attributes": "^3.0.1",
"@types/node": "^18.14.6",
"@vitejs/plugin-vue": "^4.0.0",
"autoprefixer": "^10.4.13",
"axios": "^1.3.4",
"crypto-js": "^4.1.1",
"electron": "^25.3.1",
"electron-builder": "^24.4.0",
"eslint": "^8.35.0",
"husky": "^8.0.3",
"less": "^4.1.3",
"lint-staged": "^13.1.2",
"markdown-it-link-attributes": "^4.0.1",
"npm-run-all": "^4.1.5",
"postcss": "^8.4.21",
"rimraf": "^4.2.0",
"tailwindcss": "^3.2.7",
"typescript": "~4.9.5",
"vite": "^4.2.0",
"vite-plugin-pwa": "^0.14.4",
"vue-tsc": "^1.2.0"
},
"lint-staged": {
"*.{ts,tsx,vue}": [
"pnpm lint:fix"
]
},
"build": {
"productName": "Yiai",
"appId": "ai.jiangly.com",
"icon": "icons/icon.icns",
"directories": {

View File

@@ -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 },
) {
return post<T>({
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 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,
},
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 },
) {
return post<T>({
url: '/chatgpt/chat-sync',
data: { prompt: params.prompt, options: params.options },
signal: params.signal,
onDownloadProgress: params.onDownloadProgress,
})
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",
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 },
) {
return post<T>({
url: '/chatgpt/chat-mind',
data: { prompt: params.prompt, options: params.options },
signal: params.signal,
onDownloadProgress: params.onDownloadProgress,
})
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",
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" });
}

View File

@@ -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,
@@ -45,8 +45,8 @@ export function fetchProxyImgAPI<T>(data: { url: string }): Promise<T> {
return get<T>({
url: '/midjourney/proxy',
data,
headers: {
responseType: 'arraybuffer'
}
headers: {
responseType: 'arraybuffer'
}
})
}

BIN
chat/src/assets/voice.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

View File

@@ -6,20 +6,23 @@ 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
progress?: number
tips?: string
bgColor?: string
words?: any
words?: any
}
// const words = ref<string[]>(['L', 'O', 'A', 'D', 'I', 'N', 'G'])
</script>
@@ -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 }}%
@@ -46,14 +55,14 @@ interface Props {
width: 100%;
height: 100%;
z-index: 100;
user-select: none;
user-select: none;
}
.progress{
position: absolute;
bottom: 0;
left: 50%;
transform: translateX(-50%);
.progress {
position: absolute;
bottom: 0;
left: 50%;
transform: translateX(-50%);
}
.loading-text {
@@ -72,42 +81,42 @@ 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);
-webkit-animation: blur-text 1.5s 0s infinite linear alternate;
animation: blur-text 1.5s 0s infinite linear alternate;
animation: blur-text 1.5s 0s infinite linear alternate;
}
.loading-text span:nth-child(2) {
filter: blur(0px);
-webkit-animation: blur-text 1.5s 0.2s infinite linear alternate;
animation: blur-text 1.5s 0.2s infinite linear alternate;
animation: blur-text 1.5s 0.2s infinite linear alternate;
}
.loading-text span:nth-child(3) {
filter: blur(0px);
-webkit-animation: blur-text 1.5s 0.4s infinite linear alternate;
animation: blur-text 1.5s 0.4s infinite linear alternate;
animation: blur-text 1.5s 0.4s infinite linear alternate;
}
.loading-text span:nth-child(4) {
filter: blur(0px);
-webkit-animation: blur-text 1.5s 0.6s infinite linear alternate;
animation: blur-text 1.5s 0.6s infinite linear alternate;
animation: blur-text 1.5s 0.6s infinite linear alternate;
}
.loading-text span:nth-child(5) {
filter: blur(0px);
-webkit-animation: blur-text 1.5s 0.8s infinite linear alternate;
animation: blur-text 1.5s 0.8s infinite linear alternate;
animation: blur-text 1.5s 0.8s infinite linear alternate;
}
.loading-text span:nth-child(6) {
filter: blur(0px);
-webkit-animation: blur-text 1.5s 1s infinite linear alternate;
animation: blur-text 1.5s 1s infinite linear alternate;
animation: blur-text 1.5s 1s infinite linear alternate;
}
.loading-text span:nth-child(7) {
filter: blur(0px);
-webkit-animation: blur-text 1.5s 1.2s infinite linear alternate;
animation: blur-text 1.5s 1.2s infinite linear alternate;
animation: blur-text 1.5s 1.2s infinite linear alternate;
}
@-webkit-keyframes blur-text {

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

View File

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

View File

@@ -10,7 +10,8 @@ export function defaultState(): Chat.ChatState {
groupList: [],
chatList: [],
groupKeyWord: '',
baseConfig: null
baseConfig: null,
chatPreList: [],
}
}

View File

@@ -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',
},
}
}

View File

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

View File

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

View File

@@ -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() {
@@ -180,7 +177,7 @@ function handleSignIn() {
</div>
</div>
</NPopover> -->
<NTooltip v-if="isMobile" trigger="hover" :disabled="isMobile">
<template #trigger>
<button
@@ -199,7 +196,7 @@ function handleSignIn() {
<button
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="handleExport"
v-show="!isMobile"
v-show="!isMobile"
>
<span class="text-base text-slate-500 dark:text-slate-400">
<SvgIcon
@@ -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>
滚动到底部

View File

@@ -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,106 +125,137 @@ defineExpose({ textRef })
</script>
<template>
<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">
<div class="w-full">
<div
v-if="!asRawText"
class="w-full markdown-body"
:class="[{ 'markdown-body-generate': loading }]"
v-html="text"
/>
<div v-else class="w-full whitespace-pre-wrap" v-text="text" />
<span
<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">
<div class="w-full">
<div
v-if="!asRawText"
class="w-full markdown-body"
:class="[{ 'markdown-body-generate': loading }]"
v-html="text"
/>
<div v-else class="w-full whitespace-pre-wrap" v-text="text" />
<!-- <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>
<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 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>
<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"
:icon="asRawText ? 'ic:outline-code-off' : 'ic:outline-code'"
/>
</template>
<span class="text-xs">{{
<SvgIcon
class="flex h-3 w-3 mx-1"
:icon="asRawText ? 'ic:outline-code-off' : 'ic:outline-code'"
/>
<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> -->
<!-- <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/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>
<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 }"
>
<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>
<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>

View File

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

View File

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

View File

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

View File

@@ -1,153 +1,163 @@
<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 {
return imageList.value.filter((item: any) => {
const { prompt } = item;
return prompt.includes(keyword.value);
});
}
});
const dataList = computed(() => {
if(!keyword.value){
return imageList.value
}else{
return imageList.value.filter((item: any) => {
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"
>
<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>
<div class="flex-1 ml-5">
<n-slider v-model:value="scaleWidth" :step="10" />
</div>
</div>
</div>
<div class="px-4 mb-1 pr-5">
<n-input v-model:value="keyword" placeholder="prompt关键词搜索">
<template #prefix>
<n-icon :component="FlashOutline" />
</template>
</n-input>
</div>
<div
class="market overflow-y-scroll flex-1 min-h-screen p-4 dark:bg-[#18181c] relative"
>
<div id="wapper" ref="wapperRef" class="wapper">
<GridManager2
@loadMore="loadMore"
copyPropmpt
isDrawLike
:dataList="dataList"
:scaleWidth="scaleWidth"
/>
</div>
</div>
</div>
</template>
<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>
<div class="flex-1 ml-5">
<n-slider v-model:value="scaleWidth" :step="10" />
</div>
</div>
</div>
<div class="px-4 mb-1 pr-5">
<n-input v-model:value="keyword" placeholder="prompt关键词搜索">
<template #prefix>
<n-icon :component="FlashOutline" />
</template>
</n-input>
</div>
<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" />
</div>
</div>
</div>
<style lang="less">
.market {
padding: 15px;
}
</template>
.wapper {
width: 100%;
position: relative;
height: 100%;
padding-bottom: 20px;
<style lang="less">
.market{
padding: 15px;
}
&-item {
z-index: 10;
overflow: hidden;
position: absolute;
transition: all 0.5s;
cursor: pointer;
.wapper{
width: 100%;
position: relative;
height: 100%;
padding-bottom: 20px;
&: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;
&-item{
z-index: 10;
overflow: hidden;
position: absolute;
transition: all 0.5s;
cursor: pointer;
.prompt {
height: 50px;
overflow: hidden;
}
}
&:hover{
.menu{
transition: transform 0.3s ease-in-out;
transform: translateY(-10px);
}
img{
transform: scale(1.1);
}
}
img {
user-select: none;
cursor: pointer;
transition: all 0.6s cubic-bezier(0.19, 1, 0.22, 1);
border-radius: 6px;
}
.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 .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;
.item-loading {
background: url(../../assets/img-bg.png) no-repeat center center;
filter: blur(20px);
position: absolute;
top: 0;
}
}
}
.prompt{
height: 50px;
overflow: hidden;
}
}
img{
user-select: none;
cursor: pointer;
transition: all .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 .3s;
}
.img-enter, .img-leave-to{
transform: scale(.6);
opacity: 0;
}
</style>
.img-enter-active,
.img-leave-active {
transition: transform 0.3s;
}
.img-enter,
.img-leave-to {
transform: scale(0.6);
opacity: 0;
}
</style>

View File

@@ -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
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)
})
}
})
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);
});
},
});
}
/* 删除图片 */
@@ -97,375 +107,425 @@ 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()
}
emit('queryData')
const { drawId } = item;
await fetchDrawTaskAPI({ drawId: drawId, action: 'UPSCALE', orderId });
ms.success('提交放大绘制任务成功、请等待绘制结束!');
if (authStore.token) {
await refreshUserInfo();
}
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()
}
emit('queryData')
const { drawId } = item;
await fetchDrawTaskAPI({ drawId: drawId, action: 'REGENERATE', orderId });
ms.success('提交重新生成绘制任务成功、请等待绘制结束!');
if (authStore.token) {
await refreshUserInfo();
}
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()
}
emit('queryData')
const { drawId } = item;
await fetchDrawTaskAPI({ drawId: drawId, action: 'VARIATION', orderId });
ms.success('提交图片变换绘制任务成功、请等待绘制结束!');
if (authStore.token) {
await refreshUserInfo();
}
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()
}
emit('queryData')
const { drawId } = item;
await fetchDrawTaskAPI({ drawId: drawId, action: 'VARIATION', orderId });
ms.success('提交图片调整绘制任务成功、请等待绘制结束!');
if (authStore.token) {
await refreshUserInfo();
}
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()
}
emit('queryData')
const { drawId } = item;
await fetchDrawTaskAPI({ drawId: drawId, action: 'UPSCALE', orderId });
ms.success('提交图片调整绘制任务成功、请等待绘制结束!');
if (authStore.token) {
await refreshUserInfo();
}
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="flex items-center justify-between">
<span>
<NTag size="small" :type="statusType">
{{ statusMsg }}
</NTag>
</span>
<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">
{{ statusMsg }}
</NTag>
</span>
<NSpace>
<NTooltip v-if="drawItemInfo.isGroup" placement="top" trigger="hover">
<template #trigger>
<NButton size="tiny" ghost @click="usePrompt">
<template #icon>
<SvgIcon icon="ri:brush-line" class="text-base" />
</template>
使用
</NButton>
</template>
<div style="width: 240px">
<p>{{ drawItemInfo.fullPrompt }}</p>
</div>
</NTooltip>
<NSpace>
<NTooltip
v-if="drawItemInfo.action === 'IMAGINE'"
placement="top"
trigger="hover"
>
<template #trigger>
<NButton size="tiny" ghost @click="usePrompt">
<template #icon>
<SvgIcon icon="ri:brush-line" class="text-base" />
</template>
使用
</NButton>
</template>
<div style="width: 240px">
<p>{{ drawItemInfo.fullPrompt }}</p>
</div>
</NTooltip>
<NButton size="tiny" ghost @click="handleDownloadImg(drawItemInfo)">
<template #icon>
<SvgIcon icon="mingcute:file-download-line" class="text-base" />
</template>
下载
</NButton>
<NButton size="tiny" ghost @click="handleDeleteDraw(drawItemInfo)">
<template #icon>
<SvgIcon icon="ri:delete-bin-line" class="text-base" />
</template>
删除
</NButton>
</NSpace>
</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">
<NImage
style="object-fit: contain;"
:src="drawItemInfo.fileInfo.thumbImg"
:preview-src="drawItemInfo.fileInfo.cosUrl"
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">
<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>
</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 class="mb-2 flex 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: 240px">
<p>参数释义放大某张图片如 U1 放大第一张图片以此类推</p>
</div>
</NTooltip>
</span>
<div class="flex-1">
<div class="flex items-center justify-around">
<NButton size="tiny" @click="handleUpscale(drawItemInfo, 1)">
U1
</NButton>
<NButton size="tiny" @click="handleUpscale(drawItemInfo, 2)">
U2
</NButton>
<NButton size="tiny" @click="handleUpscale(drawItemInfo, 3)">
U3
</NButton>
<NButton size="tiny" @click="handleUpscale(drawItemInfo, 4)">
U4
</NButton>
<NTooltip placement="top" trigger="hover">
<template #trigger>
<NButton size="tiny" @click="handleReGenerate(drawItemInfo, 5)">
<SvgIcon icon="solar:refresh-outline" class="text-base" />
</NButton>
</template>
<p>重新生成一次</p>
</NTooltip>
</div>
</div>
</div>
</div>
<NButton size="tiny" ghost @click="handleDownloadImg(drawItemInfo)">
<template #icon>
<SvgIcon icon="mingcute:file-download-line" class="text-base" />
</template>
下载
</NButton>
<NButton size="tiny" ghost @click="handleDeleteDraw(drawItemInfo)">
<template #icon>
<SvgIcon icon="ri:delete-bin-line" class="text-base" />
</template>
删除
</NButton>
</NSpace>
</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"
>
<NImage
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" />
<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>
</div>
<!-- footer -->
<div class="-mx-4 -mb-4 bg-[#fafafc] px-4 py-2 dark:bg-[#262629]">
<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">
<NTooltip placement="top" trigger="hover">
<template #trigger>
<SvgIcon icon="ri:error-warning-line" class="text-base" />
</template>
<div style="width: 240px">
<p>参数释义:放大某张图片如 U1 放大第一张图片,以此类推</p>
</div>
</NTooltip>
</span>
<div class="flex-1">
<div class="flex items-center justify-around">
<NButton size="tiny" @click="handleUpscale(drawItemInfo, 1)">
U1
</NButton>
<NButton size="tiny" @click="handleUpscale(drawItemInfo, 2)">
U2
</NButton>
<NButton size="tiny" @click="handleUpscale(drawItemInfo, 3)">
U3
</NButton>
<NButton size="tiny" @click="handleUpscale(drawItemInfo, 4)">
U4
</NButton>
<NTooltip placement="top" trigger="hover">
<template #trigger>
<NButton
size="tiny"
@click="handleReGenerate(drawItemInfo, 5)"
>
<SvgIcon icon="solar:refresh-outline" class="text-base" />
</NButton>
</template>
<p>重新生成一次</p>
</NTooltip>
</div>
</div>
</div>
</div>
<!-- 套图 新生成 变体图 重新生成 三种类型 -->
<div v-if="drawItemInfo.isGroup" class="w-full">
<div class="mb-2 flex 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: 240px">
<p>参数释义以某张图片为基准重新生成
V1 则变换第一张图片以此类推</p>
</div>
</NTooltip>
</span>
<div class="flex-1">
<div class="flex items-center justify-around">
<NButton size="tiny" @click="handleVariation(drawItemInfo, 1)">
V1
</NButton>
<NButton size="tiny" @click="handleVariation(drawItemInfo, 2)">
V2
</NButton>
<NButton size="tiny" @click="handleVariation(drawItemInfo, 3)">
V3
</NButton>
<NButton size="tiny" @click="handleVariation(drawItemInfo, 4)">
V4
</NButton>
<NButton size="tiny" style="opacity:0">
V5
</NButton>
</div>
</div>
</div>
</div>
<!-- 套图 新生成 变体图 重新生成 三种类型 -->
<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">
<NTooltip placement="top" trigger="hover">
<template #trigger>
<SvgIcon icon="ri:error-warning-line" class="text-base" />
</template>
<div style="width: 240px">
<p>
参数释义:以某张图片为基准重新生成 如 V1
则变换第一张图片,以此类推
</p>
</div>
</NTooltip>
</span>
<div class="flex-1">
<div class="flex items-center justify-around">
<NButton size="tiny" @click="handleVariation(drawItemInfo, 1)">
V1
</NButton>
<NButton size="tiny" @click="handleVariation(drawItemInfo, 2)">
V2
</NButton>
<NButton size="tiny" @click="handleVariation(drawItemInfo, 3)">
V3
</NButton>
<NButton size="tiny" @click="handleVariation(drawItemInfo, 4)">
V4
</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">
<!-- 图片放大或变体 并且图片还未生成成功的时候没有message_id -->
<div v-if="drawItemInfo.orderId !== 5 && !drawItemInfo.extend">
<span v-if="drawItemInfo.action === 2">
操作{{ `选中套图第${drawItemInfo.orderId || 'x'}张图片进行放大` }}
</span>
<span v-if="drawItemInfo.action === 3">
操作{{ `选中套图第${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>
<!-- 对老图片增强 单张图或生成中的图 -->
<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">
<span v-if="drawItemInfo.action === 'UPSCALE'">
操作:{{ `选中套图第${drawItemInfo.orderId || 'x'}张图片进行放大` }}
</span>
<span v-if="drawItemInfo.action === 'VARIATION'">
操作:{{ `选中套图第${drawItemInfo.orderId || 'x'}张图片进行变换` }}
</span>
</div>
<!-- 已经生成成功的单张图 可以zoom和vary -->
<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">
操作:正在对图片重新生成一次
</span>
</div>
<!-- 重新绘制套图只在生成中显示 生成完毕即会进入group套图 -->
<span v-if="drawItemInfo.orderId === 5">
操作正在对图片重新生成一次
</span>
</div>
<!-- 新图绘制中 -->
<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 && drawItemInfo.status === 2" class="w-full mb-2 flex items-center justify-between">
操作正在火速绘制中...
</div>
<!-- 绘制失败了 -->
<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.action && !drawItemInfo.extend"
class="w-full mb-2 flex items-center justify-between"
>
上级: {{ drawItemInfo.message_id || '正在加载中...' }}
</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>
<!-- 加载失败 -->
<div v-if="!drawItemInfo.isGroup && !drawItemInfo.extend" class="w-full mb-2 flex items-center justify-between">
上级 {{ drawItemInfo.message_id || '正在加载中...' }}
</div>
<!-- 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">
<NTooltip placement="top" trigger="hover">
<template #trigger>
<SvgIcon icon="ri:error-warning-line" class="text-base" />
</template>
<div style="width: 270px">
<p>参数释义Zoom 对当前图片进行无限缩放</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="handleZoom(drawItemInfo, 1)">
<template #icon>
<img :src="zoomSvg" class="w-4" alt="" />
</template>
U(Subtle)
</NButton>
</template>
<p>放大</p>
</NTooltip>
<!-- -->
<div v-if="!drawItemInfo.isGroup && drawItemInfo.orderId !== 5 && drawItemInfo.extend">
<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: 270px">
<p>参数释义Zoom 对当前图片进行无限缩放</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="handleZoom(drawItemInfo, 1)">
<template #icon>
<img :src="zoomSvg" class="w-4" alt="">
</template>
Zoom 2
</NButton>
</template>
<p>缩放2倍</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="" />
</template>
U(Creative)
</NButton>
</template>
<p>放大</p>
</NTooltip>
</NSpace>
</div>
</div>
</div>
</div>
<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="handleZoom(drawItemInfo, 2)">
<template #icon>
<img :src="zoomSvg" class="w-4" alt="">
</template>
Zoom 1.5
</NButton>
</template>
<p>缩放1.5</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>
<!-- <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>
</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

View File

@@ -59,7 +59,7 @@ const demoData = `
`
const prompt = ref('')
const initValue = `# NineAi
const initValue = `# YiAi
## 基础功能
- 支持AI聊天
- 支持GPT4

View File

@@ -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' },