浏览代码

feat: 修改主页样式

ageerle 1 月之前
父节点
当前提交
026d63677d
共有 7 个文件被更改,包括 33 次插入600 次删除
  1. 0 88
      src/api/voice.ts
  2. 1 1
      src/locales/zh-CN.ts
  3. 8 7
      src/styles/lib/highlight.less
  4. 24 47
      src/views/chat/index.vue
  5. 0 280
      src/views/sound/index.vue
  6. 0 61
      src/views/sound/layout.vue
  7. 0 116
      src/views/sound/roleList.vue

+ 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
-	})
-}
-
-
-
-
-
-
-

+ 1 - 1
src/locales/zh-CN.ts

@@ -58,7 +58,7 @@ export default {
     annouce: '公告',
     annouceContent: '本网站为网站所有内容,由用户自行承担。;本网站仅供学习之用。;使用本网站产生的任何问题由用户自行承担。',
     helpTitle: '今天我能为您做些什么呢?',
-    helpcontent: '作为你的智慧伙伴,我不仅可以写文案、出点子,还可以和你聊天、回答问题。想知道我还能做些什么吗? 点击这里快速入门! 您可以将xxx官方网站地址收藏在浏览器中,方便日后使用。;你也可以这样问我:',
+    helpcontent: '作为你的智能伙伴,我不仅能帮你写文案、出点子,还能和你聊天、解答问题。想探索我的更多功能?点这里快速了解!',
     used: '使用',
     refresh: '刷新',
     like: '点赞',

+ 8 - 7
src/styles/lib/highlight.less

@@ -535,7 +535,7 @@ html.dark {
 			color: #fff;
 		}
 		.text{
-			background-color: #141718;
+			// background-color: #141718;
 			p{
 				color: #e2e2e2;
 			}
@@ -553,7 +553,7 @@ html.dark {
 			}
 			.gpts-item{
 				background-color: #232627;
-				border: 1px solid #3a3b3c;
+				border: 1px solid #333435;
 				.name{
 					color: #a7a8a9;
 					&:hover{
@@ -1480,7 +1480,7 @@ position: relative;
 	}
 }
 .gpts-box{
-	color: #494747;
+	 color: #494747;
 	h1{
 		font-weight: 700;
 		font-size: 40px;
@@ -1492,7 +1492,7 @@ position: relative;
 		float: left;
 	}
 	.text{
-		background-color: #e8eaf1;
+		// background-color: #e8eaf1;
 		border-radius: 20px;
 		float: left;
 		width: calc(100% - 100px);
@@ -1523,13 +1523,14 @@ position: relative;
 			}
 		}
 		.gpts-item{
-			background-color: #ffffff;
+			background-color: #e8eaf1;
 			border-radius: 10px;
-			border: 1px solid #e8eaf1;
+			// border: 1px solid #e8eaf1;
 			float: left;
+			height: 120px;
 			.n-image{
 				float: left;
-				margin-top: 5px;
+				margin-top: 10px;
 			}
 			.info{
 				white-space: nowrap;

+ 24 - 47
src/views/chat/index.vue

@@ -5,7 +5,7 @@ import type { Ref } from 'vue'
 import { computed, onMounted, onUnmounted, ref,watch,h } from 'vue'
 import { useRoute, useRouter } from 'vue-router'
 import { storeToRefs } from 'pinia'
-import { NAutoComplete, NButton, NInput, useDialog, useMessage,NAvatar,NModal,NCard,NImage } from 'naive-ui'
+import { NAutoComplete, NButton, NInput, useDialog, useMessage,NAvatar,NModal,NCard,NImage,NTag,NSpace } from 'naive-ui'
 import html2canvas from 'html2canvas'
 import { Message } from './components'
 import { useScroll } from './hooks/useScroll'
@@ -23,6 +23,7 @@ import AiSiderInput from '../mj/aiSiderInput.vue'
 import aiGptInput from '../mj/aiGptInput.vue'
 import { getNotice,readNotice, getInform } from '@/api/notice'
 import to from "await-to-js";
+import { truncate } from 'fs'
 
 let controller = new AbortController()
 
@@ -641,14 +642,6 @@ load()
   </NModal>
 
   <div class="flex flex-col w-full h-full chat-content" :class="[isMobile? '' : 'chat-content-noMobile']">
-   <!-- v-if="isMobile" -->
-    <!-- <HeaderComponent
-      :haveData="!!dataSources.length"
-      :using-context="usingContext"
-      @export="handleExport"
-      @handle-clear="handleClear"
-    /> -->
-
     <main class="flex-1 overflow-hidden">
 
       <div id="scrollRef" ref="scrollRef" class="h-full overflow-hidden overflow-y-auto">
@@ -663,24 +656,10 @@ load()
 
             </div>
             <div class="gpts-box" v-else>
-              <h1>{{ href }}</h1>
-              <div class="annou" v-if="informContent.length" :style="{'margin-bottom': isMobile ? '15px' : '30px'}">
-                <div class="ai-icon">
-                  <IconSvg icon="chatGPT" :width="isMobile ? '32px' : '64px'" :height="isMobile ? '32px' : '64px'"></IconSvg>
-                </div>
-                <div class="text" :style="{padding: isMobile? '22px 10px' : '22px 68px'}">
-                  <p class="title">{{ t('chat.annouce') }}</p>
-                  <!-- <p v-for="(item,index) in t('chat.annouceContent').split(';')" :key="index">{{ item }}</p> -->
-                  <div v-for="(item, index) in informContent.slice(0, 1)" :key="index" >
-                    <!-- <p style="margin-top: 10px; font-size: 18px">{{ item.noticeTitle }}</p> -->
-                    <div v-html="item.noticeContent"></div>
-                  </div>
-                </div>
-              </div>
               <div class="help" v-if="gptsFilterList && gptsFilterList.length">
-                <div class="ai-icon">
+                <!-- <div class="ai-icon">
                   <IconSvg icon="chatGPT" :width="isMobile ? '32px' : '64px'" :height="isMobile ? '32px' : '64px'"></IconSvg>
-                </div>
+                </div> -->
                 <div class="text" :style="{padding: isMobile? '22px 10px' : '22px 68px', 'font-size': isMobile? '14px' : '16px', 'line-height': isMobile? '20px' : '28px'}">
                   <p class="title">
                     {{ t('chat.helpTitle') }}
@@ -705,13 +684,29 @@ load()
                       </div>
                     </div>
                   </div>
+
+                  <div>
+                   <p>常见问题:</p> 
+                  <n-space >
+                      <n-tag :bordered="false" :round="true" size="large">
+                        它支持插件系统吗?
+                      </n-tag>
+                      <n-tag :bordered="false" :round="true" size="large">
+                        是否支持多个 AI 服务提供商?
+                      </n-tag>
+                      <n-tag :bordered="false" :round="true" size="large">
+                        它是否支持本地语言模型?
+                      </n-tag>
+                      <n-tag :bordered="false" :round="true" size="large">
+                        部署文档在哪?
+                      </n-tag>
+                  </n-space>
+
+                  </div>
                 </div>
               </div>
             </div>
-            <!-- <div class="flex items-center justify-center mt-4 text-center text-neutral-300" v-else>
-              <SvgIcon icon="ri:bubble-chart-fill" class="mr-2 text-3xl" />
-              <span>Aha~</span>
-            </div> -->
+
           </template>
 
           <template v-else>
@@ -757,24 +752,6 @@ load()
          :searchOptions="searchOptions"  :renderOption="renderOption"
           />
         <div class="flex items-center justify-between space-x-2" v-else>
-          <!--
-          <HoverButton v-if="!isMobile" @click="handleClear">
-            <span class="text-xl text-[#4f555e] dark:text-white">
-              <SvgIcon icon="ri:delete-bin-line" />
-            </span>
-          </HoverButton>
-          <HoverButton v-if="!isMobile" @click="handleExport">
-            <span class="text-xl text-[#4f555e] dark:text-white">
-              <SvgIcon icon="ri:download-2-line" />
-            </span>
-          </HoverButton>
-          <HoverButton @click="toggleUsingContext">
-            <span class="text-xl" :class="{ 'text-[#4b9e5f]': usingContext, 'text-[#a8071a]': !usingContext }">
-              <SvgIcon icon="ri:chat-history-line" />
-            </span>
-          </HoverButton>
-          -->
-
           <NAutoComplete v-model:value="prompt" :options="searchOptions" :render-label="renderOption">
             <template #default="{ handleInput, handleBlur, handleFocus }">
               <NInput

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