Skip to content

Commit

Permalink
Add hailuo (#168)
Browse files Browse the repository at this point in the history
  • Loading branch information
hyf-github-user authored Feb 6, 2025
1 parent 76313da commit d3251fa
Show file tree
Hide file tree
Showing 138 changed files with 9,422 additions and 3,253 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"type": "patch",
"comment": "add hailuo",
"packageName": "@acedatacloud/nexior",
"email": "[email protected]",
"dependentChangeType": "patch"
}
13 changes: 13 additions & 0 deletions src/components/common/Navigator.vue
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,8 @@ import {
ROUTE_LUMA_HISTORY,
ROUTE_PIKA_INDEX,
ROUTE_PIKA_HISTORY,
ROUTE_HAILUO_INDEX,
ROUTE_HAILUO_HISTORY,
ROUTE_HEADSHOTS_INDEX,
ROUTE_HEADSHOTS_HISTORY,
ROUTE_SUNO_INDEX,
Expand Down Expand Up @@ -241,6 +243,17 @@ export default defineComponent({
routes: [ROUTE_PIKA_INDEX, ROUTE_PIKA_HISTORY]
});
}
// Add hailuo's leftmost icon
if (this.$store?.state?.site?.features?.hailuo?.enabled) {
result.push({
route: {
name: ROUTE_HAILUO_INDEX
},
displayName: this.$t('common.nav.hailuo'),
icon: 'fa-solid fa-film',
routes: [ROUTE_HAILUO_INDEX, ROUTE_HAILUO_HISTORY]
});
}
if (this.direction === 'row') {
result.push({
Expand Down
78 changes: 78 additions & 0 deletions src/components/hailuo/ConfigPanel.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
<template>
<div class="panel">
<div class="config">
<prompt-input class="mb-4" />
<model-selector class="mb-4" />
<start-image-url-input v-if="config?.model === 'minimax-i2v'" class="mb-4" />
<div class="actions">
<el-button
v-if="config?.video_url !== undefined || config?.custom"
type="primary"
class="btn w-full"
round
@click="onGenerate"
>
<font-awesome-icon icon="fa-solid fa-magic" class="mr-2" />
{{ $t('hailuo.button.extend') }}
</el-button>
<el-button v-else type="primary" class="btn w-full" round @click="onGenerate">
<font-awesome-icon icon="fa-solid fa-magic" class="mr-2" />
{{ $t('hailuo.button.generate') }}
</el-button>
</div>
</div>
</div>
</template>

<script>
import { defineComponent } from 'vue';
import { ElButton } from 'element-plus';
import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome';
import ModelSelector from './config/ModelSelector.vue';
import StartImageUrlInput from './config/StartImageUrlInput.vue';
import PromptInput from './config/PromptInput.vue';
export default defineComponent({
name: 'PresetPanel',
components: {
ElButton,
FontAwesomeIcon,
PromptInput,
StartImageUrlInput,
ModelSelector
},
emits: ['generate'],
computed: {
config() {
return this.$store.state.hailuo?.config;
}
},
methods: {
onGenerate() {
this.$emit('generate');
}
}
});
</script>
<style lang="scss" scoped>
.panel {
height: 100%;
padding: 15px;
display: flex;
flex-direction: column;
.config {
width: 100%;
height: calc(100% - 50px);
flex: 1;
}
.actions {
height: 50px;
display: flex;
justify-content: center;
align-items: center;
.btn {
width: 100%;
}
}
}
</style>
23 changes: 23 additions & 0 deletions src/components/hailuo/DetailPanel.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<template>
<div class="panel detail">
<task-detail />
</div>
</template>

<script lang="ts">
import { defineComponent } from 'vue';
import TaskDetail from './task/Detail.vue';
export default defineComponent({
name: 'DetailPanel',
components: {
TaskDetail
},
data() {
return {
job: 0
};
},
computed: {}
});
</script>
77 changes: 77 additions & 0 deletions src/components/hailuo/OperationPanel.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
<template>
<div>
<el-card v-show="operating">
<content-input class="mb-4" />
<prompt-input class="mb-4" />
<preset-selector class="mb-4" />
<div class="actions">
<el-button type="primary" class="btn w-full" round @click="onGenerate">
<font-awesome-icon icon="fa-solid fa-magic" class="mr-2" />
{{ $t('hailuo.button.generate') }}
</el-button>
</div>
</el-card>
<div>
<el-button type="primary" class="btn btn-operate" @click="operating = !operating">+</el-button>
</div>
</div>
</template>

<script lang="ts">
import { defineComponent } from 'vue';
import { ElButton, ElCard } from 'element-plus';
import PresetSelector from './config/PresetSelector2.vue';
import ContentInput from './config/ContentInput.vue';
import PromptInput from './config/PromptInput.vue';
import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome';
export default defineComponent({
name: 'OperationPanel',
components: {
ElButton,
ElCard,
PresetSelector,
ContentInput,
PromptInput,
FontAwesomeIcon
},
emits: ['generate'],
data() {
return {
operating: false
};
},
methods: {
onGenerate() {
this.$emit('generate');
this.operating = false;
}
}
});
</script>

<style lang="scss" scoped>
.el-card {
width: 580px;
height: fit-content;
overflow-y: scroll;
position: absolute;
bottom: 70px;
left: calc(50% - 300px);
@media (max-width: 767px) {
width: 100%;
left: 0;
}
}
.btn-operate {
width: 50px;
height: 50px;
border-radius: 50%;
font-size: 24px;
line-height: 40px;
padding: 0;
margin: auto;
display: block;
}
</style>
131 changes: 131 additions & 0 deletions src/components/hailuo/RecentPanel.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
<template>
<div class="panel recent">
<div v-if="tasks?.items === undefined" class="tasks">
<div v-for="_ in 3" :key="_" class="task placeholder">
<div class="left">
<el-skeleton animated>
<template #template>
<el-skeleton-item variant="image" class="avatar" />
</template>
</el-skeleton>
</div>
<div class="main">
<el-skeleton animated>
<template #template>
<el-skeleton-item variant="p" class="title" />
<el-skeleton-item variant="image" class="icon" />
</template>
</el-skeleton>
</div>
</div>
</div>
<div v-else-if="tasks?.items?.length && tasks?.items?.length > 0" class="tasks">
<task-preview v-for="(task, taskIndex) in tasks?.items" :key="taskIndex" :model-value="task" class="preview" />
</div>
<p v-if="tasks?.items?.length === 0" class="description">
{{ $t('hailuo.message.noTasks') }}
</p>
</div>
</template>

<script lang="ts">
import { defineComponent } from 'vue';
import TaskPreview from './task/Preview.vue';
import { ElSkeleton, ElSkeletonItem } from 'element-plus';
export default defineComponent({
name: 'RecentPanel',
components: {
TaskPreview,
ElSkeleton,
ElSkeletonItem
},
data() {
return {
job: 0
};
},
computed: {
tasks() {
// reverse the order of the tasks.items
return {
...this.$store.state.hailuo?.tasks,
items: this.$store.state.hailuo?.tasks?.items?.slice().reverse()
};
}
}
});
</script>

<style lang="scss" scoped>
.panel {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 100%;
&.recent {
width: 100%;
height: 100%;
display: flex;
overflow-y: auto;
flex-direction: column;
.preview {
margin-right: 15px;
}
.description {
text-align: left;
font-size: 14px;
color: var(--el-text-color-secondary);
}
.tasks {
width: 100%;
.task {
margin-bottom: 15px;
width: 100%;
height: fit-content;
text-align: left;
&.placeholder {
display: flex;
flex-direction: row;
.left {
width: 70px;
padding: 10px;
.avatar {
width: 50px;
height: 50px;
border-radius: 50%;
}
}
.main {
width: calc(100% - 70px);
flex: 1;
padding: 10px;
margin-bottom: 10px;
.icon {
display: flex;
height: 200px;
width: 300px;
}
.title {
display: block;
width: 200px;
height: 20px;
margin-bottom: 15px;
}
}
}
.operations {
height: fit-content !important;
}
}
}
}
}
</style>
40 changes: 40 additions & 0 deletions src/components/hailuo/VideoPlayer.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
<template>
<div>
<vue-plyr :options="options" class="video">
<video controls crossorigin playsinline :data-poster="modelValue?.image_url">
<source size="1080" :src="modelValue?.video_url" type="video/mp4" />
</video>
</vue-plyr>
</div>
</template>

<script lang="ts">
import { defineComponent } from 'vue';
// @ts-ignore
import VuePlyr from '@skjnldsv/vue-plyr';
// @ts-ignore
import { IHailuoVideo } from '@/models';
import '@skjnldsv/vue-plyr/dist/vue-plyr.css';
export default defineComponent({
name: 'VideoPlayer',
components: { VuePlyr },
props: {
modelValue: {
type: Object as () => IHailuoVideo | undefined,
required: true
}
},
data() {
return {
options: { quality: { default: '1080p' } }
};
}
});
</script>

<style lang="scss" scoped>
.video {
max-width: 100%;
height: 450px;
}
</style>
Loading

0 comments on commit d3251fa

Please sign in to comment.