瀏覽代碼

Merge branch 'main' of github.com:PeinYu/ruoyi-web

Y.Payne 1 月之前
父節點
當前提交
0ff898b21c

+ 174 - 151
src/api/Recognition.ts

@@ -1,165 +1,188 @@
 import { mlog } from "./mjapi";
 
-export interface recType{
-    timeOut:number
-    asrLanguage?:string
-    listener?: (result: string) => void
-    onEnd?: () => void
-    onStart?: () => void
+export interface recType {
+	timeOut: number;
+	asrLanguage?: string;
+	listener?: (result: string) => void;
+	onEnd?: () => void;
+	onStart?: () => void;
 }
 export class Recognition {
-  private recognition: any;
-  private listener?: (result: string) => void;
-  private isStop = false;
-  private recOpt: recType = { timeOut: 2000 };
-  private handleTime: any;
-  private hTime: Date | undefined;
-  private asrLanguage = "cmn-Hans-CN";
-  private onEnd?: () => void;
-  private onStart?: () => void;
-
-  public setListener(fn: (result: string) => void) {
-    this.listener = fn;
-    return this;
-  }
-
-  public setOnEnd(fn: () => void) {
-    this.onEnd = fn;
-    return this;
-  }
-
-  public setOpt(opt: recType) {
-    this.recOpt = opt;
-    if (opt.listener) this.setListener(opt.listener);
-    if (opt.onEnd) this.setOnEnd(opt.onEnd);
-    if (opt.asrLanguage) this.setLang(opt.asrLanguage);
-    if (opt.onStart) this.onStart = opt.onStart;
-    return this;
-  }
-
-  public setLang(lang: string) {
-    this.asrLanguage = lang;
-    return this;
-  }
-
-  public start() {
-    this.isStop = false;
-    if (typeof window === "undefined" || (!window.SpeechRecognition && !window.webkitSpeechRecognition)) {
-      console.warn("当前浏览器不支持 SpeechRecognition,请使用 Chrome 或 Edge");
-      return;
-    }
-
-    if (!this.recognition) {
-      const recognition = new (window.SpeechRecognition || window.webkitSpeechRecognition)();
-      this.recognition = recognition;
-    }
-    const recognition = this.recognition;
-
-    recognition.interimResults = true;
-    recognition.lang = this.asrLanguage;
-    recognition.continuous = true;
-
-    this.hTime = new Date();
-    this.handleTime = setInterval(() => this.check(this), this.recOpt.timeOut);
-
-    recognition.addEventListener("result", (event: any) => {
-      let transcript = "";
-      for (let index = 0; index < event.results.length; index++) {
-        const item = event.results[index];
-        if (transcript && this.asrLanguage.includes("Han")) transcript += ",";
-        transcript += (item as unknown as SpeechRecognitionAlternative[])[0]?.transcript;
-      }
-      if (!transcript) return;
-      this.hTime = new Date();
-      this.listener?.(transcript);
-    });
-
-    recognition.addEventListener("end", () => {
-      if (this.isStop) {
-        this.onEnd?.();
-        clearInterval(this.handleTime);
-        return;
-      }
-      setTimeout(() => recognition.start(), 1000); // 避免 Mac 触发安全限制
-    });
-
-    recognition.start();
-    this.onStart?.();
-    return this;
-  }
-
-  public stop() {
-    this.isStop = true;
-    this.recognition?.stop();
-    return this;
-  }
-
-  private check(that: Recognition) {
-    if (!that.hTime) return;
-    const dt = new Date().getTime() - that.hTime.getTime();
-    if (dt > that.recOpt.timeOut) that.stop();
-  }
+	private recognition: any;
+	private listener?: (result: string) => void;
+	private isStop = false;
+	private recOpt: recType = { timeOut: 2000 };
+	private handleTime: any;
+	private hTime: Date | undefined;
+	private asrLanguage = "cmn-Hans-CN";
+	private onEnd?: () => void;
+	private onStart?: () => void;
+
+	public setListener(fn: (result: string) => void) {
+		this.listener = fn;
+		return this;
+	}
+
+	public setOnEnd(fn: () => void) {
+		this.onEnd = fn;
+		return this;
+	}
+
+	public setOpt(opt: recType) {
+		this.recOpt = opt;
+		if (opt.listener) this.setListener(opt.listener);
+		if (opt.onEnd) this.setOnEnd(opt.onEnd);
+		if (opt.asrLanguage) this.setLang(opt.asrLanguage);
+		if (opt.onStart) this.onStart = opt.onStart;
+		return this;
+	}
+
+	public setLang(lang: string) {
+		this.asrLanguage = lang;
+		return this;
+	}
+
+	public start() {
+		this.isStop = false;
+		if (
+			typeof window === "undefined" ||
+			(!window.SpeechRecognition && !window.webkitSpeechRecognition)
+		) {
+			console.warn("当前浏览器不支持 SpeechRecognition,请使用 Chrome 或 Edge");
+			return;
+		}
+
+		if (!this.recognition) {
+			const recognition = new (window.SpeechRecognition ||
+				window.webkitSpeechRecognition)();
+			this.recognition = recognition;
+		}
+		const recognition = this.recognition;
+
+		recognition.interimResults = true;
+		recognition.lang = this.asrLanguage;
+		recognition.continuous = true;
+
+		this.hTime = new Date();
+		this.handleTime = setInterval(() => this.check(this), this.recOpt.timeOut);
+
+		recognition.addEventListener("result", (event: any) => {
+			let transcript = "";
+			for (let index = 0; index < event.results.length; index++) {
+				const item = event.results[index];
+				if (transcript && this.asrLanguage.includes("Han")) transcript += ",";
+				transcript += (item as unknown as SpeechRecognitionAlternative[])[0]
+					?.transcript;
+			}
+			if (!transcript) return;
+			this.hTime = new Date();
+			this.listener?.(transcript);
+		});
+
+		recognition.addEventListener("end", () => {
+			if (this.isStop) {
+				clearInterval(this.handleTime);
+				return;
+			}
+			setTimeout(() => recognition.start(), 1000);
+		});
+
+		recognition.start();
+		this.onStart?.();
+		return this;
+	}
+
+	private check(that: Recognition) {
+		if (!that.hTime) return;
+		const dt = new Date().getTime() - that.hTime.getTime();
+		if (dt > that.recOpt.timeOut) {
+			that.stop(); // 只在 stop 中触发 onEnd
+		}
+	}
+
+	public stop() {
+		if (this.isStop) return this; // 如果已经停止,直接返回
+		this.isStop = true;
+		this.recognition?.stop();
+		clearInterval(this.handleTime);
+		this.onEnd?.(); // 只在这里触发一次 onEnd
+		return this;
+	}
+
+	// public stop() {
+	//   this.isStop = true;
+	//   this.recognition?.stop();
+	//   return this;
+	// }
+
+	// private check(that: Recognition) {
+	//   if (!that.hTime) return;
+	//   const dt = new Date().getTime() - that.hTime.getTime();
+	//   if (dt > that.recOpt.timeOut) that.stop();
+	// }
 }
 
-
 export const supportLanguages: Record<string, string> = {
-  'cmn-Hans-CN': '普通话 (中国大陆)',
-  'cmn-Hans-HK': '普通话 (香港)',
-  'yue-Hant-HK': '粵語 (香港)',
-  'en-US': 'English(United States)',
-  'en-GB': 'English(United Kingdom)',
-  'en-IN': 'English(India)',
-  'es-ES': 'Español',
-  'fr-FR': 'Français',
-  'de-DE': 'Deutsch',
-  'it-IT': 'Italiano',
-  'ja-JP': '日本語',
-  'ko-KR': '한국어',
-  'ar-SA': 'العربية',
-  'pt-BR': 'Português',
-  'ru-RU': 'Русский',
-  'nl-NL': 'Nederlands',
-  'tr-TR': 'Türkçe',
-  'sv-SE': 'Svenska',
-  'hi-IN': 'हिन्दी',
-  'el-GR': 'Ελληνικά',
-  'he-IL': 'עברית',
-  'id-ID': 'Bahasa Indonesia',
-  'pl-PL': 'Polski',
-  'th-TH': 'ไทย',
-  'cs-CZ': 'Čeština',
-  'hu-HU': 'Magyar',
-  'da-DK': 'Dansk',
-  'fi-FI': 'Suomi',
-  'no-NO': 'Norsk',
-  'sk-SK': 'Slovenčina',
-  'uk-UA': 'Українська',
-  'vi-VN': 'Tiếng Việt',
+	"cmn-Hans-CN": "普通话 (中国大陆)",
+	"cmn-Hans-HK": "普通话 (香港)",
+	"yue-Hant-HK": "粵語 (香港)",
+	"en-US": "English(United States)",
+	"en-GB": "English(United Kingdom)",
+	"en-IN": "English(India)",
+	"es-ES": "Español",
+	"fr-FR": "Français",
+	"de-DE": "Deutsch",
+	"it-IT": "Italiano",
+	"ja-JP": "日本語",
+	"ko-KR": "한국어",
+	"ar-SA": "العربية",
+	"pt-BR": "Português",
+	"ru-RU": "Русский",
+	"nl-NL": "Nederlands",
+	"tr-TR": "Türkçe",
+	"sv-SE": "Svenska",
+	"hi-IN": "हिन्दी",
+	"el-GR": "Ελληνικά",
+	"he-IL": "עברית",
+	"id-ID": "Bahasa Indonesia",
+	"pl-PL": "Polski",
+	"th-TH": "ไทย",
+	"cs-CZ": "Čeština",
+	"hu-HU": "Magyar",
+	"da-DK": "Dansk",
+	"fi-FI": "Suomi",
+	"no-NO": "Norsk",
+	"sk-SK": "Slovenčina",
+	"uk-UA": "Українська",
+	"vi-VN": "Tiếng Việt",
 };
 
 function sleep(time: number) {
-  return new Promise((resolve) => setTimeout(resolve, time));
+	return new Promise((resolve) => setTimeout(resolve, time));
 }
 
 //浏览器文字播放
-export async function speakText(content: string, callback: (playing: boolean) => void) {
-  if (!window.speechSynthesis) return;
-  if (speechSynthesis.speaking) {
-    speechSynthesis.cancel();
-    callback(false);
-  }
-
-  await sleep(300);
-
-  const msg = new SpeechSynthesisUtterance(content);
-  msg.lang = 'zh';
-  msg.rate = 1;
-  msg.addEventListener('end', () => {
-    callback(false);
-  });
-  msg.addEventListener('error', () => {
-    callback(false);
-  });
-  callback(true);
-  speechSynthesis.speak(msg);
+export async function speakText(
+	content: string,
+	callback: (playing: boolean) => void
+) {
+	if (!window.speechSynthesis) return;
+	if (speechSynthesis.speaking) {
+		speechSynthesis.cancel();
+		callback(false);
+	}
+
+	await sleep(300);
+
+	const msg = new SpeechSynthesisUtterance(content);
+	msg.lang = "zh";
+	msg.rate = 1;
+	msg.addEventListener("end", () => {
+		callback(false);
+	});
+	msg.addEventListener("error", () => {
+		callback(false);
+	});
+	callback(true);
+	speechSynthesis.speak(msg);
 }

+ 0 - 88
src/api/voice.ts

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

+ 8 - 1
src/views/chat/index.vue

@@ -651,6 +651,13 @@ load()
 
     <main class="flex-1 overflow-hidden">
 
+      <template v-if="gptConfigStore.myData.kid">
+        <div class="flex  mt-4  text-neutral-300 chat-header">
+           <SvgIcon icon="material-symbols:book" class="mr-1 text-2xl" ></SvgIcon>
+           <span>{{ gptConfigStore.myData.kName }}</span>
+        </div>
+      </template>
+
       <div id="scrollRef" ref="scrollRef" class="h-full overflow-hidden overflow-y-auto">
 
         <div
@@ -749,7 +756,7 @@ load()
         </div>
       </div>
     </main>
-
+    
     <footer :class="footerClass" class="footer-content" v-if="local!=='draw'">
       <div class="w-full max-w-screen-xl m-auto">
         <aiGptInput @handle-clear="handleClear" @export="handleExport" v-if="['gpt-4o-mini','gpt-3.5-turbo-16k'].indexOf(gptConfigStore.myData.model)>-1 || st.inputme "

+ 1 - 1
src/views/knowledge/annex.vue

@@ -245,7 +245,7 @@ function goBack() {
 								</div>
 
 								<div class="upload-warnings">
-									<div class="warning-title"></div>
+									<div class="warning-title">{{ $t("annex.uploadNotes") }}</div>
 									<n-p class="upload-warning">
 										<span class="warning-dot">•</span>
 										{{ $t("annex.friendlyReminder") }}

+ 6 - 6
src/views/knowledge/index.vue

@@ -292,7 +292,7 @@ const columns = ref(createColumns());
 							</n-form-item>
 						</n-gi>
 
-						<!-- <n-gi :span="12">
+						<n-gi :span="12">
 							<n-form-item label="向量库" required>
 								<n-select
 									:options="getVector"
@@ -301,9 +301,9 @@ const columns = ref(createColumns());
 									clearable
 								></n-select>
 							</n-form-item>
-						</n-gi> -->
+						</n-gi>
 
-						<!-- <n-gi :span="12">
+						<n-gi :span="12">
 							<n-form-item label="提问分割符">
 								<n-input
 									v-model:value="formValue.questionSeparator"
@@ -311,7 +311,7 @@ const columns = ref(createColumns());
 									clearable
 								/>
 							</n-form-item>
-						</n-gi> -->
+						</n-gi>
 
 						<n-gi :span="12">
 							<n-form-item label="向量模型" required>
@@ -337,7 +337,7 @@ const columns = ref(createColumns());
 							</n-form-item>
 						</n-gi>
 
-						<!-- <n-gi :span="24">
+						<n-gi :span="24">
 							<n-form-item label="是否公开" label-placement="left">
 								<n-switch
 									size="large"
@@ -349,7 +349,7 @@ const columns = ref(createColumns());
 									<template #unchecked>否</template>
 								</n-switch>
 							</n-form-item>
-						</n-gi> -->
+						</n-gi>
 					</n-grid>
 				</n-form>
 			</n-space>

+ 4 - 19
src/views/mj/aiGptInput.vue

@@ -215,6 +215,7 @@ const paste = (e: ClipboardEvent) => {
 	let rz = getFileFromClipboard(e);
 	if (rz.length > 0) upFile(rz[0]);
 };
+
 const sendMic = (e: any) => {
 	mlog("sendMic", e);
 	st.value.showMic = false;
@@ -278,22 +279,6 @@ const handleSelectASR = (key: string | number) => {
 	if (key == "asr") goASR();
 	if (key == "whisper") st.value.showMic = true;
 };
-/**
- * 校验字符串的大小
- * @param inputStr 输入的字符
- * @param maxLength 字符串长度
- */
-const truncateText = (inputStr:any, maxLength = 20) => {
-	// 处理空值情况
-	if (!inputStr) return ''
-	// 类型安全校验
-	const str = String(inputStr)
-	// 判断并截断
-	return str.length > maxLength
-		? `${str.slice(0, maxLength)}...`
-		: str
-}
-
 const show = ref(false);
 function handleExport() {
 	emit("export");
@@ -355,9 +340,9 @@ function handleClear() {
 						</template>
 						<template v-else>
 							<SvgIcon icon="heroicons:sparkles" />
-							<span>模型:{{
-								nGptStore.modelLabel ? truncateText(nGptStore.modelLabel,20) : "gpt-4o-mini"
-							}} {{nGptStore.kid?'知识库:'+truncateText(nGptStore.kName,10):''}}</span>
+							<span>{{
+								nGptStore.modelLabel ? nGptStore.modelLabel : "gpt-4o-mini"
+							}}</span>
 						</template>
 						<SvgIcon icon="icon-park-outline:right" />
 					</div>

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

@@ -52,7 +52,7 @@ const fetchDataGetKnowledge = async () => {
         }));
 
         // 请求成功
-        options.value.push({ label: '暂不配置', value: '' });
+        options.value.push({ label: 'please select', value: '' });
       }
     } catch (error) {
       console.error('Error fetching data:', error);

+ 0 - 280
src/views/sound/index.vue

@@ -1,280 +0,0 @@
-<script setup lang="ts">
-import { ref, computed, onMounted } from 'vue';
-import {
-  NButton, NCard, NAvatar, NPagination, NDrawer, NDrawerContent,
-  NForm, NFormItem, NInput, NDivider, NSpace, NUpload,
-  NProgress, NModal, NSlider, useMessage,NUploadDragger,UploadFileInfo
-} from 'naive-ui';
-import { useRouter } from 'vue-router';
-import { getToken } from '@/store/modules/auth/helper';
-import { createRole, getRole, simpleGenerateReq, delRole } from '@/api/voice';
-
-import to from "await-to-js";
-import { t } from '@/locales';
-
-const router = useRouter();
-const message = useMessage();
-const token = getToken();
-const headers = { Authorization: `Bearer ${token}` };
-const active = ref(false);
-const showModal = ref(false);
-const audioUrl = ref('');
-const isPercentage = ref(false);
-const percentage = ref(0);
-const formValue = ref({ name: '', description: '', avatar: '' ,prompt: ''});
-const simpleGenerate = ref({ model: 'reecho-neural-voice-001', randomness: 97, stability_boost: 100, voiceId: '', text: '' });
-const tableData = ref([]);
-const currentPage = ref(1);
-
-const paginatedData = computed(() => {
-  const start = (currentPage.value - 1) * 9;
-  return tableData.value.slice(start, start + 9);
-});
-
-onMounted(fetchData);
-
-function increaseProgress() {
-  isPercentage.value = true;
-  const interval = setInterval(() => {
-    if (percentage.value < 99) {
-      percentage.value += Math.floor(Math.random() * 5) + 1;
-      if (percentage.value > 99) percentage.value = 99;
-    } else {
-      clearInterval(interval);
-    }
-  }, 1000);
-}
-
-async function fetchData() {
-  const [err, result] = await to(getRole());
-  if (!err) tableData.value = result;
-}
-
-/**
- * 头像上传
- */
-async function handleUploadFinish({ event }: { event?: ProgressEvent }) {
-  const response = JSON.parse(event?.target?.response);
-  formValue.value.avatar = response.data.url;
-  message.success(t('voice.uploudSuccessful'));
-}
-
-/**
- * 文件上传
- */
- function handleFinish({event,file}: {
-  file: UploadFileInfo
-  event?: ProgressEvent
-}) {
-  const ext = (event?.target as XMLHttpRequest).response
-  // ext 转成json对象
-  const fileData =  JSON.parse(ext);
-  formValue.value.prompt = fileData.data.url
-  message.success(t('voice.uploudSuccessful'));
-}
-
-
-
-
-async function submitForm() {
-  const [err] = await to(createRole(formValue.value));
-  if (!err) {
-    message.success(t('voice.operationSuccessful'));
-    fetchData();
-  }
-}
-
-async function submitSimpleGenerate() {
-  increaseProgress();
-  const [err, result] = await to(simpleGenerateReq(simpleGenerate.value));
-  if (!err) {
-    audioUrl.value = result?.data.audio;
-    message.success(t('voice.operationSuccessful'));
-    isPercentage.value = false;
-  }else{
-    message.error(err.message);
-  }
-}
-
-function handleActionButtonClick(row: any) {
-  simpleGenerate.value.voiceId = row.voiceId;
-  showModal.value = true;
-}
-
-async function handleDelButtonClick(row: any) {
-  const [err] = await to(delRole(row.id));
-  if (!err) message.success(t('voice.operationSuccessful'));
-  // 刷新页面
-  fetchData();
-}
-
-function handlePageChange(page: number) {
-  currentPage.value = page;
-}
-
-function playAudio(url: string) {
-  new Audio(url).play();
-}
-</script>
-
-<template>
-  <div style="display: flex; justify-content: flex-start; margin:10px;" class="sound-button-box top-header">
-    <n-button :bordered="false" @click="active = true" type="primary" style="margin-right: 10px;">{{ $t('voice.createRole') }}</n-button>
-    <n-button :bordered="false" @click="router.push('/roleList/t')" type="warning">{{ $t('voice.soundMarket') }}</n-button>
-  </div>
- 
-  <div class="flex h-full flex-col role-card table-box">
-    <main class="flex-1 overflow-hidden">
-      <div class="card-container">
-        <n-card v-for="item in paginatedData" :key="item.key" class="card-item" bordered hoverable>
-          <div class="flex justify-between">
-            <div class="card-description">
-              <h3>{{ item.name }}</h3>
-              <p class="ellipsis" :title="item.description">{{ item.description || '——'}}</p>
-            </div>
-            <n-avatar size="large" :src="item.avatar" class="card-avatar" />
-          </div>
-          <n-divider />
-          <div class="flex mt-4 button-list">
-            <n-button :bordered="false" secondary round type="info" @click="playAudio(item.fileUrl)"> {{ $t('voice.play') }}</n-button>
-            <n-button :bordered="false" secondary round type="primary" @click="handleActionButtonClick(item)"> {{ $t('voice.generate') }}</n-button>
-            <n-button :bordered="false" secondary round type="error" @click="handleDelButtonClick(item)">{{ $t('voice.delete') }}</n-button>
-          </div>
-        </n-card>
-        <n-pagination v-model:page="currentPage" :page-size="9" :item-count="tableData.length"
-          @update:page="handlePageChange" class="pagination voice-pagination" />
-      </div>
-    </main>
-  </div>
-
-  <n-drawer v-model:show="active" class="add-role-draw" :width="540" placement="right">
-    <n-drawer-content :title="$t('voice.addRole')">
-      <n-space vertical>
-        <n-form>
-          <n-form-item :label="$t('voice.roleName')">
-            <n-input v-model:value="formValue.name" :placeholder="$t('voice.roleNameDescribe')" />
-          </n-form-item>
-          <n-form-item :label="$t('voice.roleDescribe')">
-            <n-input v-model:value="formValue.description" :placeholder="$t('voice.roleExplain')" />
-          </n-form-item>
-          <n-form-item :label="$t('voice.avatar')">
-            <n-upload action="/api/resource/oss/upload" :max="1" list-type="image-card" :headers="headers" class="role-avatar-upload"
-              @finish="handleUploadFinish">
-              <div style="margin: 30px 0 10px;">
-              <IconSvg icon="add" width="30px" height="30px"></IconSvg>
-              </div>
-              <p style="font-size: 14px">{{ $t('voice.upload') }}</p>
-              </n-upload>
-          </n-form-item>
-
-          <n-form-item :label="$t('voice.audioSamples')">
-                <!-- @before-upload="beforeUpload" -->
-                <n-upload
-                  directory-dnd
-                  action="/api/resource/oss/upload"
-                  name="file"
-                  :headers="headers"
-                  @finish="handleFinish"
-                  class="add-role-upload"
-                  :max="1">
-                      <n-upload-dragger>
-                        <div style="margin-bottom: 12px">
-                          <n-icon size="48" :depth="3">
-                            <archive-icon />
-                          </n-icon>
-                        </div>
-                        <n-text style="font-size: 16px">
-                          {{ $t('voice.prompt1') }}
-                        </n-text>
-                        <br>
-                        <n-p depth="3" style="margin: 8px 0 0 0">
-                          {{ $t('voice.prompt2') }}
-                        </n-p>
-                      </n-upload-dragger>
-                </n-upload>
-              </n-form-item>
-          <n-button :bordered="false" class="add-role-button" @click="submitForm" type="primary">{{ $t('voice.add') }}</n-button>
-        </n-form>
-      </n-space>
-    </n-drawer-content>
-  </n-drawer>
-
-  <n-modal class="voice-drawer" v-model:show="showModal" :title="$t('voice.generate')" :auto-focus="false" preset="card"
-    style="width: 95%; max-width: 540px;">
-    <n-input maxlength="1000" type="textarea" v-model:value="simpleGenerate.text"
-      :placeholder="$t('voice.proposal')" />
-    <n-space vertical>
-      <br>
-      <section class=" flex justify-between items-center">
-        <div> {{ $t('voice.diversity') }}
-        </div>
-        <div class=" flex justify-end items-center w-[80%] max-w-[240px]">
-          <div class=" w-[200px]"><n-slider v-model:value="simpleGenerate.randomness" :step="1" :max="100" /></div>
-          <div class="w-[40px] text-right">{{ simpleGenerate.randomness }}</div>
-        </div>
-      </section>
-      <div class="mb-4 text-[12px] text-gray-300 dark:text-gray-300/20">
-       {{ $t('voice.generateInfo') }}</div>
-
-      <section class=" flex justify-between items-center">
-        <div> {{ $t('voice.stability') }}
-        </div>
-        <div class=" flex justify-end items-center w-[80%] max-w-[240px]">
-          <div class=" w-[200px]"><n-slider v-model:value="simpleGenerate.stability_boost" :step="1" :max="100" /></div>
-          <div class="w-[40px] text-right">{{ simpleGenerate.stability_boost }}</div>
-        </div>
-      </section>
-      <div class="mb-4 text-[12px] text-gray-300 dark:text-gray-300/20 stabilityInfo">
-        </div>
-        {{ $t('voice.stabilityInfo') }}
-
-      <!-- 进度条 -->
-      <n-progress v-if="isPercentage" :percentage="percentage"></n-progress>
-
-      <audio v-if="audioUrl" :src="audioUrl" controls></audio>
-
-    </n-space>
-
-
-
-    <br>
-    <div style="display: flex; justify-content: flex-end">
-      <n-button :bordered="false" @click="submitSimpleGenerate" type="primary" class="addvoicebutton">
-        {{ $t('voice.start') }}
-      </n-button>
-    </div>
-
-
-
-  </n-modal>
-</template>
-
-
-
-<style scoped>
-.card-container {
-  display: flex;
-  flex-wrap: wrap;
-  justify-content: flex-start;
-}
-
-.card-item {
-  width: calc(30%);
-  margin: 10px;
-  border-radius: 10px;
-  height: 23vh;
-}
-
-.pagination {
-  position: absolute;
-  right: 10px;
-  bottom: 10px;
-}
-
-.ellipsis {
-  overflow: hidden;
-  text-overflow: ellipsis;
-  white-space: nowrap;
-  width: 200px;
-}
-</style>

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

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

+ 0 - 116
src/views/sound/roleList.vue

@@ -1,116 +0,0 @@
-<script setup lang="ts">
-import { ref, computed, onMounted } from 'vue';
-import { NButton, NCard, NAvatar, NPagination, useMessage,NDivider } from 'naive-ui';
-import { getRoleList, Character, copyRoleList } from '@/api/voice';
-import to from 'await-to-js';
-import { t } from '@/locales';
-
-const allData = ref<Character[]>([]);
-const currentPage = ref(1);
-const pageSize = ref(9);
-const message = useMessage();
-
-onMounted(async () => {
-  const [err, result] = await to(getRoleList());
-  if (err) {
-    message.error(err.message);
-  } else {
-    allData.value = result.data;
-  }
-});
-
-const tableData = computed(() => {
-  const start = (currentPage.value - 1) * pageSize.value;
-  return allData.value.slice(start, start + pageSize.value);
-});
-
-const totalItems = computed(() => {
-  return allData.value ? allData.value.length : 0;
-});
-
-function playAudio(url: string) {
-  const audio = new Audio(url);
-  audio.play();
-}
-
-async function handleActionButtonClick(item: Character) {
-  const [err] = await to(copyRoleList(item));
-  if (err) {
-    message.error(err.message);
-  } else {
-    message.success(t('voice.collectionSuccessful'));
-  }
-}
-
-const goBack = () => {
-  window.history.back();
-};
-</script>
-
-
-
-<template>
-
-  <div style="display: flex; justify-content: flex-start; margin:10px;" class="top-header">
-    <n-button @click="goBack" type="primary" :bordered="false" class="success-button"> {{ $t('voice.return') }}</n-button>
-  </div>
-
-  <div class="flex h-full flex-col role-card">
-    <main class="flex-1 overflow-hidden">
-      <div class="card-container">
-        <n-card v-for="item in tableData" :key="item.id" class="card-item" bordered hoverable>
-          <div class="flex justify-between">
-            <div>
-              <h3>{{ item.name }}</h3>
-              <p class="ellipsis" :title="item.description">{{ item.description || '——' }}</p>
-            </div>
-            <n-avatar size="large" :src="item.avatar" />
-          </div>
-          <n-divider />
-          <div class="flex justify-between mt-4 button-list">
-            <n-button secondary round type="info" @click="playAudio(item.previewAudio)">
-            {{ $t('voice.playSound') }}
-            </n-button>
-            <n-button secondary round type="primary" @click="handleActionButtonClick(item)">
-            {{ $t('voice.collection') }}
-            </n-button>
-          </div>
-        </n-card>
-      </div>
-      <n-pagination :page="currentPage" :page-size="pageSize" :item-count="totalItems"
-        @update:page="currentPage = $event" class="pagination  voice-pagination" />
-    </main>
-  </div>
-  </template>
-  
-  
-  
-
-  <style scoped>
-  .card-container {
-    display: flex;
-    flex-wrap: wrap;
-    justify-content: space-around;
-  }
-
-  .card-item {
-    width: calc(30%);
-    margin: 10px;
-    border-radius: 10px;
-    height: 23vh;
-  }
-
-  .pagination {
-  position: absolute;
-  right: 10px;
-  bottom: 10px;
-}
-
-  .ellipsis {
-    overflow: hidden;
-    text-overflow: ellipsis;
-    white-space: nowrap;
-    width: 200px; /* Adjust width as needed */
-  }
-  </style>
-