wuyuncheng-26's picture
upload files from Harry-zklcdc/go-proxy-bingai
d669ddb verified
<script setup lang="ts">
import { ref } from 'vue';
import { NModal, NList, NListItem, NButton, useMessage, NSpace, NInput, NUpload, NVirtualList, type UploadFileInfo, NEmpty } from 'naive-ui';
import { usePromptStore, type IPrompt, type IPromptDownloadConfig } from '@/stores/modules/prompt';
import { storeToRefs } from 'pinia';
import ChatPromptItem from './ChatPromptItem.vue';
const messgae = useMessage();
const promptStore = usePromptStore();
const { promptDownloadConfig, isShowPromptSotre, promptList, keyword, searchPromptList, optPromptConfig } = storeToRefs(promptStore);
const isShowDownloadPop = ref(false);
const isImporting = ref(false);
const isExporting = ref(false);
const showAddPromptPop = () => {
optPromptConfig.value.isShow = true;
optPromptConfig.value.type = 'add';
optPromptConfig.value.title = '添加提示词';
optPromptConfig.value.newPrompt = {
act: '',
prompt: '',
};
};
const savePrompt = () => {
const { type, tmpPrompt, newPrompt } = optPromptConfig.value;
if (!newPrompt.act) {
return messgae.error('提示词标题不能为空');
}
if (!newPrompt.prompt) {
return messgae.error('提示词描述不能为空');
}
if (type === 'add') {
promptList.value = [newPrompt, ...promptList.value];
messgae.success('添加提示词成功');
} else if (type === 'edit') {
if (newPrompt.act === tmpPrompt?.act && newPrompt.prompt === tmpPrompt?.prompt) {
messgae.warning('提示词未变更');
optPromptConfig.value.isShow = false;
return;
}
const rawIndex = promptList.value.findIndex((x) => x.act === tmpPrompt?.act && x.prompt === tmpPrompt?.prompt);
if (rawIndex > -1) {
promptList.value[rawIndex] = newPrompt;
messgae.success('编辑提示词成功');
} else {
messgae.error('编辑提示词出错');
}
}
optPromptConfig.value.isShow = false;
};
const readFile = (file: File): Promise<string> => {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = function (ev) {
resolve(ev.target?.result as string);
};
reader.onerror = reject;
reader.readAsText(file);
});
};
const importPrompt = async (options: { file: UploadFileInfo; fileList: Array<UploadFileInfo>; event?: Event }) => {
// console.log(options.file);
if (options.file.file) {
isImporting.value = true;
const fileText = await readFile(options.file.file);
const promptData = JSON.parse(fileText);
const result = promptStore.addPrompt(promptData);
if (result.result) {
messgae.info(`上传文件含 ${promptData.length} 条数据`);
messgae.success(`成功导入 ${result.data?.successCount} 条有效数据`);
} else {
messgae.error(result.msg || '提示词格式有误');
}
isImporting.value = false;
} else {
messgae.error('上传文件有误');
}
};
const exportPrompt = () => {
if (promptList.value.length === 0) {
return messgae.error('暂无可导出的提示词数据');
}
isExporting.value = true;
const jsonDataStr = JSON.stringify(promptList.value);
const blob = new Blob([jsonDataStr], { type: 'application/json' });
const url = URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = url;
link.download = 'BingAIPrompts.json';
link.click();
URL.revokeObjectURL(url);
messgae.success('导出提示词库成功');
isExporting.value = false;
};
const clearPrompt = () => {
promptList.value = [];
messgae.success('清空提示词库成功');
};
const downloadPrompt = async (config: IPromptDownloadConfig) => {
if (!config.url) {
return messgae.error('请先输入下载链接');
}
config.isDownloading = true;
let jsonData: Array<IPrompt>;
if (config.url.endsWith('.json')) {
jsonData = await fetch(config.url).then((res) => res.json());
} else if (config.url.endsWith('.csv')) {
const csvData = await fetch(config.url).then((res) => res.text());
console.log(csvData);
jsonData = csvData
.split('\n')
.filter((x) => x)
.map((x) => {
const arr = x.split('","');
return {
act: arr[0].slice(1),
prompt: arr[1]?.slice(1),
};
});
jsonData.shift();
} else {
config.isDownloading = false;
return messgae.error('暂不支持下载此后缀的提示词');
}
config.isDownloading = false;
const result = promptStore.addPrompt(jsonData);
if (result.result) {
messgae.info(`下载文件含 ${jsonData.length} 条数据`);
messgae.success(`成功导入 ${result.data?.successCount} 条有效数据`);
} else {
messgae.error(result.msg || '提示词格式有误');
}
};
</script>
<template>
<NModal class="w-11/12 xl:w-[900px]" v-model:show="isShowPromptSotre" preset="card" title="提示词库">
<div class="flex justify-start flex-wrap gap-2 px-5 pb-2">
<NInput class="basis-full xl:basis-0 xl:min-w-[300px]" placeholder="搜索提示词" v-model:value="keyword" :clearable="true"></NInput>
<NButton secondary type="info" @click="isShowDownloadPop = true">下载</NButton>
<NButton secondary type="info" @click="showAddPromptPop">添加</NButton>
<NUpload class="w-[56px] xl:w-auto" accept=".json" :default-upload="false" :show-file-list="false" @change="importPrompt">
<NButton secondary type="success" :loading="isImporting">导入</NButton>
</NUpload>
<!-- <NButton secondary type="success">导入</NButton> -->
<NButton secondary type="success" @click="exportPrompt" :loading="isExporting">导出</NButton>
<NButton secondary type="error" @click="clearPrompt">清空</NButton>
</div>
<NVirtualList
v-if="searchPromptList.length > 0"
class="h-[40vh] xl:h-[60vh] overflow-y-auto"
:item-size="131"
item-resizable
:items="searchPromptList"
>
<template #default="{ item, index }">
<ChatPromptItem :index="index" :source="item" />
</template>
</NVirtualList>
<NEmpty v-else class="h-[40vh] xl:h-[60vh] flex justify-center items-center" description="暂无数据">
<template #extra>
<NButton secondary type="info" @click="isShowDownloadPop = true">下载提示词</NButton>
</template>
</NEmpty>
</NModal>
<NModal class="w-11/12 xl:w-[600px]" v-model:show="optPromptConfig.isShow" preset="card" :title="optPromptConfig.title">
<NSpace vertical>
标题
<NInput placeholder="请输入标题" v-model:value="optPromptConfig.newPrompt.act"></NInput>
描述
<NInput placeholder="请输入描述" type="textarea" v-model:value="optPromptConfig.newPrompt.prompt"></NInput>
<NButton block secondary type="info" @click="savePrompt">保存</NButton>
</NSpace>
</NModal>
<NModal class="w-11/12 xl:w-[600px]" v-model:show="isShowDownloadPop" preset="card" title="下载提示词">
<NList class="overflow-y-auto rounded-lg" hoverable clickable>
<NListItem v-for="(config, index) in promptDownloadConfig" :key="index">
<a v-if="config.type === 1" class="no-underline text-blue-500" :href="config.url" target="_blank" rel="noopener noreferrer">{{ config.name }}</a>
<NInput v-else-if="config.type === 2" placeholder="请输入下载链接,支持 json 及 csv " v-model:value="config.url"></NInput>
<template #suffix>
<div class="flex justify-center gap-5">
<a class="no-underline" v-if="config.type === 1" :href="config.refer" target="_blank" rel="noopener noreferrer">
<NButton secondary>来源</NButton>
</a>
<NButton secondary type="info" @click="downloadPrompt(config)" :loading="config.isDownloading">下载</NButton>
</div>
</template>
</NListItem>
</NList>
</NModal>
</template>