Merge remote-tracking branch 'origin/main'

# Conflicts:
#	layouts/default.vue
#	nuxt.config.ts
#	package.json
#	yarn.lock
This commit is contained in:
2024-06-22 01:50:23 +08:00
12 changed files with 363 additions and 17 deletions

View File

@@ -6,6 +6,12 @@ const router = useRouter()
const modal = useModal() const modal = useModal()
const loginState = useLoginState() const loginState = useLoginState()
useHead({
titleTemplate(title) {
return title ? `${title} - 眩生花 AI 助手` : '眩生花 AI 助手'
},
})
useSeoMeta({ useSeoMeta({
viewport: 'width=device-width, initial-scale=1.0, user-scalable=no', viewport: 'width=device-width, initial-scale=1.0, user-scalable=no',
}) })
@@ -27,6 +33,8 @@ onMounted(() => {
<template> <template>
<div> <div>
<NuxtLoadingIndicator />
<NuxtLayout> <NuxtLayout>
<NuxtPage/> <NuxtPage/>
</NuxtLayout> </NuxtLayout>

View File

@@ -4,7 +4,6 @@
@layer base { @layer base {
html { 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; box-sizing: border-box;
} }
} }

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

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

View File

@@ -1,24 +1,38 @@
<script setup lang="ts"> <script setup lang="tsx">
import ModalAuthentication from '~/components/ModalAuthentication.vue'
const loginState = useLoginState() const loginState = useLoginState()
defineProps({ defineProps({
contentClass: { contentClass: {
type: String, type: String,
default: '' default: '',
} },
}) })
const modal = useModal()
</script> </script>
<template> <template>
<ClientOnly>
<div v-if="!loginState.is_logged_in" <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"> 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"/> <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> <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>
<div :class="contentClass" v-else> <div :class="contentClass" v-else>
<slot/> <slot/>
</div> </div>
</ClientOnly>
</template> </template>
<style scoped> <style scoped>

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

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

View File

@@ -17,8 +17,8 @@ import { trimObject } from '@uniiem/object-trim'
import ModalAuthentication from '~/components/ModalAuthentication.vue' import ModalAuthentication from '~/components/ModalAuthentication.vue'
import NewSessionScreen from '~/components/aigc/chat/NewSessionScreen.vue' import NewSessionScreen from '~/components/aigc/chat/NewSessionScreen.vue'
useHead({ useSeoMeta({
title: '聊天 | XSH AI', title: '聊天',
}) })
const dayjs = useDayjs() const dayjs = useDayjs()

53
pages/aigc/generation.vue Normal file
View 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>

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

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

View File

@@ -12,8 +12,8 @@ import {useHistory} from '~/composables/useHistory';
import {del, set} from 'idb-keyval'; import {del, set} from 'idb-keyval';
import ReferenceFigureSelector from '~/components/aigc/ReferenceFigureSelector.vue'; import ReferenceFigureSelector from '~/components/aigc/ReferenceFigureSelector.vue';
useHead({ useSeoMeta({
title: '绘画 | XSH AI', title: '绘画',
}) })
const toast = useToast() const toast = useToast()