Merge remote-tracking branch 'origin/main'
# Conflicts: # layouts/default.vue # nuxt.config.ts # package.json # yarn.lock
This commit is contained in:
8
app.vue
8
app.vue
@@ -6,6 +6,12 @@ const router = useRouter()
|
||||
const modal = useModal()
|
||||
const loginState = useLoginState()
|
||||
|
||||
useHead({
|
||||
titleTemplate(title) {
|
||||
return title ? `${title} - 眩生花 AI 助手` : '眩生花 AI 助手'
|
||||
},
|
||||
})
|
||||
|
||||
useSeoMeta({
|
||||
viewport: 'width=device-width, initial-scale=1.0, user-scalable=no',
|
||||
})
|
||||
@@ -27,6 +33,8 @@ onMounted(() => {
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<NuxtLoadingIndicator />
|
||||
|
||||
<NuxtLayout>
|
||||
<NuxtPage/>
|
||||
</NuxtLayout>
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
|
||||
@layer base {
|
||||
html {
|
||||
font-family: 'Rubik', 'Noto Sans SC', ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Helvetica Neue, Arial, Noto Sans, sans-serif, Apple Color Emoji, Segoe UI Emoji, Segoe UI Symbol, Noto Color Emoji;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
}
|
||||
|
||||
38
components/BubbleTitle.vue
Normal file
38
components/BubbleTitle.vue
Normal file
@@ -0,0 +1,38 @@
|
||||
<script setup lang="ts">
|
||||
const props = defineProps({
|
||||
title: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
subtitle: {
|
||||
type: String,
|
||||
required: false,
|
||||
},
|
||||
bubble: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="relative font-sans select-none">
|
||||
<h1
|
||||
v-if="subtitle"
|
||||
class="text-base text-neutral-300 italic tracking-wide font-black leading-none"
|
||||
>{{ subtitle }}</h1>
|
||||
|
||||
<h1 class="text-xl font-bold text-neutral-700 leading-none relative z-[1]">
|
||||
{{ title }}
|
||||
</h1>
|
||||
|
||||
<div
|
||||
v-if="bubble"
|
||||
class="absolute -left-1.5 -bottom-1.5 w-4 h-4 rounded-full bg-primary-500/50 z-[0]"
|
||||
></div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
19
components/GradientDivider.vue
Normal file
19
components/GradientDivider.vue
Normal file
@@ -0,0 +1,19 @@
|
||||
<script setup lang="ts">
|
||||
const props = defineProps({
|
||||
vertical: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
class="bg-gradient-to-r from-primary-500/50 to-primary-300/50 rounded-full my-4"
|
||||
:class="{'w-full h-[1px]': !vertical, 'w-[1px] h-full': vertical}"
|
||||
></div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
@@ -1,24 +1,38 @@
|
||||
<script setup lang="ts">
|
||||
<script setup lang="tsx">
|
||||
import ModalAuthentication from '~/components/ModalAuthentication.vue'
|
||||
|
||||
const loginState = useLoginState()
|
||||
|
||||
defineProps({
|
||||
contentClass: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
default: '',
|
||||
},
|
||||
})
|
||||
|
||||
const modal = useModal()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ClientOnly>
|
||||
<div v-if="!loginState.is_logged_in"
|
||||
class="w-full flex flex-col justify-center items-center gap-2 bg-neutral-100 dark:bg-neutral-900">
|
||||
<Icon name="i-tabler-user-circle" class="text-7xl text-neutral-300 dark:text-neutral-700"/>
|
||||
<p class="text-sm text-neutral-500 dark:text-neutral-400">请登录后使用</p>
|
||||
<UButton class="mt-2 font-bold" to="/login" color="black" variant="solid" size="xs">登录</UButton>
|
||||
<UButton
|
||||
class="mt-2 font-bold"
|
||||
color="black"
|
||||
variant="solid"
|
||||
size="xs"
|
||||
@click="modal.open(ModalAuthentication)"
|
||||
>
|
||||
登录
|
||||
</UButton>
|
||||
</div>
|
||||
<div :class="contentClass" v-else>
|
||||
<slot/>
|
||||
</div>
|
||||
</ClientOnly>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
37
components/ppt/NavItem.vue
Normal file
37
components/ppt/NavItem.vue
Normal file
@@ -0,0 +1,37 @@
|
||||
<script setup lang="ts">
|
||||
const props = defineProps({
|
||||
icon: {
|
||||
type: String,
|
||||
default: 'i-tabler-photo-filled',
|
||||
},
|
||||
label: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
to: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
})
|
||||
|
||||
const route = useRoute()
|
||||
|
||||
const active = computed(() => {
|
||||
return route.path === props.to
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<NuxtLink
|
||||
class="px-4 py-3 flex items-center gap-2 rounded-lg transition cursor-pointer"
|
||||
:class="active ? 'bg-primary text-white' : 'hover:bg-neutral-200'"
|
||||
:to="to"
|
||||
>
|
||||
<Icon :name="icon" class="text-xl inline"/>
|
||||
<h1 class="text-[14px] font-medium">{{ label }}</h1>
|
||||
</NuxtLink>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
93
components/ppt/PPTGenerationRecord.vue
Normal file
93
components/ppt/PPTGenerationRecord.vue
Normal file
@@ -0,0 +1,93 @@
|
||||
<script setup lang="ts">
|
||||
const props = defineProps({
|
||||
record: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
})
|
||||
|
||||
const dayjs = useDayjs()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
class="relative w-full aspect-video rounded-lg overflow-hidden shadow"
|
||||
>
|
||||
<NuxtImg :src="record.video_cover"></NuxtImg>
|
||||
<div class="absolute inset-0 flex flex-col justify-between bg-black/10 bg-gradient-to-t from-black/20">
|
||||
<div class="flex justify-between items-start p-2.5 gap-2">
|
||||
<div>
|
||||
<UButton icon="i-solar-play-circle-bold-duotone">预览</UButton>
|
||||
</div>
|
||||
<div class="flex flex-col items-end gap-1">
|
||||
<UTooltip :text="record.task_id" :close-delay="300">
|
||||
<h1 class="text-white text-xs font-bold font-sans">
|
||||
ID: {{ record.task_id.slice(0, 6) }}
|
||||
</h1>
|
||||
</UTooltip>
|
||||
<UTooltip :text="dayjs(record.create_time * 1000).format('YYYY-MM-DD HH:mm:ss')">
|
||||
<h1 class="text-white text-xs font-bold font-sans">
|
||||
{{ dayjs(record.create_time * 1000).fromNow() }}
|
||||
</h1>
|
||||
</UTooltip>
|
||||
</div>
|
||||
<!-- <UProgress-->
|
||||
<!-- size="md"-->
|
||||
<!-- indicator-->
|
||||
<!-- :ui="{-->
|
||||
<!-- wrapper: 'flex-col-reverse',-->
|
||||
<!-- progress: {-->
|
||||
<!-- base: '!bg-opacity-50'-->
|
||||
<!-- }-->
|
||||
<!-- }"-->
|
||||
<!-- :value="10"-->
|
||||
<!-- :max="100"-->
|
||||
<!-- :animation="'carousel'"-->
|
||||
<!-- />-->
|
||||
</div>
|
||||
<div class="flex justify-between items-center p-2.5 gap-2">
|
||||
<div class="overflow-hidden whitespace-nowrap">
|
||||
<UTooltip
|
||||
:text="record.title"
|
||||
:popper="{
|
||||
placement: 'bottom-start'
|
||||
}"
|
||||
:open-delay="300"
|
||||
:close-delay="300"
|
||||
class="w-full"
|
||||
>
|
||||
<h1 class="text-white text-base font-bold font-sans drop-shadow overflow-hidden text-ellipsis leading-none">
|
||||
{{ record.title }}
|
||||
</h1>
|
||||
</UTooltip>
|
||||
</div>
|
||||
<div class="flex-1 whitespace-nowrap flex gap-1.5">
|
||||
<UButton
|
||||
size="xs"
|
||||
color="red"
|
||||
variant="soft"
|
||||
icon="i-tabler-trash"
|
||||
/>
|
||||
<UButton
|
||||
size="xs"
|
||||
color="primary"
|
||||
variant="soft"
|
||||
icon="i-solar-subtitles-linear"
|
||||
>
|
||||
字幕
|
||||
</UButton>
|
||||
<UButton
|
||||
size="xs"
|
||||
icon="i-tabler-download"
|
||||
>
|
||||
下载
|
||||
</UButton>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
@@ -17,8 +17,8 @@ import { trimObject } from '@uniiem/object-trim'
|
||||
import ModalAuthentication from '~/components/ModalAuthentication.vue'
|
||||
import NewSessionScreen from '~/components/aigc/chat/NewSessionScreen.vue'
|
||||
|
||||
useHead({
|
||||
title: '聊天 | XSH AI',
|
||||
useSeoMeta({
|
||||
title: '聊天',
|
||||
})
|
||||
|
||||
const dayjs = useDayjs()
|
||||
|
||||
53
pages/aigc/generation.vue
Normal file
53
pages/aigc/generation.vue
Normal file
@@ -0,0 +1,53 @@
|
||||
<script setup lang="tsx">
|
||||
import NavItem from '~/components/ppt/NavItem.vue'
|
||||
|
||||
useSeoMeta({
|
||||
title: '智能生成',
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="w-full flex relative">
|
||||
|
||||
<div class="absolute -translate-x-full md:sticky md:translate-x-0 z-10 flex flex-col h-[calc(100vh-4rem)] bg-neutral-100 dark:bg-neutral-900 p-4 w-full md:w-[300px]
|
||||
border-r border-neutral-200 dark:border-neutral-700 transition-all duration-300 ease-out">
|
||||
<div class="flex flex-col flex-1 overflow-auto overflow-x-hidden">
|
||||
<div class="flex flex-col gap-1">
|
||||
<ClientOnly>
|
||||
<NavItem
|
||||
icon="tabler:presentation-analytics"
|
||||
label="微课视频生成"
|
||||
to="/aigc/generation/video-generate"
|
||||
/>
|
||||
<NavItem
|
||||
icon="tabler:user-screen"
|
||||
label="数字讲师"
|
||||
to="/aigc/generation/digital-teachers"
|
||||
/>
|
||||
</ClientOnly>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<LoginNeededContent
|
||||
content-class="h-[calc(100vh-4rem)] flex-1 bg-white dark:bg-neutral-900"
|
||||
>
|
||||
<Transition name="subpage" mode="out-in">
|
||||
<NuxtPage :page-key="route => route.fullPath"/>
|
||||
</Transition>
|
||||
</LoginNeededContent>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style>
|
||||
.subpage-enter-active,
|
||||
.subpage-leave-active {
|
||||
transition: opacity 0.3s;
|
||||
}
|
||||
|
||||
.subpage-enter-from,
|
||||
.subpage-leave-to {
|
||||
opacity: 0;
|
||||
}
|
||||
</style>
|
||||
16
pages/aigc/generation/digital-teachers.vue
Normal file
16
pages/aigc/generation/digital-teachers.vue
Normal file
@@ -0,0 +1,16 @@
|
||||
<script setup lang="ts">
|
||||
useSeoMeta({
|
||||
title: '数字化教师',
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="p-4">
|
||||
<BubbleTitle title="数字化讲师" subtitle="DIGITAL" />
|
||||
<GradientDivider />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
69
pages/aigc/generation/video-generate.vue
Normal file
69
pages/aigc/generation/video-generate.vue
Normal file
@@ -0,0 +1,69 @@
|
||||
<script setup lang="ts">
|
||||
useSeoMeta({
|
||||
title: '课程视频生成',
|
||||
})
|
||||
|
||||
const testItem = {
|
||||
'id': 1599,
|
||||
'device_id': 'Test_Device_V3',
|
||||
'user_id': 1,
|
||||
'task_id': 'SQOeN1j2heRoQeGGTFh3Tu2WP9kUcz4L',
|
||||
'create_time': 1713408239,
|
||||
'token': 'not use',
|
||||
'progress': 100,
|
||||
'digital_human_id': 40696,
|
||||
'complete_time': 1713409821,
|
||||
'duration': 1578819,
|
||||
'video_url': 'https://static-xsh.oss-cn-chengdu.aliyuncs.com/file/2024-04-18/75d1e1cee595a7f5758c59289d1a74b9.mp4',
|
||||
'subtitle_url': 'https://static-xsh.oss-cn-chengdu.aliyuncs.com/file/2024-04-18/a95cfef4524e90f5509a5c248e5c2061.srt',
|
||||
'video_cover': 'https://static-xsh.oss-cn-chengdu.aliyuncs.com/file/2024-04-18/b4161a85573fc09be82fa7cf7dd9abfa.png',
|
||||
'custom_video': '[]',
|
||||
'title': '1-2 一键启动:零基础快速搭建Keil开发环境实操教程',
|
||||
'ppt_url': 'https://static-xsh.oss-cn-chengdu.aliyuncs.com/material/2024-04-18/0a8827a1ae32ece196536a19bab1dff5.pptx',
|
||||
'opening_url': '',
|
||||
'ending_url': '',
|
||||
'video_duration': 507,
|
||||
'message': 'ok',
|
||||
'speed': 1,
|
||||
}
|
||||
const testItem2 = {
|
||||
'id': 1599,
|
||||
'device_id': 'Test_Device_V3',
|
||||
'user_id': 1,
|
||||
'task_id': 'SQOeN1j2heRoQeGGTFh3Tu2WP9kUcz4L',
|
||||
'create_time': 1713408239,
|
||||
'token': 'not use',
|
||||
'progress': null,
|
||||
'digital_human_id': 40696,
|
||||
'complete_time': 1713409821,
|
||||
'duration': 1578819,
|
||||
'video_url': 'https://static-xsh.oss-cn-chengdu.aliyuncs.com/file/2024-04-18/75d1e1cee595a7f5758c59289d1a74b9.mp4',
|
||||
'subtitle_url': 'https://static-xsh.oss-cn-chengdu.aliyuncs.com/file/2024-04-18/a95cfef4524e90f5509a5c248e5c2061.srt',
|
||||
'video_cover': 'https://static-xsh.oss-cn-chengdu.aliyuncs.com/file/2024-04-18/b4161a85573fc09be82fa7cf7dd9abfa.png',
|
||||
'custom_video': '[]',
|
||||
'title': '1-2 一键启动:零基础快速搭建Keil开发环境实操教程',
|
||||
'ppt_url': 'https://static-xsh.oss-cn-chengdu.aliyuncs.com/material/2024-04-18/0a8827a1ae32ece196536a19bab1dff5.pptx',
|
||||
'opening_url': '',
|
||||
'ending_url': '',
|
||||
'video_duration': 507,
|
||||
'message': 'ok',
|
||||
'speed': 1,
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="p-4">
|
||||
<BubbleTitle title="我的微课" subtitle="VIDEOS"/>
|
||||
<GradientDivider/>
|
||||
|
||||
<div class="grid grid-cols-3 gap-4">
|
||||
<PPTGenerationRecord :record="testItem"/>
|
||||
<PPTGenerationRecord :record="testItem2"/>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
@@ -12,8 +12,8 @@ import {useHistory} from '~/composables/useHistory';
|
||||
import {del, set} from 'idb-keyval';
|
||||
import ReferenceFigureSelector from '~/components/aigc/ReferenceFigureSelector.vue';
|
||||
|
||||
useHead({
|
||||
title: '绘画 | XSH AI',
|
||||
useSeoMeta({
|
||||
title: '绘画',
|
||||
})
|
||||
|
||||
const toast = useToast()
|
||||
|
||||
Reference in New Issue
Block a user