param builder component is ready

This commit is contained in:
GeekMaster
2025-09-10 17:04:37 +08:00
parent 1ca58606da
commit 896b5de0a4
29 changed files with 850 additions and 656 deletions

10
api/test/test_test.go Normal file
View File

@@ -0,0 +1,10 @@
package test
import (
"fmt"
"testing"
)
func Test(t *testing.T) {
fmt.Println("test")
}

View File

@@ -1,8 +1,8 @@
@font-face { @font-face {
font-family: "iconfont"; /* Project id 4125778 */ font-family: "iconfont"; /* Project id 4125778 */
src: url('iconfont.woff2?t=1756954977612') format('woff2'), src: url('iconfont.woff2?t=1757465848673') format('woff2'),
url('iconfont.woff?t=1756954977612') format('woff'), url('iconfont.woff?t=1757465848673') format('woff'),
url('iconfont.ttf?t=1756954977612') format('truetype'); url('iconfont.ttf?t=1757465848673') format('truetype');
} }
.iconfont { .iconfont {
@@ -13,6 +13,22 @@
-moz-osx-font-smoothing: grayscale; -moz-osx-font-smoothing: grayscale;
} }
.icon-action:before {
content: "\e658";
}
.icon-dancing:before {
content: "\e659";
}
.icon-running:before {
content: "\e65e";
}
.icon-shuziren:before {
content: "\e6df";
}
.icon-cube:before { .icon-cube:before {
content: "\e72c"; content: "\e72c";
} }

File diff suppressed because one or more lines are too long

View File

@@ -5,6 +5,34 @@
"css_prefix_text": "icon-", "css_prefix_text": "icon-",
"description": "", "description": "",
"glyphs": [ "glyphs": [
{
"icon_id": "5215282",
"name": "动作",
"font_class": "action",
"unicode": "e658",
"unicode_decimal": 58968
},
{
"icon_id": "7250581",
"name": "跳舞",
"font_class": "dancing",
"unicode": "e659",
"unicode_decimal": 58969
},
{
"icon_id": "8153037",
"name": "动作",
"font_class": "running",
"unicode": "e65e",
"unicode_decimal": 58974
},
{
"icon_id": "42680536",
"name": "数字人",
"font_class": "shuziren",
"unicode": "e6df",
"unicode_decimal": 59103
},
{ {
"icon_id": "544492", "icon_id": "544492",
"name": "cube", "name": "cube",

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 1022 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1016 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1002 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 856 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 877 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 698 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 948 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1021 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 874 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 809 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

View File

@@ -1,84 +0,0 @@
<template>
<div class="right flex-center">
<div class="logo">
<el-image
:src="logo"
alt=""
style="max-width: 300px; max-height: 300px"
class="rounded-full"
/>
</div>
<div>welcome</div>
<footer-bar />
</div>
</template>
<script setup>
import FooterBar from '@/components/FooterBar.vue'
import { getSystemInfo } from '@/store/cache'
import { ref } from 'vue'
const logo = ref('')
const title = ref('')
getSystemInfo()
.then((res) => {
logo.value = res.data.logo
title.value = res.data.title
})
.catch((err) => {
console.log(err)
logo.value = '/images/logo.png'
title.value = 'Geek-AI'
})
</script>
<style lang="scss" scoped>
.right {
font-size: 40px;
font-weight: bold;
color: #fff;
flex-direction: column;
background-image: url('~@/assets/img/login-bg.png');
background-size: cover;
background-position: center;
width: 50%;
min-height: 100vh;
max-height: 100vh;
background-repeat: no-repeat;
position: relative;
overflow: hidden;
z-index: 1;
:deep(.foot-container) {
position: absolute;
bottom: 20px;
width: 100%;
background: none;
color: var(--sm-txt);
font-size: 12px;
text-align: center;
.footer {
a,
span {
color: var(--text-fff);
}
}
}
}
.logo {
margin-bottom: 26px;
width: 200px;
height: 200px;
background: #fff;
border-radius: 50%;
img {
width: 100%;
object-fit: cover;
height: 100%;
}
}
</style>

View File

@@ -1,102 +0,0 @@
<template>
<div>
<ThemeChange />
<div @click="goBack" class="flex back animate__animated animate__pulse animate__infinite">
<el-icon><ArrowLeftBold /></el-icon>{{ title === '注册' ? '首页' : '返回' }}
</div>
<div class="title">{{ title }}</div>
<div class="smTitle" v-if="title !== '重置密码'">
{{ title === '登录' ? '没有账号' : '已有账号'
}}<span @click="goPageFun" class="text-color-primary sign"
>赶紧{{ title === '登录' ? '注册' : '登录' }}</span
>
</div>
<slot></slot>
<div class="flex orline" v-if="title !== '重置密码'">
<div class="lineor"></div>
<span></span>
<div class="lineor"></div>
</div>
</div>
</template>
<script setup>
import ThemeChange from '@/components/ThemeChange.vue'
import { ArrowLeftBold } from '@element-plus/icons-vue'
import { defineProps } from 'vue'
import { useRouter } from 'vue-router'
const props = defineProps({
title: {
type: String,
default: '登录',
},
smTitle: { type: String, default: '没有账号?' },
goPage: {
type: String,
default: '/register',
},
})
const router = useRouter()
const goBack = () => {
if (props.title === '注册') {
router.push('/')
} else {
router.go(-1)
}
}
const goPageFun = () => {
if (props.title === '登录') {
router.push('/register')
} else {
router.push('/login')
}
}
</script>
<style lang="scss" scoped>
.back {
color: var(--sm-txt);
font-size: 14px;
margin-bottom: 140px;
margin-top: 18px;
cursor: pointer;
.el-icon {
margin-right: 6px;
}
}
.title {
font-size: 36px;
margin-bottom: 16px;
color: var(--text-color);
}
.smTitle {
color: var(--text-color);
font-size: 14px;
margin-bottom: 36px;
}
.sign {
text-decoration: underline;
cursor: pointer;
}
.orline {
color: var(--text-secondary);
span {
font-size: 14px;
margin: 0 10px;
}
.lineor {
width: 182px;
height: 1px;
background: var(--text-secondary);
}
}
</style>

View File

@@ -145,7 +145,7 @@ const imageList = computed({
if (props.multiple || props.maxCount > 1) { if (props.multiple || props.maxCount > 1) {
return Array.isArray(props.modelValue) ? props.modelValue : [] return Array.isArray(props.modelValue) ? props.modelValue : []
} else { } else {
return props.modelValue ? [props.modelValue] : [] return props.modelValue && props.modelValue.length > 0 ? [props.modelValue] : []
} }
}, },
set(value) { set(value) {

View File

@@ -0,0 +1,258 @@
<template>
<div class="param-builder flex flex-col">
<ParamEmpty
v-if="items.length === 0"
:progress="progress"
:title="title"
:status-text="statusText"
:description="description"
/>
<div v-else class="flex flex-col w-full space-y-5">
<el-select
v-model="selectedModel"
placeholder="请选择模型"
@change="changeModel"
popper-class="model-select"
value-key="name"
>
<template #prefix>
<i class="iconfont icon-model"></i>
</template>
<el-option v-for="item in items" :key="item.name" :label="item.name" :value="item">
<div class="flex justify-start">
<span
class="flex items-center justify-center text-white !text-xl model-version mr-2 w-[40px] h-[40px] rounded-lg"
>{{ item.version }}</span
>
<div class="flex !items-start flex-col py-2 space-y-1">
<span class="label text-sm">{{ item.name }}</span>
<div class="whitespace-pre-line">
<span
class="text-xs text-gray-500 break-words whitespace-pre-line line-clamp-1"
:title="item.label"
>{{ item.label }}</span
>
</div>
</div>
</div>
</el-option>
</el-select>
<div v-for="param in selectedModel.params" :key="param.name" class="w-full">
<div class="w-full flex flex-col !items-start space-y-2" v-if="param.type === 'switch'">
<div class="w-full flex justify-between">
<label class="label font-bold">{{ param.label }}</label>
<el-switch v-model="modelValue[param.name]" size="large" />
</div>
<p v-if="param.info" class="text-xs text-gray-500 mb-1">{{ param.info }}</p>
</div>
<div class="w-full flex flex-col !items-start space-y-2" v-else>
<label class="label font-bold">
{{ param.label }}
<span v-if="param.required" class="text-red-500 ml-1">*</span>
</label>
<p v-if="param.info" class="text-xs text-gray-500 mb-1">{{ param.info }}</p>
<div class="flex w-full">
<el-input
v-if="param.type === 'text'"
v-model="modelValue[param.name]"
:placeholder="param.placeholder"
/>
<el-input-number
v-if="param.type === 'number'"
v-model="modelValue[param.name]"
class="!w-full"
:placeholder="param.placeholder"
:min="param.min"
:max="param.max"
:step="param.step"
/>
<el-slider
v-if="param.type === 'slider'"
v-model="modelValue[param.name]"
:min="param.min"
:max="param.max"
:step="param.step"
/>
<el-date-picker
v-if="param.type === 'date'"
v-model="modelValue[param.name]"
:placeholder="param.placeholder"
/>
<el-time-picker
v-if="param.type === 'time'"
v-model="modelValue[param.name]"
:placeholder="param.placeholder"
/>
<el-select
v-if="param.type === 'select'"
v-model="modelValue[param.name]"
:placeholder="param.placeholder"
:popper-class="param.popperClass"
>
<el-option
v-for="option in param.options"
:key="option.value"
:label="option.label"
:value="option.value"
>
<div class="flex justify-start" v-if="option.image">
<span class="flex py-3 mr-2">
<img :src="option.image" class="w-[54px] h-[54px] rounded-lg"
/></span>
<div class="flex !items-start flex-col py-2 space-y-1">
<span class="label text-sm">{{ option.label }}</span>
<span class="text-xs text-gray-500">{{ option.value }}</span>
</div>
</div>
<div class="flex justify-start items-center h-full" v-else>
<span class="label text-sm">{{ option.label }}</span>
</div>
</el-option>
</el-select>
<el-input
type="textarea"
v-if="param.type === 'textarea'"
v-model="modelValue[param.name]"
:autosize="param.autosize"
:maxlength="param.maxlength"
:show-word-limit="param.showWordLimit"
:placeholder="param.placeholder"
/>
<ImageUpload
v-if="param.type === 'image'"
v-model="modelValue[param.name]"
:max-count="param.maxCount"
:multiple="param.multiple"
:max-size="param.maxSize"
:accept="param.accept"
/>
</div>
</div>
</div>
</div>
</div>
</template>
<script setup>
import ImageUpload from './ImageUpload.vue'
import ParamEmpty from './ui/ParamEmpty.vue'
const title = ref('参数构建器')
const statusText = ref('功能正在开发中')
const description = ref('我们正在努力完善当前功能,敬请期待!')
const props = defineProps({
modelValue: {
type: Object,
required: true,
},
items: {
type: Array,
required: true,
},
progress: {
type: Number,
default: 65,
validator: (value) => value >= 0 && value <= 100,
},
})
const selectedModel = ref({ label: '请选择模型' })
const emit = defineEmits(['update:modelValue'])
// 初始化 modelValue 默认值
const initModelValue = (model) => {
const defaultValues = {}
if (model && model.params) {
model.params.forEach((param) => {
// 根据参数类型设置默认值
switch (param.type) {
case 'text':
case 'textarea':
defaultValues[param.name] = param.value || ''
break
case 'number':
defaultValues[param.name] = param.value || 0
break
case 'slider':
defaultValues[param.name] = param.value || param.min || 0
break
case 'select':
// 如果有选项,选择第一个选项作为默认值
defaultValues[param.name] =
param.value || (param.options && param.options[0] ? param.options[0].value : '')
break
case 'checkbox':
case 'switch':
defaultValues[param.name] = param.value || false
break
case 'date':
case 'time':
defaultValues[param.name] = param.value || null
break
case 'image':
defaultValues[param.name] = param.value || []
break
default:
defaultValues[param.name] = param.value || ''
}
})
}
return defaultValues
}
// 初始化默认值
const modelValue = ref(initModelValue(selectedModel.value))
// 监听 modelValue 变化,通知父组件
watch(
modelValue,
(newValue) => {
emit('update:modelValue', newValue)
},
{ deep: true }
)
// 组件挂载时初始化
onMounted(() => {
// 确保初始值被正确设置
if (props.modelValue && Object.keys(props.modelValue).length > 0) {
modelValue.value = { ...props.modelValue }
} else {
modelValue.value = initModelValue(selectedModel.value)
}
})
const changeModel = (item) => {
if (item) {
selectedModel.value = item
// 更新 modelValue 为选中模型的默认值
modelValue.value = initModelValue(item)
}
}
</script>
<style lang="scss">
.param-builder {
.model-version {
background: url('@/assets/img/model-version.png') no-repeat center center;
background-size: cover;
}
.el-select__wrapper {
min-height: 34px;
line-height: 25px;
}
}
.model-select {
.el-select-dropdown__item {
height: auto !important;
}
.model-version {
background: url('@/assets/img/model-version.png') no-repeat center center;
background-size: cover;
}
}
</style>

View File

@@ -279,7 +279,7 @@ const items = [
{ {
icon: 'xmind', icon: 'xmind',
index: '/admin/config/markmap', index: '/admin/config/markmap',
title: '思维导图配置', title: '思维导图',
}, },
], ],
}, },

View File

@@ -0,0 +1,148 @@
<template>
<div class="flex flex-col justify-center items-center py-8 px-4">
<!-- 开发中图标和动画 -->
<div class="relative mb-4">
<div
class="w-16 h-16 bg-gradient-to-br from-blue-500 to-purple-600 rounded-full flex items-center justify-center shadow-lg animate-float animate-glow"
>
<svg
class="w-8 h-8 text-white animate-pulse"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z"
></path>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"
></path>
</svg>
</div>
<!-- 旋转的装饰环 -->
<div
class="absolute inset-0 w-16 h-16 border-2 border-blue-200 rounded-full animate-spin"
style="animation-duration: 3s"
></div>
<div
class="absolute inset-1 w-14 h-14 border border-purple-200 rounded-full animate-spin"
style="animation-duration: 2s; animation-direction: reverse"
></div>
</div>
<!-- 主标题 -->
<h3 class="text-lg font-semibold text-gray-700 mb-2">{{ title }}</h3>
<!-- 开发中提示 -->
<div class="text-center space-y-2">
<p class="text-gray-500 text-sm">🚀 {{ statusText }}</p>
<p class="text-xs text-gray-400 max-w-xs leading-relaxed">
{{ description }}
</p>
</div>
<!-- 进度条 -->
<div class="w-48 mt-4">
<div class="bg-gray-200 rounded-full h-2 overflow-hidden">
<div
class="bg-gradient-to-r from-blue-500 to-purple-600 h-full rounded-full progress-bar"
:style="{ width: progress + '%' }"
></div>
</div>
<p class="text-xs text-gray-400 text-center mt-1">开发进度 {{ progress }}%</p>
</div>
<!-- 装饰性元素 -->
<div class="flex space-x-1 mt-4">
<div
class="w-2 h-2 bg-blue-400 rounded-full animate-bounce"
style="animation-delay: 0s"
></div>
<div
class="w-2 h-2 bg-purple-400 rounded-full animate-bounce"
style="animation-delay: 0.1s"
></div>
<div
class="w-2 h-2 bg-pink-400 rounded-full animate-bounce"
style="animation-delay: 0.2s"
></div>
</div>
</div>
</template>
<script setup>
const props = defineProps({
// 进度百分比 (0-100)
progress: {
type: Number,
default: 65,
validator: (value) => value >= 0 && value <= 100,
},
// 主标题
title: {
type: String,
default: '参数构建器',
},
// 状态文本
statusText: {
type: String,
default: '功能正在开发中',
},
// 描述文本
description: {
type: String,
default: '我们正在努力完善当前功能,敬请期待!',
},
})
</script>
<style lang="scss">
/* 自定义动画效果 */
@keyframes float {
0%,
100% {
transform: translateY(0px);
}
50% {
transform: translateY(-10px);
}
}
@keyframes glow {
0%,
100% {
box-shadow: 0 0 5px rgba(59, 130, 246, 0.5);
}
50% {
box-shadow: 0 0 20px rgba(59, 130, 246, 0.8), 0 0 30px rgba(147, 51, 234, 0.6);
}
}
.animate-float {
animation: float 3s ease-in-out infinite;
}
.animate-glow {
animation: glow 2s ease-in-out infinite;
}
/* 进度条动画 */
@keyframes progress {
0% {
width: 0%;
}
100% {
width: v-bind(progress + '%');
}
}
.progress-bar {
animation: progress 2s ease-out forwards;
}
</style>

View File

@@ -1,4 +1,16 @@
export const paramsMap = { import central_orbit from '@/assets/img/jimeng/central_orbit.webp'
import clockwise_swivel from '@/assets/img/jimeng/clockwise_swivel.webp'
import counterclockwise_swivel from '@/assets/img/jimeng/counterclockwise_swivel.webp'
import crane_push from '@/assets/img/jimeng/crane_push.webp'
import dynamic_orbit from '@/assets/img/jimeng/dynamic_orbit.webp'
import handheld from '@/assets/img/jimeng/handheld.webp'
import hitchcock_dolly_in from '@/assets/img/jimeng/hitchcock_dolly_in.webp'
import hitchcock_dolly_out from '@/assets/img/jimeng/hitchcock_dolly_out.webp'
import quick_pull_back from '@/assets/img/jimeng/quick_pull_back.webp'
import rapid_push_pull from '@/assets/img/jimeng/rapid_push_pull.webp'
import robo_arm from '@/assets/img/jimeng/robo_arm.webp'
export const JimengParams = {
image: [ image: [
{ {
name: '图片 2.1 文生图', name: '图片 2.1 文生图',
@@ -9,11 +21,15 @@ export const paramsMap = {
{ {
name: 'prompt', name: 'prompt',
label: '提示词', label: '提示词',
type: 'text', type: 'textarea',
showWordLimit: true,
maxlength: 800,
autosize: { minRows: 3, maxRows: 5 },
required: true, required: true,
placeholder: '请输入提示词', placeholder: '请输入提示词',
info: '用于生成图像的提示词 ,中英文均可输入', info: '用于生成图像的提示词 ,中英文均可输入',
}, },
{ {
name: 'size', name: 'size',
type: 'select', type: 'select',
@@ -57,8 +73,8 @@ export const paramsMap = {
}, },
{ {
name: 'use_pre_llm', name: 'use_pre_llm',
type: 'boolean', type: 'switch',
required: true, required: false,
label: '开启文本扩写', label: '开启文本扩写',
info: '开启后,系统会自动扩写提示词,提高生成质量', info: '开启后,系统会自动扩写提示词,提高生成质量',
value: true, value: true,
@@ -74,7 +90,10 @@ export const paramsMap = {
{ {
name: 'prompt', name: 'prompt',
label: '提示词', label: '提示词',
type: 'text', type: 'textarea',
showWordLimit: true,
maxlength: 800,
autosize: { minRows: 3, maxRows: 5 },
required: true, required: true,
placeholder: '请输入提示词', placeholder: '请输入提示词',
info: '用于生成图像的提示词 ,中英文均可输入', info: '用于生成图像的提示词 ,中英文均可输入',
@@ -131,10 +150,11 @@ export const paramsMap = {
}, },
{ {
name: 'use_pre_llm', name: 'use_pre_llm',
type: 'boolean', type: 'switch',
required: true, required: true,
label: '开启文本扩写', label: '开启文本扩写',
info: '开启后,系统会自动扩写提示词,提高生成质量', info: '开启后,系统会自动扩写提示词,提高生成质量',
value: true,
}, },
], ],
}, },
@@ -147,7 +167,10 @@ export const paramsMap = {
{ {
name: 'prompt', name: 'prompt',
label: '提示词', label: '提示词',
type: 'text', type: 'textarea',
showWordLimit: true,
maxlength: 800,
autosize: { minRows: 3, maxRows: 5 },
required: true, required: true,
placeholder: '请输入提示词', placeholder: '请输入提示词',
info: '用于生成图像的提示词 ,中英文均可输入', info: '用于生成图像的提示词 ,中英文均可输入',
@@ -204,10 +227,11 @@ export const paramsMap = {
}, },
{ {
name: 'use_pre_llm', name: 'use_pre_llm',
type: 'boolean', type: 'switch',
required: true, required: true,
label: '开启文本扩写', label: '开启文本扩写',
info: '开启后,系统会自动扩写提示词,提高生成质量', info: '开启后,系统会自动扩写提示词,提高生成质量',
value: true,
}, },
], ],
}, },
@@ -221,8 +245,81 @@ export const paramsMap = {
{ {
name: 'prompt', name: 'prompt',
label: '提示词', label: '提示词',
type: 'text', type: 'textarea',
required: true, required: true,
showWordLimit: true,
maxlength: 800,
autosize: { minRows: 3, maxRows: 5 },
placeholder: '请输入用于编辑图像的提示词把xxx改成xxx删除xxx添加xxx等',
info: '建议长度<=120字符最长不超过800字符',
},
{
name: 'image_urls',
label: '参考图片',
type: 'image',
required: true,
placeholder: '请上传图片',
maxSize: 5,
accept: '.png,.jpg,.jpeg',
info: '长边与短边比例在3以内超出此比例或比例相对极端会导致报错。',
},
{
name: 'scale',
label: '文本描述影响的程度',
type: 'slider',
min: 0,
max: 1,
step: 0.1,
value: 0.5,
info: '该值越大代表文本描述影响程度越大,且输入图片影响程度越小',
},
{
name: 'size',
type: 'select',
required: true,
placeholder: '请选择尺寸',
label: '图片尺寸',
options: [
{
label: '11 (1328 * 1328)',
value: '1328x1328',
},
{
label: '43 (1472 * 1104)',
value: '1472x1104',
},
{
label: '32 (1584 * 1056)',
value: '1584x1056',
},
{
label: '169 (1664 * 936)',
value: '1664x936',
},
{
label: '219 (2016 * 864)',
value: '2016x864',
},
],
},
],
},
{
name: '图片 4.0 文/图生图',
version: '4.0',
label:
'支持文本、单图和多图输入,实现基于主体一致性的多图融合创作、图像编辑、组图生成等多样玩法',
key: 'jimeng_i2i_v30',
params: [
{
name: 'prompt',
label: '提示词',
type: 'textarea',
required: true,
showWordLimit: true,
maxlength: 800,
autosize: { minRows: 3, maxRows: 5 },
placeholder: '请输入用于编辑图像的提示词把xxx改成xxx删除xxx添加xxx等', placeholder: '请输入用于编辑图像的提示词把xxx改成xxx删除xxx添加xxx等',
info: '建议长度<=120字符最长不超过800字符', info: '建议长度<=120字符最长不超过800字符',
}, },
@@ -289,7 +386,10 @@ export const paramsMap = {
{ {
name: 'prompt', name: 'prompt',
label: '提示词', label: '提示词',
type: 'text', type: 'textarea',
showWordLimit: true,
maxlength: 800,
autosize: { minRows: 3, maxRows: 5 },
required: true, required: true,
placeholder: '请输入提示词', placeholder: '请输入提示词',
info: '用于生成视频的提示词 ,中英文均可输入', info: '用于生成视频的提示词 ,中英文均可输入',
@@ -354,7 +454,10 @@ export const paramsMap = {
{ {
name: 'prompt', name: 'prompt',
label: '提示词', label: '提示词',
type: 'text', type: 'textarea',
showWordLimit: true,
maxlength: 800,
autosize: { minRows: 3, maxRows: 5 },
required: true, required: true,
placeholder: '请输入提示词', placeholder: '请输入提示词',
info: '用于生成视频的提示词 ,中英文均可输入', info: '用于生成视频的提示词 ,中英文均可输入',
@@ -364,7 +467,6 @@ export const paramsMap = {
label: '首帧图片', label: '首帧图片',
type: 'image', type: 'image',
required: false, required: false,
placeholder: '请上传图片',
multiple: false, multiple: false,
maxCount: 1, maxCount: 1,
maxSize: 5, maxSize: 5,
@@ -397,21 +499,24 @@ export const paramsMap = {
{ {
name: 'prompt', name: 'prompt',
label: '提示词', label: '提示词',
type: 'text', type: 'textarea',
showWordLimit: true,
maxlength: 800,
autosize: { minRows: 3, maxRows: 5 },
required: true, required: true,
placeholder: '请输入提示词', placeholder: '请输入提示词',
info: '用于生成视频的提示词 ,中英文均可输入', info: '用于生成视频的提示词 ,中英文均可输入',
}, },
{ {
name: 'image_urls', name: 'image_urls',
label: '首帧图片', label: '首帧图片',
type: 'image', type: 'image',
required: false, required: false,
placeholder: '请上传图片',
multiple: true, multiple: true,
maxCount: 2, maxCount: 2,
maxSize: 5, maxSize: 5,
accept: '.png,.jpg,.jpeg', accept: '.png,.jpg,.jpeg',
info: '请上传两张图片,第一张为起始帧,第二张为尾帧',
}, },
{ {
name: 'duration', name: 'duration',
@@ -440,7 +545,10 @@ export const paramsMap = {
{ {
name: 'prompt', name: 'prompt',
label: '提示词', label: '提示词',
type: 'text', type: 'textarea',
showWordLimit: true,
maxlength: 800,
autosize: { minRows: 3, maxRows: 5 },
required: true, required: true,
placeholder: '请输入提示词', placeholder: '请输入提示词',
info: '用于生成视频的提示词 ,中英文均可输入', info: '用于生成视频的提示词 ,中英文均可输入',
@@ -452,7 +560,7 @@ export const paramsMap = {
required: false, required: false,
placeholder: '请上传图片', placeholder: '请上传图片',
maxSize: 5, maxSize: 5,
multiple: false, multiple: true,
maxCount: 1, maxCount: 1,
accept: '.png,.jpg,.jpeg', accept: '.png,.jpg,.jpeg',
}, },
@@ -460,60 +568,73 @@ export const paramsMap = {
name: 'template_id', name: 'template_id',
label: '运镜控制', label: '运镜控制',
type: 'select', type: 'select',
required: false, required: true,
placeholder: '请选择运镜控制', placeholder: '请选择运镜控制',
popperClass: 'model-select',
options: [ options: [
{ {
label: '希区柯克推进', label: '希区柯克推进',
value: 'hitchcock_dolly_in', value: 'hitchcock_dolly_in',
image: hitchcock_dolly_in,
}, },
{ {
label: '希区柯克拉远', label: '希区柯克拉远',
value: 'hitchcock_dolly_out', value: 'hitchcock_dolly_out',
image: hitchcock_dolly_out,
}, },
{ {
label: '机械臂', label: '机械臂',
value: 'robo_arm', value: 'robo_arm',
image: robo_arm,
}, },
{ {
label: '动感环绕', label: '动感环绕',
value: 'dynamic_orbit', value: 'dynamic_orbit',
image: dynamic_orbit,
}, },
{ {
label: '中心环绕', label: '中心环绕',
value: 'central_orbit', value: 'central_orbit',
image: central_orbit,
}, },
{ {
label: '起重机', label: '起重机',
value: 'crane_push', value: 'crane_push',
image: crane_push,
}, },
{ {
label: '超级拉远', label: '超级拉远',
value: 'quick_pull_back', value: 'quick_pull_back',
image: quick_pull_back,
}, },
{ {
label: '逆时针回旋', label: '逆时针回旋',
value: 'counterclockwise_swivel', value: 'counterclockwise_swivel',
image: counterclockwise_swivel,
}, },
{ {
label: '顺时针回旋', label: '顺时针回旋',
value: 'clockwise_swivel', value: 'clockwise_swivel',
image: clockwise_swivel,
}, },
{ {
label: '手持运镜', label: '手持运镜',
value: 'handheld', value: 'handheld',
image: handheld,
}, },
{ {
label: '快速推拉', label: '快速推拉',
value: 'rapid_push_pull', value: 'rapid_push_pull',
image: rapid_push_pull,
}, },
], ],
value: 'hitchcock_dolly_in',
}, },
{ {
name: 'camera_strength', name: 'camera_strength',
label: '运镜强度', label: '运镜强度',
type: 'select', type: 'select',
required: false, required: true,
placeholder: '请选择运镜强度', placeholder: '请选择运镜强度',
options: [ options: [
{ {
@@ -529,6 +650,76 @@ export const paramsMap = {
value: 'strong', value: 'strong',
}, },
], ],
value: 'medium',
},
{
name: 'duration',
type: 'select',
label: '视频时长',
options: [
{
label: '5秒',
value: '5',
},
{
label: '10秒',
value: '10',
},
],
value: '5',
},
],
},
{
name: '视频 3.0 1080P-文生视频',
version: '3.0',
label: '视觉表达流畅一致支持1080P高清渲染',
key: 'jimeng_t2v_v30_1080p',
params: [
{
name: 'prompt',
label: '提示词',
type: 'textarea',
showWordLimit: true,
maxlength: 800,
autosize: { minRows: 3, maxRows: 5 },
required: true,
placeholder: '请输入提示词',
info: '用于生成视频的提示词 ,中英文均可输入',
},
{
name: 'aspect_ratio',
label: '视频比例',
type: 'select',
required: false,
placeholder: '请选择视频比例',
options: [
{
label: '16:9 (横版)',
value: '16:9',
},
{
label: '4:3 (标准)',
value: '4:3',
},
{
label: '1:1 (正方形)',
value: '1:1',
},
{
label: '3:4 (竖版)',
value: '3:4',
},
{
label: '9:16 (竖屏)',
value: '9:16',
},
{
label: '21:9 (超宽)',
value: '21:9',
},
],
}, },
{ {
name: 'duration', name: 'duration',
@@ -549,15 +740,109 @@ export const paramsMap = {
}, },
{ {
name: '视频 3.0Pro 图生视频', name: '视频 3.0 1080P-图生视频-首帧',
version: '3.0', version: '3.0',
label: '根据提示词 + 首帧图片生成视频', label: '根据提示词 + 首帧图片生成1080P视频',
key: 'jimeng_ti2v_v30_pro', key: 'jimeng_i2v_first_v30_1080',
params: [ params: [
{ {
name: 'prompt', name: 'prompt',
label: '提示词', label: '提示词',
type: 'text', type: 'textarea',
showWordLimit: true,
maxlength: 800,
autosize: { minRows: 3, maxRows: 5 },
required: true,
placeholder: '请输入提示词',
info: '用于生成视频的提示词 ,中英文均可输入',
},
{
name: 'image_urls',
label: '首帧图片',
type: 'image',
required: false,
multiple: false,
maxCount: 1,
maxSize: 5,
accept: '.png,.jpg,.jpeg',
},
{
name: 'duration',
type: 'select',
label: '视频时长',
options: [
{
label: '5秒',
value: '5',
},
{
label: '10秒',
value: '10',
},
],
},
],
},
{
name: '视频 3.0 1080P-图生视频-首尾帧',
version: '3.0',
label: '根据提示词 + 首尾帧图片生成1080P视频',
key: 'jimeng_i2v_first_tail_v30_1080',
params: [
{
name: 'prompt',
label: '提示词',
type: 'textarea',
showWordLimit: true,
maxlength: 800,
autosize: { minRows: 3, maxRows: 5 },
required: true,
placeholder: '请输入提示词',
info: '用于生成视频的提示词 ,中英文均可输入',
},
{
name: 'image_urls',
label: '首尾帧图片',
type: 'image',
required: false,
multiple: true,
maxCount: 2,
maxSize: 5,
accept: '.png,.jpg,.jpeg',
info: '请上传两张图片,第一张为起始帧,第二张为尾帧',
},
{
name: 'duration',
type: 'select',
label: '视频时长',
options: [
{
label: '5秒',
value: '5',
},
{
label: '10秒',
value: '10',
},
],
},
],
},
{
name: '视频 3.0Pro 1080P-图生视频',
version: '3.0',
label: '根据提示词 + 首帧图片生成1080P视频',
key: 'jimeng_ti2v_v30_pro',
params: [
{
name: 'prompt',
label: '提示词',
type: 'textarea',
showWordLimit: true,
maxlength: 800,
autosize: { minRows: 3, maxRows: 5 },
required: true, required: true,
placeholder: '请输入提示词', placeholder: '请输入提示词',
info: '用于生成视频的提示词 ,中英文均可输入', info: '用于生成视频的提示词 ,中英文均可输入',
@@ -567,7 +852,6 @@ export const paramsMap = {
label: '首帧图片', label: '首帧图片',
type: 'image', type: 'image',
required: false, required: false,
placeholder: '请上传图片',
info: '只支持上传首帧图片', info: '只支持上传首帧图片',
multiple: false, multiple: false,
maxCount: 1, maxCount: 1,
@@ -623,6 +907,7 @@ export const paramsMap = {
value: '10', value: '10',
}, },
], ],
value: '5',
}, },
], ],
}, },
@@ -630,3 +915,26 @@ export const paramsMap = {
virtualHuman: [], virtualHuman: [],
actionTransfer: [], actionTransfer: [],
} }
export const JimengFunctions = [
{
key: 'image',
icon: 'iconfont icon-image',
name: '图片生成',
},
{
key: 'video',
icon: 'icon-video',
name: '视频生成',
},
{
key: 'virtualHuman',
icon: 'icon-shuziren',
name: '数字人',
},
{
key: 'actionTransfer',
icon: 'icon-action',
name: '动作模仿',
},
]

View File

@@ -6,6 +6,7 @@
// * +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ // * +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
import { checkSession } from '@/store/cache' import { checkSession } from '@/store/cache'
import { JimengParams } from '@/store/data'
import { useSharedStore } from '@/store/sharedata' import { useSharedStore } from '@/store/sharedata'
import { showMessageError, showMessageOK } from '@/utils/dialog' import { showMessageError, showMessageOK } from '@/utils/dialog'
import { httpDownload, httpGet, httpPost } from '@/utils/http' import { httpDownload, httpGet, httpPost } from '@/utils/http'
@@ -44,224 +45,7 @@ export const useJimengStore = defineStore('jimeng', () => {
// 登录弹窗 // 登录弹窗
const shareStore = useSharedStore() const shareStore = useSharedStore()
const paramsMap = { const paramsMap = JimengParams
image: [
{
name: '图片 2.1',
version: '2.1',
label: '平面绘感强,可生成文字海报',
key: 'jimeng_high_aes_general_v21_L',
params: [
{
name: 'prompt',
label: '提示词',
type: 'text',
required: true,
placeholder: '请输入提示词',
info: '用于生成图像的提示词 ,中英文均可输入',
},
{
name: 'size',
type: 'select',
required: true,
placeholder: '请选择尺寸',
label: '图片尺寸',
options: [
{
label: '21:9 (1195x512)',
value: '1195x512',
},
{
label: '16:9 (1024x576)',
value: '1024x576',
},
{
label: '3:2 (1024x682)',
value: '1024x682',
},
{
label: '4:3 (1024x768)',
value: '1024x768',
},
{
label: '1:1 (1024x1024)',
value: '1024x1024',
},
{
label: '3:4 (768x1024)',
value: '768x1024',
},
{
label: '2:3 (682x1024)',
value: '682x1024',
},
{
label: '9:16 (576x1024)',
value: '576x1024',
},
],
},
{
name: 'use_pre_llm',
type: 'boolean',
required: true,
label: '开启文本扩写',
info: '开启后,系统会自动扩写提示词,提高生成质量',
value: true,
},
],
},
{
name: '图片 3.0 文生图',
version: '3.0',
label: '影视质感文字更准直出2k高清图',
key: 'jimeng_t2i_v30',
params: [
{
name: 'prompt',
label: '提示词',
type: 'text',
required: true,
placeholder: '请输入提示词',
info: '用于生成图像的提示词 ,中英文均可输入',
},
{
name: 'size',
type: 'select',
required: true,
placeholder: '请选择尺寸',
label: '图片尺寸',
options: [
{
label: '1:1 (1328x1328)',
value: '1328x1328',
},
{
label: '4:3 (1472x1104)',
value: '1472x1104',
},
{
label: '3:2 (1584x1056)',
value: '1584x1056',
},
{
label: '16:9 (1664x936)',
value: '1664x936',
},
{
label: '21:9 (2016x864)',
value: '2016x864',
},
{
label: '1:1 高清2K (2048x2048)',
value: '2048x2048',
},
{
label: '4:3 高清2K (2304x1728)',
value: '2304x1728',
},
{
label: '3:2 高清2K (2496x1664)',
value: '2496x1664',
},
{
label: '16:9 高清2K (2560x1440)',
value: '2560x1440',
},
{
label: '21:9 高清2K (3024x1296)',
value: '3024x1296',
},
],
},
{
name: 'use_pre_llm',
type: 'boolean',
required: true,
label: '开启文本扩写',
info: '开启后,系统会自动扩写提示词,提高生成质量',
},
],
},
{
name: '图片 3.1',
version: '3.1',
label: '文生图3.0',
key: 'jimeng_t2i_v31',
params: [
{
name: 'prompt',
label: '提示词',
type: 'text',
required: true,
placeholder: '请输入提示词',
info: '用于生成图像的提示词 ,中英文均可输入',
},
{
name: 'size',
type: 'select',
required: true,
placeholder: '请选择尺寸',
label: '图片尺寸',
options: [
{
label: '1:1 (1328x1328)',
value: '1328x1328',
},
{
label: '4:3 (1472x1104)',
value: '1472x1104',
},
{
label: '3:2 (1584x1056)',
value: '1584x1056',
},
{
label: '16:9 (1664x936)',
value: '1664x936',
},
{
label: '21:9 (2016x864)',
value: '2016x864',
},
{
label: '1:1 高清2K (2048x2048)',
value: '2048x2048',
},
{
label: '4:3 高清2K (2304x1728)',
value: '2304x1728',
},
{
label: '3:2 高清2K (2496x1664)',
value: '2496x1664',
},
{
label: '16:9 高清2K (2560x1440)',
value: '2560x1440',
},
{
label: '21:9 高清2K (3024x1296)',
value: '3024x1296',
},
],
},
{
name: 'use_pre_llm',
type: 'boolean',
required: true,
label: '开启文本扩写',
info: '开启后,系统会自动扩写提示词,提高生成质量',
},
],
},
],
video: [],
virtualHuman: [],
actionTransfer: [],
}
// 功能分类配置 // 功能分类配置
const categories = [ const categories = [

View File

@@ -1,6 +1,6 @@
<template> <template>
<div class="privacy-config container" v-loading="loading"> <div class="privacy-config container w-full" v-loading="loading">
<div class="container"> <div class="w-full">
<md-editor <md-editor
class="mgb20" class="mgb20"
v-model="privacy" v-model="privacy"

View File

@@ -1,237 +1,65 @@
<template> <template>
<div class="audio-chat-page"> <div class="audio-chat-page">
<!-- 图像特效 --> <div class="flex m-3">
<div class="jimeng-create__function-panel"> <el-select v-model="currentFunction" placeholder="请选择功能" popper-class="custom-select">
<div class="jimeng-create__param-line"> <template #prefix>
<span class="jimeng-create__param-label">上传图片</span>
</div>
<div class="jimeng-create__param-line">
<div class="jimeng-create__upload">
<input
ref="imageEffectsInput"
type="file"
accept=".jpg,.png,.jpeg"
@change="
(e) =>
jimengStore.onImageUpload({
file: e.target.files[0],
name: e.target.files[0]?.name,
})
"
class="hidden"
/>
<div @click="$refs.imageEffectsInput?.click()" class="jimeng-create__upload-content">
<i <i
v-if="!jimengStore.imageEffectsParams.image_input1.length" class="iconfont !text-xl"
class="jimeng-create__upload-icon iconfont icon-upload" :class="functions.find((f) => f.key === currentFunction).icon"
></i> ></i>
<span </template>
v-if="!jimengStore.imageEffectsParams.image_input1.length" <el-option v-for="f in functions" :value="f.key" :key="f.key" :label="f.name">
class="jimeng-create__upload-text" <div class="flex items-center space-x-2">
>上传图片</span <i class="iconfont !text-xl" :class="f.icon"></i>
> <span>{{ f.name }}</span>
<div v-else class="jimeng-create__upload-preview">
<el-image
:src="
jimengStore.imageEffectsParams.image_input1[0]?.url ||
jimengStore.imageEffectsParams.image_input1[0]?.content
"
fit="cover"
class="w-32 h-32 rounded"
/>
<button
@click.stop="jimengStore.imageEffectsParams.image_input1 = []"
class="jimeng-create__upload-remove-btn"
>
<i class="iconfont icon-close"></i>
</button>
</div>
</div>
</div> </div>
</el-option>
</el-select>
</div> </div>
<div class="jimeng-create__param-line"> <div class="p-3">
<span class="jimeng-create__param-label">特效模板</span> <param-builder
</div> v-model="formData"
<div class="jimeng-create__param-line"> :items="params[currentFunction]"
<CustomSelect :progress="progress[currentFunction]"
v-model="jimengStore.imageEffectsParams.template_id"
:options="
jimengStore.imageEffectsTemplateOptions.map((opt) => ({
label: opt.label,
value: opt.value,
}))
"
label="特效模板"
title="选择特效模板"
@update:modelValue="handleTemplateChange"
/> />
</div>
<div class="jimeng-create__param-line"> <!-- 调试信息 -->
<span class="jimeng-create__param-label">输出尺寸</span> <div class="mt-6 p-4 bg-gray-100 rounded-lg">
<h3 class="text-lg font-bold mb-2">调试信息</h3>
<div class="text-sm">
<p><strong>当前功能:</strong> {{ currentFunction }}</p>
<p><strong>表单数据:</strong></p>
<pre class="bg-white p-2 rounded mt-1 overflow-auto">{{
JSON.stringify(formData, null, 2)
}}</pre>
</div> </div>
<div class="jimeng-create__param-line">
<CustomSelect
v-model="jimengStore.imageEffectsParams.size"
:options="
jimengStore.imageSizeOptions.map((opt) => ({
label: opt.label,
value: opt.value,
}))
"
label="输出尺寸"
title="选择尺寸"
/>
</div>
</div>
<!-- 文生视频 -->
<div
v-if="jimengStore.activeFunction === 'text_to_video'"
class="jimeng-create__function-panel"
>
<div class="jimeng-create__param-line">
<span class="jimeng-create__param-label">提示词</span>
</div>
<div class="jimeng-create__param-line">
<textarea
v-model="jimengStore.currentPrompt"
placeholder="描述你想要的视频内容"
class="jimeng-create__form-section-textarea"
rows="4"
maxlength="2000"
/>
<div class="jimeng-create__form-section-counter">
<span>{{ jimengStore.currentPrompt.length }}/2000</span>
</div>
</div>
<div class="jimeng-create__param-line">
<span class="jimeng-create__param-label">视频比例</span>
</div>
<div class="jimeng-create__param-line">
<CustomSelect
v-model="jimengStore.textToVideoParams.aspect_ratio"
:options="
jimengStore.videoAspectRatioOptions.map((opt) => ({
label: opt.label,
value: opt.value,
}))
"
label="视频比例"
title="选择比例"
/>
</div>
</div>
<!-- 图生视频 -->
<div
v-if="jimengStore.activeFunction === 'image_to_video'"
class="jimeng-create__function-panel"
>
<div class="jimeng-create__param-line">
<span class="jimeng-create__param-label">上传图片</span>
</div>
<div class="jimeng-create__param-line">
<div class="jimeng-create__upload">
<input
ref="imageToVideoInput"
type="file"
accept=".jpg,.png,.jpeg"
multiple
@change="(e) => jimengStore.handleMultipleImageUpload(e)"
class="hidden"
/>
<div @click="$refs.imageToVideoInput?.click()" class="jimeng-create__upload-content">
<i
v-if="!jimengStore.imageToVideoParams.image_urls.length"
class="jimeng-create__upload-icon iconfont icon-upload"
></i>
<span
v-if="!jimengStore.imageToVideoParams.image_urls.length"
class="jimeng-create__upload-text"
>上传图片</span
>
<div v-else class="jimeng-create__upload-multiple">
<div
v-for="(image, index) in jimengStore.imageToVideoParams.image_urls"
:key="index"
class="jimeng-create__upload-multiple-item"
>
<el-image
:src="image?.url || image?.content"
fit="cover"
class="w-24 h-24 rounded"
/>
<button
@click.stop="jimengStore.removeImage(index)"
class="jimeng-create__upload-remove-btn"
>
<i class="iconfont icon-close"></i>
</button>
</div>
<div
v-if="jimengStore.imageToVideoParams.image_urls.length < 2"
@click.stop="$refs.imageToVideoInput?.click()"
class="jimeng-create__upload-multiple-add"
>
<i class="iconfont icon-plus"></i>
</div>
</div>
</div>
</div>
</div>
<div class="jimeng-create__param-line">
<span class="jimeng-create__param-label">提示词</span>
</div>
<div class="jimeng-create__param-line">
<textarea
v-model="jimengStore.currentPrompt"
placeholder="描述你想要的视频效果"
class="jimeng-create__form-section-textarea"
rows="4"
maxlength="2000"
/>
<div class="jimeng-create__form-section-counter">
<span>{{ jimengStore.currentPrompt.length }}/2000</span>
</div>
</div>
<div class="jimeng-create__param-line">
<span class="jimeng-create__param-label">视频比例</span>
</div>
<div class="jimeng-create__param-line">
<CustomSelect
v-model="jimengStore.imageToVideoParams.aspect_ratio"
:options="
jimengStore.videoAspectRatioOptions.map((opt) => ({
label: opt.label,
value: opt.value,
}))
"
label="视频比例"
title="选择比例"
/>
</div> </div>
</div> </div>
</div> </div>
</template> </template>
<script setup> <script setup>
const connect = () => {} import ParamBuilder from '@/components/ParamBuilder.vue'
import { JimengFunctions, JimengParams } from '@/store/data'
import { ref } from 'vue'
const functions = JimengFunctions
const params = JimengParams
const formData = ref({})
const currentFunction = ref('image')
const progress = ref({
image: 100,
video: 100,
virtualHuman: 38,
actionTransfer: 65,
})
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
.audio-chat-page { .custom-select {
display: flex; .el-select-dropdown__item.is-selected {
flex-flow: column; background-color: var(--el-fill-color-light);
justify-content: center;
align-items: center;
} }
canvas {
background-color: transparent;
} }
</style> </style>