LaiTool V3.1.1.

This commit is contained in:
2024-09-12 14:13:09 +08:00
parent 5dc75f018a
commit 918d06e990
123 changed files with 8028 additions and 756 deletions
+3 -2
View File
@@ -1,5 +1,5 @@
<template>
<n-spin style="z-index: 1000 !important;" :show="softwareStore.spin.spinning">
<n-spin style="z-index: 1000 !important" :show="softwareStore.spin.spinning">
<template #description> {{ softwareStore.spin.tip }} </template>
<n-config-provider
:hljs="hljs"
@@ -47,6 +47,7 @@ export default defineComponent({
softwareStore.SoftColor = SoftColor
window.api.getSettingDafultData(async (value) => {
await window.darkMode.toggle(value.theme)
softwareStore.globalSetting = value
})
let software = await window.setting.GetSoftwareSetting()
if (software.code == 0) {
@@ -58,7 +59,7 @@ export default defineComponent({
if (software.data.length == 0) {
throw new Error('初始化信息错误: 未获取到数据')
}
softwareStore.softWare = software.data[0]
softwareStore.softWare = software.data
await window.darkMode.toggle(softwareStore.softWare.theme)
})
@@ -1,25 +1,28 @@
<template>
<div v-if="data.subValue && data.subValue.length != 0">
<div v-for="(item, index) in data.subValue">
<n-input
v-model:value="item.srt_value"
type="textarea"
size="tiny"
:autosize="{ minRows: 1, maxRows: 5 }"
@input="InputDebounced"
>
</n-input>
<div style="height: 135px; width : 100%;overflow: auto; display: flex; margin: auto">
<div v-if="data.subValue && data.subValue.length != 0" style="width : 100%">
<div v-for="(item, index) in data.subValue" style="width : 100%">
<n-input
v-model:value="item.srt_value"
type="textarea"
style="width : 100%"
size="tiny"
:autosize="{ minRows: 1, maxRows: 5 }"
@input="InputDebounced"
>
</n-input>
</div>
</div>
<n-input
v-else
v-model:value="data.afterGpt"
type="textarea"
size="tiny"
:autosize="{ minRows: 6, maxRows: 6 }"
@input="InputDebounced"
>
</n-input>
</div>
<n-input
v-else
v-model:value="data.afterGpt"
type="textarea"
size="tiny"
:autosize="{ minRows: 6, maxRows: 6 }"
@input="InputDebounced"
>
</n-input>
</template>
<script>
@@ -0,0 +1,322 @@
<template>
<div>
<div style="height: 22px; display: flex">
<div v-if="reverseManageStore.selectBookTask.imageCategory == 'mj'" style="z-index: 100">
进度{{ data.mjMessage?.progress }}%
</div>
<n-tag
v-if="data.mjMessage?.status != 'error'"
type="success"
style="height: 18px; margin: 2px 0 2px 5px"
>{{
data.mjMessage?.status != null && data.mjMessage?.status != ''
? data.mjMessage?.status
: 'wait'
}}</n-tag
>
<div v-else style="margin-left: 5px">
<n-popover style="padding: 0; margin: 0" trigger="hover">
<template #trigger>
<n-tag style="height: 18px; margin: 2px 0 2px 0" type="error">error</n-tag>
</template>
<span>{{ data.mjMessage?.message }}</span>
</n-popover>
</div>
<n-popover
v-if="reverseManageStore.selectBookTask.imageCategory == 'mj'"
style="padding: 0; margin: 0"
trigger="hover"
>
<template #trigger>
<div
style="
margin-left: 5px;
width: 100px;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
"
>
{{ data.mjMessage?.messageId }}
</div>
</template>
<span>{{ data.mjMessage?.messageId }}</span>
</n-popover>
</div>
<div></div>
<div
style="display: flex; margin-right: 10px; height: auto; min-height: 120px"
@dragstart="imageDragStart"
@drop="imageDragDrop"
@dragover="imageDragOver"
>
<div style="display: flex; align-items: center; position: relative">
<n-image
class="g-image-class generate-image-show"
:src="data.outImagePath ? data.outImagePath : softwareStore.globalSetting.space_image"
fit="cover"
:width="120"
:height="120"
lazy
/>
<n-button
text
:type="data.imageLock ? 'error' : 'primary'"
style="font-size: 24px; position: absolute; left: -12px; top: -5px"
@click="ModifyLock"
>
<n-icon>
<LockClosed v-if="data.imageLock" />
<LockOpen v-else />
</n-icon>
</n-button>
<n-button
text
type="info"
style="font-size: 24px; position: absolute; left: 8px; top: -5px"
@click="DownloadImage"
>
<n-icon>
<Download />
</n-icon>
</n-button>
</div>
<div>
<n-divider vertical style="height: 100%" />
</div>
<div style="display: flex; align-items: center">
<n-image-group>
<div
style="
display: flex;
flex-wrap: wrap;
align-items: center;
height: 122px;
overflow: auto;
"
>
<n-image
subImage="true"
class="g-image-class"
draggable="true"
:width="56"
style="margin: 1px"
v-for="(image, index) in data.subImagePath"
:key="image.id"
:src="image"
alt="图片描述"
lazy
>
</n-image>
</div>
</n-image-group>
</div>
</div>
</div>
</template>
<script setup>
import { ref, onMounted, onUnmounted, watch } from 'vue'
import {
NImage,
useDialog,
NImageGroup,
NDivider,
NIcon,
NButton,
NPopover,
NTag,
useMessage
} from 'naive-ui'
import { LockClosed, LockOpen, Download } from '@vicons/ionicons5'
import { useSoftwareStore } from '../../../../../stores/software'
import { useReverseManageStore } from '../../../../../stores/reverseManage'
import { useImageStore } from '../../../../../stores/image'
let props = defineProps({
initData: undefined,
index: undefined
})
let imageStore = useImageStore()
let message = useMessage()
let dialog = useDialog()
let data = ref(props.initData)
let images = ref(props.initData.subImagePath)
let outImagePath = ref(props.initData.outImagePath)
let width = ref(null)
let dragTarget = null
let reverseManageStore = useReverseManageStore()
let softwareStore = useSoftwareStore()
// 监听 props.initData.subImagePath 修改
watch(
() => props.initData.subImagePath,
(value) => {
images.value = value
width.value = 170 / 3
setNodeAttribute()
}
)
// 监听 props.initData.outImagePath 修改
watch(
() => props.initData.outImagePath,
(value) => {
outImagePath.value = value + `?t=${new Date().getTime()}`
}
)
// 监听 props.initData 修改
watch(
() => props.initData,
(value) => {
data.value = value
setNodeAttribute()
},
{ deep: true }
)
onMounted(async () => {
setNodeAttribute()
outImagePath.value = outImagePath.value + `?t=${new Date().getTime()}`
})
async function setNodeAttribute() {
let ele = document.querySelectorAll('.g-image-class')
ele.forEach((item) => {
item.setAttribute('data-id', data.value.id)
Array.from(item.getElementsByTagName('img')).forEach((img) => {
img.setAttribute('data-id', data.value.id)
})
})
let mainImage = document.querySelectorAll('.generate-image-show')
mainImage.forEach((item) => {
item.setAttribute('mainImage', '1')
Array.from(item.getElementsByTagName('img')).forEach((img) => {
img.setAttribute('mainImage', '1')
})
})
}
// 图片拖拽开始事件
async function imageDragStart(e) {
e.dataTransfer.effectAllowed = 'move'
console.log('move', e.target)
dragTarget = e.target
imageStore.SetDragTarget(e.target)
}
// 图片拖拽结束事件
async function imageDragDrop(e) {
console.log('end', e.target, dragTarget)
// 判断原始数据是不是为空
// if (dragTarget == null) {
// message.error('只能拖拽到当前行的图片上')
// return
// }
// 判断结束位置是不是主图片
if (e.target.getAttribute('mainImage') != '1') {
message.error('图片只能拖拽到主图片上')
return
}
let source_img = imageStore.GetDragTarget.src
.split('?')[0]
.replace(/^file:\/\/\//, '')
.replace(/^\//, '')
let target_img = e.target.src
.split('?')[0]
.replace(/^file:\/\/\//, '')
.replace(/^\//, '')
// 修改主图片
await window.api.DownloadImageFile([source_img, 'replace', target_img], (value) => {
if (value.code == 0) {
message.error(value.message)
return
}
data.value.outImagePath =
e.target.src.split('?')[0].replaceAll('\\', '/') + '?time=' + new Date().getTime()
})
}
// 图片拖拽进入事件
async function imageDragOver(e) {
e.preventDefault()
}
async function ModifyLock() {
// 锁定之前,判断是不是有
if (!outImagePath.value) {
message.error('没有生成图片,不能锁定')
return
}
let lock = !data.value.imageLock
// 修改数据库数据
let res = await window.db.UpdateBookTaskDetailData(data.value.id, {
imageLock: lock
})
if (res.code == 0) {
message.error(res.message)
return
}
message.success('图片锁定/解锁成功')
reverseManageStore.selectBookTaskDetail[props.index].imageLock = data.value.imageLock
data.value.imageLock = lock
}
/**
* 下载对应的分镜图片
*/
async function DownloadImage() {
// 目前只能判断是不是有mj_message
dialog.warning({
title: '下载图片警告',
content: '单个图片的下载,只支持 MJ API 生图,当前有图片的话会被覆盖掉,是否继续?',
positiveText: '继续',
negativeText: '取消',
onPositiveClick: async () => {
if (data.value.mj_message) {
debugger
// 加判断
// 只要状态不是success 和 error ,其他的都重新获取
if (data.value.mj_message.status == 'error') {
message.error('失败状态不能采集图片')
return
}
if (isEmpty(data.value.mj_message.message_id)) {
message.error('没有消息ID,不能采集图片')
return
}
window.mj.GetGeneratedMJImageAndSplit(JSON.stringify([toRaw(data.value)]), (value) => {
if (value.code == 0) {
message.error(value.message)
return
}
// 修改数据
for (let i = 0; i < value.data.length; i++) {
const element = value.data[i]
// 修改对应的数据
data.value.outImagePath =
'file://' +
element.outImagePath.replaceAll('\\', '/') +
'?time=' +
new Date().getTime()
data.value.subImagePath = element.subImagePath.map(
(item) => 'file://' + item.replaceAll('\\', '/') + '?time=' + new Date().getTime()
)
data.value.mj_message.image_click = element.result
data.value.mj_message.image_path = element.image_path
data.value.mj_message.progress = 100
data.value.mj_message.status = 'success'
}
})
} else if (data.value.mjMessage) {
message.error('暂时不支持')
return
} else {
message.error('没有生图数据')
}
}
})
}
</script>
@@ -159,11 +159,12 @@ export default defineComponent({
// 处理批次的翻译
function TranslateBatchParams(from, to) {
debugger
let translateData = []
for (let i = index.value; i < reverseManageStore.selectBookTaskDetail.length; i++) {
const element = reverseManageStore.selectBookTaskDetail[i]
// 这边判断GPT提示词是不是存在,存在的话翻译GPT提示词
if (!isEmpty(data.value.gptPrompt)) {
if (!isEmpty(element.gptPrompt)) {
translateData.push({
text: element.gptPrompt,
from: from,
@@ -0,0 +1,59 @@
<template>
<div style="display: flex">
<span>出图</span>
<n-select
style="width: 100px; margin-left: 5px"
size="tiny"
:options="image_options"
@update:value="UpdateImageGenerateCategory"
v-model:value="reverseManageStore.selectBookTask.imageCategory"
placeholder="选择生图模式"
></n-select>
<n-button
v-if="reverseManageStore.selectBookTask.imageCategory == 'mj'"
color="#7c461e"
size="tiny"
style="margin-left: 5px"
@click="MJGetImage"
>MJ采集图片</n-button
>
<n-button
v-if="reverseManageStore.selectBookTask.imageCategory == 'mj'"
color="#e18a3b"
size="tiny"
style="margin-left: 5px"
@click="OneSplitFour"
>1拆4</n-button
>
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue'
import { NSelect, NButton, useMessage } from 'naive-ui'
import { useSoftwareStore } from '../../../../../stores/software'
import { useReverseManageStore } from '../../../../../stores/reverseManage'
let softwareStore = useSoftwareStore()
let message = useMessage()
let reverseManageStore = useReverseManageStore()
let image_options = ref([])
onMounted(async () => {
// 获取生图下拉列表的方式
await window.api.GetImageGenerateCategory((value) => {
if (value.code == 0) {
message.error(value.message)
return
}
image_options.value = value.data
})
})
async function UpdateImageGenerateCategory(value, options) {
// 调用下保存
let res = await window.db.UpdateBookTaskData(reverseManageStore.selectBookTask.id, {
imageCategory: value
})
window.api.showGlobalMessage(res)
}
</script>
@@ -95,7 +95,6 @@ export default defineComponent({
// 选择风格
async function SelectStyle() {
debugger
// 先获取风格
// 判断当前数据是不是存在
// 处理数据。获取当前的所有的数据
@@ -0,0 +1,303 @@
<template>
<div style="margin-top: 30px">
<div style="display: flex; align-items: center">
<n-button color="#b6a014" size="small" @click="formateWrite">一键格式化</n-button>
<n-popover trigger="hover">
<template #trigger>
<n-button quaternary circle size="tiny" color="#b6a014" @click="AddSplitChar">
<template #icon>
<n-icon size="25"> <AddCircleOutline /> </n-icon>
</template>
</n-button>
</template>
<span>添加分割标识符</span>
</n-popover>
<n-popover trigger="hover">
<template #trigger>
<n-input-number
style="margin-left: 10px; width: 100px"
size="small"
placeholder="输入合并的行数"
:max="10000"
:min="1"
@update:value="mergeCountChange"
:show-button="false"
v-model:value="write_setting.merge_count"
></n-input-number>
</template>
<span>设置将多少行进行合并</span>
</n-popover>
<n-popover trigger="hover">
<template #trigger>
<n-input
style="margin-left: 10px; width: 80px"
size="small"
placeholder="输入中间拼接的字符"
@update:value="mergeCountChange"
:show-button="false"
v-model:value="write_setting.merge_char"
></n-input>
</template>
<span>设置中间拼接的字符</span>
</n-popover>
<n-popover trigger="hover">
<template #trigger>
<n-input
style="margin-left: 10px; width: 80px"
size="small"
placeholder="输入结尾拼接的字符"
@update:value="mergeCountChange"
:show-button="false"
v-model:value="write_setting.end_char"
></n-input>
</template>
<span>设置结尾拼接的字符</span>
</n-popover>
<n-button color="#80a492" size="small" style="margin-left: 10px" @click="mergeWord"
>合并</n-button
>
<n-button color="#f3a694" size="small" style="margin-left: 10px" @click="CopyWord"
>一键复制内容</n-button
>
</div>
<n-input
style="margin-top: 5px"
v-model:value="word"
:autosize="{ minRows: 30, maxRows: 30 }"
type="textarea"
placeholder="输入分镜文案"
@update:value="ChangeWordInput"
/>
<div style="margin-top: 5px; display: flex">
<div style="flex: 1"> {{ rowCount }} 行分镜当前数量不是最后数量最后会删除空行</div>
</div>
</div>
</template>
<script>
import { ref, h, onMounted, defineComponent, toRaw } from 'vue'
import {
NImage,
useMessage,
NButton,
useDialog,
NInput,
NIcon,
NPopover,
NInputNumber
} from 'naive-ui'
import { AddCircleOutline } from '@vicons/ionicons5'
import InputDialogContent from '../../../Original/Components/InputDialogContent.vue'
export default defineComponent({
components: {
NImage,
NButton,
NInput,
NInputNumber,
NIcon,
AddCircleOutline,
NPopover
},
props: ['initData'],
setup(props) {
let message = useMessage()
let dialog = useDialog()
let rowCount = ref(0)
let data = ref(props.initData)
let word = ref(null)
let word_arr = ref([])
let split_ref = ref(null)
let write_setting = ref({
split_char: '。,“”‘’!?【】《》()…—:;.,\'\'""!?[]<>()...-:;',
merge_count: 3,
merge_char: '',
end_char: '。'
})
onMounted(async () => {
// 拼接word
let tmp_arr = []
for (let i = 0; i < data.value.length; i++) {
const item = data.value[i]
tmp_arr.push(item.after_gpt)
}
word.value = tmp_arr.join('\n')
ChangeWordInput()
// 加载初始配置数据
await window.api.GetDefineConfigJsonByProperty(
JSON.stringify(['clip_setting', 'write_setting', false, null]),
(value) => {
if (value.code == 0) {
message.error(value.message)
return
}
if (value.data) {
write_setting.value = value.data
}
}
)
})
/**
* 修改数据框时触发的事件
*/
async function ChangeWordInput() {
let word_arr = word.value.split('\n')
// 先判断是不是有空行
if (rowCount.value == 1 && word_arr[0] == '') {
rowCount.value = 0
} else {
rowCount.value = word_arr.length
}
}
/**
* 添加分割符号
*/
async function AddSplitChar() {
// 判断当前数据是不是存在
// 处理数据。获取当前的所有的数据
let dialogWidth = 400
let dialogHeight = 150
dialog.create({
title: '添加分割符',
showIcon: false,
closeOnEsc: false,
content: () =>
h(InputDialogContent, {
ref: split_ref,
initData: write_setting.value.split_char,
placeholder: '请输入分割符'
}),
style: `width : ${dialogWidth}px; min-height : ${dialogHeight}px`,
maskClosable: false,
onClose: async () => {
write_setting.value.split_char = split_ref.value.data
// 保存数据
await window.api.SaveDefineConfigJsonByProperty(
JSON.stringify(['clip_setting', 'write_setting', toRaw(write_setting.value), false]),
(value) => {
console.log(value)
if (value.code == 0) {
message.error(value.message)
return
}
message.success(value.message)
}
)
}
})
}
/**
* 更新input-number事件
*/
let timer = null
async function mergeCountChange(value) {
// 添加一个防抖函数,防止修改次数太多导致卡顿
clearTimeout(timer)
timer = setTimeout(async () => {
// 执行你的逻辑代码
//
await window.api.SaveDefineConfigJsonByProperty(
JSON.stringify(['clip_setting', 'write_setting', toRaw(write_setting.value), false]),
(value) => {
console.log(value)
if (value.code == 0) {
message.error(value.message)
return
}
message.success(value.message)
}
)
}, 500)
}
/**
* 一键格式化
*/
async function formateWrite() {
let split_arr = Array.from(write_setting.value.split_char)
split_arr.forEach((item) => {
let specialCharacters = [
'.',
'*',
'?',
'+',
'^',
'$',
'[',
']',
'(',
')',
'{',
'}',
'|',
'\\'
]
let regex
if (specialCharacters.includes(item)) {
regex = new RegExp('\\' + item, 'g')
} else {
regex = new RegExp(item, 'g')
}
word.value = word.value.replace(regex, '\n')
})
// 删除空行
let word_arr = word.value.split('\n')
word_arr = word_arr.filter((item) => item != '' && item != null)
word.value = word_arr.join('\n')
ChangeWordInput()
}
/**
* 合并数据
*/
async function mergeWord() {
let step = write_setting.value.merge_count ? write_setting.value.merge_count : 3
let tmp_arr = []
let word_arr = word.value.split('\n')
for (let i = 0; i < word_arr.length; i += step) {
let group = word_arr.slice(i, i + step)
tmp_arr.push(group.join(write_setting.value.merge_char) + write_setting.value.end_char)
}
word.value = tmp_arr.join('\n')
ChangeWordInput()
}
/**
* 复制数据到剪贴板
*/
async function CopyWord() {
// 获取当前的所有的word
try {
await navigator.clipboard.writeText(word.value)
message.success('复制成功')
} catch (error) {
message.error('复制失败')
}
}
return {
data,
rowCount,
ChangeWordInput,
word,
word_arr,
AddSplitChar,
split_ref,
write_setting,
mergeCountChange,
formateWrite,
mergeWord,
CopyWord
}
}
})
</script>
@@ -0,0 +1,718 @@
<template>
<div id="import_word_and_srt">
<div style="height: 30px; margin-bottom: 5px">
<n-button
v-if="type == 'original' || type == 'new_original'"
color="#1e90ff"
size="small"
style="margin-left: 20px"
@click="OpenOneDialog"
>导入字幕</n-button
>
<n-button color="#1e90ff" size="small" style="margin-left: 20px" @click="ImportSrtAndGetTime"
>导入srt</n-button
>
<n-button
color="#1e90ff"
size="small"
style="margin-left: 20px"
@click="SaveCopywritingInformation"
>保存文案信息</n-button
>
</div>
<n-data-table
:max-height="maxHeight"
:row-key="rowKey"
:columns="columns"
:data="data"
:pagination="false"
:bordered="false"
@update:checked-row-keys="handleCheck"
/>
</div>
</template>
<script>
import { h, defineComponent, onMounted, ref, toRaw, watch, onUnmounted } from 'vue'
import { NButton, NDataTable, useMessage, NInput, NImage, useDialog } from 'naive-ui'
import { v4 as uuidv4 } from 'uuid'
import EditWord from './EditWord.vue'
import { checkStringValueDeleteSuffix } from '../../../../../../main/Public/generalTools'
import { useReverseManageStore } from '../../../../../../stores/reverseManage'
import InputDialogContent from '../../../Original/Components/InputDialogContent.vue'
import { isEmpty } from 'lodash'
import { OperateBookType } from '../../../../../../define/enum/bookEnum'
const buttonArr = [
{
title: '下对齐',
key: 'AlignNextWord',
color: '#8a2be0'
}
]
export default defineComponent({
components: {
NButton,
NDataTable,
NInput,
NImage
},
props: ['initData', 'menuType', 'height', 'type'],
setup(props) {
let hh = props.height
let maxHeight = props.height - 80
debugger
let data = ref(JSON.parse(JSON.stringify(props.initData)))
const message = useMessage()
let dialog = useDialog()
let selectKey = ref([])
let editWordRef = ref(null)
let wenkeRef = ref(null)
let type = ref(props.type ? props.type : 'original')
let reverseManageStore = useReverseManageStore()
const createColumns = ({ AlignNextWord }) => {
return [
{
type: 'selection',
disabled(row) {
return row.name === 'Edward King 3'
}
},
{
title: '编号',
key: 'no',
width: 70
},
{
title: '分镜文案',
key: 'after_gpt',
width: 300,
render(row, index) {
return h(NInput, {
value: row.word,
type: 'textarea',
autosize: true,
onUpdateValue(v) {
data.value[index].word = v
}
})
}
},
{
title: '字幕',
key: 'srt',
width: '300',
render(row) {
let tmp = row.subValue.map((item) =>
h('div', { style: 'display:flex; height : auto; margin: 2px 0' }, [
h(NInput, {
size: 'tiny',
value: item.srt_value
}),
h(
NButton,
{
style: 'margin : 0 2px',
size: 'tiny',
color: '#ecd452',
onClick: () => Upper(row, item, true)
},
{ default: () => '向上' }
),
h(
NButton,
{
style: 'margin : 0 2px',
size: 'tiny',
color: '#ee7959',
onClick: () => Down(row, item)
},
{ default: () => '向下' }
)
])
)
return tmp
}
},
{
title: '操作',
key: 'options',
width: 130,
render(row) {
const btn = buttonArr.map((item) => {
return h(
NButton,
{
style: 'margin : 2px',
strong: true,
size: 'tiny',
color: item.color,
onClick: () => {
if (item.key == 'AlignNextWord') {
AlignNextWord(row)
}
}
},
{
default: () => item.title
}
)
})
return btn
}
},
{
title: '时间范围',
key: 'timeLimit',
width: 120
}
]
}
// 设置窗体的高度
function setHeight() {
let div = document.getElementById('import_word_and_srt')
div.style.height = hh + 'px'
}
onMounted(async () => {
setHeight()
// 监听 ctrl + alt + w 键盘的按键事件
window.addEventListener('keydown', ImportWenKeSrt)
})
onUnmounted(() => {
window.removeEventListener('keydown', ImportWenKeSrt)
})
/**
* 导入文刻软件里面的srt
*/
async function ImportWenKeSrt(e) {
if (!(e.ctrlKey && e.altKey && e.key == 'k')) {
return
}
let dialogWidth = 400
let dialogHeight = 150
dialog.create({
name: 'importWenkeWord',
title: '导入外部对齐文件',
showIcon: false,
closeOnEsc: false,
content: () =>
h(InputDialogContent, {
ref: wenkeRef,
initData: null,
placeholder: '请导入外部对齐文件'
}),
style: `width : ${dialogWidth}px; min-height : ${dialogHeight}px`,
maskClosable: false,
onClose: async () => {
debugger
console.log(wenkeRef.value.word)
let word = wenkeRef.value.data
if (word == null || word == '') {
message.error('数据为空')
return
}
word = JSON.parse(word)
// 将文案数据写入到本地
data.value = []
let word_arr = word.rows
let lastId = ''
let end_word = []
for (let i = 0; i < word_arr.length; i++) {
const element = word_arr[i]
// 判断下面的text是不是存在,存在的话,获取数据然后记录事件
let row_text = ''
let start_time = 0
let end_time = 0
let subValue = []
if (element.texts) {
for (let j = 0; j < element.texts.length; j++) {
const text = element.texts[j]
row_text += text.text + ','
if (j == 0) {
start_time = text.start
}
if (j == element.texts.length - 1) {
end_time = text.start + text.duration
}
subValue.push({
id: uuidv4(),
srt_value: text.text,
start_time: text.start / 1000,
end_time: text.start / 1000 + text.duration / 1000
})
}
}
let id = uuidv4()
lastId = id
data.value.push({
no: i + 1,
id: id,
lastId: lastId,
word: checkStringValueDeleteSuffix(row_text, ','),
after_gpt: checkStringValueDeleteSuffix(row_text, ','),
start_time: start_time / 1000,
end_time: end_time / 1000,
timeLimit: `${start_time / 1000} -- ${end_time / 1000}`,
subValue: subValue
})
end_word.push(checkStringValueDeleteSuffix(row_text, ','))
}
// 将文案数据写入到本地
let local_word = end_word.filter((item) => item != '' && item != null)
await window.api.saveWordTxt(toRaw(local_word), (value) => {
if (value.code == 0) {
message.error('文案写入失败' + value.message)
return
}
})
return true
}
})
}
/**
* 重新计算指定索引值的时间
* @param {int} index
*/
function modifyTime(index) {
// 判断下一个行或者是上一行是不是存在,存在才会计算时间
if (data.value[index]) {
let s_v = data.value[index].subValue
let len = s_v.length
if (len > 0) {
let s_t = s_v[0].start_time
let e_t = s_v[len - 1].end_time
data.value[index].start_time = s_t
data.value[index].end_time = e_t
data.value[index].timeLimit = `${s_t} -- ${e_t}`
} else {
data.value[index].start_time = null
data.value[index].end_time = null
data.value[index].timeLimit = ``
}
}
}
/**
* 判断当前行的子数据的长度是不是大于0。不大于0 的话下面的字幕往上替补
* @param {data value 的索引} index
*/
function moveUp(index) {
let s_v = data.value[index].subValue
if (s_v.length == 0) {
// 将下面所有的字幕数据往上移动
for (let i = index; i < data.value.length; i++) {
// const element = data.value[i];
if (data.value[i + 1] != null) {
data.value[i].subValue = data.value[i + 1].subValue
} else {
// 检查当前这个是不是为空。为空直接删除
// 判断当前行的文案、洗稿后文件、以及字幕是不是还有。(没有的话直接删除)
if (
data.value[i].word == null ||
data.value[i].word == '' ||
data.value[i].after_gpt == null ||
data.value[i].after_gpt == ''
) {
// 直接删除
data.value.splice(i, 1)
}
}
}
}
}
/**
* 字幕文件向上
*/
async function Upper(row, item, isM) {
// 向上合并。
// 当前item。向上的话。判断是不是第一个。是第一个的话。直接网上一个合并。
// 不是第一个的话。将上面的所有的合并。
// 若是当前只有一个。判断当前文案和洗稿后文件是不是存在。存在不删除。没有的话直接删除。
// 向下合并。逻辑相同。
if (row.lastId == null || row.lastId == '') {
message.error('当前是第一个。不能向上合并')
return
}
//判断当前行是不是第一个
let itemId = item.id
let itemIndex = row.subValue.findIndex((item) => item.id == itemId)
let thisIndex = data.value.findIndex((item) => item.id == row.id)
if (itemIndex < 0 && isM) {
message.error('数据错误')
return
} else {
// 先获取上一行的数据
let lastRowIndex = data.value.findIndex((item) => item.id == row.lastId)
//将当前及之前的数据向上合并
for (let i = 0; i <= itemIndex; i++) {
const element = row.subValue[i]
data.value[lastRowIndex].subValue.push(element)
}
// 删除指定的数据
data.value[lastRowIndex + 1].subValue.splice(0, itemIndex + 1)
moveUp(thisIndex)
// 判断当前行的文案和洗稿后文件是不是还有。(没有的话直接删除)
if (
data.value[thisIndex] &&
data.value[thisIndex].subValue == null &&
(row.word == null || row.word == '' || row.after_gpt == null || row.after_gpt == '')
) {
// 先调整lastId (判断下一行是不是存在。存在的话修改lastId为当前的lastId
if (data.value[thisIndex + 1] != null) {
data.value[thisIndex + 1].lastId = row.lastId
}
// 直接删除
data.value.splice(thisIndex, 1)
// 调整下面的所有的编号
for (let i = thisIndex; i < data.value.length; i++) {
data.value[i].no = data.value[i].no - 1
}
}
// 合并之后重新计算时间(计算上一个和当前的)
modifyTime(lastRowIndex)
modifyTime(thisIndex)
}
}
/**
* 字幕文件向下
*/
async function Down(row, item) {
// 向下合并。逻辑相同。
// 当前item。。判断是不是最后一个。是最后一个的话。直接向下合并。
// 不是第一个的话。将下面的所有的合并。
// 若是当前只有一个。判断当前文案和洗稿后文件是不是存在。存在不删除。没有的话直接删除。
//判断当前行是不是第一个
let itemId = item.id
let itemIndex = row.subValue.findIndex((item) => item.id == itemId)
let thisIndex = data.value.findIndex((item) => item.id == row.id)
// 判断下一个是不是存在
if (data.value[thisIndex + 1] == null) {
message.error('当前已经是最后一个。不能向下合并')
return
}
if (itemIndex < 0) {
message.error('数据错误')
return
} else {
// 获取下一行数据
let nextRowIndex = data.value.findIndex((item) => item.lastId == row.id)
//将当前以及下面的所有进行合并
for (let i = row.subValue.length - 1; i >= itemIndex; i--) {
const element = row.subValue[i]
data.value[nextRowIndex].subValue.unshift(element)
}
// 删除指定的数据
data.value[thisIndex].subValue.splice(itemIndex)
// 判断该当前字幕是不是为空(为空的话。下面的往上移动)
moveUp(thisIndex)
// 判断当前行的文案、洗稿后文件、以及字幕是不是还有。(没有的话直接删除)
if (
data.value[thisIndex].subValue == null &&
(row.word == null || row.word == '' || row.after_gpt == null || row.after_gpt == '')
) {
// 先调整lastId (判断下一行是不是存在。存在的话修改lastId为当前的lastId
if (data.value[nextRowIndex] != null) {
data.value[nextRowIndex].lastId = row.lastId
}
// 直接删除
data.value.splice(thisIndex, 1)
// 调整下面的所有的编号
for (let i = thisIndex; i < data.value.length; i++) {
data.value[i].no = data.value[i].no - 1
}
}
// 合并之后重新计算时间(计算上一个和当前的)
modifyTime(nextRowIndex)
modifyTime(thisIndex)
}
}
function removePunctuationIncludingEllipsis(sentence) {
// 扩展正则表达式以包含中文标点符号和省略号
// 注意英文省略号可能由三个连续点表示,也可能直接使用特殊的省略号字符
const punctuationRegExp = /[., \/#!$%\^&\*;:{}=\-_`~()\[\],。、;:?!‘’“”()【】《》…]+/g
// 使用正则表达式的replace方法替换掉所有匹配到的标点符号为空字符串
return sentence.replace(punctuationRegExp, '')
}
/**
* 对齐当前列即下面的数据
*/
async function AlignNextWord(row) {
// 判断是不是已经到到导入过字幕了
dialog.create({
type: 'warning',
title: '警告',
showIcon: true,
content: '会自动对齐下面的数据。没有对齐成功就要手动调整在自动对齐',
style: `width : 400px;`,
maskClosable: false,
positiveText: '确定',
negativeText: '取消',
onPositiveClick: async () => {
// 下对齐
let itemId = row.id
let thisIndex = data.value.findIndex((item) => item.id == itemId)
if (thisIndex < 0) {
message.error('数据错误')
return
}
let nextSrt = []
// 先将当前行下面的所有的数据拿出来
for (let i = thisIndex; i < data.value.length; i++) {
const element = data.value[i]
nextSrt = nextSrt.concat(element.subValue)
}
let init_num = thisIndex
// let data
let tmp_str = ''
for (let i = 0; i < nextSrt.length; ) {
const element = nextSrt[i]
let current_text = `${thisIndex + 1} 数据。字幕:${
element.srt_value
}与文案不匹配。亲检查上下文。`
tmp_str += element.srt_value
if (
removePunctuationIncludingEllipsis(data.value[thisIndex].after_gpt).startsWith(
removePunctuationIncludingEllipsis(tmp_str)
)
) {
// data.value[thisIndex].subValue.push(element);
if (init_num < thisIndex || i > 0) {
Upper(data.value[thisIndex + 1], element, false)
}
i++
} else {
if (
removePunctuationIncludingEllipsis(data.value[thisIndex + 1].after_gpt).startsWith(
removePunctuationIncludingEllipsis(element.srt_value)
)
) {
thisIndex++
tmp_str = ''
} else {
window.api.showGlobalMessageDialog({
code: 0,
message: current_text
})
return
}
}
}
}
})
}
/**
* 保存字幕文案信息,用于自动生成视频
*/
async function SaveCopywritingInformation() {
if (type.value == 'original') {
if (props.menuType == 'mj') {
await window.mj.SvaeMJWordSrt([toRaw(data.value), 'srt_time_information'], (value) => {
if (value.code == 0) {
message.error(value.message)
return
}
message.success('保存成功')
})
return
} else {
// 先保存单个文件(然后在config文件中指向对应的文件)
await window.api.OriginalAddWebuiJson(JSON.stringify(data.value), async (value) => {
if (value.code == 0) {
message.error(value.message)
return
}
if (value.data) {
for (let i = 0; i < data.value.length; i++) {
let name = String(data.value[i].no).padStart(5, '0')
data.value[i].prompt_json = value.data + '/' + name + '.png.json'
data.value[i].name = name + '.png'
}
}
await window.api.SaveCopywritingInformation(
[toRaw(data.value), 'srt_time_information'],
(value) => {
if (value.code != 1) {
message.error(value.message)
return
}
message.success('保存成功')
}
)
})
}
} else if (type.value == 'mj_reverse' || type.value == 'sd_reverse') {
debugger
if (data.value.length != reverseManageStore.selectBookTaskDetail.length) {
message.error('检测到导入的字幕数据和分镜对不上,请先对齐')
return
}
// 将对齐后的数据写入到数据库中
for (let i = 0; i < data.value.length; i++) {
const element = data.value[i]
let res = await window.db.UpdateBookTaskDetailData(element.id, {
startTime: element.start_time,
endTime: element.end_time,
timeLimit: element.timeLimit,
subValue: JSON.stringify(element.subValue)
})
}
} else if (type.value == 'new_original') {
// 判断是不是没有对齐(就是所有的数据 文案 时间 srt 都要有,不然就报错)
for (let i = 0; i < data.value.length; i++) {
const element = data.value[i]
if (
isEmpty(element.word) ||
isEmpty(element.after_gpt) ||
!element.hasOwnProperty('start_time') ||
!element.hasOwnProperty('end_time') ||
!element.hasOwnProperty('subValue') ||
element.subValue.length == 0
) {
message.error("保存数据失败,数据不完整,请检查'文案'、'时间'、'字幕' 等,数据是否完整")
return
}
}
// 开始保存数据(这个要全部保存。会提前判断是不是又分镜信息存在,存在的话就直修改时间,但是需要判断分镜数量是不是对的,不存在的话就会新建分镜数据)后端判断
let res = await window.book.SaveCopywriting(
reverseManageStore.selectBookTask.id,
toRaw(data.value),
OperateBookType.BOOKTASK
)
window.api.showGlobalMessageDialog(res)
} else {
message.error('未知的小说类型,请检查')
}
}
/**
* 导入字幕并进行分镜输出,显示时间,将分镜给他传进去
*/
async function ImportSrtAndGetTime() {
let srt_path = null
// 先选择SRT文件
await window.api.SelectFile(['srt'], (value) => {
if (value.code == 0) {
message.error(value.message)
return
}
srt_path = value.value
})
await window.api.ImportSrtAndGetTime([toRaw(data.value), srt_path], (value) => {
if (value.code == 0) {
message.error(value.message)
return
}
// 写入时间
for (let i = 0; i < value.data.length; i++) {
const item = value.data[i]
if (i >= data.value.length) {
item.no = i + 1
item.timeLimit = `${item.start_time} -- ${item.end_time}`
data.value.push(item)
} else {
data.value[i].start_time = item.start_time
data.value[i].end_time = item.end_time
data.value[i].timeLimit = `${item.start_time} -- ${item.end_time}`
data.value[i].subValue = item.subValue
}
}
})
}
/**
* 打开添加字幕信息的窗口
*/
async function OpenOneDialog() {
// 判断当前数据是不是存在
// 处理数据。获取当前的所有的数据
let dialogWidth = window.innerWidth * 0.7
let dialogHeight = window.innerHeight * 0.9
// ImportWordAndSrt
dialog.create({
showIcon: false,
closeOnEsc: false,
content: () => h(EditWord, { ref: editWordRef, initData: data }),
style: `width : ${dialogWidth}px; minHeight : ${dialogHeight}px`,
maskClosable: false,
positiveText: '保存',
negativeText: '取消',
onPositiveClick: async () => {
data.value = []
let word_arr = editWordRef.value.word.split('\n')
let lastId = ''
for (let i = 0; i < word_arr.length; i++) {
const element = word_arr[i]
if (element != '' && element != null) {
let id = uuidv4()
let obj = {
no: i + 1,
id: id,
lastId: lastId,
word: element,
after_gpt: element,
subValue: []
}
lastId = id
data.value.push(obj)
}
}
// 将文案数据写入到本地
let local_word = word_arr.filter((item) => item != '' && item != null)
await window.api.saveWordTxt(toRaw(local_word), (value) => {
if (value.code == 0) {
message.error('文案写入失败' + value.message)
return
}
})
return true
}
})
}
return {
data,
columns: createColumns({
AlignNextWord
}),
rowKey: (row) => row.id,
handleCheck(rowKeys) {
selectKey.value = rowKeys
},
ImportSrtAndGetTime,
SaveCopywritingInformation,
OpenOneDialog,
maxHeight,
hh,
wenkeRef,
type,
reverseManageStore
}
}
})
</script>
@@ -119,6 +119,7 @@ import { useReverseManageStore } from '../../../../../../stores/reverseManage.ts
import { CloseSharp } from '@vicons/ionicons5'
import { cloneDeep } from 'lodash'
import { useSoftwareStore } from '../../../../../../stores/software.ts'
import { BookType } from '../../../../../../define/enum/bookEnum'
export default defineComponent({
components: {
@@ -175,6 +176,16 @@ export default defineComponent({
} else if (propertyName == 'srtPath') {
ext = ['srt']
}
// 判断是不是原创,原创不能选择视频文件
if (
reverseManageStore.selectBook.type == BookType.ORIGINAL &&
propertyName == 'oldVideoPath'
) {
message.error('原创小说不能选择视频文件')
return
}
// 选择MP4文件夹
await window.api.SelectFile(ext, (value) => {
debugger
@@ -212,8 +223,17 @@ export default defineComponent({
}
let rules = {
name: ruleObj('书名必填'),
oldVideoPath: ruleObj('反推的视频文件地址必填'),
type: ruleObj('小说类型必填')
type: ruleObj('小说类型必填'),
oldVideoPath: {
required: true,
validator(rule, value) {
// 判断是不是原创,原创不能选择视频文件,反推必选
if(reverseManageStore.selectBook.type == BookType.SD_REVERSE || reverseManageStore.selectBook.type == BookType.MJ_REVERSE){
return new Error("MJ反推和SD反推,视频文件必选")
}
},
trigger: ['input', 'blur', 'change']
}
}
/**
@@ -291,7 +311,8 @@ export default defineComponent({
bookRef,
softwareStore,
OpenFolder,
backgroundMusicOptions,loading
backgroundMusicOptions,
loading
}
}
})
@@ -774,7 +774,8 @@ export default defineComponent({
after_gpt: element.afterGpt,
subValue: element.subValue ? element.subValue : [],
name: element.name + '.png',
prompt: element.prompt
prompt: element.prompt,
timeLimit: element.timeLimit
})
}
@@ -0,0 +1,361 @@
<template>
<div>
<div style="width: 500px; position: relative">
<n-form ref="formRef" label-placement="top" :model="characterData" :rules="rules">
<n-form-item label="人物名称" path="label">
<n-input
style="width: 400px"
v-model:value="characterData.label"
placeholder="请输入人物名称"
/>
<n-checkbox style="margin-left: 20px" v-model:checked="characterData.isShow"
>显示</n-checkbox
>
<div
style="position: absolute; left: 520px; top: 0; display: flex; flex-direction: column"
>
<n-image
width="120"
height="120"
:src="png_base64 ? png_base64 : characterData.show_image"
/>
<n-button color="#e18a3b" style="margin-top: 5px" size="tiny" @click="SelecImage"
>本地上传图片</n-button
>
</div>
</n-form-item>
<n-form-item label="人物别名(可多个)">
<n-dynamic-tags type="success" v-model:value="alias_tags" />
</n-form-item>
<n-form-item label="人物提示词(中文)">
<n-input
type="textarea"
:rows="2"
v-model:value="characterData.chinese_prompt"
placeholder="请输入人物提示词"
/>
<!-- 里面的按钮设置上下居中 -->
<div style="width: 100px; margin-left: 20px">
<n-button color="#a76283" size="tiny" @click="TranslatePrompt" :loading="loading"
>翻译提示词</n-button
>
<n-button
color="#e18a3b"
size="tiny"
style="margin-top: 10px"
@click="GenerateCharacterImage"
:loading="imageLoading"
>SD生成图片</n-button
>
</div>
</n-form-item>
<n-form-item label="人物提示词(英文)" path="prompt">
<n-input
type="textarea"
:rows="2"
v-model:value="characterData.prompt"
placeholder="请输入人物提示词"
/>
</n-form-item>
<n-form-item label="MJ:人物图片链接(cref">
<n-input
v-model:value="characterData.image_url"
placeholder="请输入人物图片链接"
style="margin-right: 20px"
/>
<n-image
v-if="characterData.image_url"
:src="characterData.image_url"
style="width: 80px; height: 80px"
:alt="characterData.image_url"
/>
</n-form-item>
<n-form-item label="MJ:角色迁移 cw 值(0-100">
<n-input-number
:show-button="false"
v-model:value="characterData.cref_cw"
min="0"
max="100"
placeholder="请输入人物风格迁移的CW值"
/>
</n-form-item>
<n-form-item label="SDLora选择">
<n-select
v-model:value="characterData.lora"
style="width: 250px"
:options="lora_options"
placeholder="选择人物lora"
/>
<n-input-number
style="width: 120px; margin-left: 10px"
placeholder="权重"
v-model:value="characterData.lora_weight"
:show-button="false"
:max="2"
:step="0.01"
:precision="2"
S
:min="0"
/>
</n-form-item>
</n-form>
<div style="text-align: right">
<n-button type="success" @click="SaveCharacterTag">保存</n-button>
</div>
</div>
</div>
</template>
<script>
import { ref, h, onMounted, defineComponent, toRaw, watch, nextTick } from 'vue'
import {
NImage,
useMessage,
NButton,
useDialog,
NInput,
NForm,
NFormItem,
NDynamicTags,
NInputNumber,
NSelect,
NCheckbox
} from 'naive-ui'
import { v4 as uuidv4 } from 'uuid'
import { isEmpty } from 'lodash'
export default defineComponent({
components: {
NImage,
NButton,
NInput,
NForm,
NFormItem,
NDynamicTags,
NInputNumber,
NSelect,
NCheckbox
},
props: ['currentCharacter', 'initFunc', 'currentTags', 'lora_options'],
setup(props) {
let message = useMessage()
let alias_tags = ref(props.currentTags)
let characterData = ref(props.currentCharacter)
let lora_options = ref(props.lora_options)
let loading = ref(false)
let imageLoading = ref(false)
let formRef = ref(null)
let png_base64 = ref(null)
// 监听数据变化
watch(
() => props.currentCharacter,
(value) => {
characterData.value = value
},
{ deep: true }
)
watch(
() => props.currentTags,
(value) => {
alias_tags.value = value
},
{ deep: true }
)
onMounted(async () => {})
async function SaveCharacterTag(e) {
e.preventDefault()
formRef.value?.validate(async (errors) => {
if (errors) {
message.error('请检查必填字段')
return
}
// 保存之前要处理数据(判断该当前的将别名数组中的数据进行拆分)
let children = []
alias_tags.value.forEach((item) => {
children.push({
label: item,
key: uuidv4(),
type: 'min',
chinese_prompt: characterData.value.chinese_prompt,
prompt: characterData.value.prompt,
image_url: characterData.value.image_url,
show_image: characterData.value.show_image
? characterData.value.show_image.split('?t')[0]
: null,
cref_cw: characterData.value.cref_cw,
lora: characterData.value.lora,
lora_weight: characterData.value.lora_weight
})
})
characterData.value['children'] = children
console.log(characterData.value)
characterData.value['type'] = 'character_main'
characterData.value['show_image'] = characterData.value.show_image
? characterData.value.show_image.split('?t')[0]
: null
// 开始保存
await window.mj.SaveTagPropertyData(
[JSON.stringify(characterData.value), 'character_tags'],
(value) => {
console.log(value)
if (value.code == 0) {
message.error(value.message)
return
}
message.success(value.message)
//保存成功在initFunc
props.initFunc()
// 清楚数据
characterData.value = {
label: null,
key: null,
type: 'character_main',
chinese_prompt: null,
prompt: null,
image_url: null,
show_image: window.config.space_image,
cref_cw: 20,
chinese_prompt: null,
lora: '无',
lora_weight: 1
}
png_base64.value = null
alias_tags.value = []
}
)
})
}
// 翻译提示词
async function TranslatePrompt() {
loading.value = true
if (characterData.value.chinese_prompt == '' || characterData.value.chinese_prompt == null) {
message.error('请输入中文提示词')
loading.value = false
return
}
await window.api.TranslateReturnNow(
[characterData.value.chinese_prompt, 'zh', 'en', false],
(value) => {
if (value.code == 0) {
message.error(value.message)
return
}
characterData.value.prompt = value.data[0].src
}
)
loading.value = false
}
let ruleObj = (errorMessage) => {
return [
{
required: true,
validator(rule, value) {
if (value == null || value == '') return new Error(errorMessage)
return true
},
trigger: ['input', 'blur', 'change']
}
]
}
let rules = {
label: ruleObj('必填人物名称'),
prompt: ruleObj('必填英文提示词')
}
/**
* 单张生成角色图片,预览
*/
async function GenerateCharacterImage() {
// 判断lora是不是存在
let lora = ''
if (
characterData.value.lora != null &&
characterData.value.lora != '' &&
characterData.value.lora != '无'
) {
lora = `<lora:${characterData.value.lora}:${characterData.value.lora_weight}>`
}
let d = {
prompt: `${characterData.value.prompt}, ${lora}`,
width: 1024,
height: 1024
}
if (isEmpty(characterData.value.prompt)) {
message.error('请填写英文提示词')
return
}
imageLoading.value = true
await window.sd.txt2img(JSON.stringify([d]), async (value) => {
if (value.code == 0) {
message.error(value.message)
imageLoading.value = false
return
}
if (value.data && value.data.length > 0) {
let d = value.data[0]
characterData.value.show_image = d.image_path.replaceAll('\\', '/')
png_base64.value = 'data:image/png;base64,' + d.base64
console.log(characterData.value.show_image)
imageLoading.value = false
}
// 使用 Vue.nextTick 等待 Vue 更新视图
imageLoading.value = false
})
}
/**
* 选择本地图片
*/
async function SelecImage() {
await window.api.SelectFile(
[
'jpeg',
'jpg',
'png',
'gif',
'bmp',
'tiff',
'tif',
'webp',
'svg',
'raw',
'cr2',
'nef',
'arw'
],
(value) => {
if (value.code == 0) {
message.error(value.message)
return
}
png_base64.value = null
characterData.value.show_image = value.value
}
)
}
return {
characterData,
SelecImage,
alias_tags,
SaveCharacterTag,
TranslatePrompt,
loading,
rules,
formRef,
lora_options,
imageLoading,
GenerateCharacterImage,
png_base64
}
}
})
</script>
@@ -0,0 +1,86 @@
<template>
<div style="width: 500px;">
<n-form label-placement="top">
<n-form-item label="前缀名称">
<n-input v-model:value="prefixData.label" placeholder="请输入场景名称" />
</n-form-item>
<n-form-item label="前缀提示词描述">
<n-input type="textarea" :rows="3" v-model:value="prefixData.prompt" placeholder="请输入前缀提示词描述" />
</n-form-item>
<!-- <n-form-item label="MJ:风格垫图链接(sref">
<n-input v-model:value="prefixData.image_url" placeholder="请输入风格垫图链接" style="margin-right: 20px;" />
<n-image v-if="prefixData.image_url" :src="prefixData.image_url" style="width: 80px; height: 80px" />
</n-form-item>
<n-form-item label="MJ:风格迁移 cw 值(0-1000">
<n-input-number :show-button="false" v-model:value="prefixData.sref_cw" min="0" max="1000"
placeholder="请输入人物风格迁移的CW值" />
</n-form-item>
<n-form-item label="SDLora选择">
<n-input v-model:value="prefixData.lora" placeholder="请输入风格Lora" />
<n-button color="#e18a3b" style="margin-left: 20px;">选择</n-button>
</n-form-item> -->
</n-form>
<div style="text-align: right;">
<n-button type="success" @click="SavePrefixTag">保存</n-button>
</div>
</div>
</template>
<script>
import { ref, h, onMounted, defineComponent, toRaw, watch } from "vue"
import { NImage, useMessage, NButton, useDialog, NInput, NForm, NFormItem, NDynamicTags, NInputNumber } from "naive-ui";
import { v4 as uuidv4 } from "uuid";
export default defineComponent({
components: {
NImage, NButton, NInput, NForm, NFormItem, NDynamicTags, NInputNumber
},
props: ["currentPrefix", "initFunc"],
setup(props) {
let message = useMessage();
let prefixData = ref(props.currentPrefix);
// 监听数据变化
watch(() => props.currentPrefix, (value) => {
prefixData.value = value;
}, { deep: true })
onMounted(async () => {
})
async function SavePrefixTag() {
// 直接保存
// 开始保存
prefixData.value['type'] = "prefix_main";
await window.mj.SaveTagPropertyData([JSON.stringify(prefixData.value), "prefix_tags"], (value) => {
console.log(value);
if (value.code == 0) {
message.error(value.message);
return;
}
message.success(value.message);
//保存成功在initFunc
props.initFunc();
// 清楚数据
prefixData.value = {
label: null,
key: null,
type: "prefix_main",
prompt: null,
// image_url: null,
// cref_cw: 20,
// lora: null,
};
})
}
return {
prefixData,
SavePrefixTag
}
}
})
</script>
@@ -0,0 +1,191 @@
<template>
<div style="width: 500px">
<n-form label-placement="top" ref="formRef" :rules="rules" :model="sceneData">
<n-form-item label="场景名称" path="label">
<n-input
style="width: 400px"
v-model:value="sceneData.label"
placeholder="请输入场景名称"
/>
<n-checkbox style="margin-left: 20px" v-model:checked="sceneData.isShow"
>显示</n-checkbox
>
</n-form-item>
<n-form-item label="场景提示词描述(中文)">
<n-input
type="textarea"
:rows="3"
v-model:value="sceneData.chinese_prompt"
placeholder="请输入场景提示词描述"
/>
<n-button
type="info"
size="small"
style="margin-left: 20px"
@click="TranslatePrompt"
:loading="loading"
>翻译提示词</n-button
>
</n-form-item>
<n-form-item label="场景提示词描述(英文)" path="prompt">
<n-input
type="textarea"
:rows="3"
v-model:value="sceneData.prompt"
placeholder="请输入场景提示词描述"
/>
</n-form-item>
<!-- <n-form-item label="MJ:风格垫图链接(sref">
<n-input v-model:value="sceneData.image_url" placeholder="请输入风格垫图链接" style="margin-right: 20px;" />
<n-image v-if="sceneData.image_url" :src="sceneData.image_url" style="width: 80px; height: 80px" />
</n-form-item>
<n-form-item label="MJ:风格迁移 cw 值(0-1000">
<n-input-number :show-button="false" v-model:value="sceneData.sref_cw" min="0" max="1000"
placeholder="请输入人物风格迁移的CW值" />
</n-form-item>
<n-form-item label="SDLora选择">
<n-input v-model:value="sceneData.lora" placeholder="请输入风格Lora" />
<n-button color="#e18a3b" style="margin-left: 20px;">选择</n-button>
</n-form-item> -->
</n-form>
<div style="text-align: right">
<n-button type="success" @click="SaveSceneTag">保存</n-button>
</div>
</div>
</template>
<script>
import { ref, h, onMounted, defineComponent, toRaw, watch } from 'vue'
import {
NImage,
useMessage,
NButton,
useDialog,
NInput,
NForm,
NFormItem,
NDynamicTags,
NInputNumber,
NCheckbox
} from 'naive-ui'
import { v4 as uuidv4 } from 'uuid'
export default defineComponent({
components: {
NImage,
NButton,
NInput,
NForm,
NFormItem,
NDynamicTags,
NInputNumber,
NCheckbox
},
props: ['currentScene', 'initFunc'],
setup(props) {
let message = useMessage()
let sceneData = ref(props.currentScene)
let loading = ref(false)
let formRef = ref(null)
// 监听数据变化
watch(
() => props.currentScene,
(value) => {
sceneData.value = value
},
{ deep: true }
)
onMounted(async () => {})
// 保存操作
async function SaveSceneTag(e) {
e.preventDefault()
formRef.value?.validate(async (errors) => {
if (errors) {
message.error('请检查必填字段')
return
}
// 直接保存
// 开始保存
sceneData.value['type'] = 'scene_main'
await window.mj.SaveTagPropertyData(
[JSON.stringify(sceneData.value), 'scene_tags'],
(value) => {
console.log(value)
if (value.code == 0) {
message.error(value.message)
return
}
message.success(value.message)
//保存成功在initFunc
props.initFunc()
// 清楚数据
sceneData.value = {
label: null,
key: null,
type: 'scene_main',
prompt: null
// image_url: null,
// cref_cw: 20,
// lora: null,
}
}
)
})
}
// 翻译提示词
async function TranslatePrompt() {
loading.value = true
if (sceneData.value.chinese_prompt == '' || sceneData.value.chinese_prompt == null) {
message.error('请输入中文提示词')
loading.value = false
return
}
await window.api.TranslateReturnNow(
[sceneData.value.chinese_prompt, 'zh', 'en', false],
(value) => {
if (value.code == 0) {
message.error(value.message)
return
}
//
console.log(value.data)
sceneData.value.prompt = value.data[0].src
}
)
loading.value = false
}
let ruleObj = (errorMessage) => {
return [
{
required: true,
validator(rule, value) {
if (value == null || value == '') return new Error(errorMessage)
return true
},
trigger: ['input', 'blur', 'change']
}
]
}
let rules = {
label: ruleObj('必填人物名称'),
prompt: ruleObj('必填英文提示词')
}
return {
sceneData,
SaveSceneTag,
loading,
TranslatePrompt,
rules,
formRef
}
}
})
</script>
@@ -0,0 +1,327 @@
<template>
<div style="width: 500px">
<n-form label-placement="top" ref="formRef" :rules="rules" :model="styleData">
<n-form-item label="风格名称" path="label">
<n-input v-model:value="styleData.label" placeholder="请输入风格名称" />
<div style="position: absolute; left: 520px; top: 0; display: flex; flex-direction: column">
<n-image width="120" height="120" :src="png_base64 ? png_base64 : styleData.show_image" />
<n-button color="#e18a3b" style="margin-top: 5px" size="tiny" @click="SelecImage"
>本地上传图片</n-button
>
</div>
</n-form-item>
<n-form-item label="风格提示词描述(中文)">
<n-input
type="textarea"
:rows="2"
v-model:value="styleData.chinese_prompt"
placeholder="请输入风格提示词描述(中文)"
/>
<div style="width: 100px; margin-left: 20px">
<n-button type="info" size="tiny" @click="TranslatePrompt" :loading="loading"
>翻译提示词</n-button
>
<n-tooltip trigger="hover">
<template #trigger>
<n-button
color="#e18a3b"
size="tiny"
style="margin-top: 10px"
@click="GenerateStyleImage"
:loading="imageLoading"
>SD生成图片</n-button
>
</template>
使用SD出图模式生成风格图片提示词为1gril加上风格提示词
</n-tooltip>
</div>
</n-form-item>
<n-form-item label="风格提示词描述(英文)" path="prompt">
<n-input
type="textarea"
:rows="3"
v-model:value="styleData.prompt"
placeholder="请输入风格提示词描述(英文)"
/>
</n-form-item>
<n-form-item label="MJ:风格垫图链接(sref">
<n-input
v-model:value="styleData.image_url"
placeholder="请输入风格垫图链接"
style="margin-right: 20px"
/>
<n-image
v-if="styleData.image_url"
:src="styleData.image_url"
style="width: 80px; height: 80px"
/>
</n-form-item>
<n-form-item label="MJ:风格迁移 sw 值(0-1000">
<n-input-number
:show-button="false"
v-model:value="styleData.sref_sw"
min="0"
max="1000"
placeholder="请输入人物风格迁移的SW值"
/>
</n-form-item>
<n-form-item label="SDLora选择">
<n-select :options="lora_options" v-model:value="styleData.lora"> </n-select>
<n-input-number
style="width: 120px; margin-left: 10px"
placeholder="权重"
v-model:value="styleData.lora_weight"
:show-button="false"
:max="2"
:step="0.01"
:precision="2"
S
:min="0"
/>
</n-form-item>
</n-form>
<div style="text-align: right">
<n-button type="success" @click="SaveStyleTag">保存</n-button>
</div>
</div>
</template>
<script>
import { ref, h, onMounted, defineComponent, toRaw, watch } from 'vue'
import {
NImage,
useMessage,
NButton,
useDialog,
NInput,
NForm,
NFormItem,
NDynamicTags,
NInputNumber,
NSelect,
NTooltip
} from 'naive-ui'
import { isEmpty } from 'lodash'
export default defineComponent({
components: {
NImage,
NButton,
NInput,
NForm,
NFormItem,
NDynamicTags,
NInputNumber,
NSelect,
NTooltip
},
props: ['currentStyle', 'initFunc', 'lora_options'],
setup(props) {
let message = useMessage()
let styleData = ref(
props.currentStyle
? props.currentStyle
: { show_image: window.config.space_image, lora_weight: 1, sref_sw: 50, lora: '无' }
)
let loading = ref(false)
let imageLoading = ref(false)
let formRef = ref(null)
let lora_options = ref(props.lora_options)
let png_base64 = ref(null)
// 监听数据变化
watch(
() => props.currentStyle,
(value) => {
styleData.value = value
},
{ deep: true }
)
onMounted(async () => {})
async function SaveStyleTag(e) {
e.preventDefault()
formRef.value?.validate(async (errors) => {
if (errors) {
message.error('请检查必填字段')
return
}
styleData.value['type'] = 'style_main'
styleData.value['show_image'] = styleData.value.show_image
? styleData.value.show_image.split('?t')[0]
: null
// 直接保存
// 开始保存
await window.mj.SaveTagPropertyData(
[JSON.stringify(styleData.value), 'style_tags'],
(value) => {
console.log(value)
if (value.code == 0) {
message.error(value.message)
return
}
message.success(value.message)
//保存成功在initFunc
props.initFunc()
// 清楚数据
styleData.value = {
label: null,
key: null,
type: 'style_main',
prompt: null,
show_image: window.config.space_image,
image_url: null,
cref_cw: 20,
lora: '无'
}
png_base64.value = null
}
)
})
}
/**
* 翻译中文提示词
*/
async function TranslatePrompt() {
loading.value = true
if (styleData.value.chinese_prompt == '' || styleData.value.chinese_prompt == null) {
message.error('请输入中文提示词')
loading.value = false
return
}
await window.api.TranslateReturnNow(
[styleData.value.chinese_prompt, 'zh', 'en', false],
(value) => {
if (value.code == 0) {
message.error(value.message)
return
}
console.log(value.data)
styleData.value.prompt = value.data[0].src
}
)
loading.value = false
}
let ruleObj = (errorMessage) => {
return [
{
required: true,
validator(rule, value) {
if (value == null || value == '') return new Error(errorMessage)
return true
},
trigger: ['input', 'blur', 'change']
}
]
}
let rules = {
label: ruleObj('必填人物名称')
}
/**
* 生成风格图片
*/
async function GenerateStyleImage() {
if (isEmpty(styleData.value.prompt)) {
message.error('请输入提示词')
imageLoading.value = false
return
}
// 判断lora是不是存在
let lora = ''
if (
styleData.value.lora != null &&
styleData.value.lora != '' &&
styleData.value.lora != '无'
) {
lora = `<lora:${styleData.value.lora}:${styleData.value.lora_weight}>`
}
let d = {
prompt: `1gril, ${styleData.value.prompt}, ${lora}`,
width: 1024,
height: 1024
}
imageLoading.value = true
await window.sd.txt2img(JSON.stringify([d]), async (value) => {
debugger
if (value.code == 0) {
message.error(value.message)
imageLoading.value = false
return
}
if (value.data && value.data.length > 0) {
let d = value.data[0]
styleData.value.show_image =
d.image_path.replaceAll('\\', '/') + '?t=' + new Date().getTime()
png_base64.value = 'data:image/png;base64,' + d.base64
console.log(styleData.value.show_image)
imageLoading.value = false
}
// 使用 Vue.nextTick 等待 Vue 更新视图
imageLoading.value = false
})
}
/**
* 选择本地的图片应用到风格图片
*/
async function SelecImage() {
await window.api.SelectFile(
[
'jpeg',
'jpg',
'png',
'gif',
'bmp',
'tiff',
'tif',
'webp',
'svg',
'raw',
'cr2',
'nef',
'arw'
],
(value) => {
if (value.code == 0) {
message.error(value.message)
return
}
// 读取文件并转换为Base64
fetch(value.value)
.then((response) => response.blob())
.then((blob) => {
const reader = new FileReader()
reader.onloadend = () => {
png_base64.value = reader.result
}
reader.readAsDataURL(blob)
})
// 更新显示的图片
png_base64.value = null
styleData.value.show_image = value.value + '?t=' + new Date().getTime()
}
)
}
return {
styleData,
SelecImage,
SaveStyleTag,
loading,
imageLoading,
rules,
formRef,
TranslatePrompt,
lora_options,
png_base64,
GenerateStyleImage
}
}
})
</script>
@@ -0,0 +1,86 @@
<template>
<div style="width: 500px;">
<n-form label-placement="top">
<n-form-item label="后缀名称">
<n-input v-model:value="suffixData.label" placeholder="请输入后缀名称" />
</n-form-item>
<n-form-item label="后缀提示词描述">
<n-input type="textarea" :rows="3" v-model:value="suffixData.prompt" placeholder="请输入后缀提示词描述" />
</n-form-item>
<!-- <n-form-item label="MJ:风格垫图链接(sref">
<n-input v-model:value="suffixData.image_url" placeholder="请输入风格垫图链接" style="margin-right: 20px;" />
<n-image v-if="suffixData.image_url" :src="suffixData.image_url" style="width: 80px; height: 80px" />
</n-form-item>
<n-form-item label="MJ:风格迁移 cw 值(0-1000">
<n-input-number :show-button="false" v-model:value="suffixData.sref_cw" min="0" max="1000"
placeholder="请输入人物风格迁移的CW值" />
</n-form-item>
<n-form-item label="SDLora选择">
<n-input v-model:value="suffixData.lora" placeholder="请输入风格Lora" />
<n-button color="#e18a3b" style="margin-left: 20px;">选择</n-button>
</n-form-item> -->
</n-form>
<div style="text-align: right;">
<n-button type="success" @click="SavePrefixTag">保存</n-button>
</div>
</div>
</template>
<script>
import { ref, h, onMounted, defineComponent, toRaw, watch } from "vue"
import { NImage, useMessage, NButton, useDialog, NInput, NForm, NFormItem, NDynamicTags, NInputNumber } from "naive-ui";
import { v4 as uuidv4 } from "uuid";
export default defineComponent({
components: {
NImage, NButton, NInput, NForm, NFormItem, NDynamicTags, NInputNumber
},
props: ["currentSuffix", "initFunc"],
setup(props) {
let message = useMessage();
let suffixData = ref(props.currentSuffix);
// 监听数据变化
watch(() => props.currentSuffix, (value) => {
suffixData.value = value;
}, { deep: true })
onMounted(async () => {
})
async function SavePrefixTag() {
// 直接保存
// 开始保存
suffixData.value['type'] = "suffix_main";
await window.mj.SaveTagPropertyData([JSON.stringify(suffixData.value), "suffix_tags"], (value) => {
console.log(value);
if (value.code == 0) {
message.error(value.message);
return;
}
message.success(value.message);
//保存成功在initFunc
props.initFunc();
// 清楚数据
suffixData.value = {
label: null,
key: null,
type: "suffix_main",
prompt: null,
// image_url: null,
// cref_cw: 20,
// lora: null,
};
})
}
return {
suffixData,
SavePrefixTag
}
}
})
</script>
@@ -0,0 +1,118 @@
<template>
<div style="margin-top: 30px">
<n-card>
<n-tabs
class="card-tabs"
default-value="autoAnalyze"
size="large"
animated
pane-wrapper-style="margin: 0 -4px"
pane-style="padding-left: 4px; padding-right: 4px; box-sizing: border-box;"
>
<n-tab-pane name="autoAnalyze" tab="自动分析">
<n-button color="#ee7959" size="medium" @click="autoAnalyzeCharacter" :loading="loading"
>自动分析</n-button
>
<n-input
style="margin-top: 20px"
type="textarea"
:autosize="{
minRows: 10,
maxRows: 10
}"
placeholder="自动分析的数据"
v-model:value="autoCharacterString"
></n-input>
<div>
<div style="color: red">注意上面推理的不满意可以手动修改</div>
<div style="color: red">自动推理的的人物只能对内置的四种模式生效</div>
</div>
<div style="text-align: right">
<n-button type="success" @click="SaveAutoAnalyzeCharacter">保存</n-button>
</div>
</n-tab-pane>
<n-tab-pane name="Analyze" tab="手动设置标签集">
<CharacterTags :height="height"></CharacterTags>
</n-tab-pane>
</n-tabs>
</n-card>
</div>
</template>
<script setup>
import { ref, h, onMounted, toRaw } from 'vue'
import {
NImage,
useMessage,
NButton,
useDialog,
NInput,
NCard,
NTabs,
NTabPane,
NTree
} from 'naive-ui'
import { v4 as uuidv4 } from 'uuid'
import CharacterTags from './CharacterTags.vue'
import { useReverseManageStore } from '../../../../../../stores/reverseManage'
let reverseManageStore = useReverseManageStore()
let autoCharacterString = ref('')
let props = defineProps({
word: undefined,
height: undefined
})
let message = useMessage()
let word = ref(props.word)
let height = ref(props.height)
let loading = ref(false)
onMounted(async () => {
autoCharacterString.value = reverseManageStore.selectBookTask.autoAnalyzeCharacter
})
/**
* 自动分析角色
*/
async function autoAnalyzeCharacter() {
if (word.value == '' || word.value == null) {
message.error('请输入要分析的文案')
return
}
loading.value = true
await window.api.AutoAnalyzeCharacter(toRaw(word.value), (value) => {
loading.value = false
if (value.code == 0) {
message.error(value.message)
return
}
autoCharacterString.value = value.data
})
}
/**
* 保存自动分析的设置
*/
async function SaveAutoAnalyzeCharacter() {
if (autoCharacterString.value == '' || autoCharacterString.value == null) {
message.error('没有分析出来的人物信息')
return
}
// 保存信息
reverseManageStore.selectBookTask.autoAnalyzeCharacter = autoCharacterString.value
let res = await window.db.UpdateBookTaskData(reverseManageStore.selectBookTask.id, {
autoAnalyzeCharacter: autoCharacterString.value
})
if (res.code == 1) {
message.success(res.message)
} else {
message.error(res.message)
}
}
</script>
<style scoped>
.card-tabs .n-tabs-nav--bar-type {
padding-left: 4px;
}
</style>
@@ -0,0 +1,470 @@
<template>
<div>
<n-split direction="horizontal" :max="0.3" :min="0.18" :default-size="0.18">
<template #1>
<div id="tree_define_content" style="display: flex; overflow-y: auto">
<n-tree :node-props="nodeProps" style="width: 200px" block-line :data="treeData" />
</div>
</template>
<template #2>
<div style="margin-left: 20px">
<AddCharacterTag
v-if="currentType.startsWith('character')"
:currentCharacter="currentCharacter"
:currentTags="currentTags"
:initFunc="InitData"
:lora_options="lora_options"
/>
<AddStyleTags
v-else-if="currentType.startsWith('style')"
:currentStyle="currentStyle"
:initFunc="InitData"
:lora_options="lora_options"
/>
<AddSceneTags
v-else-if="currentType.startsWith('scene')"
:currentScene="currentScene"
:initFunc="InitData"
/>
<AddPrefixTags
v-else-if="currentType.startsWith('prefix')"
:currentPrefix="currentPrefix"
:initFunc="InitData"
/>
<AddSuffixTags
v-else-if="currentType.startsWith('suffix')"
:currentSuffix="currentSuffix"
:initFunc="InitData"
/>
<n-empty v-else description="是一个飞机">
<template #icon>
<n-icon>
<airplane />
</n-icon>
</template>
</n-empty>
</div>
</template>
</n-split>
</div>
</template>
<script>
import { ref, h, onMounted, defineComponent, toRaw } from 'vue'
import {
NImage,
useMessage,
NButton,
useDialog,
NInput,
NCard,
NTabs,
NTabPane,
NTree,
NSplit,
NIcon,
NEmpty
} from 'naive-ui'
import { DEFINE_STRING } from '../../../../../../define/define_string'
import { v4 as uuidv4 } from 'uuid'
import AddCharacterTag from './AddCharacterTag.vue'
import AddStyleTags from './AddStyleTags.vue'
import AddSceneTags from './AddSceneTags.vue'
import AddPrefixTags from './AddPrefixTags.vue'
import AddSuffixTags from './AddSuffixTags.vue'
import { Trash, Airplane } from '@vicons/ionicons5'
export default defineComponent({
components: {
NImage,
NButton,
NInput,
NCard,
NTabs,
NTabPane,
NTree,
NSplit,
AddCharacterTag,
AddStyleTags,
AddSceneTags,
AddPrefixTags,
AddSuffixTags,
NIcon,
Trash,
NEmpty,
Airplane
},
props: ['height'],
setup(props) {
let message = useMessage()
let currentCharacter = ref({})
let currentStyle = ref({})
let currentScene = ref({})
let currentPrefix = ref({})
let currentSuffix = ref({})
let currentTags = ref([])
let currentType = ref('default')
let lora_options = ref([])
let treeData = ref([
{
label: '人物',
key: uuidv4(),
children: [],
type: 'character',
suffix: () =>
h(
NButton,
{
text: true,
color: '#e18a3b',
onClick: (e) => {
AddCharacterTags(e)
}
},
{ default: () => '新加' }
)
},
{
label: '场景',
key: uuidv4(),
children: [],
type: 'scene',
suffix: () =>
h(
NButton,
{
text: true,
color: '#e18a3b',
onClick: (e) => {
AddSceneTags(e)
}
},
{ default: () => '新加' }
)
},
{
label: '风格预设',
key: uuidv4(),
children: [],
type: 'style',
suffix: () =>
h(
NButton,
{
text: true,
color: '#e18a3b',
onClick: (e) => {
AddStyleTags(e)
}
},
{ default: () => '新加' }
)
},
{
label: '前缀',
key: uuidv4(),
children: [],
type: 'prefix',
suffix: () =>
h(
NButton,
{
text: true,
color: '#e18a3b',
onClick: (e) => {
AddPrefixTags(e)
}
},
{ default: () => '新加' }
)
},
{
label: '后缀',
key: uuidv4(),
children: [],
type: 'suffix',
suffix: () =>
h(
NButton,
{
text: true,
color: '#e18a3b',
onClick: (e) => {
AddSuffixTags(e)
}
},
{ default: () => '新加' }
)
}
])
/**
* 初始化标签数据
*/
async function InitData() {
// 获取动态配置里面是不是又标签数(有加载,没有设置默认值)
await window.mj.GetTagDataByTypeAndProperty(['dynamic', null], (value) => {
if (value.code == 0) {
message.error(value.message)
return
}
// 判断是不是包含指定的属性
if (value.data.hasOwnProperty('character_tags')) {
treeData.value[0].children = value.data.character_tags
}
if (value.data.hasOwnProperty('scene_tags')) {
treeData.value[1].children = value.data.scene_tags
}
if (value.data.hasOwnProperty('style_tags')) {
treeData.value[2].children = value.data.style_tags
}
if (value.data.hasOwnProperty('prefix_tags')) {
treeData.value[3].children = value.data.prefix_tags
}
if (value.data.hasOwnProperty('suffix_tags')) {
treeData.value[4].children = value.data.suffix_tags
}
for (let i = 0; i < treeData.value.length; i++) {
treeData.value[i].children.map((item) => {
item.suffix = () =>
h(
NButton,
{
text: true,
type: 'error',
size: 'medium',
onClick: (e) => {
DeleteTags(e, item)
}
},
{ default: () => h(NIcon, null, { default: () => h(Trash) }) }
)
})
}
})
// 加载lora数据
// 获取当前mj配置信息
await window.api.GetDefineConfigJsonByProperty(
JSON.stringify(['sd_setting', 'lora', false, []]),
(value) => {
if (value.code == 0) {
message.error(value.message)
return
}
// 判断是不是有数据
if (value.data) {
for (let i = 0; i < value.data.length; i++) {
const element = value.data[i]
lora_options.value.push({
label: element.name,
value: element.name
})
}
}
}
)
}
onMounted(async () => {
SetAutoHeight()
await InitData()
})
// 设置标签集的自动高度,传一个外面的高度进来
function SetAutoHeight() {
let div = document.getElementById('tree_define_content')
div.style.height = props.height - 180 + 'px'
}
/**
* 删除指定的数据
*/
async function DeleteTags(e, value) {
if (e) {
e.stopPropagation()
}
let type = value.type.replace('main', 'tags')
await window.mj.DeleteTagPropertyData([value.key, type], async (value) => {
if (value.code == 0) {
message.error(value.message)
return
}
message.success(value.message)
await InitData()
})
}
/**
* 新加人物标签
*/
async function AddCharacterTags(e) {
if (e) {
e.stopPropagation()
}
// 初始化值,然后赋值给当前添加人物的组件
currentCharacter.value = {
label: null,
key: uuidv4(),
children: [],
type: 'character_main',
chinese_prompt: null,
prompt: null,
image_url: null,
show_image: window.config.space_image,
cref_cw: 50,
lora: '无',
lora_weight: 1,
isShow: false
}
currentTags.value = []
currentType.value = 'character_main'
}
/**
* 新加场景标签
*/
function AddSceneTags(e) {
if (e) {
e.stopPropagation()
}
currentScene.value = {
label: null,
key: uuidv4(),
type: 'scene_main',
prompt: null,
isShow: false
}
currentType.value = 'scene_main'
}
/**
* 新加 风格迁移预设
* @param {*} e
*/
function AddStyleTags(e) {
if (e) {
e.stopPropagation()
}
currentStyle.value = {
label: null,
key: uuidv4(),
type: 'style_main',
show_image: window.config.space_image,
prompt: null,
image_url: null,
sref_sw: 50,
lora: '无',
lora_weight: 1
}
}
/**
* 添加前缀
*/
function AddPrefixTags(e) {
if (e) {
e.stopPropagation()
}
currentPrefix.value = {
label: null,
key: uuidv4(),
type: 'prefix_main',
prompt: null
}
}
/**
* 添加后缀
* @param {*} e
*/
function AddSuffixTags(e) {
if (e) {
e.stopPropagation()
}
currentSuffix.value = {
label: null,
key: uuidv4(),
type: 'suffix_main',
prompt: null
}
}
function nodeProps({ option }) {
return {
onClick() {
// 执行切换组件和数据
if (option.type == 'style') {
currentType.value = 'style'
AddStyleTags(null)
return
} else if (option.type == 'scene') {
AddSceneTags(null)
currentType.value = 'scene'
// message.error("主节点不允许操作");
return
} else if (option.type == 'character') {
AddCharacterTags(null)
currentType.value = 'character'
return
} else if (option.type == 'prefix') {
AddPrefixTags(null)
currentType.value = 'prefix'
return
} else if (option.type == 'suffix') {
AddSuffixTags(null)
currentType.value = 'suffix'
return
} else if (option.type == 'min') {
message.error('别名节点不允许操作')
} else {
console.log(option)
currentType.value = option.type
// 修改当前属性
if (currentType.value.startsWith('character')) {
// 修改tags的值
currentCharacter.value = option
if (!currentCharacter.value.hasOwnProperty('isShow')) {
currentCharacter.value.isShow = false
}
currentTags.value = option.children.map((item) => {
return item.label
})
} else if (currentType.value.startsWith('scene')) {
currentScene.value = option
} else if (currentType.value.startsWith('style')) {
currentStyle.value = option
} else if (currentType.value.startsWith('prefix')) {
currentPrefix.value = option
} else if (currentType.value.startsWith('suffix')) {
currentSuffix.value = option
} else {
message.error('未知的类型')
}
}
}
}
}
return {
treeData,
AddCharacterTags,
AddSceneTags,
AddStyleTags,
nodeProps,
currentCharacter,
currentTags,
InitData,
currentType,
currentStyle,
currentScene,
currentPrefix,
currentSuffix,
lora_options
}
}
})
</script>
@@ -38,7 +38,7 @@ export default defineComponent({
{
title: 'No.',
key: 'name',
width: 80,
width: 50,
fixed: 'left'
},
{
@@ -145,7 +145,7 @@ export default defineComponent({
debugger
// 视口的高度
let height = window.innerHeight
maxHeight.value = height - 120
maxHeight.value = height - 100
}
return {
+18 -16
View File
@@ -1,20 +1,22 @@
<template>
<div style="margin-bottom: 5px">
<ManageBookButton />
</div>
<div style="margin-right: 5px">
<n-data-table
:columns="columns"
:size="softwareStore.softWare.reverse_data_table_size"
:data="reverseManageStore.bookData"
:pagination="pagination"
:bordered="false"
:remote="true"
:striped="softwareStore.softWare.reverse_show_book_striped"
:scroll-x="1000"
:row-props="rowProps"
:max-height="maxHeight"
/>
<div>
<div style="margin-bottom: 5px">
<ManageBookButton />
</div>
<div style="margin-right: 5px">
<n-data-table
:columns="columns"
:size="softwareStore.softWare.reverse_data_table_size"
:data="reverseManageStore.bookData"
:pagination="pagination"
:bordered="false"
:remote="true"
:striped="softwareStore.softWare.reverse_show_book_striped"
:scroll-x="1000"
:row-props="rowProps"
:max-height="maxHeight"
/>
</div>
</div>
</template>
@@ -1,11 +1,18 @@
<template>
<div style="height: 100%; display: flex; width: 100%; overflow: auto">
<div v-if="true" class="left_container" style="flex: 10; min-width: 500px">
<ManageBookDetailButton />
<ManageBookReverseTable style="margin-top: 5px" />
</div>
<div v-if="softwareStore.show_logger">
<n-divider vertical style="height: 100%"></n-divider>
<div style="height: 100%; display: flex; width: 100%; overflow: auto; padding: 0; margin: 0">
<div>
<div v-if="reverseManageStore.selectBook.type == bookType.MJ_REVERSE">
<div v-if="true" class="left_container" style="flex: 10; min-width: 500px">
<ManageBookDetailButton />
<ManageBookReverseTable style="margin-top: 5px" />
</div>
<div v-if="softwareStore.show_logger">
<n-divider vertical style="height: 100%"></n-divider>
</div>
</div>
<OriginalMainPage
v-else-if="reverseManageStore.selectBook.type == bookType.ORIGINAL"
></OriginalMainPage>
</div>
<div v-if="softwareStore.show_logger" class="right_container">
<ManageBookShowLogger />
@@ -21,16 +28,18 @@ import ManageBookReverseTable from './MJReverse/ManageBookReverseTable.vue'
import ManageBookShowLogger from './Components/ManageBook/ManageBookShowLogger.vue'
import { useSoftwareStore } from '../../../../stores/software'
import { DEFINE_STRING } from '../../../../define/define_string'
import { DialogType } from '../../../../define/enum/bookEnum'
import { BookType, DialogType } from '../../../../define/enum/bookEnum'
import { ResponseMessageType } from '../../../../define/enum/softwareEnum'
import { useReverseManageStore } from '../../../../stores/reverseManage'
import OriginalMainPage from './Original/OriginalMainPage.vue'
export default defineComponent({
components: {
ManageBookDetailButton,
ManageBookReverseTable,
ManageBookShowLogger,
NDivider
NDivider,
OriginalMainPage
},
setup() {
@@ -38,6 +47,7 @@ export default defineComponent({
let message = useMessage()
let softwareStore = useSoftwareStore()
let reverseManageStore = useReverseManageStore()
let bookType = ref(BookType)
onMounted(async () => {
// 添加一个总的监听器,主要监听后端数据变化,修改后端数据后,前端数据也会随之变化
if (!window['mainReturn']) {
@@ -89,8 +99,6 @@ export default defineComponent({
})
function MJImageResponse(value) {
debugger
console.log(value)
//开始修改
if (value.data) {
let dataIndex = reverseManageStore.selectBookTaskDetail.findIndex(
@@ -179,7 +187,7 @@ export default defineComponent({
}
}
return { softwareStore, reverseManageStore }
return { softwareStore, reverseManageStore, bookType }
}
})
</script>
@@ -0,0 +1,40 @@
<template>
<div
v-if="
reverseManageStore.selectBookTask.imageCategory == 'sd' ||
reverseManageStore.selectBookTask.imageCategory == 'flux-forge'
"
>
<n-checkbox
style="margin-left: 5px; height: 100%"
:checked="row.adetailer"
label="修脸"
@update:checked="onUpdateChecked"
/>
</div>
<div v-else-if="reverseManageStore.selectBookTask.imageCategory == 'mj'"></div>
<div v-else-if="reverseManageStore.selectBookTask.imageCategory == 'd3'"></div>
</template>
<script setup>
import { ref } from 'vue'
import { NCheckbox } from 'naive-ui'
import { useSoftwareStore } from '../../../../../stores/software'
import { useReverseManageStore } from '../../../../../stores/reverseManage'
let softwareStore = useSoftwareStore()
let reverseManageStore = useReverseManageStore()
let props = defineProps({
initData: undefined,
index: undefined
})
let row = ref(props.initData)
async function onUpdateChecked(value) {
console.log(value)
row.value.adetailer = value
let res = await window.db.UpdateBookTaskDetailData(row.value.id, {
adetailer: value
})
window.api.showGlobalMessage(res)
}
</script>
@@ -0,0 +1,199 @@
<template>
<div style="width: 100%">
<div style="width: 100%">
<div style="height: 65px; overflow-y: auto; width: 100%">
<span>人物</span>
<n-tag
v-for="(character, index) in characterTags"
v-model:checked="character.checked"
checkable
@click="UpdateCharacterTagsDebounced(character, index)"
size="small"
style="margin: 0 3px 3px 0"
>
{{ character.label }}
</n-tag>
</div>
<div style="margin-top: 5px; height: 65px; overflow-y: auto; width: 100%">
<span>场景</span>
<n-tag
v-for="(scene, index) in sceneTags"
v-model:checked="scene.checked"
checkable
@click="UpdateSceneTagsDebounced(scene, index)"
size="small"
style="margin: 0 3px 3px 0"
>
{{ scene.label }}
</n-tag>
</div>
</div>
<!-- <div v-else>
<div>
<div>人物</div>
<n-select
style="width: 190px"
size="tiny"
:render-tag="renderTag"
v-model:value="select_character_tags"
multiple
filterable
:options="characterTags"
placeholder="选择人物"
@update:value="UpdateCharacterSelect"
/>
</div>
<div style="margin-top: 3px">
<div>场景</div>
<n-select
style="width: 190px"
size="tiny"
:render-tag="renderTag"
v-model:value="select_scene_tags"
multiple
filterable
:options="sceneTags"
placeholder="选择场景"
@update:value="UpdateSceneSelect"
/>
</div>
</div> -->
</div>
</template>
<script setup>
import { ref, onMounted, watch, h } from 'vue'
import { NSelect, NTag, useMessage } from 'naive-ui'
import { useSoftwareStore } from '../../../../../stores/software'
import { useReverseManageStore } from '../../../../../stores/reverseManage'
import { debounce } from 'lodash'
let softwareStore = useSoftwareStore()
let reverseManageStore = useReverseManageStore()
let message = useMessage()
let props = defineProps({
initData: undefined,
index: undefined
})
let row = ref(props.initData)
let characterTags = ref([])
let sceneTags = ref([])
// 存储当权选择的tag数据
let select_character_tags = ref([])
let select_scene_tags = ref([])
watch(
() => row.value.characterTags,
(value) => {
characterTags.value = characterTags.value?.map((item) => {
return {
id: item.id,
value: item.id,
label: item.label,
checked: value && value.includes(item.id) ? true : false
}
})
},
{ deep: true }
)
watch(
() => reverseManageStore.characterTags,
(value) => {
characterTags.value = value?.map((item) => {
return {
id: item.id,
value: item.id,
label: item.label,
checked: row.value.characterTags && row.value.characterTags.includes(item.id) ? true : false
}
})
},
{ deep: true }
)
watch(
() => reverseManageStore.sceneTags,
(value) => {
sceneTags.value = value?.map((item) => {
return {
id: item.id,
value: item.id,
label: item.label,
checked: row.value.sceneTags && row.value.sceneTags.includes(item.id) ? true : false
}
})
},
{ deep: true }
)
onMounted(() => {})
// 选择人物时更行的方法
let UpdateCharacterTagsDebounced = debounce(UpdateCharacterTags, 500)
async function UpdateCharacterTags(character, index) {
debugger
// 开始保存,获取所有的选中的人物
let checkedCharacterTags = characterTags.value
.filter((item) => item.checked)
.map((item) => item.id)
// 开始保存
let findIndex = reverseManageStore.selectBookTaskDetail.findIndex(
(item) => item.id == row.value.id
)
if (findIndex != -1) {
reverseManageStore.selectBookTaskDetail[findIndex].characterTags = checkedCharacterTags
}
let res = await window.db.UpdateBookTaskDetailData(row.value.id, {
characterTags: checkedCharacterTags
})
if (res.code == 0) {
message.error(res.message)
}
}
let UpdateSceneTagsDebounced = debounce(UpdateSceneTags, 500)
async function UpdateSceneTags(scene, index) {
debugger
console.log('1233', scene, index)
// 开始保存,获取所有的选中的场景
let checkedSceneTags = sceneTags.value.filter((item) => item.checked).map((item) => item.id)
// 开始保存
let findIndex = reverseManageStore.selectBookTaskDetail.findIndex(
(item) => item.id == row.value.id
)
if (findIndex != -1) {
reverseManageStore.selectBookTaskDetail[findIndex].sceneTags = checkedSceneTags
}
let res = await window.db.UpdateBookTaskDetailData(row.value.id, {
sceneTags: checkedSceneTags
})
if (res.code == 0) {
message.error(res.message)
}
}
// 渲染tag
const renderTag = ({ option, handleClose }) => {
return h(
NTag,
{
size: 'tiny',
type: 'success',
closable: true,
onClose: (e) => {
e.stopPropagation()
handleClose()
// 移除当前option
let k = option.key
characterTags.value.forEach((item) => {
if (item.key == k) {
item.checked = false
}
})
}
},
{ default: () => option.label }
)
}
</script>
@@ -0,0 +1,163 @@
<template>
<div>
<n-dropdown trigger="hover" :options="translate_options" @select="TranslateSelect">
<n-button
size="tiny"
:color="softwareStore.SoftColor.BROWN_YELLOW"
style="margin-right: 5px"
@click="TranslateSelect('english')"
>翻译英文</n-button
>
</n-dropdown>
<n-button
size="tiny"
:color="softwareStore.SoftColor.BROWN_YELLOW"
style="margin-right: 5px"
@click="SinglePrompt"
>单句推理</n-button
>
<n-button
:color="softwareStore.SoftColor.BROWN_YELLOW"
style="margin-right: 5px"
size="tiny"
@click="NextGptPrompt"
>下推理</n-button
>
<n-input
size="tiny"
placeholder="请生成或推理提示词"
type="textarea"
v-model:value="row.gptPrompt"
@input="InputDebounced"
:autosize="{ minRows: 6, maxRows: 6 }"
/>
</div>
</template>
<script setup>
import { ref } from 'vue'
import { NInput, NButton, NDropdown, useMessage } from 'naive-ui'
import { debounce } from 'lodash'
import { TranslateType } from '../../../../../define/enum/translate'
import { DEFINE_STRING } from '../../../../../define/define_string'
import { useSoftwareStore } from '../../../../../stores/software'
import { useReverseManageStore } from '../../../../../stores/reverseManage'
import { OperateBookType } from '../../../../../define/enum/bookEnum'
let props = defineProps({
initData: undefined,
index: undefined
})
let message = useMessage()
let row = ref(props.initData)
let softwareStore = useSoftwareStore()
let reverseManageStore = useReverseManageStore()
let translate_options = ref([
{ label: '翻译中文', key: 'chinese' },
{ label: '下翻译 英文 2 中文', key: 'down_english_chinese' },
{ label: '下翻译 中文 2 英文', key: 'down_chinese_english' }
])
let InputDebounced = debounce(handleInput, 500)
async function handleInput() {
console.log('开始修改')
let res = await window.db.UpdateBookTaskDetailData(row.value.id, {
gptPrompt: row.value.gptPrompt
})
if (res.code == 0) {
message.error(res.message)
}
}
// 单句翻译和下翻译
async function TranslateSelect(key) {
let translateData = []
if (key == 'english') {
let tempData = {
text: row.value.gptPrompt,
from: 'zh',
to: 'en',
type: TranslateType.GPT_PROMPT_TRANSLATE,
isSplit: false,
bookTaskDetailId: row.value.id,
responseMessgeName: DEFINE_STRING.TRANSLATE.GPT_PROMPT_TRANSLATE_RETRUN
}
translateData.push(tempData)
} else if (key == 'chinese') {
let tempData = {
text: row.value.gptPrompt,
from: 'en',
to: 'zh',
type: TranslateType.GPT_PROMPT_TRANSLATE,
isSplit: false,
bookTaskDetailId: row.value.id,
responseMessgeName: DEFINE_STRING.TRANSLATE.GPT_PROMPT_TRANSLATE_RETRUN
}
translateData.push(tempData)
} else if (key == 'down_english_chinese') {
// 讲下面的所有的进行翻译
for (let i = 0; i < reverseManageStore.selectBookTaskDetail.length; i++) {
const element = reverseManageStore.selectBookTaskDetail[i]
if (i >= props.index) {
translateData.push({
text: row.value.gptPrompt,
from: 'en',
to: 'zh',
type: TranslateType.GPT_PROMPT_TRANSLATE,
isSplit: false,
bookTaskDetailId: element.id,
responseMessgeName: DEFINE_STRING.TRANSLATE.GPT_PROMPT_TRANSLATE_RETRUN
})
}
}
} else if (key == 'down_chinese_english') {
for (let i = 0; i < reverseManageStore.selectBookTaskDetail.length; i++) {
const element = reverseManageStore.selectBookTaskDetail[i]
if (i >= props.index) {
translateData.push({
text: row.value.gptPrompt,
from: 'zh',
to: 'en',
type: TranslateType.GPT_PROMPT_TRANSLATE,
isSplit: false,
bookTaskDetailId: element.id,
responseMessgeName: DEFINE_STRING.TRANSLATE.GPT_PROMPT_TRANSLATE_RETRUN
})
}
}
} else {
message.error('未知的翻译类型')
}
if (translateData.length <= 0) {
message.error('没有需要翻译的数据')
return
}
// 开始提交翻译任务
softwareStore.spin.spinning = true
softwareStore.spin.tip = `翻译中 ... 0 / ${translateData.length}`
// 开始翻译
let res = await window.translate.TranslateNowReturn(translateData)
softwareStore.spin.spinning = false
window.api.showGlobalMessageDialog(res)
}
// 单句推理
async function SinglePrompt() {
softwareStore.spin.spinning = true
softwareStore.spin.tip = '即将开始执行推理提示词任务,请稍等。。。'
let res = await window.book.OriginalGetPrompt(row.value.id, OperateBookType.BOOKTASKDETAIL, true)
softwareStore.spin.spinning = false
window.api.showGlobalMessage(res)
}
// 下推理
async function NextGptPrompt() {
softwareStore.spin.spinning = true
softwareStore.spin.tip = '即将开始执行推理提示词任务,请稍等。。。'
let res = await window.book.OriginalGetPrompt(row.value.id, OperateBookType.UNDERBOOKTASK, true)
softwareStore.spin.spinning = false
window.api.showGlobalMessage(res)
}
</script>
@@ -0,0 +1,156 @@
<template>
<div style="display: flex">
<span style="margin-right: 5px">人物/场景</span>
<!-- <n-select
style="width: 75px"
size="tiny"
:options="characterSelectMoedlOptions"
@update:value="UpdateImageGenerateCategory"
v-model:value="softwareStore.globalSetting.character_select_model"
placeholder="选择模式"
></n-select> -->
<n-popover trigger="hover">
<template #trigger>
<n-button
text
color="#e18a3b"
style="margin-left: 5px; font-size: 24px"
@click="RefreshTagData"
>
<n-icon>
<ReloadCircle />
</n-icon>
</n-button>
</template>
<span>刷新任务标签数据</span>
</n-popover>
<n-popover trigger="hover">
<template #trigger>
<n-button
size="tiny"
color="#c0d695"
style="margin-left: 5px; font-size: 24px"
text
@click="AutoMatchUser"
>
<n-icon>
<ExtensionPuzzle />
</n-icon>
</n-button>
</template>
<span>自动匹配人物</span>
</n-popover>
</div>
</template>
<script setup>
import { ref, toRaw } from 'vue'
import {
NImage,
useMessage,
NButton,
useDialog,
NInput,
NTag,
NSelect,
NIcon,
NPopover
} from 'naive-ui'
import { ReloadCircle, ExtensionPuzzle } from '@vicons/ionicons5'
import { BookTagSelectType } from '../../../../../define/enum/bookEnum'
import { useSoftwareStore } from '../../../../../stores/software'
import { useReverseManageStore } from '../../../../../stores/reverseManage'
let dialog = useDialog()
let message = useMessage()
let softwareStore = useSoftwareStore()
let reverseManageStore = useReverseManageStore()
let characterSelectMoedlOptions = ref([
{ label: '标签', value: BookTagSelectType.TAG },
{ label: '下拉', value: BookTagSelectType.DROP }
])
// 重新加载预设数据
async function RefreshTagData() {
softwareStore.spin.spinning = true
softwareStore.spin.tip = '正在刷新任务标签数据,请稍等...'
let res = await reverseManageStore.GetPresetData()
softwareStore.spin.spinning = false
if (res.code == 1) {
message.success('刷新任务标签数据成功')
} else {
message.error(res.message)
}
}
// 自动匹配人物
async function AutoMatchUser() {
softwareStore.spin.spinning = true
softwareStore.spin.tip = '正在自动匹配人物,请稍等...'
await window.mj.AutoMatchUser(
JSON.stringify(reverseManageStore.selectBookTaskDetail),
async (res) => {
softwareStore.spin.spinning = false
if (res.code == 1) {
message.success('自动匹配人物成功')
// 这边修改数据
console.log('AutoMatchUser', res.data)
for (let i = 0; i < res.data.length; i++) {
let element = res.data[i]
let findIndex = reverseManageStore.selectBookTaskDetail.findIndex(
(item) => item.id == element.id
)
if (findIndex != -1) {
// TODO 这边后面修改数据库,这边要修改数据,讲key 修改为id
reverseManageStore.selectBookTaskDetail[findIndex].characterTags =
element.match_character.map((item) => item.key)
// TODO 后面修改为后端报错
// 手动保存一下
let res = await window.db.UpdateBookTaskDetailData(element.id, {
characterTags: element.match_character.map((item) => item.key)
})
if (res.code == 0) {
message.error(res.message)
}
}
}
} else {
message.error(res.message)
}
}
)
}
// 更新图片生成类型
async function UpdateImageGenerateCategory(value, options) {
softwareStore.globalSetting.character_select_model = value
// 同步修改下数据库文件
let saveRes = await window.setting.SaveSoftWareSetting({
globalSetting: JSON.stringify(toRaw(softwareStore.globalSetting))
})
if (saveRes.code == 0) {
message.error(saveRes.message)
return
}
// 保存文件
window.api.ModifySampleSetting(
JSON.stringify({
character_select_model: value
}),
(value) => {
if (value.code == 1) {
window.api.getSettingDafultData((value) => {
window.config = value
softwareStore.globalSetting = value
})
message.success('标签选择方式修改成功')
} else {
message.error(value.message)
}
}
)
}
</script>
@@ -0,0 +1,291 @@
<template>
<div>
<span style="margin-right: 5px">推理提示词</span>
<n-popover trigger="hover">
<template #trigger>
<n-button
:color="softwareStore.SoftColor.ZHUYANTUO"
style="margin-left: 5px"
size="tiny"
@click="AddPrefix"
>通用前缀</n-button
>
</template>
<span>添加通用前缀只能添加英文并且通用前缀不为空推理会自动添加前缀</span>
</n-popover>
<n-popover trigger="hover">
<template #trigger>
<n-button
:color="softwareStore.SoftColor.ZHUYANTUO"
style="margin-left: 5px"
size="tiny"
@click="AddSuffix"
>通用后缀</n-button
>
</template>
<span>添加通用后缀只能添加英文并且通用后缀不为空推理会自动添加后缀</span>
</n-popover>
<n-popover trigger="hover">
<template #trigger>
<n-button
:color="softwareStore.SoftColor.ZHUYANTUO"
style="margin-left: 5px"
size="tiny"
@click="SelectStyle"
>风格</n-button
>
</template>
<span>选择生成图片的风格</span>
</n-popover>
<n-popover trigger="hover">
<template #trigger>
<span>
<n-dropdown trigger="hover" :options="translate_options" @select="TranslateAll">
<n-button
:color="softwareStore.SoftColor.ZHUYANTUO"
style="margin-left: 5px"
size="tiny"
@click="TranslateAll('english')"
>一键翻译</n-button
>
</n-dropdown>
</span>
</template>
<span
>将下面的推理提示词或者是手写的提示词翻译成英文只会翻译存在中文的数据下面的翻译中文会将所有的推理提示词翻译为中文全部都会翻译</span
>
</n-popover>
</div>
</template>
<script setup>
import { ref, h, toRaw } from 'vue'
import { NPopover, NButton, NDropdown, useMessage, useDialog } from 'naive-ui'
import { useReverseManageStore } from '../../../../../stores/reverseManage'
import { useSoftwareStore } from '../../../../../stores/software'
import SelectImageStyle from '../../Components/SelectImageStyle.vue'
import InputDialogContent from '../../Original/Components/InputDialogContent.vue'
import { ContainsChineseOrPunctuation } from '../../../../../define/Tools/common'
import { isEmpty } from 'lodash'
import { TranslateType } from '../../../../../define/enum/translate'
import { DEFINE_STRING } from '../../../../../define/define_string'
let softwareStore = useSoftwareStore()
let reverseManageStore = useReverseManageStore()
let message = useMessage()
let dialog = useDialog()
let translate_options = ref([{ label: '一键 英文 2 中文', key: 'chinese' }])
let selectStyleRef = ref(null)
let prefixPromptRef = ref(null)
let suffixPromptRef = ref(null)
// 添加/修改通用前缀
async function AddPrefix() {
// 判断当前数据是不是存在
// 处理数据。获取当前的所有的数据
let dialogWidth = 400
let dialogHeight = 150
dialog.create({
title: '添加前缀',
showIcon: false,
closeOnEsc: false,
content: () =>
h(InputDialogContent, {
ref: prefixPromptRef,
initData: reverseManageStore.selectBookTask.prefixPrompt,
placeholder: '请输入前缀'
}),
style: `width : ${dialogWidth}px; min-height : ${dialogHeight}px`,
maskClosable: false,
onClose: async () => {
let prefix = prefixPromptRef.value.data
reverseManageStore.selectBookTask.prefixPrompt = prefix
let res = await window.db.UpdateBookTaskData(reverseManageStore.selectBookTask.id, {
prefixPrompt: prefix
})
if (res.code == 1) {
message.success('修改小说通用前缀成功')
} else {
message.error(res.message)
}
}
})
}
// 添加/修改通用前缀
async function AddSuffix() {
// 判断当前数据是不是存在
// 处理数据。获取当前的所有的数据
let dialogWidth = 400
let dialogHeight = 150
dialog.create({
title: '添加后缀',
showIcon: false,
closeOnEsc: false,
content: () =>
h(InputDialogContent, {
ref: suffixPromptRef,
initData: reverseManageStore.selectBookTask.suffixPrompt,
placeholder: '请输入后缀'
}),
style: `width : ${dialogWidth}px; min-height : ${dialogHeight}px`,
maskClosable: false,
onClose: async () => {
let suffix = suffixPromptRef.value.data
reverseManageStore.selectBookTask.suffixPrompt = suffix
let res = await window.db.UpdateBookTaskData(reverseManageStore.selectBookTask.id, {
suffixPrompt: suffix
})
if (res.code == 1) {
message.success('修改小说人物通用后缀成功')
} else {
message.error(res.message)
}
}
})
}
// 一键 中文翻译英文
async function TranslateChineseToEnglish() {
let da = dialog.warning({
title: '一键翻译英文提示',
content: `一键翻译英文,会将所有的分镜推理提示词中的中文翻译为英文,没有中文不翻译,当前执行并发为 ${softwareStore.globalSetting.task_number},在执行翻译时不可停止,是否继续?`,
positiveText: '继续',
negativeText: '取消',
onPositiveClick: async () => {
da?.destroy()
// 获取数据
let translateData = []
for (let i = 0; i < reverseManageStore.selectBookTaskDetail.length; i++) {
const element = reverseManageStore.selectBookTaskDetail[i]
if (isEmpty(element.gptPrompt)) {
continue
}
// 判断是不是有中文
if (!ContainsChineseOrPunctuation(element.gptPrompt)) {
continue
}
let tempData = {
text: element.gptPrompt,
from: 'zh',
to: 'en',
type: TranslateType.GPT_PROMPT_TRANSLATE,
isSplit: false, // 该参数设置为true时,会将文本按照标点符号分割成多个句子进行翻译
bookTaskDetailId: element.id,
responseMessgeName: DEFINE_STRING.TRANSLATE.GPT_PROMPT_TRANSLATE_RETRUN
}
translateData.push(tempData)
}
if (translateData.length <= 0) {
message.error('当前所有分镜中不存在推理提示词或者是提示词中没有中文')
return
}
// 开始提交翻译任务
softwareStore.spin.spinning = true
softwareStore.spin.tip = `翻译中 ... 0 / ${translateData.length}`
// 开始翻译
let res = await window.translate.TranslateNowReturn(translateData)
softwareStore.spin.spinning = false
window.api.showGlobalMessageDialog(res)
}
})
}
// 一键 英文翻译中文
async function TranslateEnglishToChinese() {
let da = dialog.warning({
title: '一键翻译中文提示',
content: `一键翻译中文,会将所有的分镜推理提示词翻译为中文,当前执行并发为 ${softwareStore.globalSetting.task_number},在执行翻译时不可停止,是否继续?`,
positiveText: '继续',
negativeText: '取消',
onPositiveClick: async () => {
da?.destroy()
// 获取数据
let translateData = []
for (let i = 0; i < reverseManageStore.selectBookTaskDetail.length; i++) {
const element = reverseManageStore.selectBookTaskDetail[i]
if (isEmpty(element.gptPrompt)) {
continue
}
let tempData = {
text: element.gptPrompt,
from: 'en',
to: 'zh',
type: TranslateType.GPT_PROMPT_TRANSLATE,
isSplit: false, // 该参数设置为true时,会将文本按照标点符号分割成多个句子进行翻译
bookTaskDetailId: element.id,
responseMessgeName: DEFINE_STRING.TRANSLATE.GPT_PROMPT_TRANSLATE_RETRUN
}
translateData.push(tempData)
}
if (translateData.length <= 0) {
message.error('当前所有分镜中不存在推理提示词')
return
}
// 开始提交翻译任务
softwareStore.spin.spinning = true
softwareStore.spin.tip = `翻译中 ... 0 / ${translateData.length}`
// 开始翻译
let res = await window.translate.TranslateNowReturn(translateData)
softwareStore.spin.spinning = false
window.api.showGlobalMessageDialog(res)
}
})
}
// 一键翻译
async function TranslateAll(key) {
if (key == 'english') {
// 中文翻译英文
await TranslateChineseToEnglish()
} else if (key == 'chinese') {
await TranslateEnglishToChinese()
} else {
message.error('未知的翻译类型')
}
}
// 选择风格
async function SelectStyle() {
// 先获取风格
// 判断当前数据是不是存在
// 处理数据。获取当前的所有的数据
let dialogWidth = window.innerWidth * 0.8
let dialogHeight = window.innerHeight * 0.9
// ImportWordAndSrt
dialog.create({
showIcon: false,
closeOnEsc: false,
content: () =>
h(SelectImageStyle, {
height: dialogHeight,
ref: selectStyleRef,
imageStyle: reverseManageStore.selectBookTask.imageStyle,
customizeImageStyle: reverseManageStore.selectBookTask.customizeImageStyle
}),
style: `width : ${dialogWidth}px; height : ${dialogHeight}px`,
maskClosable: false,
onClose: async () => {
debugger
console.log(selectStyleRef)
// 将数据保存到当前的数据中
// reverseManageStore.selectBookTask.styleList = toRaw(selectStyleRef.value.selectStyle)
// 保存当前的风格到数据库
let res = await window.book.SaveImageStyle(
toRaw(selectStyleRef.value.selectStyle),
reverseManageStore.selectBookTask.id
)
if (res.code == 0) {
message.error(res.message)
return
}
reverseManageStore.selectBookTask.imageStyle = res.data.imageStyle
reverseManageStore.selectBookTask.customizeImageStyle = res.data.customizeImageStyle
message.success('保存成功')
}
})
}
</script>
@@ -0,0 +1,170 @@
<template>
<div style="display: flex">
<span style="margin-right: 5px">提示词命令</span>
<n-popover trigger="hover">
<template #trigger>
<n-button
:color="softwareStore.SoftColor.ZHUYANTUO"
@click="SetMergeModel"
text
style="font-size: 24px"
>
<n-icon>
<Construct />
</n-icon>
</n-button>
</template>
<span>设置合并排序模式</span>
</n-popover>
<n-dropdown trigger="hover" :options="MergePromptOptions" @select="MergePromptSelect">
<n-button
size="tiny"
style="margin-left: 5px"
@click="MergePrompt(null)"
:color="softwareStore.SoftColor.ZHUYANTUO"
>合并命令</n-button
>
</n-dropdown>
<n-button
:color="softwareStore.SoftColor.ZHUYANTUO"
size="tiny"
style="margin-left: 5px"
@click="MJBadPromptCheck"
>敏感词检查</n-button
>
</div>
</template>
<script setup>
import { ref, h, toRaw, warn } from 'vue'
import { NPopover, NButton, NDropdown, NIcon, useDialog, useMessage } from 'naive-ui'
import { Construct } from '@vicons/ionicons5'
import DynamicTagsSelect from '../../Components/DynamicTagsSelect.vue'
import { useSoftwareStore } from '../../../../../stores/software'
import { useReverseManageStore } from '../../../../../stores/reverseManage'
import { OperateBookType } from '../../../../../define/enum/bookEnum'
let message = useMessage()
let dialog = useDialog()
let softwareStore = useSoftwareStore()
let reverseManageStore = useReverseManageStore()
let MergePromptOptions = ref([
{ label: 'SD模式合并', key: 'sd_merge' },
{ label: 'MJ模式合并', key: 'mj_merge' }
])
/**
* 保存合并模式的方法
* @param value
* @param options
*/
async function SaveFunction(value, options) {
// 判断当前的数据是不是options中的数据数量是不是相同
if (value.length != options.length) {
message.error('要保存的数据量和下拉选择的的数据量不一致')
return
}
// 开始保存
await window.pmpt.SavePromptSort(value, (value) => {
if (value.code == 0) {
message.error(value.message)
reutrn
}
message.success(value.message)
})
}
/**
* 设置合并的模式
*/
async function SetMergeModel() {
let promptSortRes = await window.pmpt.GetPromptSort()
if (promptSortRes.code == 0) {
message.error(value.message)
return
}
let promptSort = promptSortRes.data
// 获取当前的排序数据
await window.pmpt.GetPromptSortOptions((value) => {
let options = value.data
dialog.create({
name: 'setMergeModel',
title: '设置合并排序模式',
content: () =>
h(DynamicTagsSelect, {
options: options,
tagType: 'success',
placeholder: '选择当前的prompt排序方式',
SaveFunction: SaveFunction,
initData: promptSort,
max: 4
}),
style: `width : 500px; `,
maskClosable: false,
onPositiveClick: async () => {}
})
})
}
// 合并提示词的方法
async function MergePrompt(type = null) {
let image_generate_category = reverseManageStore.selectBookTask.imageCategory
let mergeType = 'mj_merge'
if (!type) {
if (image_generate_category == 'sd') {
mergeType = 'sd_merge'
} else if (image_generate_category == 'mj') {
mergeType = 'mj_merge'
} else if (image_generate_category == 'd3') {
message.error('D3模式不支持')
} else if (image_generate_category == 'flux-api' || image_generate_category == 'flux-forge') {
mergeType = 'sd_merge'
} else {
throw new Error('未知的合并模式,请检查')
}
} else {
mergeType = type
}
// 合并之前,要确认保存
softwareStore.spin.spinning = true
softwareStore.spin.tip = '合并命令中。。。'
let res = await window.book.MergePrompt(
reverseManageStore.selectBookTask.id,
mergeType,
OperateBookType.BOOKTASK
)
softwareStore.spin.spinning = false
if (res.code == 0) {
window.api.showGlobalMessageDialog(res)
return
}
// 修改前端显示数据
for (let i = 0; i < res.data.length; i++) {
const element = res.data[i]
let selectBookDetailIndex = reverseManageStore.selectBookTaskDetail.findIndex(
(item) => item.id == element.id
)
reverseManageStore.selectBookTaskDetail[selectBookDetailIndex].prompt = element.prompt
}
message.success(res.message)
}
// 下拉框合并提示词
async function MergePromptSelect(key) {
if (key == 'sd_merge') {
await MergePrompt('sd_merge')
} else if (key == 'mj_merge') {
await MergePrompt('mj_merge')
} else {
message.error('未知的合并模式,请检查')
}
}
// 敏感词检测
async function MJBadPromptCheck() {
message.error('敏感词检测功能暂未开放')
}
</script>
@@ -0,0 +1,265 @@
<template>
<div>
<n-button
size="tiny"
:color="softwareStore.SoftColor.BROWN_YELLOW"
style="margin-right: 5px"
@click="SingleMergePrompt"
>单句合并</n-button
>
<n-button
size="tiny"
:color="softwareStore.SoftColor.BROWN_YELLOW"
style="margin-right: 5px"
@click="SingleGenerateImage"
>单句生图</n-button
>
<n-button
size="tiny"
:color="softwareStore.SoftColor.BROWN_YELLOW"
style="margin-right: 5px"
@click="NextGenerateImage"
>下生图</n-button
>
<n-popover trigger="hover">
<template #trigger>
<n-button
:color="softwareStore.SoftColor.BROWN_YELLOW"
style="margin-right: 5px"
size="tiny"
@click="ImportMJImageUrl"
>导入图片</n-button
>
</template>
<span>粘贴MJ里面的图片链接自动下载到对应的行并分割</span>
</n-popover>
<n-input
:status="input_status"
size="tiny"
placeholder="请生成出图命令或是提示词"
type="textarea"
v-model:value="row.prompt"
@input="InputDebounced"
:autosize="{ minRows: 6, maxRows: 6 }"
/>
</div>
</template>
<script setup>
import { ref } from 'vue'
import { NButton, NInput, NPopover, useMessage } from 'naive-ui'
import { useSoftwareStore } from '../../../../../stores/software'
import { useReverseManageStore } from '../../../../../stores/reverseManage'
import {
BookBackTaskType,
BookImageCategory,
OperateBookType,
TaskExecuteType
} from '../../../../../define/enum/bookEnum'
import { DEFINE_STRING } from '../../../../../define/define_string'
import { debounce } from 'lodash'
let props = defineProps({
initData: undefined,
index: undefined
})
let softwareStore = useSoftwareStore()
let reverseManageStore = useReverseManageStore()
let message = useMessage()
let row = ref(props.initData)
let InputDebounced = debounce(handleInput, 500)
async function handleInput() {
console.log('开始修改')
let res = await window.db.UpdateBookTaskDetailData(row.value.id, {
prompt: row.value.prompt
})
if (res.code == 0) {
message.error(res.message)
}
}
// 单句合并
async function SingleMergePrompt() {
let image_generate_category = reverseManageStore.selectBookTask.imageCategory
let mergeType = 'mj_merge'
if (image_generate_category == 'sd') {
mergeType = 'sd_merge'
} else if (image_generate_category == 'mj') {
mergeType = 'mj_merge'
} else if (image_generate_category == 'd3') {
message.error('D3模式不支持')
} else if (image_generate_category == 'flux-api' || image_generate_category == 'flux-forge') {
mergeType = 'sd_merge'
} else {
throw new Error('未知的合并模式,请检查')
}
let res = await window.book.MergePrompt(row.value.id, mergeType, OperateBookType.BOOKTASKDETAIL)
if (res.code == 1) {
for (let i = 0; i < res.data.length; i++) {
const element = res.data[i]
let selectBookDetailIndex = reverseManageStore.selectBookTaskDetail.findIndex(
(item) => item.id == element.id
)
reverseManageStore.selectBookTaskDetail[selectBookDetailIndex].prompt = element.prompt
}
message.success(res.message)
} else {
message.error(res.message)
}
}
// 单句生图
async function SingleGenerateImage() {
let res = undefined
// 判断当前是不是被锁定
if (row.value.imageLock) {
message.error('当前分镜图片已被锁定,无法生成图片')
return
}
if (reverseManageStore.selectBookTask.imageCategory == BookImageCategory.MJ) {
res = await window.mj.AddMJGenerateImageTask(
row.value.id,
OperateBookType.BOOKTASKDETAIL,
DEFINE_STRING.BOOK.MJ_IMAGE_GENERATE_RETURN,
true
)
} else if (reverseManageStore.selectBookTask.imageCategory == BookImageCategory.SD) {
// 直接添加SD出图任务
res = await window.task.AddBookBackTask(
reverseManageStore.selectBook.id,
BookBackTaskType.SD_IMAGE,
TaskExecuteType.AUTO,
reverseManageStore.selectBookTask.id,
row.value.id,
DEFINE_STRING.BOOK.SD_IMAGE_GENERATE_RETURN
)
} else if (reverseManageStore.selectBookTask.imageCategory == BookImageCategory.D3) {
res = await window.task.AddBookBackTask(
reverseManageStore.selectBook.id,
BookBackTaskType.D3_IMAGE,
TaskExecuteType.AUTO,
reverseManageStore.selectBookTask.id,
row.value.id,
DEFINE_STRING.BOOK.D3_IMAGE_GENERATE_RETURN
)
} else if (reverseManageStore.selectBookTask.imageCategory == BookImageCategory.FLUX_API) {
res = await window.task.AddBookBackTask(
reverseManageStore.selectBook.id,
BookBackTaskType.FLUX_API_IMAGE,
TaskExecuteType.AUTO,
reverseManageStore.selectBookTask.id,
row.value.id,
DEFINE_STRING.BOOK.FLUX_API_IMAGE_GENERATE_RETURN
)
} else if (reverseManageStore.selectBookTask.imageCategory == BookImageCategory.FLUX_FORGE) {
res = await window.task.AddBookBackTask(
reverseManageStore.selectBook.id,
BookBackTaskType.FLUX_FORGE_IMAGE,
TaskExecuteType.AUTO,
reverseManageStore.selectBookTask.id,
row.value.id,
DEFINE_STRING.BOOK.FLUX_FORGE_IMAGE_GENERATE_RETURN
)
} else {
message.error('不支持的生图类型')
return
}
if (res?.code == 1) {
message.success(res.message)
} else {
message.error(res.message)
}
}
// 下生图
async function NextGenerateImage() {
debugger
let res = undefined
let bookTaskDetails = reverseManageStore.selectBookTaskDetail.filter(
(item, index) => index >= props.index && !item.imageLock
)
if (bookTaskDetails.length <= 0) {
message.error('未找到可生成图片的分镜,或者分镜都被锁定')
return
}
if (reverseManageStore.selectBookTask.imageCategory == BookImageCategory.MJ) {
// 添加MJ生图任务
res = await window.mj.AddMJGenerateImageTask(
row.value.id,
OperateBookType.UNDERBOOKTASK,
DEFINE_STRING.BOOK.MJ_IMAGE_GENERATE_RETURN
)
} else if (reverseManageStore.selectBookTask.imageCategory == BookImageCategory.SD) {
// 添加SD出图任务
let tasksList = []
for (let i = 0; i < bookTaskDetails.length; i++) {
const element = bookTaskDetails[i]
tasksList.push({
bookId: reverseManageStore.selectBook.id,
type: BookBackTaskType.SD_IMAGE,
executeType: TaskExecuteType.AUTO,
bookTaskId: reverseManageStore.selectBookTask.id,
bookTaskDetailId: element.id,
messageName: DEFINE_STRING.BOOK.SD_IMAGE_GENERATE_RETURN
})
}
res = await window.task.AddMultiBookBackTask(tasksList)
} else if (reverseManageStore.selectBookTask.imageCategory == BookImageCategory.FLUX_FORGE) {
// 本地的forge flux
let tasksList = []
for (let i = 0; i < bookTaskDetails.length; i++) {
const element = bookTaskDetails[i]
tasksList.push({
bookId: reverseManageStore.selectBook.id,
type: BookBackTaskType.FLUX_FORGE_IMAGE,
executeType: TaskExecuteType.AUTO,
bookTaskId: reverseManageStore.selectBookTask.id,
bookTaskDetailId: element.id,
messageName: DEFINE_STRING.BOOK.FLUX_FORGE_IMAGE_GENERATE_RETURN
})
}
res = await window.task.AddMultiBookBackTask(tasksList)
} else if (reverseManageStore.selectBookTask.imageCategory == BookImageCategory.FLUX_API) {
// 本地的forge flux
let tasksList = []
for (let i = 0; i < bookTaskDetails.length; i++) {
const element = bookTaskDetails[i]
tasksList.push({
bookId: reverseManageStore.selectBook.id,
type: BookBackTaskType.FLUX_API_IMAGE,
executeType: TaskExecuteType.AUTO,
bookTaskId: reverseManageStore.selectBookTask.id,
bookTaskDetailId: element.id,
messageName: DEFINE_STRING.BOOK.FLUX_API_IMAGE_GENERATE_RETURN
})
}
res = await window.task.AddMultiBookBackTask(tasksList)
} else if (reverseManageStore.selectBookTask.imageCategory == BookImageCategory.D3) {
let tasksList = []
for (let i = 0; i < bookTaskDetails.length; i++) {
const element = bookTaskDetails[i]
tasksList.push({
bookId: reverseManageStore.selectBook.id,
type: BookBackTaskType.D3_IMAGE,
executeType: TaskExecuteType.AUTO,
bookTaskId: reverseManageStore.selectBookTask.id,
bookTaskDetailId: element.id,
messageName: DEFINE_STRING.BOOK.D3_IMAGE_GENERATE_RETURN
})
}
res = await window.task.AddMultiBookBackTask(tasksList)
} else {
message.error('不支持的生图类型')
return
}
if (res?.code == 1) {
message.success(res.message)
} else {
message.error(res.message)
}
}
</script>
@@ -0,0 +1,502 @@
<template>
<div style="display: flex">
<n-button strong secondary @click="ReturnMain" size="small">返回</n-button>
<n-divider vertical style="height: 30px" />
<!-- <n-button strong secondary @click="ReturnMain" size="small">一键全自动</n-button>
<n-divider vertical style="height: 30px" /> -->
<n-button :color="softwareStore.SoftColor.ORANGE" @click="ImportWord" size="small"
>导入字幕/SRT</n-button
>
<n-button
:color="softwareStore.SoftColor.ORANGE"
style="margin-left: 10px"
@click="CharacterLibrary"
size="small"
>角色分析标签集</n-button
>
<n-button
:color="softwareStore.SoftColor.ORANGE"
style="margin-left: 10px"
@click="ImportPrompt"
size="small"
>导入提示词</n-button
>
<n-dropdown trigger="hover" :options="GptButtonOptions" @select="ButtonSelect">
<n-button
:color="softwareStore.SoftColor.ORANGE"
style="margin-left: 10px"
@click="GetPromptAll"
size="small"
>一键推理提示词</n-button
>
</n-dropdown>
<n-dropdown trigger="hover" :options="GenerateImageOptions" @select="ButtonSelect">
<n-button
:color="softwareStore.SoftColor.ORANGE"
style="margin-left: 10px"
@click="GenerateImageAll(true)"
size="small"
>生成所有的图片</n-button
>
</n-dropdown>
<n-dropdown trigger="hover" :options="ResetDataOptions" @select="ButtonSelect">
<n-button
:color="softwareStore.SoftColor.ORANGE"
style="margin-left: 10px"
@click="resetALLData"
size="small"
>重置数据</n-button
>
</n-dropdown>
<n-button
:color="softwareStore.SoftColor.ORANGE"
style="margin-left: 10px"
@click="OpenPromptSetting"
size="small"
>设置</n-button
>
</div>
</template>
<script setup>
import { h, ref } from 'vue'
import { NButton, useDialog, NPopover, NDropdown, NDivider, useMessage } from 'naive-ui'
import { useSoftwareStore } from '../../../../../stores/software'
import { useReverseManageStore } from '../../../../../stores/reverseManage'
import ImportWordAndSrt from '../Components/ImportWord/ImportWordAndSrt.vue'
import CharacterAnalyze from '../Components/PresetLibrary/CharacterAnalyze.vue'
import PromptSetting from '../../Original/Components/PromptSetting.vue'
import {
BookBackTaskType,
BookImageCategory,
BookType,
OperateBookType,
TaskExecuteType
} from '../../../../../define/enum/bookEnum'
import { useRouter } from 'vue-router'
import { DEFINE_STRING } from '../../../../../define/define_string'
let softwareStore = useSoftwareStore()
let reverseManageStore = useReverseManageStore()
let dialog = useDialog()
let message = useMessage()
let router = useRouter()
let GptButtonOptions = ref([
{ label: '推理空白提示词', key: 'get_space_prompt' },
{ label: '重置推理提示词', key: 'reset_prompt' }
])
let GenerateImageOptions = ref([
{ label: '生成未出图分镜', key: 'generate_space_image' },
{ label: '停止生成图片任务', key: 'stop_generate_image' }
])
let ResetDataOptions = ref([
{ label: '重置GPT提示词', key: 'reset_gpt_prompt' },
{ label: '重置提示词', key: 'reset_prompt' },
{ label: '重置图片', key: 'reset_image' }
])
function ReturnMain() {
router.push('/book_management')
}
// 导入原创文案,需对齐数据
async function ImportWord() {
let dialogWidth = window.innerWidth * 0.95
let dialogHeight = window.innerHeight * 0.95
let tempData = []
for (
let i = 0;
reverseManageStore.selectBookTaskDetail && i < reverseManageStore.selectBookTaskDetail.length;
i++
) {
const element = reverseManageStore.selectBookTaskDetail[i]
tempData.push({
no: element.no,
id: element.id,
lastId: i == 0 ? '' : reverseManageStore.selectBookTaskDetail[i - 1].id,
word: element.word,
after_gpt: element.afterGpt,
subValue: element.subValue ? element.subValue : [],
name: element.name + '.png',
prompt: element.prompt,
timeLimit: element.timeLimit
})
}
// ImportWordAndSrt
let da = dialog.create({
showIcon: false,
closeOnEsc: false,
content: () =>
h(ImportWordAndSrt, {
initData: tempData,
height: dialogHeight,
type: BookType.NEW_ORIGINAL
}),
style: `width : ${dialogWidth}px; min-height : ${dialogHeight}px`,
maskClosable: false,
onClose: async () => {
da.destroy()
// 重新加载数据
softwareStore.loading.originDatatableDataLoading = true
let res = await reverseManageStore.GetBookTaskDetail(reverseManageStore.selectBookTask.id)
softwareStore.loading.originDatatableDataLoading = false
if (res.code == 0) {
message.error(res.message)
}
}
})
}
// 角色分析(标签集)
async function CharacterLibrary() {
// 判断当前数据是不是存在
// 处理数据。获取当前的所有的数据
let dialogWidth = window.innerWidth * 0.6
let dialogHeight = window.innerHeight * 0.9
let word = '' // 这边要拼接一下word
debugger
let temp_arr = []
for (let i = 0; i < reverseManageStore.selectBookTaskDetail.length; i++) {
const element = reverseManageStore.selectBookTaskDetail[i]
temp_arr.push(element.afterGpt)
}
word = temp_arr.join('\n')
dialog.create({
showIcon: false,
closeOnEsc: false,
content: () => h(CharacterAnalyze, { word: word, height: dialogHeight }),
style: `width : ${dialogWidth}px; min-height : ${dialogHeight}px`,
maskClosable: false,
onClose: async () => {
// // 重新加载数据
// // 判断是不是已经有生成(有有生成显示)
// await window.api.GetConfigJson(JSON.stringify(['auto_analyze_character', '']), (value) => {
// if (value.code == 0) {
// message.error(value.message)
// return
// }
// AnalyzeCharacter.value = value.data
// })
// // 判断是不是有标签树
// await window.api.GetConfigJson(JSON.stringify(['tag_tree', []]), async (value) => {
// if (value.code == 0) {
// message.error(value.message)
// return
// }
// tagTreeData.value = value.data
// })
}
})
}
// GPT推理空白提示词
async function GetSpacePrompt() {
// GPT推理空白提示词
let da = dialog.warning({
title: '生成空白提示词提示',
content: `即将开始生成 空白 提示词,当前并发数为 ${softwareStore.globalSetting.task_number},在生成提示词时,不可取消,是否继续?`,
positiveText: '继续',
negativeText: '取消',
onPositiveClick: async () => {
da?.destroy()
let res = undefined
softwareStore.spin.spinning = true
softwareStore.spin.tip = '即将开始执行推理提示词任务,请稍等。。。'
res = await window.book.OriginalGetPrompt(
reverseManageStore.selectBookTask.id,
OperateBookType.BOOKTASK,
false
)
softwareStore.spin.spinning = false
window.api.showGlobalMessageDialog(res)
}
})
}
// 重置所有的GPT提示词数据
async function ResetGPTPrompt() {
let da = dialog.warning({
title: '重置推理提示词提示',
content: '执行该操作会删除所有的GPT推理提示词,并且此操作不可逆,是否继续?',
positiveText: '继续',
negativeText: '取消',
onPositiveClick: async () => {
da?.destroy()
softwareStore.spin.spinning = true
debugger
softwareStore.spin.tip = '正在重置推理提示词,请稍后。。。'
let res = await window.book.ResetGptReverseData(
reverseManageStore.selectBookTask.id,
OperateBookType.BOOKTASK,
BookType.ORIGINAL
)
softwareStore.spin.spinning = false
if (res.code == 1) {
// 删掉所有的数据
for (let i = 0; i < reverseManageStore.selectBookTaskDetail.length; i++) {
reverseManageStore.selectBookTaskDetail[i].gptPrompt = ''
}
}
}
})
}
// 重置所有的生成图片信息
async function ResetImage() {
message.info('ResetImage')
let da = dialog.warning({
title: '重置图片提示',
content: '该操作会删除 未被锁定的分镜 的生成图片信息,并且此操作不可逆,是否继续?',
positiveText: '继续',
negativeText: '取消',
onPositiveClick: async () => {
da?.destroy()
softwareStore.spin.spinning = true
softwareStore.spin.tip = '正在重置图片,请稍后。。。'
let res = await window.book.ResetGenerateImage(
reverseManageStore.selectBookTask.id,
OperateBookType.BOOKTASK,
false
)
softwareStore.spin.spinning = false
if (res.code == 1) {
for (let i = 0; i < res.data.length; i++) {
const element = res.data[i]
let findIndex = reverseManageStore.selectBookTaskDetail.findIndex(
(item) => item.id == element.id
)
if (findIndex != -1) {
reverseManageStore.selectBookTaskDetail[findIndex].mjMessage = undefined
reverseManageStore.selectBookTaskDetail[findIndex].outImagePath = ''
reverseManageStore.selectBookTaskDetail[findIndex].subImagePath = []
}
}
message.success(res.message)
} else {
message.error(res.message)
}
}
})
}
// 下拉选项的选择按钮操作
async function ButtonSelect(key) {
if (key == 'get_space_prompt') {
// 推理空白提示词
await GetSpacePrompt()
} else if (key == 'reset_prompt') {
// 重置推理提示词
await ResetGPTPrompt()
} else if (key == 'generate_space_image') {
// 生成未出图分镜
await GenerateImageAll(false)
} else if (key == 'reset_image') {
await ResetImage()
} else {
message.error('未知的操作')
}
}
// 获取所有的GPT提示词
async function GetPromptAll() {
let da = dialog.warning({
title: '生成提示词提示',
content: `即将开始生成提示词,当前并发数为 ${softwareStore.globalSetting.task_number},在生成提示词时,不可取消,是否继续?`,
positiveText: '继续',
negativeText: '取消',
onPositiveClick: async () => {
da?.destroy()
softwareStore.spin.spinning = true
softwareStore.spin.tip = '即将开始执行推理提示词任务,请稍等。。。'
let res = await window.book.OriginalGetPrompt(
reverseManageStore.selectBookTask.id,
OperateBookType.BOOKTASK,
true
)
softwareStore.spin.spinning = false
window.api.showGlobalMessageDialog(res)
}
})
}
/**
* 根据指定类型为所有书籍任务生成图像
* @param {String} type - 小说图像类别
* @param {Boolean} cover - 是否覆盖现有图像
*/
async function AddFluxImageAll(type, cover) {
debugger
let messageName = DEFINE_STRING.BOOK.FLUX_API_IMAGE_GENERATE_RETURN
let taskType = BookBackTaskType.FLUX_API_IMAGE
if (type == BookImageCategory.FLUX_FORGE) {
messageName = DEFINE_STRING.BOOK.FLUX_FORGE_IMAGE_GENERATE_RETURN
taskType = BookBackTaskType.FLUX_FORGE_IMAGE
} else if (type == BookImageCategory.D3) {
messageName = DEFINE_STRING.BOOK.D3_IMAGE_GENERATE_RETURN
taskType = BookBackTaskType.D3_IMAGE
} else if (type == BookImageCategory.SD) {
messageName = DEFINE_STRING.BOOK.SD_IMAGE_GENERATE_RETURN
taskType = BookBackTaskType.SD_IMAGE
} else {
message.error('未知的图像类别')
return
}
// 这边过滤所有的锁定的分镜
let bookTaskDetails = reverseManageStore.selectBookTaskDetail.filter((item) => !item.imageLock)
if (cover) {
// 添加全部的任务
let tasks = []
for (let i = 0; i < bookTaskDetails.length; i++) {
const element = bookTaskDetails[i]
tasks.push({
bookId: reverseManageStore.selectBook.id,
type: taskType,
executeType: TaskExecuteType.AUTO,
bookTaskId: reverseManageStore.selectBookTask.id,
bookTaskDetailId: element.id,
messageName: messageName
})
}
if (tasks.length <= 0) {
message.error('没有需要生成的分镜')
return
}
let res = await await window.task.AddMultiBookBackTask(tasks)
window.api.showGlobalMessage(res)
} else {
// 添加未生成的任务
let tasks = []
for (let i = 0; i < bookTaskDetails.length; i++) {
const element = bookTaskDetails[i]
if (!element.mjMessage) {
tasks.push({
bookId: reverseManageStore.selectBook.id,
type: taskType,
executeType: TaskExecuteType.AUTO,
bookTaskId: reverseManageStore.selectBookTask.id,
bookTaskDetailId: element.id,
messageName: messageName
})
}
}
if (tasks.length == 0) {
message.error('没有未生成的分镜,或者都被锁定了')
return
}
let res = await await window.task.AddMultiBookBackTask(tasks)
window.api.showGlobalMessage(res)
}
return
}
// 生成所有图片
async function GenerateImageAll(cover = true) {
let da = dialog.warning({
title: '生成所有图片提示',
content: `即将开始生成 ${cover ? '所有的' : '从未生成过的分镜'} 图片,当前的生图模式为 ${
reverseManageStore.selectBookTask.imageCategory
},开始执行会有1-20秒的延迟,除了被锁定的图片,其余的图片都会重新生成,是否继续?`,
positiveText: '继续',
negativeText: '取消',
onPositiveClick: async () => {
da?.destroy()
let res = undefined
if (reverseManageStore.selectBookTask.imageCategory == BookImageCategory.MJ) {
res = await window.mj.AddMJGenerateImageTask(
reverseManageStore.selectBookTask.id,
OperateBookType.BOOKTASK,
DEFINE_STRING.BOOK.MJ_IMAGE_GENERATE_RETURN,
cover
)
} else if (reverseManageStore.selectBookTask.imageCategory == BookImageCategory.SD) {
await AddFluxImageAll(BookImageCategory.SD, cover)
} else if (reverseManageStore.selectBookTask.imageCategory == BookImageCategory.FLUX_FORGE) {
await AddFluxImageAll(BookImageCategory.FLUX_FORGE, cover)
} else if (reverseManageStore.selectBookTask.imageCategory == BookImageCategory.FLUX_API) {
await AddFluxImageAll(BookImageCategory.FLUX_FORGE, cover)
} else if (reverseManageStore.selectBookTask.imageCategory == BookImageCategory.D3) {
await AddFluxImageAll(BookImageCategory.D3, cover)
} else {
message.error('不支持的生图类型')
return
}
if (res?.code == 1) {
message.success(res.message)
} else {
message.error(res.message)
}
}
})
}
/**
* 打开新的弹窗
*/
async function OpenPromptSetting() {
// 判断当前数据是不是存在
// 处理数据。获取当前的所有的数据
let dialogWidth = window.innerWidth * 0.4
let dialogHeight = window.innerHeight * 0.9
dialog.create({
showIcon: false,
closeOnEsc: false,
content: () => h(PromptSetting, {}),
style: `width : ${dialogWidth}px; min-height : ${dialogHeight}px`,
maskClosable: false
})
}
// 导入提示词
async function ImportPrompt() {
message.error('导入提示词该方法暂未实现')
return
// 弹窗选择文件
window.api.SelectFile(['txt'], async (value) => {
if (value.code == 0) {
message.error(value.message)
return
}
// 拿到提示词文件地址,然后还是获取
await window.pmpt.OpenPromptFileTxt(value.value, (res) => {
debugger
console.log(res)
if (res.code == 0) {
message.error(res.message)
return
}
// 修改数据
// 判断获取的数据是不是小于当前的data的行
if (res.data.length > data.value.length) {
dialog.warning({
title: '提示',
content: '导入的数据行数大于当前的数据行数,多余的数据会被删除,是否继续导入?',
positiveText: '继续',
negativeText: '取消',
onPositiveClick: async () => {
debugger
// 开始导入
for (let i = 0; i < data.value.length && i < res.data.length; i++) {
const element = res.data[i]
// 开始修改
data.value[i].gpt_prompt = element
}
}
})
} else {
// 开始导入
for (let i = 0; i < data.value.length && i < res.data.length; i++) {
const element = res.data[i]
// 开始修改
data.value[i].gpt_prompt = element
}
}
})
})
}
</script>
@@ -0,0 +1,179 @@
<template>
<div>
<n-data-table
id="1111"
:max-height="maxHeight"
:loading="softwareStore.loading.originDatatableDataLoading"
:row-key="rowKey"
:columns="createColumns({})"
:data="reverseManageStore.selectBookTaskDetail"
:pagination="false"
:bordered="false"
:scroll-x="0"
@update:checked-row-keys="handleCheck"
/>
</div>
</template>
<script setup>
import {
ref,
h,
onMounted,
defineComponent,
onUnmounted,
watch,
toRaw,
render,
onBeforeUnmount,
provide,
nextTick
} from 'vue'
import {
NDataTable,
NImage,
useMessage,
NInput,
NButton,
NSwitch,
NPopover,
useDialog,
NCheckbox,
NIcon
} from 'naive-ui'
import DatatableHeaderAfterGpt from '../Components/DatatableHeaderAfterGpt.vue'
import DatatableAfterGpt from '../Components/DatatableAfterGpt.vue'
import ODatatableHeaderCharacterAndScene from './ODataTableHeaderCharacterAndScene.vue'
import ODatatableCharacterAndScene from './ODataTableCharacterAndScene.vue'
import ODataTableGptPrompt from './ODataTableGptPrompt.vue'
import ODataTableHeaderGptPrompt from './ODataTableHeaderGptPrompt.vue'
import ODataTableHeaderPrompt from './ODatatableHeaderPrompt.vue'
import ODatatablePrompt from './ODatatablePrompt.vue'
import ODataTableAction from './ODataTableAction.vue'
import DatatableHeaderGenerateImage from '../Components/DatatableHeaderGenerateImage.vue'
import DatatableGenerateImage from '../Components/DatatableGenerateImage.vue'
import { useReverseManageStore } from '../../../../../stores/reverseManage'
import { useSoftwareStore } from '../../../../../stores/software'
import { set } from 'lodash'
let reverseManageStore = useReverseManageStore()
let softwareStore = useSoftwareStore()
let maxHeight = ref(0)
let message = useMessage()
onMounted(async () => {
setTimeout(() => {
let div = document.getElementsByClassName('empty-margin')
for (let i = 0; i < div.length; i++) {
div[i].style.padding = '0px 5px'
}
getMaxHeight()
}, 100)
// 加载初始数据
// 这边开始加载数据(就是小说分镜任务)
softwareStore.spin.spinning = false
let data = await reverseManageStore.GetBookTaskDetail(reverseManageStore.selectBookTask.id)
softwareStore.loading.originDatatableDataLoading = false
if (data.code == 0) {
message.error(data.message)
}
// 加载全部的人物和场景预设数据
let res = await reverseManageStore.GetPresetData()
if (res.code == 0) {
message.error(res.message)
}
})
// 计算当前程序界面的高度,减去上下的高度,设置maxHegiht
async function getMaxHeight() {
let height = window.innerHeight
maxHeight.value = height - 100
}
const createColumns = ({}) => {
return [
{
title: 'No.',
key: 'no',
width: 50,
fixed: 'left'
},
{
title(row) {
return h(DatatableHeaderAfterGpt)
},
key: 'srt',
width: '180',
className: 'empty-margin',
fixed: 'left',
render(row, index) {
return h(DatatableAfterGpt, { initData: row, index: index })
}
},
{
title() {
return h(ODatatableHeaderCharacterAndScene)
},
className: 'empty-margin',
key: 'character',
width: '210',
render(row, index) {
return h(ODatatableCharacterAndScene, {
initData: row,
index: index
})
}
},
{
title(row) {
return h(ODataTableHeaderGptPrompt)
},
key: 'gpt_prompt',
className: 'empty-margin',
resizable: true,
minWidth: 330,
maxWidth: 700,
width: '350',
render(row, index) {
return h(ODataTableGptPrompt, { initData: row, index: index })
}
},
{
// 提示词命令
title(row) {
return h(ODataTableHeaderPrompt)
},
key: 'prompt',
width: '300',
className: 'empty-margin',
render(row, index) {
return h(ODatatablePrompt, { initData: row, index: index })
}
},
{
// 参数
title: '参数',
key: 'parameter',
width: '130',
render(row, index) {
return h(ODataTableAction, { initData: row, index: index })
}
},
{
// 出图
title(row) {
return h(DatatableHeaderGenerateImage)
},
key: 'options',
className: 'empty-margin',
width: '300',
render(row, index) {
return h(DatatableGenerateImage, {
initData: row,
index: index
})
}
}
]
}
</script>
@@ -0,0 +1,135 @@
<template>
<div>
<OriginalMainButton />
</div>
<div>
<OriginalMainDatatale />
</div>
</template>
<script setup>
import { onMounted, onUnmounted } from 'vue'
import { useMessage } from 'naive-ui'
import { useSoftwareStore } from '../../../../../stores/software'
import { useReverseManageStore } from '../../../../../stores/reverseManage'
import OriginalMainButton from './OriginalMainButton.vue'
import OriginalMainDatatale from './OriginalMainDatatable.vue'
import { DEFINE_STRING } from '../../../../../define/define_string'
let softwareStore = useSoftwareStore()
let reverseManageStore = useReverseManageStore()
let message = useMessage()
// 获取推理提示词任务返回
function OriginalGetPromptReturn(value) {
console.log(' OriginalGetPromptReturn ', value)
if (value.code == 1) {
if (!value.id) {
message.error('没有返回的ID')
}
let index = reverseManageStore.selectBookTaskDetail.findIndex((item) => item.id == value.id)
if (index == -1) {
message.error('没有找到对应的ID')
}
reverseManageStore.selectBookTaskDetail[index].gptPrompt = value.data.content
softwareStore.spin.tip = `正在推理提示词,推理进度 ${value.data.progress.current} / ${value.data.progress.total}`
} else {
window.api.showGlobalMessageDialog({
code: value.code,
message: value.message
})
}
}
// 获取GPT推理词翻译返回
function OriginalGPTPromptReturn(value) {
console.log(' OriginalGPTPromptReturn ', value)
// 开始修改数据
if (value.code == 1 && value.data) {
let findIndex = reverseManageStore.selectBookTaskDetail.findIndex(
(item) => item.id == value.data.bookTaskDetailId
)
if (findIndex != -1) {
reverseManageStore.selectBookTaskDetail[findIndex].gptPrompt = value.data.prompt
}
softwareStore.spin.tip = `翻译中 ... ${value.data.progress} / ${value.data.total}`
}
}
// 原创MJ生图返回的数据进行修改
function OriginalMJImageResponseReturn(value) {
//开始修改
console.log('OriginalMJImageResponseReturn', value)
if (value.data) {
let dataIndex = reverseManageStore.selectBookTaskDetail.findIndex(
(item) => item.id == value.data.id
)
if (dataIndex >= 0) {
reverseManageStore.selectBookTaskDetail[dataIndex].mjMessage = value.data
reverseManageStore.selectBookTaskDetail[dataIndex].outImagePath = value.data.outImagePath
reverseManageStore.selectBookTaskDetail[dataIndex].subImagePath = value.data.subImagePath
if (value.data.status == 'error') {
message.error(value.data.message)
}
}
}
}
// 原创SD生图返回的数据进行修改
function OriginalSDImageResponseReturn(value) {
console.log('OriginalSDImageResponseReturn', value)
// 开始修改
if (value.data) {
let dataIndex = reverseManageStore.selectBookTaskDetail.findIndex(
(item) => item.id == value.data.id
)
if (dataIndex >= 0) {
reverseManageStore.selectBookTaskDetail[dataIndex].mjMessage = value.data
reverseManageStore.selectBookTaskDetail[dataIndex].outImagePath = value.data.outImagePath
reverseManageStore.selectBookTaskDetail[dataIndex].subImagePath = value.data.subImagePath
}
if (value.data.status == 'error') {
message.error(value.data.message)
}
}
}
onMounted(async () => {
// 监听推理提示词任务返回
window.api.setEventListen([DEFINE_STRING.BOOK.ORIGINAL_GPT_PROMPT_RETURN], (value) =>
OriginalGetPromptReturn(value)
)
// 监听GPT推理词翻译返回
window.api.setEventListen([DEFINE_STRING.TRANSLATE.GPT_PROMPT_TRANSLATE_RETRUN], (value) =>
OriginalGPTPromptReturn(value)
)
window.api.setEventListen([DEFINE_STRING.BOOK.MJ_IMAGE_GENERATE_RETURN], (value) =>
OriginalMJImageResponseReturn(value)
)
window.api.setEventListen([DEFINE_STRING.BOOK.SD_IMAGE_GENERATE_RETURN], (value) =>
OriginalSDImageResponseReturn(value)
)
window.api.setEventListen([DEFINE_STRING.BOOK.FLUX_FORGE_IMAGE_GENERATE_RETURN], (value) =>
OriginalSDImageResponseReturn(value)
)
window.api.setEventListen([DEFINE_STRING.BOOK.FLUX_API_IMAGE_GENERATE_RETURN], (value) =>
OriginalSDImageResponseReturn(value)
)
window.api.setEventListen([DEFINE_STRING.BOOK.D3_IMAGE_GENERATE_RETURN], (value) => {
OriginalSDImageResponseReturn(value)
})
})
onUnmounted(() => {
window.api.removeEventListen(DEFINE_STRING.BOOK.ORIGINAL_GPT_PROMPT_RETURN)
window.api.removeEventListen(DEFINE_STRING.TRANSLATE.GPT_PROMPT_TRANSLATE_RETRUN)
window.api.removeEventListen(DEFINE_STRING.BOOK.MJ_IMAGE_GENERATE_RETURN)
window.api.removeEventListen(DEFINE_STRING.BOOK.SD_IMAGE_GENERATE_RETURN)
window.api.removeEventListen(DEFINE_STRING.BOOK.FLUX_FORGE_IMAGE_GENERATE_RETURN)
window.api.removeEventListen(DEFINE_STRING.BOOK.FLUX_API_IMAGE_GENERATE_RETURN)
window.api.removeEventListen(DEFINE_STRING.BOOK.D3_IMAGE_GENERATE_RETURN)
})
</script>
+9 -8
View File
@@ -22,7 +22,7 @@
>
</n-menu>
</n-layout-sider>
<n-layout-content content-style="padding: 20px 5px 5px 20px; height:100%">
<n-layout-content content-style="padding: 5px 5px 5px 10px; height:100%">
<!-- <Setting></Setting> -->
<router-view></router-view>
</n-layout-content>
@@ -201,17 +201,18 @@ export default defineComponent({
})
})
window.api.setEventListen([DEFINE_STRING.SHOW_GLOABAL_MESSAGE], (value) => {
window.api.setEventListen([DEFINE_STRING.SHOW_MAIN_MESSAGE], (value) => {
let mess = value.message
let type = 'success'
let title = '成功'
if (value.code == 0) {
type = 'error'
title = '成功但失败1'
message.error(mess)
} else if (value.code == 2) {
;(type = 'warning'), (title = '警告')
message.warning(mess)
} else if (value.code == 1) {
message.success(mess)
} else {
message.error('未知的提示框类型')
}
message[type](mess, title)
})
// 弹窗,获取机器码
@@ -513,7 +514,7 @@ export default defineComponent({
onClick: () => {
OpneBackTask()
},
style : 'font-weight: bold;'
style: 'font-weight: bold;'
},
{
default: () => '后台任务'
@@ -366,7 +366,6 @@ export default defineComponent({
let temp_data = props.func.getData(null)
// 调用方法,然后,传递一个回调函数
props.func.modifyData(() => {
console.log(temp_data)
// 修改当前指定ID的data行
let index = temp_data.findIndex((x) => x.id == value.id)
@@ -2,7 +2,7 @@
<div v-if="type == 'title'">
<span style="margin-right: 5px">参数</span>
</div>
<div v-else-if="type.startsWith('data') && type.endsWith('sd')">
<div v-else-if="type.startsWith('data') && (type.endsWith('sd') || type.endsWith('flux-api') || type.endsWith('flux-forge'))">
<n-checkbox
style="margin-left: 5px; height: 100%"
:checked="row.adetailer"
@@ -11,6 +11,7 @@
/>
</div>
<div v-else-if="type.startsWith('data') && type.endsWith('mj')"></div>
<div v-else-if="type.startsWith('data') && type.endsWith('mj')"></div>
<div v-else-if="type.startsWith('data') && type.endsWith('d3')"></div>
</template>
@@ -144,6 +144,12 @@ export default defineComponent({
)
} else if (ca == 'd3') {
// D3 生图
} else if (ca == 'flux-api') {
message.info(ca)
// FLUX API 生图
} else if (ca == 'flux-forge') {
message.info(ca)
} else {
message.error('生图的类别错误')
}
@@ -2,7 +2,7 @@
<div v-if="type == 'title'" style="display: flex">
<span>出图</span>
<n-select
style="width: 70px; margin-left: 5px"
style="width: 100px; margin-left: 5px"
size="tiny"
:options="image_options"
@update:value="UpdateImageGenerateCategory"
@@ -564,7 +564,7 @@ export default defineComponent({
// 计算当前程序界面的高度,减去上下的高度,设置maxHegiht
async function getMaxHeight() {
let height = window.innerHeight
maxHeight.value = height - 120
maxHeight.value = height - 100
}
// 监听程序界面的高度变化
@@ -172,10 +172,7 @@ export default defineComponent({
* GPT分析提示词
*/
async function GPTPrompt() {
//
console.log(data.value)
// prompt
let tmpData = cloneDeep(toRaw(data.value))
tmpData = tmpData.filter((item) => {
return item.gpt_prompt == null || item.gpt_prompt == ''
@@ -92,6 +92,18 @@
placeholder="输入迭代步数"
/>
</n-form-item>
<n-form-item
style="width: 300px"
label="FLXU API模型(和GPT公用一个服务商)"
path="flux_model"
>
<n-select
placeholder="请选择FLUX API的生图模型"
v-model:value="formValue.flux_model"
:options="flux_model_options"
>
</n-select>
</n-form-item>
<n-form-item>
<n-button attr-type="button" type="primary" @click="SaveSDConfig"> 保存设置 </n-button>
</n-form-item>
@@ -118,6 +130,7 @@ import {
NCheckbox
} from 'naive-ui'
import SDADetailerSetting from '../Components/SDADetailerSetting.vue'
import { FLxuAPIImageType } from '../../../../define/enum/image'
export default defineComponent({
components: {
@@ -147,7 +160,8 @@ export default defineComponent({
style_weight: 1,
cfg_scale: 1,
sd_models: null,
lora: null
lora: null,
flux_model: FLxuAPIImageType.FLUX
})
let samplers_options = ref([])
@@ -274,7 +288,29 @@ export default defineComponent({
LoadSDServiceData,
samplers_options,
lora_options,
sd_models_options
sd_models_options,
flux_model_options: [
{
label: 'FLUX',
value: FLxuAPIImageType.FLUX
},
{
label: 'FLUX DEV',
value: FLxuAPIImageType.FLUX_DEV
},
{
label: 'FLUX PRO',
value: FLxuAPIImageType.FLUX_PRO
},
{
label: 'FLUX PRO MAX',
value: FLxuAPIImageType.FLUX_PRO_MAX
},
{
label: 'FLUX SCHNELL',
value: FLxuAPIImageType.FLUX_SCHNELL
}
]
}
}
})
@@ -293,6 +293,15 @@ export default defineComponent({
message.error('请检查必填字段')
return
}
//
let saveRes = await window.setting.SaveSoftWareSetting({
globalSetting: JSON.stringify(toRaw(formValue.value))
})
if (saveRes.code == 0) {
message.error(saveRes.message)
return
}
softwareStore.globalSetting = toRaw(formValue.value)
//
window.api.ModifySampleSetting(JSON.stringify(toRaw(formValue.value)), (value) => {
if (value.code == 1) {
@@ -305,7 +314,6 @@ export default defineComponent({
}
})
softwareStore.SetSoftware({ theme: formValue.value.theme })
await softwareStore.SaveSoftware()
})
}
@@ -478,24 +486,6 @@ export default defineComponent({
label: '备用主站点',
value: LaiAPIType.BAK_MAIN
}
],
translation_options: [
{
label: '百度翻译',
value: TranslateAPIType.BAIDU
},
{
label: '腾讯翻译',
value: TranslateAPIType.TENCENT
},
{
label: '火山翻译',
value: TranslateAPIType.VOLCENGINE
},
{
label: '阿里翻译',
value: TranslateAPIType.ALI
}
]
}
}