Initial commit 添加MJ功能

This commit is contained in:
2024-05-15 12:57:15 +08:00
commit 74009113fa
602 changed files with 43708 additions and 0 deletions
+17
View File
@@ -0,0 +1,17 @@
<!doctype html>
<html>
<head>
<meta charset="UTF-8" />
<title>LAITool 123</title>
<!-- <meta http-equiv="Content-Security-Policy"
content="default-src '*'; connect-src *; script-src '*'; img-src 'self' file: data: ; style-src 'self' 'unsafe-inline'" /> -->
<link rel="stylesheet" type="text/css" href="./styles.css">
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.js"></script>
</body>
</html>
+45
View File
@@ -0,0 +1,45 @@
<template>
<n-config-provider :hljs="hljs" :theme="themeData == 'dark' ? darkTheme : null">
<n-message-provider>
<n-dialog-provider>
<n-notification-provider>
<RouterView></RouterView>
</n-notification-provider>
</n-dialog-provider>
</n-message-provider>
</n-config-provider>
</template>
<script>
import { defineComponent, ref, onMounted } from 'vue'
import hljs from 'highlight.js/lib/core'
import javascript from 'highlight.js/lib/languages/javascript'
import { NMessageProvider, NDialogProvider, NConfigProvider, darkTheme, NNotificationProvider } from "naive-ui"
hljs.registerLanguage('javascript', javascript)
export default defineComponent({
components: {
NConfigProvider, NDialogProvider, NMessageProvider, NNotificationProvider
},
setup() {
let themeData = ref("");
onMounted(() => {
window.api.getSettingDafultData(async (value) => {
themeData.value = value.theme;
await window.darkMode.toggle(value.theme)
})
})
return {
javascript,
hljs,
darkTheme,
themeData
}
}
})
</script>
<style lang="less">
@import './assets/css/styles.less';
</style>
+207
View File
@@ -0,0 +1,207 @@
body {
display: flex;
flex-direction: column;
font-family:
Roboto,
-apple-system,
BlinkMacSystemFont,
'Helvetica Neue',
'Segoe UI',
'Oxygen',
'Ubuntu',
'Cantarell',
'Open Sans',
sans-serif;
color: #86a5b1;
width: 100%;
height: 100%;
}
* {
padding: 0;
margin: 0;
}
ul {
list-style: none;
}
code {
font-weight: 600;
padding: 3px 5px;
border-radius: 2px;
background-color: #26282e;
font-family:
ui-monospace,
SFMono-Regular,
SF Mono,
Menlo,
Consolas,
Liberation Mono,
monospace;
font-size: 85%;
}
a {
color: #9feaf9;
font-weight: 600;
cursor: pointer;
text-decoration: none;
outline: none;
}
a:hover {
border-bottom: 1px solid;
}
#app {
flex: 1;
display: flex;
flex-direction: column;
margin: 0 auto;
padding: 0;
width: 100%;
height: 100%;
}
.versions {
margin: 0 auto;
float: none;
clear: both;
overflow: hidden;
font-family: 'Menlo', 'Lucida Console', monospace;
color: #c2f5ff;
line-height: 1;
transition: all 0.3s;
li {
display: block;
float: left;
border-right: 1px solid rgba(194, 245, 255, 0.4);
padding: 0 20px;
font-size: 13px;
opacity: 0.8;
&:last-child {
border: none;
}
}
}
.hero-logo {
margin-top: -0.4rem;
transition: all 0.3s;
}
@media (max-width: 840px) {
.versions {
display: none;
}
.hero-logo {
margin-top: -1.5rem;
}
}
.hero-text {
font-weight: 400;
color: #c2f5ff;
text-align: center;
margin-top: -0.5rem;
margin-bottom: 10px;
}
@media (max-width: 660px) {
.hero-logo {
display: none;
}
.hero-text {
margin-top: 20px;
}
}
.hero-tagline {
text-align: center;
margin-bottom: 14px;
}
.links {
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 24px;
font-size: 18px;
font-weight: 500;
a {
font-weight: 500;
}
.link-item {
padding: 0 4px;
}
}
.features {
display: flex;
flex-wrap: wrap;
margin: -6px;
.feature-item {
width: 33.33%;
box-sizing: border-box;
padding: 6px;
}
article {
background-color: rgba(194, 245, 255, 0.1);
border-radius: 8px;
box-sizing: border-box;
padding: 12px;
height: 100%;
}
span {
color: #d4e8ef;
word-break: break-all;
}
.title {
font-size: 17px;
font-weight: 500;
color: #c2f5ff;
line-height: 22px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.detail {
font-size: 14px;
font-weight: 500;
line-height: 22px;
margin-top: 6px;
}
}
@media (max-width: 660px) {
.features .feature-item {
width: 50%;
}
}
@media (max-width: 480px) {
.links {
flex-direction: column;
line-height: 32px;
.link-dot {
display: none;
}
}
.features .feature-item {
width: 100%;
}
}
+34
View File
@@ -0,0 +1,34 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<symbol id="electron" viewBox="0 0 900 300">
<g fill="none" fill-rule="evenodd">
<g class="hero-apps" style="fill: #71abb7;">
<path d="M15 138l-4.9-.64L8 133l-2.1 4.36L1 138l3.6 3.26-.93 4.74L8 143.67l4.33 2.33-.93-4.74z"></path>
<path d="M897.2 114.0912l-5.2 3.63v-2.72c0-.55-.45-1-1-1h-8c-.55 0-1 .45-1 1v9c0 .55.45 1 1 1h8c.55 0 1-.45 1-1v-2.72l5.2 3.63c.33.23.8 0 .8-.41v-10c0-.41-.47-.64-.8-.41z"></path>
<path d="M65.4 188.625h-1.6c.88 0 1.6-.7313 1.6-1.625v-1.625c0-.8937-.72-1.625-1.6-1.625h-1.6c-.88 0-1.6.7313-1.6 1.625V187c0 .8937.72 1.625 1.6 1.625h-1.6c-.88 0-1.6.7313-1.6 1.625v3.25h1.6v4.875c0 .8937.72 1.625 1.6 1.625h1.6c.88 0 1.6-.7313 1.6-1.625V193.5H67v-3.25c0-.8937-.72-1.625-1.6-1.625zm-3.2-3.25h1.6V187h-1.6v-1.625zm3.2 6.5h-1.6v6.5h-1.6v-6.5h-1.6v-1.625h4.8v1.625zm3.344-5.6875c0-3.2175-2.576-5.8337-5.744-5.8337-3.168 0-5.744 2.6162-5.744 5.8337 0 .455.048.8937.144 1.3162v3.2175c-.976-1.2512-1.6-2.8112-1.6-4.55 0-4.03 3.232-7.3125 7.2-7.3125s7.2 3.2825 7.2 7.3125c0 1.7225-.624 3.2988-1.6 4.55v-3.2175c.096-.4387.144-.8612.144-1.3162zm6.256 0c0 4.68-2.608 8.7425-6.4 10.7738v-1.7063c2.976-1.885 4.944-5.2325 4.944-9.0675 0-5.915-4.72-10.7087-10.544-10.7087-5.824 0-10.544 4.7937-10.544 10.7087 0 3.835 1.968 7.1825 4.944 9.0675v1.7063c-3.792-2.0313-6.4-6.0938-6.4-10.7738C51 179.46 56.376 174 63 174s12 5.46 12 12.1875z"></path>
<path d="M830.7143 142.3333c-.8643 0-1.5714.7125-1.5714 1.5834v3.1666c0 .871.707 1.5834 1.5713 1.5834h12.5714c.8643 0 1.5714-.7125 1.5714-1.5834v-3.1666c0-.871-.707-1.5834-1.5713-1.5834h-12.5714zm12.5714 2.771l-1.9643 1.979h-2.357L837 145.1043l-1.9643 1.979h-2.357l-1.9644-1.979v-1.1876h1.1786l1.964 1.979 1.9644-1.979h2.3572l1.9643 1.979 1.964-1.979h1.1787v1.1875zm-9.4286 5.1457h6.286v1.5833h-6.286V150.25zM837 136c-6.0657 0-11 4.6075-11 10.2917v7.125c0 .8708.707 1.5833 1.5714 1.5833h18.8572c.8643 0 1.5714-.7125 1.5714-1.5833v-7.125C848 140.6075 843.0657 136 837 136zm9.4286 17.4167h-18.8572v-7.125c0-4.8925 4.1486-8.851 9.4286-8.851 5.28 0 9.4286 3.9585 9.4286 8.851v7.125z"></path>
<path d="M75 91.8065V96h4.1935L90.376 84.8174l-4.1934-4.1935L75 91.8064zm4.1935 2.7957h-2.7957v-2.7957h1.398v1.3978h1.3977v1.398zM93.591 81.6024l-1.817 1.817-4.1935-4.1934 1.817-1.817c.5453-.5453 1.426-.5453 1.971 0l2.2226 2.2224c.5453.5452.5453 1.4258 0 1.971z"></path>
<path d="M797 187h4v4h-4v-4zm12-1v19c0 1.1-.9 2-2 2h-20c-1.1 0-2-.9-2-2v-24c0-1.1.9-2 2-2h15l7 7zm-2 1l-6-6h-14v22l6-10 4 8 4-4 6 6v-16z"></path>
<path d="M138 125c-6.62 0-12 5-12 11 0 9.04 12 21 12 21s12-11.96 12-21c0-6-5.38-11-12-11zm0 29.1c-3.72-4.06-10-12.22-10-18.1 0-4.96 4.5-9 10-9 2.68 0 5.22.96 7.12 2.72 1.84 1.72 2.88 3.94 2.88 6.28 0 5.88-6.28 14.04-10 18.1zm4-18.1c0 2.22-1.78 4-4 4-2.22 0-4-1.78-4-4 0-2.22 1.78-4 4-4 2.22 0 4 1.78 4 4z"></path>
<path d="M771 82h8v2h-8v-2zm0 6h8v-2h-8v2zm0 4h8v-2h-8v2zm22-10h-8v2h8v-2zm0 4h-8v2h8v-2zm0 4h-8v2h8v-2zm4-12v18c0 1.1-.9 2-2 2h-11l-2 2-2-2h-11c-1.1 0-2-.9-2-2V78c0-1.1.9-2 2-2h11l2 2 2-2h11c1.1 0 2 .9 2 2zm-16 1l-1-1h-11v18h12V79zm14-1h-11l-1 1v17h12V78z"></path>
<path d="M176 203h-24c-1.1 0-2 .9-2 2v16c0 1.1.9 2 2 2h4v7l7-7h13c1.1 0 2-.9 2-2v-16c0-1.1-.9-2-2-2zm0 18h-14l-4 4v-4h-6v-16h24v16z"></path>
<path d="M673 88.921c0 2.18-.9 4.18-2.34 5.66l-1.34-1.34c1.1-1.12 1.78-2.62 1.78-4.32 0-1.7-.68-3.22-1.78-4.32l1.34-1.34c1.44 1.44 2.34 3.44 2.34 5.66zm-8.56-11.48l-7.44 7.44h-4c-1.1 0-2 .9-2 2v4c0 1.1.9 2 2 2h4l7.44 7.44c.94.94 2.56.28 2.56-1.06v-20.76c0-1.34-1.62-2-2.56-1.06zm11.88.16l-1.34 1.34c2.56 2.56 4.12 6.06 4.12 9.96 0 3.88-1.56 7.4-4.12 9.96l1.34 1.34c2.9-2.9 4.68-6.9 4.68-11.32 0-4.44-1.78-8.44-4.68-11.32v.04zm-2.82 2.82l-1.38 1.34c1.84 1.84 2.96 4.38 2.96 7.16 0 2.78-1.12 5.32-2.96 7.12l1.38 1.34c2.16-2.16 3.5-5.16 3.5-8.46 0-3.3-1.34-6.32-3.5-8.5z"></path>
<path d="M226 79h-16c0-1.1-.9-2-2-2h-8c-1.1 0-2 .9-2 2-1.1 0-2 .9-2 2v18c0 1.1.9 2 2 2h28c1.1 0 2-.9 2-2V81c0-1.1-.9-2-2-2zm-18 4h-8v-2h8v2zm9 14c-3.88 0-7-3.12-7-7s3.12-7 7-7 7 3.12 7 7-3.12 7-7 7zm5-7c0 2.76-2.26 5-5 5s-5-2.26-5-5 2.26-5 5-5 5 2.26 5 5z"></path>
<path d="M725.8393 157h-15.6498c-1.1807 0-1.1807-.82-1.1807-2 0-1.18 0-2 1.1807-2h15.6298C727 153 727 153.82 727 155c0 1.18 0 2-1.1807 2h.02zm-11.6473-10c-1.1807 0-1.1807-.82-1.1807-2 0-1.18 0-2 1.1807-2h11.6273C727 143 727 143.82 727 145c0 1.18 0 2-1.1807 2H714.192zM695 146.82l2.8218-2.6 3.182 3.18 8.185-8.4 2.8218 2.82-11.0068 11-6.0038-6zM710.1895 163h15.6298C727 163 727 163.82 727 165c0 1.18 0 2-1.1807 2h-15.6298c-1.1807 0-1.1807-.82-1.1807-2 0-1.18 0-2 1.1807-2z"></path>
<path d="M223 152v24c0 1.65 1.35 3 3 3h36c1.65 0 3-1.35 3-3v-24c0-1.65-1.35-3-3-3h-36c-1.65 0-3 1.35-3 3zm39 0l-18 15-18-15h36zm-36 4.5l12 9-12 9v-18zm3 19.5l10.5-9 4.5 4.5 4.5-4.5 10.5 9h-30zm33-1.5l-12-9 12-9v18z"></path>
<path d="M648 182h-3v4.5c0 .84-.66 1.5-1.5 1.5h-6c-.84 0-1.5-.66-1.5-1.5V182h-9v4.5c0 .84-.66 1.5-1.5 1.5h-6c-.84 0-1.5-.66-1.5-1.5V182h-3c-1.65 0-3 1.35-3 3v33c0 1.65 1.35 3 3 3h33c1.65 0 3-1.35 3-3v-33c0-1.65-1.35-3-3-3zm0 36h-33v-27h33v27zm-24-33h-3v-6h3v6zm18 0h-3v-6h3v6zm-15 12h-3v-3h3v3zm6 0h-3v-3h3v3zm6 0h-3v-3h3v3zm6 0h-3v-3h3v3zm-24 6h-3v-3h3v3zm6 0h-3v-3h3v3zm6 0h-3v-3h3v3zm6 0h-3v-3h3v3zm6 0h-3v-3h3v3zm-24 6h-3v-3h3v3zm6 0h-3v-3h3v3zm6 0h-3v-3h3v3zm6 0h-3v-3h3v3zm6 0h-3v-3h3v3zm-24 6h-3v-3h3v3zm6 0h-3v-3h3v3zm6 0h-3v-3h3v3zm6 0h-3v-3h3v3z"></path>
</g>
<g class="hero-icons" style="fill: #c2f5ff;">
<path d="M441.1132 69.724c7.681 0 13.9075-6.207 13.9075-13.8636 0-7.6565-6.2266-13.8634-13.9075-13.8634-7.681 0-13.9076 6.207-13.9076 13.8634 0 7.6566 6.2266 13.8635 13.9076 13.8635zm0-5.7932c-4.4713 0-8.096-3.6132-8.096-8.0704 0-4.457 3.6247-8.0703 8.096-8.0703 4.4712 0 8.096 3.6133 8.096 8.0704 0 4.4572-3.6248 8.0704-8.096 8.0704z"></path>
<path d="M354.8995 220.2693c7.681 0 13.9075-6.207 13.9075-13.8635s-6.2266-13.8634-13.9075-13.8634c-7.681 0-13.9075 6.207-13.9075 13.8634 0 7.6566 6.2266 13.8635 13.9075 13.8635zm0-5.793c-4.4713 0-8.096-3.6133-8.096-8.0705 0-4.457 3.6247-8.0703 8.096-8.0703s8.096 3.6132 8.096 8.0703c0 4.4572-3.6247 8.0704-8.096 8.0704z"></path>
<path d="M541.0343 206.4058c0-7.6565-6.2266-13.8634-13.9075-13.8634-7.681 0-13.9075 6.207-13.9075 13.8634 0 7.6566 6.2266 13.8635 13.9075 13.8635 7.681 0 13.9075-6.207 13.9075-13.8635zm-5.8115 0c0 4.4572-3.6247 8.0704-8.096 8.0704s-8.096-3.6132-8.096-8.0704c0-4.457 3.6247-8.0703 8.096-8.0703s8.096 3.6132 8.096 8.0703z"></path>
<path d="M397.6943 214.5258c9.7012 27.0033 25.5723 43.629 43.419 43.629 13.0157 0 25.0578-8.8443 34.4482-24.4154.827-1.371.3822-3.1507-.9932-3.975-1.3755-.824-3.1607-.3808-3.9876.9902-8.439 13.9938-18.8052 21.6072-29.4675 21.6072-14.8247 0-28.9803-14.8288-37.9476-39.7892-.541-1.506-2.2044-2.2897-3.7153-1.7504-1.511.5394-2.297 2.1975-1.756 3.7036z"></path>
<path d="M514.124 163.4733c18.5545-21.85 25.033-43.826 16.122-59.2117-6.557-11.321-20.419-17.2982-38.841-17.537-1.6047-.021-2.9225 1.259-2.9434 2.8586-.0208 1.5996 1.263 2.9132 2.8678 2.934 16.5683.2148 28.5106 5.3642 33.8836 14.641 7.4018 12.7797 1.6243 32.3774-15.5247 52.5722-1.037 1.221-.8844 3.0487.3405 4.0822 1.2248 1.0336 3.0584.8817 4.0952-.3393z"></path>
<path d="M411.5672 88.457c-28.3373-5.1448-50.7424.24-59.672 15.6575-6.6635 11.505-4.7588 26.7585 4.6193 43.0637.7982 1.3878 2.574 1.8678 3.966 1.072 1.3923-.7956 1.874-2.5656 1.0756-3.9534-8.4477-14.688-10.0915-27.8524-4.628-37.2857 7.418-12.8074 27.403-17.6105 53.5978-12.8546 1.579.2866 3.092-.7568 3.3794-2.3307.2876-1.5738-.7592-3.082-2.338-3.3687z"></path>
<path d="M486.3075 209.2436c5.022-15.998 7.7194-34.453 7.7194-53.6842 0-47.9875-16.849-89.3545-40.8478-99.977-1.4667-.649-3.1837.0098-3.835 1.472-.6512 1.462.01 3.1735 1.4766 3.8227 21.404 9.474 37.3945 48.7337 37.3945 94.6824 0 18.6574-2.612 36.5297-7.454 51.954-.4794 1.5268.3736 3.1518 1.9052 3.6295s3.1617-.3727 3.641-1.8994z"></path>
<path d="M466.439 89.4215c-16.7763 3.583-34.6332 10.5886-51.7827 20.4585-42.434 24.4216-70.1147 60.4323-66.2703 86.5432.233 1.5828 1.709 2.6776 3.297 2.4453 1.5877-.2323 2.686-1.7037 2.453-3.2865-3.4135-23.1838 22.825-57.3183 63.426-80.685 16.6365-9.5746 33.9267-16.3578 50.0946-19.811 1.5692-.335 2.5687-1.8748 2.2325-3.439-.336-1.5642-1.8807-2.5606-3.45-2.2255z"></path>
<path d="M371.2508 166.997c11.458 12.5516 26.3438 24.3243 43.3203 34.0947 41.106 23.6572 84.866 29.9805 106.4328 15.3217 1.326-.9013 1.668-2.7033.7638-4.025-.904-1.3217-2.712-1.6626-4.0378-.7614-19.302 13.1195-60.871 7.1128-100.253-15.5523-16.469-9.4783-30.8834-20.8782-41.9277-32.9767-1.08-1.1832-2.9178-1.2695-4.1048-.1928-1.187 1.0766-1.2735 2.9086-.1934 4.0918z"></path>
<path d="M443.2374 165.3634c-5.432 1.17-10.7838-2.2712-11.9598-7.686-1.1714-5.415 2.2785-10.7498 7.7106-11.922 5.432-1.17 10.7838 2.2712 11.9598 7.686 1.1737 5.415-2.2785 10.7498-7.7106 11.922z"></path>
</g>
</g>
</symbol>
</svg>

After

Width:  |  Height:  |  Size: 8.8 KiB

@@ -0,0 +1,224 @@
<template>
<div style="margin-top: 30px;">
<n-card title="自动保存(在高清的时候)">
<div class="first_row" style="display: flex; margin-top: 10px">
<div>
<div style="margin-top: 10px;"></div>
<div>
<n-checkbox v-model:checked="auto_save_setting.auto_save">自动保存选择后生效</n-checkbox>
</div>
<div>
<n-checkbox v-model:checked="auto_save_setting.auto_match">自动匹配再跑批量的时候匹配</n-checkbox>
</div>
</div>
<div style="margin-left: 10px;">
<div style="margin-bottom: 3px;">选择输出文件夹</div>
<n-input placeholder="请选择主文件夹" v-model:value="auto_save_setting.main_save_folder"
style="width: 200px; margin-right: 10px;"></n-input>
<n-button type="info" @click="SelectMainFolder">选择文件夹</n-button>
</div>
<div style="margin-left: 10px;">
<div style="margin-bottom: 3px; opacity: 0; ">12</div>
<n-button type="success" @click="SaveImageAutoSaveSetting">保存设置</n-button>
</div>
</div>
<div class="first_row" style="display: flex; margin-top: 15px">
<!-- <div style="margin-left: 10px; ">
<div style="margin-bottom: 3px;">保存文件夹分类</div>
<n-select v-model:value="auto_save_setting.save_folder_classify" :options="setting_options"
placeholder="请选择保存文件夹分类" style="width: 200px;">
</n-select>
</div> -->
<n-tooltip trigger="hover">
<template #trigger>
<div style="margin-left: 10px; ">
<div style="margin-bottom: 3px;">保存前面多少行空为全部</div>
<n-input-number v-model:value="auto_save_setting.save_match_count" :show-button="false"
placeholder=""></n-input-number>
</div>
</template>
勾选并设置之后每次出图之后出设置的张数其余的会自动在设置的文件中随机匹配
</n-tooltip>
</div>
</n-card>
<n-card style=" margin-top: 10px;" title="手动保存">
<div class="first_row" style="display: flex; margin-top: 15px">
<div style="margin-left: 10px; ">
<div style="margin-bottom: 3px;">选择保存到的文件夹</div>
<n-input placeholder="选择保存到的文件夹" v-model:value="save_setting.save_folder"
style="width: 200px; margin-right: 10px;"></n-input>
<n-button type="info" @click="SelectOutFolder">选择文件夹</n-button>
</div>
<div style="margin-left: 10px; ">
<div style="margin-bottom: 3px;">保存前面多少行空为全部</div>
<n-input-number v-model:value="save_setting.save_match_count" :show-button="false"
placeholder=""></n-input-number>
</div>
<div style="margin-left: 10px;">
<div style="margin-bottom: 3px; opacity: 0; ">12</div>
<n-dropdown trigger="hover" :options="options" @select="handleSelect">
<n-button type="success" @click="SaveImage">开始另存</n-button>
</n-dropdown>
</div>
</div>
<!-- <div class="first_row" style="display: flex; margin-top: 15px">
<div style="margin-left: 10px; ">
<div style="margin-bottom: 3px;">选择要保存的文件夹</div>
<n-input placeholder="选择要保存的文件夹" v-model:value="save_setting.select_folder"
style="width: 400px; margin-right: 10px;"></n-input>
<n-button type="info" @click="SelectSaveFolder">选择文件夹</n-button>
</div>
</div> -->
</n-card>
</div>
</template>
<script>
import { defineComponent, ref, onMounted, toRaw } from "vue";
import { NButton, useMessage, NSpin, NSpace, NSelect, NDivider, NInput, NCode, NCard, NCheckbox, NGradientText, NFormItem, NForm, NInputNumber, NTooltip, NDropdown } from "naive-ui"
import { DEFINE_STRING } from "../../../../define/define_string.js"
import { isEmpty, isNull } from "lodash";
export default defineComponent({
components: {
NButton, NSpin, NSpace, NSelect, NDivider, NInput, NCode, NCard, NCheckbox, NGradientText, NForm, NFormItem, NInputNumber, NTooltip, NDropdown
},
setup() {
let message = useMessage()
let auto_save_setting = ref({
auto_save: false,
auto_match: false,
save_match_count: 0,
main_save_folder: null,
save_folder_classify: null,
})
let save_setting = ref({
save_folder: null,
save_match_count: 0,
select_folder: null
})
let setting_options = ref([])
/**
* 选择保存图片的主文件夹
*/
async function SelectMainFolder() {
await window.api.selectFolder({ default_path: toRaw(auto_save_setting.value).main_save_folder }, (value) => {
if (value.length > 0) {
auto_save_setting.value.main_save_folder = value[0]
}
})
}
async function InitData() {
await window.api.GetAutoSaveImageClassifyOptions((value) => {
setting_options.value = value.data
})
// 获取自动保存设置
await window.api.GetImageAutoSaveSetting((value) => {
if (value.code == 0) {
message.error(value.message)
return
}
auto_save_setting.value = value.data
save_setting.value.save_match_count = value.data.save_match_count
})
}
onMounted(async () => {
await InitData();
})
/**
* 保存图片自动保存设置
*/
async function SaveImageAutoSaveSetting() {
await window.api.SaveImageAutoSaveSetting(JSON.stringify(toRaw(auto_save_setting.value)), (value) => {
if (value.code == 0) {
window.api.showGlobalMessageDialog(value)
return
}
window.api.showGlobalMessageDialog({ code: 1, message: "保存成功" })
})
}
/**
* 下拉框点击事件
* @param {*} value
*/
async function handleSelect(value) {
if (value == 'stop_save') {
await window.api.DeleteBackTask([DEFINE_STRING.QUEUE_BATCH.IMAGE_SAVE_TO_OTHER_FOLDER, "all", "default"], (value) => {
if (value.code == 0) {
message.error(value.message);
return
}
message.success("停止成功")
})
}
}
/**
* 另存图片的方法
*/
async function SaveImage() {
debugger
if (isEmpty(save_setting.value.save_folder)) {
message.error("请选择保存到的文件夹")
return
}
if (save_setting.value.save_match_count == 0) {
message.error("请填写保存前面多少行,不能为0")
return
}
await window.api.SaveImageToOtherFolder(JSON.stringify(toRaw(save_setting.value)), (value) => {
if (value.code == 0) {
message.error(value.message)
return
}
message.success("保存成功")
})
}
/**
* 选择要保存的文件夹
*/
async function SelectSaveFolder() {
}
/**
* 选择要保存到的文件夹
*/
async function SelectOutFolder() {
await window.api.selectFolder({ default_path: toRaw(auto_save_setting.value).main_save_folder }, (value) => {
if (value.length > 0) {
save_setting.value.save_folder = value[0]
}
})
}
return {
auto_save_setting,
SelectMainFolder,
setting_options,
SaveImageAutoSaveSetting,
save_setting,
options: [
{ label: '停止任务', key: 'stop_save' },
],
handleSelect,
SaveImage,
SelectOutFolder
}
}
})
</script>
@@ -0,0 +1,623 @@
<template>
<div id="word_option"
style=" margin-left: 20px; position:absolute; left: 0; top: 0; z-index: 100; height: 30px; width: 100%;">
<n-button color="#1e90ff" size="small" style=" margin-left: 20px;" @click="AIModifyWord">一键洗稿</n-button>
<n-button color="#1e90ff" size="small" style=" margin-left: 20px;"
@click="ContinueAIModifyWord">继续写稿勾选了没有出来的</n-button>
<n-button color="#1e90ff" size="small" style=" margin-left: 20px;" @click="SaveNewWord">保存洗稿后文件</n-button>
<n-button color="#1e90ff" size="small" style=" margin-left: 20px;" @click="ImportSrtAndGetTime">导入字幕</n-button>
<n-button color="#1e90ff" size="small" style=" margin-left: 20px;"
@click="SaveCopywritingInformation">保存文案信息</n-button>
</div>
<n-data-table style="margin-top: 15px;" :row-key="rowKey" :columns="columns" :data="data" :pagination="pagination"
:bordered="false" @update:checked-row-keys="handleCheck" />
</template>
<script>
import { h, defineComponent, onMounted, ref, toRaw } from "vue";
import { NButton, NDataTable, useMessage, NInput, NImage, useDialog } from "naive-ui";
const buttonArr = [
{
title: "切换",
key: "switch",
color: "#8a2be2"
},
{
title: "洗稿",
key: 'aiWord',
color: "#8a2be2"
},
{
title: "下对齐",
key: 'AlignNextWord',
color: "#8a2be0"
}
]
export default defineComponent({
components: {
NButton, NDataTable, NInput, NImage
},
setup() {
const message = useMessage();
let data = ref([]);
let selectKey = ref([]);
let textData = [];
let dialog = useDialog();
let replace_before = ref("");
let replace_after = ref("");
const createColumns = ({
AIModifyOneWord,
SwitchOldWord
}) => {
return [
{
type: "selection",
disabled(row) {
return row.name === "Edward King 3";
}
},
{
title: "编号",
key: 'no',
width: 50
},
{
title: "文案",
key: "word",
width: 250,
render(row, index) {
return h(NInput, {
value: row.word,
type: "textarea",
autosize: true,
onUpdateValue(v) {
data.value[index].word = v
}
})
}
},
{
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 == "aiWord") {
AIModifyOneWord(row);
} else if (item.key == "switch") {
SwitchOldWord(row);
} else if (item.key == "AlignNextWord") {
AlignNextWord(row);
}
}
}, {
default: () => item.title
}
)
});
return btn;
}
},
{
title(row) {
return h("div", {
style: "display : flex; align-items: center;"
}, [
h("span", {
style: "margin-right : 2px; width : 80px "
}, "洗稿后"),
h("div", {
style: " margin-right : 5px"
}, [
h(NInput, {
size: "tiny",
placeholder: "替换前的值",
value: replace_before.value,
onUpdateValue(v) {
replace_before.value = v
}
}),
h(NInput, {
size: "tiny",
placeholder: "替换后的值",
value: replace_after.value,
onUpdateValue(v) {
replace_after.value = v
}
})
]),
h("div", {}, [
h(NButton, {
style: "",
size: "tiny",
color: "#70a1ff",
onClick: () => ReplaceText()
}, { default: () => "替换" }),
])
])
},
key: "after_gpt",
width: 300,
render(row, index) {
return h(NInput, {
value: row.after_gpt,
placeholder: "洗稿后的值",
type: "textarea",
autosize: true,
onUpdateValue(v) {
data.value[index].after_gpt = v
}
})
}
},
{
title: "字幕",
key: "srt",
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: "#5f4321",
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: "timeLimit",
width: 120
}, {
title: "原图",
width: 120,
render(row) {
return h(NImage, {
src: "file:\\" + (row.old_image ? row.old_image : row.outImagePath),
alt: 'Image',
width: '120'
});
}
}
];
};
onMounted(async () => {
// 初始化加载项目下面的分镜好的文案
// 并判断是不是有洗稿后的文件。一并加载
await window.api.GetProjectWord(value => {
debugger;
data.value = value.data;
})
let div = document.getElementById("word_option");
if (window.config.theme == "dark") {
div.style.backgroundColor = "#24242c"
} else {
div.style.backgroundColor = "#FFF"
}
});
/**
* 替换洗稿后的所有数据
*/
async function ReplaceText() {
// 开始替换
for (let i = 0; i < data.value.length; i++) {
data.value[i].after_gpt = data.value[i].after_gpt.replaceAll(replace_before.value, replace_after.value);
}
}
/**
* 重新计算指定索引值的时间
* @param {data value 值的索引} index
*/
function modifyTime(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) {
debugger
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].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("数据错误 2222");
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 SwitchOldWord(row) {
data.value.forEach(item => {
if (item.no == row.no) {
item.after_gpt = item.word;
}
})
}
/**
* 单句写稿
*/
async function AIModifyOneWord(row) {
await window.api.AIModifyOneWord([row.no, row.word], (value) => {
if (value.code != 1) {
message.error("未知错误");
return;
}
// 修改数据
let filter_data = data.value.filter(item => item.no == value.data.no);
filter_data[0].after_gpt = value.data.content;
})
}
/**
* 一键洗稿,AI修改选择的
*/
async function AIModifyWord() {
let word_data = [];
for (let i = 0; i < selectKey.value.length; i++) {
const item = selectKey.value[i];
let obj = {
no: data.value[item - 1].no,
word: data.value[item - 1].word
}
word_data.push(obj);
}
for (let i = 0; i < word_data.length; i++) {
const item = word_data[i];
await AIModifyOneWord(item)
}
// 洗完稿之后全局提示
window.api.showGlobalMessageDialog({ code: 1, message: "一键洗稿完成" })
}
/**
* 保存洗稿后文件
*/
async function SaveNewWord() {
let new_data_word = [];
// 先填充没有洗稿的
for (let i = 0; i < data.value.length; i++) {
const item = data.value[i];
if (!item.after_gpt) {
item.after_gpt = item.word;
}
new_data_word.push(item.after_gpt);
}
textData = new_data_word;
// 开始保存
await window.api.SaveNewWord(new_data_word, (value) => {
if (value.code != 1) {
message.error("未知错误");
return;
}
message.success("保存成功,并写入到了剪贴板");
})
}
/**
* 保存字幕文案信息,用于自动生成视频
*/
async function SaveCopywritingInformation() {
await window.api.SaveCopywritingInformation([toRaw(data.value), "srt_time_information"], (value) => {
if (value.code != 1) {
message.error("未知错误");
return;
}
message.success("保存成功");
})
}
/**
* 导入字幕并进行分镜输出,显示时间,将分镜给他传进去
*/
async function ImportSrtAndGetTime() {
if (textData.length <= 0) {
message.error("请先保存洗稿后文件,不洗稿也要保存一下");
return;
}
await window.api.ImportSrtAndGetTime([toRaw(data.value)], (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 ContinueAIModifyWord() {
// 获取所有被选中的data
let word_data = [];
for (let i = 0; i < selectKey.value.length; i++) {
const item = selectKey.value[i];
if (!data.value[item - 1].after_gpt) {
let obj = {
no: data.value[item - 1].no,
word: data.value[item - 1].word
}
word_data.push(obj);
}
}
for (let i = 0; i < word_data.length; i++) {
const item = word_data[i];
await AIModifyOneWord(item)
}
// 洗完稿之后全局提示
window.api.showGlobalMessageDialog({ code: 1, message: "继续写稿完成" })
}
return {
data,
columns: createColumns({
AIModifyOneWord,
SwitchOldWord
}),
rowKey: (row) => row.no,
handleCheck(rowKeys) {
selectKey.value = rowKeys;
},
pagination: false,
AIModifyWord,
AIModifyOneWord,
ContinueAIModifyWord,
ImportSrtAndGetTime,
SaveNewWord,
SaveCopywritingInformation,
replace_before,
replace_after,
ReplaceText
};
}
});
</script>
@@ -0,0 +1,190 @@
<template>
<n-tabs animated default-value="inGetFrame">
<n-tab-pane size="large" name="draftGetFrame" tab="剪映草稿抽帧">
<n-spin :show="show">
<template #description>
正在抽帧
</template>
<n-space vertical>
<div style="color: red;">
注意该项是使用剪映内的草稿进行分镜
</div>
<n-select v-model:value="selectedValue" filterable placeholder="选择草稿" :options="options" />
<n-button round type="primary" @click="getFrameFunc">开始抽帧</n-button>
</n-space>
</n-spin>
<n-divider />
</n-tab-pane>
<n-tab-pane name="inGetFrame" tab="软件内抽帧">
<div style="color: red; font-size: large;">
注意该功能需要对环境要求较高环境需要自行安装第一次执行需要下载模型需要魔法<span class="url_class" @click="OpenTeachDoc">环境安装教程</span>
<br>
分割敏感度值越小分割越精细值越大分割越粗糙
</div>
<n-form style="margin-top: 50px;" ref="formRef" label-placement="left" inline :model="frameValue"
:rules="rules" size="medium">
<n-form-item label="分割敏感度" path="sensitivity" style="width: 300px;">
<n-slider v-model:value=frameValue.sensitivity show-tooltip />
</n-form-item>
<n-form-item label="分镜视频地址" path="video_path">
<n-input v-model:value="frameValue.video_path" placeholder="选择或输入要分镜的视频地址" style="width: 300px;" />
<n-button type="primary" @click="GetVideoFile">
选择文件(MP4)
</n-button>
</n-form-item>
<n-form-item>
<n-button type="info" @click="StartStoryboarding" :loading="storyLoading">
开始分镜
</n-button>
</n-form-item>
</n-form>
<n-divider />
</n-tab-pane>
</n-tabs>
<n-code style="padding: 0; font-size: small;" :code="code" language="js" word-wrap />
</template>
<script>
import { defineComponent, ref, onMounted, toRaw } from "vue";
import { NSelect, NButton, useMessage, NSpin, NDivider, NCode, NTabs, NTabPane, NSpace, NForm, NFormItem, NSlider, NInput } from "naive-ui"
import { DEFINE_STRING } from "../../../../define/define_string.js"
export default defineComponent({
components: {
NSelect, NButton, NSpin, NDivider, NCode, NTabs, NTabPane, NSpace, NForm, NFormItem, NSlider, NInput
},
setup() {
let options = [];
let out_dir = ref(null);
let selectedValue = ref(null);
let show = ref(false);
let code = ref("");
const message = useMessage();
let storyLoading = ref(false);
let frameValue = ref({
sensitivity: 30,
video_path: null
})
onMounted(() => {
window.api.getDraftFileList((value) => {
value.forEach(element => {
let obj = {
label: element,
value: element
}
options.push(obj)
});
})
window.api.setEventListen([DEFINE_STRING.GET_FRAME_RETUN], (value) => {
if (value.code == 0) {
message.error(value.message);
code.value = code.value + "\n" + value.data;
return;
}
// 完成
if (value.type == 0) {
storyLoading.value = false;
message.success("分镜抽帧完成");
}
code.value = code.value + "\n" + value.data;
})
})
async function getFrameFunc(e) {
out_dir = window.config.project_path
if (selectedValue.value == null || selectedValue.value == undefined) {
message.error("请选择剪映草稿和输出草稿")
return;
}
show.value = true
// 抽帧
await window.api.getFrame([selectedValue.value, out_dir], (value) => {
if (value.code == 0) {
message.error("抽帧失败");
code.value = value.message;
return;
}
message.success("抽帧成功");
code.value = value.data;
show.value = false;
})
}
function selectExportFolder(e) {
window.api.selectFolder(null, (value) => {
if (value.length <= 0) {
message.error("必须选择输出文件夹位置");
return;
}
out_dir.value = value[0]
})
}
/**
* 选择指定的视频文件
*/
async function GetVideoFile() {
await window.api.SelectFile(['mp4'], (value) => {
debugger
if (value.code == 0) {
message.error(value.message);
return;
}
frameValue.value.video_path = value.value;
});
}
/**
* 开始分镜执行分镜任务
*/
async function StartStoryboarding() {
storyLoading.value = true;
if (frameValue.value.video_path == null) {
message.error("选择分镜的视频地址");
return;
}
debugger
if (toRaw(frameValue.value).video_path.split('.')[toRaw(frameValue.value).video_path.split('.').length - 1].toUpperCase() != "MP4") {
message.error("目前只支持MP4格式");
return;
}
await window.api.StartStoryboarding(toRaw(frameValue.value))
}
/**
* 打开环境安装网站
*/
function OpenTeachDoc() {
window.api.OpenUrl("https://pvwu1oahp5m.feishu.cn/docx/VrBVd2KUDosmNfxat3OceWuInjd?from=from_copylink");
}
return {
selectedValue,
options,
out_dir,
getFrameFunc,
selectExportFolder,
show,
code,
frameValue,
GetVideoFile,
StartStoryboarding,
OpenTeachDoc,
storyLoading
}
}
});
</script>
<style>
.url_class {
color: blue;
}
.url_class:hover {
color: brown;
cursor: pointer;
}
</style>
@@ -0,0 +1,387 @@
<template>
<div>一键矩阵功能有对应的字幕配置文件直接自动的生图高清合成视频 <span
style="margin-left: 20px ;font-size: 20px; color: red;">注意前三步还是必做</span></div>
<n-form ref="configRef" inline :model="config" :rules="rules">
<n-form-item label="SRT字幕文件" path="srt_path">
<n-input disabled v-model:value="config.srt_path" placeholder="选择SRT文件" />
<n-button color="#e5a84b" @click="SelectSrt">
<n-icon :size="20">
<folder-open />
</n-icon>
</n-button>
</n-form-item>
<n-form-item label="配音文件" path="audio_path">
<n-input disabled="" v-model:value="config.audio_path" placeholder="选择配音文件" />
<n-button color="#e5a84b" @click="SelectAudio">
<n-icon :size="20">
<folder-open />
</n-icon>
</n-button>
</n-form-item>
<n-form-item label="背景音乐文件夹" path="background_music">
<n-select style="width: 300px;" v-model:value="config.background_music" :options="background_music_options"
placeholder="选择背景音乐文件夹" />
</n-form-item>
<n-form-item>
<n-button :type="buttonLabel.type" @click="AutoConditionCheck">
{{ buttonLabel.label }}
</n-button>
</n-form-item>
<n-form-item>
<n-button type="success" @click="ActionTask">
一键全自动
</n-button>
</n-form-item>
</n-form>
<n-divider />
<n-data-table :columns="columns" :data="data" size="small" />
</template>
<script>
import { defineComponent, ref, onMounted, toRaw, h, onUnmounted } from "vue";
import { NSelect, NForm, NFormItem, NButton, NInput, NIcon, NPopover, useMessage, NDivider, NDataTable, NTag } from "naive-ui"
import { FolderOpenSharp as FolderOpen } from '@vicons/ionicons5'
import { DEFINE_STRING } from "../../../../define/define_string";
function statusAndMessage(row) {
if (row.status == 'wait') {
return {
tagLabel: "等待中",
color: '',
type: "warning"
}
} else if (row.status == 'queue') {
return {
tagLabel: "队列中",
color: '',
type: "info"
}
} else if (row.status == 'ok') {
return {
tagLabel: "生图完成(未高清)",
color: { color: "#2ed573", textColor: '#ffffff' },
type: ""
}
}
else if (row.status == 'error') {
return {
tagLabel: "生图错误",
color: "",
type: "error"
}
}
else if (row.status == "video_improvied") {
return {
tagLabel: "高清完成",
color: { color: "#70a1ff", textColor: '#ffffff' },
type: ""
}
} else if (row.status == "video_improving") {
return {
tagLabel: "高清中",
color: { color: "#fedc5e", textColor: '#ffffff' },
type: ""
}
} else if (row.status == "video_improvie_error") {
return {
tagLabel: "高清失败",
color: { color: "#d23918", textColor: '#ffffff' },
type: ""
}
}
else if (row.status == "video_queue") {
return {
tagLabel: "合成队列中",
color: { color: "#55efc4", textColor: '#ffffff' },
type: ""
}
} else if (row.status == "video_error") {
return {
tagLabel: "合成失败",
color: { color: "#ff4757", textColor: '#ffffff' },
type: ""
}
} else if (row.status == "video_ok") {
return {
tagLabel: "合成完成",
color: { color: "#1dd1a1", textColor: '#ffffff' },
type: ""
}
} else {
return {
tagLabel: "状态错误",
color: { color: "#d2dae2", textColor: '#ffffff' },
type: ""
}
}
}
const createColumns = ({
RemoveTask
}) => {
return [
{
title: "批次名称",
key: "out_folder"
},
{
title: "状态",
key: "status",
render(row) {
return h(
NTag,
{
type: (() => statusAndMessage(row))().type,
color: (() => statusAndMessage(row))().color,
bordered: false
},
{
default: (() => statusAndMessage(row).tagLabel)
}
);
}
},
{
title: "操作",
key: "actions",
render(row) {
return h(
NButton,
{
size: "small",
type: "warning",
onClick: async () => await RemoveTask(row)
},
{ default: () => "停止任务" }
);
}
}
];
};
export default defineComponent({
components: {
NSelect, NForm, NFormItem, NButton, NInput, NIcon, FolderOpen, NPopover, NDivider, NDataTable, NTag
},
setup() {
let config = ref({
srt_path: null,
audio_path: null,
draft_srt_style: "0",
background_music: null,
friendly_reminder: "0"
})
let background_music_options = ref([]);
let configRef = ref(null);
let message = useMessage();
let buttonLabel = ref({
label: "未进行条件检测",
type: "warning"
})
let data = ref([]);
onMounted(async () => {
// 获取队列信息
// 获取背景音乐文件夹信息
await window.api.GetBackgroundMusicConfigList((value) => {
if (value.code == 0) {
message.error(value.message);
return;
}
for (let i = 0; i < value.value.length; i++) {
const element = value.value[i];
let obj = {
label: element.name,
value: element.id,
}
background_music_options.value.push(obj);
}
})
// 获取上一次选择的字幕,配音。未年检
await window.api.GetVideoGenerateConfig((value) => {
if (value.code == 0) {
message.error("获取初始化数据失败");
return;
}
config.value.srt_path = value.data.srt_path;
config.value.audio_path = value.data.audio_path;
config.value.background_music = value.data.background_music;
})
// 获取初始化数据(队列任务-这边不能添加)
await window.api.GetGenerateTaskList((value) => {
debugger;
console.log(value)
data.value = value.data.task_list;
})
// 监听状态刷新事件
window.api.setEventListen([DEFINE_STRING.VIDEO_GENERATE_STATUS_REFRESH], (value) => {
debugger;
console.log(value)
let index = data.value.findIndex(item => item.id == value.id);
if (index < 0) {
return;
}
// 修改
data.value[index].status = value.status;
})
})
onUnmounted(() => {
window.api.removeEventListen(DEFINE_STRING.VIDEO_GENERATE_STATUS_REFRESH);
})
/**
* 全自动条件检测
*/
async function AutoConditionCheck(e) {
e.preventDefault();
configRef.value?.validate(async (errors) => {
debugger
if (errors) {
message.error("请检查必填字段");
return;
}
await window.api.AutoConditionCheck(toRaw(config.value), (value) => {
if (value.code == 0) {
message.error(value.message);
buttonLabel.value = {
label: "条件检测失败",
type: "error"
}
return;
}
message.success("条件检测成功");
buttonLabel.value = {
label: "条件检测成功",
type: "success"
}
})
});
}
/**
* 开始执行全自动任务
*/
async function ActionTask(e) {
e.preventDefault();
configRef.value?.validate(async (errors) => {
debugger
if (errors) {
message.error("请检查必填字段");
return;
}
if (buttonLabel.value.type != "success") {
message.error("请先进行条件检测");
return;
}
// 像后端传递传递开始任务
await window.api.ActionAutoVideoTask([[...toRaw(data.value)], toRaw(config.value)], (value) => {
debugger;
console.log(value);
// let index = data.value.findIndex(item=>item.id == value)
window.api.showGlobalMessageDialog(value)
})
});
}
/**
* 选择对应的字幕文件
*/
async function SelectSrt() {
window.api.SelectFile(['srt'], (value) => {
if (value.code == 0) {
message.error(value.message);
return;
}
config.value.srt_path = value.value;
});
}
/**
* 选择对应的字幕文件
*/
async function SelectAudio() {
window.api.SelectFile(['mp3', 'wav'], (value) => {
if (value.code == 0) {
message.error(value.message);
return;
}
config.value.audio_path = value.value;
});
}
/**
* 移除任务
* @param {*} row
*/
async function RemoveTask(row) {
// 先删除出现的任务
await window.api.DeleteBackTask([DEFINE_STRING.QUEUE_BATCH.AUTO_VIDEO_GENERATE, "all", `${row.out_folder}_image`], async (value) => {
if (value.code == 0) {
message.error(value.message);
return;
}
// 在删除高清任务
await window.api.DeleteBackTask([DEFINE_STRING.QUEUE_BATCH.AUTO_VIDEO_GENERATE, "all", `${row.out_folder}_improve`], async (value) => {
if (value.code == 0) {
message.error(value.message);
return;
}
// 在删除和合成视频任务
await window.api.DeleteBackTask([DEFINE_STRING.QUEUE_BATCH.AUTO_VIDEO_GENERATE, "all", `${row.out_folder}_video`], async (value) => {
if (value.code == 0) {
message.error(value.message);
return;
}
await window.api.ModifyGenerateTaskStatus(['out_folder', row.out_folder, 'wait'], (value) => {
if (value.code == 0) {
message.error(value.message);
return;
}
})
})
})
})
}
return {
config,
SelectSrt,
SelectAudio,
background_music_options,
AutoConditionCheck,
ActionTask,
configRef,
buttonLabel,
data,
columns: createColumns({
RemoveTask
}),
rules: {
srt_path: {
required: true,
message: "请输入SRT文件",
trigger: ["input", "blur"]
},
audio_path: {
required: true,
message: "请选择配音文件",
trigger: ["input", "blur"]
},
background_music: {
required: true,
message: "请选择背景音乐",
trigger: ["input", "blur"]
}
},
}
}
})
</script>
@@ -0,0 +1,99 @@
<template>
<n-spin :show="show">
<template #description>
正在反推
</template>
<n-button type="info" @click="PushBackPrompt"> 开始反推 </n-button>
<n-button type="error" style="margin-left: 50px;" @click="DeleteBadPrompt"> 一键去除所有不想要的值 </n-button>
<n-button type="warning" style="margin-left: 50px;" @click="ManagePrompt"> 管理不想要的值 </n-button>
<n-divider />
</n-spin>
<n-code style="padding: 0;" :code="code" language="js" word-wrap />
</template>
<script>
import { defineComponent, ref, onMounted, h, toRaw } from "vue";
import { NButton, useMessage, useDialog, NDivider, NCode, NSpin } from "naive-ui"
import { DEFINE_STRING } from "../../../../define/define_string";
import ManageBadPrompt from "../Components/ManageBadPrompt.vue"
export default defineComponent({
components: {
NButton, NDivider, NCode, NSpin
},
setup() {
let dialog = useDialog();
let code = ref("");
let show = ref(false);
let promprRef = ref();
let message = useMessage();
// 开始反推
async function PushBackPrompt() {
show.value = true;
await window.api.PushBackPrompt((value) => {
if (value.code == 0) {
message.error("反推报错");
code.value = value.message;
return;
}
message.success("反推成功");
code.value = value.data;
show.value = false;
})
}
/**
* 管理不想要的值
*/
async function ManagePrompt() {
// 弹窗,管理不想要的值
dialog.create({
showIcon: false,
title: "管理不想要的值",
content: () => h(ManageBadPrompt, { ref: promprRef }),
style: `width : 650px;`,
maskClosable: false,
positiveText: "确定",
negativeText: "取消",
onPositiveClick: async () => {
console.log(toRaw(promprRef.value.tags));
// 将数据写入
await window.api.SaveBadPrompt(toRaw(promprRef.value.tags), (value) => {
console.log(value)
if (value.code == 0) {
message.error(value.message);
return false;
}
message.success("添加成功");
})
return true;
}
})
}
/**
* 一键删除不想要的值
*/
async function DeleteBadPrompt() {
await window.api.DeleteBadPrompt((value) => {
debugger
console.log(value);
if (value.code == 0) {
message.error(value.message);
return;
}
message.success("去除成功");
})
}
return {
code,
show,
PushBackPrompt,
ManagePrompt,
DeleteBadPrompt
}
}
})
</script>
@@ -0,0 +1,495 @@
<template>
<div>
<div>当前选择风格<n-button style="margin-left: 20px;" type="info" @click="ShowImageStyleDialog">选择风格</n-button>
<n-button style="margin-left: 20px;" color="#dc6b82" @click="SaveImageSetting">保存图片设置</n-button>
</div>
<div>
<ShowImageTag :selectStyle="selectStyle"></ShowImageTag>
</div>
<n-form ref="formRef" inline :label-width="80" :model="formValue">
<n-form-item label="前缀" path="image_style">
<n-input v-model:value="formValue.image_style" placeholder="填写前缀" />
</n-form-item>
<n-form-item label="输出轮次" path="output_rounds">
<n-input-number v-model:value="formValue.output_rounds" placeholder="输出轮次" />
</n-form-item>
<n-form-item>
<n-button type="info" @click="AddImageTask">
添加任务
</n-button>
</n-form-item>
<n-form-item>
<n-button type="info" @click="GenerateAllImage">
批量生成
</n-button>
</n-form-item>
<n-form-item>
<n-button type="info" @click="RefreshTaskList">
刷新列表
</n-button>
</n-form-item>
<n-form-item>
<n-button type="warning" @click="StopTask">
停止生成任务
</n-button>
</n-form-item>
</n-form>
</div>
<n-data-table :row-key="rowKey" @update:checked-row-keys="handleCheck" :columns="columns" :data="task_list_data"
:pagination="false" :bordered="true" />
</template>
<script>
import { defineComponent, ref, h, onMounted, toRaw, vModelCheckbox } from "vue";
import { NButton, useMessage, NSpin, NInputNumber, NDivider, NForm, NFormItem, NInput, NDataTable, NTag, useDialog, NPopover, NImage } from "naive-ui"
import { DEFINE_STRING } from "../../../../define/define_string.js"
import SelectImageStyle from "../Components/SelectImageStyle.vue"
import ShowImageTag from "../Components/ShowImageTag.vue"
import BatchSaveImageSetting from "./BatchSaveImageSetting.vue"
export default defineComponent({
components: {
NButton, NSpin, NInputNumber, NDivider, NForm, NFormItem, NInput, NDataTable, NTag, NPopover, ShowImageTag
},
setup() {
let show = ref(false);
let count = ref(0);
let message = useMessage();
let selectKey = ref([]);
let formValue = ref({
image_style: null,
lora: null,
output_rounds: 0,
image_style_list: []
})
let task_list_data = ref([]);
let dialog = useDialog();
let selectStyle = ref([]);
const createColumns = ({
DeleteImageTaskList,
RemoveImageTask
}) => {
return [{
type: "selection",
},
{
title: "编号",
key: "no"
},
{
title: "输出文件夹",
key: "out_folder"
},
{
title: "风格",
key: "image_style_list",
render(row, index) {
return h("div", {}, [
h(ShowImageTag, { selectStyle: row.image_style_list_information })
])
}
},
{
title: "前缀",
key: "image_style"
},
{
title: "状态",
key: "status",
render(row) {
return h(NPopover, {
trigger: 'hover',
disabled: (() => {
debugger
if (row.status == "error") {
return false;
} else {
return true
}
})()
},
{
default: () => (() => {
if (row.status == "wait") {
return "warning"
} else if (row.status == "queue") {
return "info"
} else if (row.status == "ok") {
return "success"
} else if (row.status == "error") {
return row.errorMessage
}
})(),
trigger: () => h(
NTag,
{
type: (() => {
if (row.status == "wait") {
return "warning"
} else if (row.status == "queue") {
return "info"
} else if (row.status == "ok") {
return "success"
} else if (row.status == "error") {
return "error"
} else if (row.status.startsWith("video")) {
return "success"
}
})(),
color: '',
},
{
default: () => {
if (row.status == "wait") {
return "等待中"
} else if (row.status == "queue") {
return "队列中"
} else if (row.status == "ok") {
return "完成"
} else if (row.status == "error") {
return "错误"
} else if (row.status.startsWith("video")) {
return "合成视频相关"
}
}
}
)
})
}
},
{
title: "操作",
key: "actions",
render(row) {
return h("div", {}, [
h(NButton,
{
strong: true,
tertiary: true,
type: "error",
size: "small",
onClick: () => DeleteImageTaskList(row)
},
{ default: () => "删除" }),
h(NButton,
{
strong: true,
tertiary: true,
style: "margin-left: 10px;",
type: "error",
size: "small",
onClick: async () => await RemoveImageTask(row)
},
{ default: () => "停止当前任务" })]
);
}
}
];
};
async function getInitData() {
await window.api.GetGenerateTaskList((value) => {
if (value.code == 0) {
message.error(value.message);
return;
}
task_list_data.value = value.data.task_list;
})
for (let i = 0; i < task_list_data.value.length; i++) {
const element = task_list_data.value[i];
// 获取指定的风格数据
await window.api.GetImageStyleInfomation(JSON.stringify(toRaw(element.image_style_list)), (value) => {
if (value.code == 0) {
message.error(value.message);
return;
}
// selectStyle.value = JSON.parse(value.data[0].value);
task_list_data.value[i]['image_style_list_information'] = value.data;
})
}
await window.api.GetConfigJson(JSON.stringify(["image_style", []]), async (value) => {
debugger;
if (value.code == 0) {
message.error(value.message);
return;
}
await window.api.GetImageStyleInfomation(JSON.stringify(toRaw(value.data)), (value) => {
if (value.code == 0) {
message.error(value.message);
return;
}
selectStyle.value = value.data;
})
})
}
onMounted(async () => {
await getInitData();
// 修改一个
window.api.setEventListen([DEFINE_STRING.IMAGE_TASK_STATUS_REFRESH], (value) => {
debugger;
console.log(value)
// 修改当前状态
let index = task_list_data.value.findIndex(item => item.out_folder == value.out_folder);
if (index < 0) {
message.error("数据错误");
return;
}
// 获取指定的数据进行状态更新
task_list_data.value[index].status = value.status;
})
})
/**
* 保存批次
*/
async function AddImageTask() {
if (formValue.value.output_rounds <= 0) {
message.error("输出轮次不能为0或更小");
return;
}
// 添加listId
let tmp_arr = [];
for (let i = 0; i < selectStyle.value.length; i++) {
const element = selectStyle.value[i];
tmp_arr.push(element.id);
}
formValue.value.image_style_list = tmp_arr;
await window.api.AddImageTask(toRaw(formValue.value), (value) => {
debugger;
if (value.code == 0) {
message.error(value.message);
return;
}
task_list_data.value = value.data.task_list;
})
await getInitData();
}
/**
* 删除对应的行的任务
*/
async function DeleteImageTaskList(row) {
// 判断当前的状态
if (row.status == "queue") {
message.error("队列中的状态不能删除");
return;
}
if (row.status == "ok" || row.status.startsWith("video")) {
dialog.warning({
title: "确认",
content: "确定删除?会将生成的图全部删除!",
positiveText: "确定",
negativeText: "取消",
maskClosable: false,
onPositiveClick: async () => {
await window.api.DeleteImageTaskList(row.id, async (value) => {
console.log(value);
if (value.code == 0) {
message.error(value.message);
return;
}
message.success("删除成功");
await window.api.GetGenerateTaskList((value) => {
if (value.code == 0) {
message.error(value.message);
return;
}
task_list_data.value = value.data.task_list;
})
})
},
})
return;
}
await window.api.DeleteImageTaskList(row.id, async (value) => {
console.log(value);
if (value.code == 0) {
message.error(value.message);
return;
}
message.success("删除成功");
await window.api.GetGenerateTaskList((value) => {
if (value.code == 0) {
message.error(value.message);
return;
}
task_list_data.value = value.data.task_list;
})
})
}
/**
* 生成全部
*/
async function GenerateAllImage() {
debugger
// 生成 对应的json 文件
await window.api.AddWebuiJson(async (value) => {
console.log(value);
if (value.code == 0) {
message.error(value.message);
return;
}
await window.api.GenerateImageInSelectTask(toRaw(selectKey.value), (value) => {
if (value.code == 0) {
message.error(value.message);
return;
}
message.success("生图任务添加到队列成功");
})
})
}
/**
* 刷新任务列表
*/
async function RefreshTaskList() {
await getInitData();
}
/**
* 显示风格选择对话框
*/
async function ShowImageStyleDialog() {
// 判断当前数据是不是存在
// 处理数据。获取当前的所有的数据
let dialogWidth = window.innerWidth * 0.8;
let dialogHeight = window.innerHeight * 0.9;
// ImportWordAndSrt
dialog.create({
showIcon: false,
closeOnEsc: false,
content: () => h(SelectImageStyle, { selectStyle: toRaw(selectStyle.value), height: dialogHeight }),
style: `width : ${dialogWidth}px; height : ${dialogHeight}px`,
maskClosable: false,
onClose: async () => {
// 重新加载数据
debugger;
console.log(selectStyle.value);
let tmp_arr = [];
for (let i = 0; i < selectStyle.value.length; i++) {
const element = selectStyle.value[i];
tmp_arr.push(element.id);
}
// 将当前的风格保存
await window.api.SaveCopywritingInformation([JSON.stringify(tmp_arr), "image_style", true], (value) => {
if (value.code == 0) {
message.error(value.message);
return;
}
message.success("保存成功");
})
}
})
}
async function StopTask() {
await getInitData();
await window.api.DeleteBackTask([DEFINE_STRING.QUEUE_BATCH.SD_BACKSTEP_GENERATE_IMAGE, null, "default"], (value) => {
if (value.code == 0) {
message.error(value.message);
return;
}
message.success("停止成功,但正在进行的任务无法停止");
})
}
async function RemoveImageTask(row) {
//`${task_list.out_folder}_${images[0]}`, batch, task_list.out_folder
// 停止当前行的生图任务
debugger;
console.log(row);
await getInitData();
// 判断该当前状态
// if (row.status != "queue") {
// message.error("只有在队列中的任务才能停止");
// return;
// }
await window.api.DeleteBackTask([DEFINE_STRING.QUEUE_BATCH.SD_BACKSTEP_GENERATE_IMAGE, "all", row.out_folder], async (value) => {
if (value.code == 0) {
message.error(value.message);
return;
}
message.success("停止成功");
// 修改状态
await window.api.ModifyGenerateTaskStatus(['id', row.id, 'wait'], (value) => {
if (value.code == 0) {
message.error(value.message);
return;
}
})
await getInitData();
})
}
async function ShowAutoImage() {
// 判断当前数据是不是存在
// 处理数据。获取当前的所有的数据
let dialogWidth = 800;
let dialogHeight = 600;
// ImportWordAndSrt
dialog.create({
showIcon: false,
closeOnEsc: false,
content: () => h(BatchSaveImageSetting, { selectStyle: toRaw(selectStyle.value), height: dialogHeight }),
style: `width : ${dialogWidth}px; height : ${dialogHeight}px`,
maskClosable: false,
onClose: async () => {
}
})
}
/**
* 自动保存图片的设置
*/
async function SaveImageSetting() {
// 获取当前权限
await window.api.GetPermission(async (value) => {
let permission = value.permissions;
// let permission = [];
if (permission && permission.length >= 0) {
if (permission.indexOf(DEFINE_STRING.PERMISSIONS.AUTO_SAVE_IMAGE_PERMISSION) == -1) {
message.error("没有权限");
return;
} else {
await ShowAutoImage();
}
} else {
await ShowAutoImage();
}
});
}
return {
show,
count,
formValue,
task_list_data,
selectKey,
AddImageTask,
rowKey: (row) => row.id,
handleCheck(rowKeys) {
selectKey.value = rowKeys;
},
GenerateAllImage,
columns: createColumns({
DeleteImageTaskList,
RemoveImageTask
}),
RefreshTaskList,
ShowImageStyleDialog,
selectStyle,
StopTask,
SaveImageSetting
}
}
})
</script>
@@ -0,0 +1,26 @@
<template>
<n-tabs default-value="reDraws" 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="reDraws" tab="重绘/单独合成">
<oneImageReDrwa></oneImageReDrwa>
</n-tab-pane>
<n-tab-pane name="autoAll" tab="一键全自动">
<OneKeyMatrix></OneKeyMatrix>
</n-tab-pane>
</n-tabs>
</template>
<script>
import { defineComponent, ref, onMounted, onUnmounted, h, toRaw } from "vue";
import { NTabPane, NTabs } from "naive-ui"
import OneKeyMatrix from "../Backstep/OneKeyMatrix.vue"
import oneImageReDrwa from "../Backstep/oneImageReDrwa.vue"
export default defineComponent({
components: {
NTabPane, NTabs, OneKeyMatrix, oneImageReDrwa
},
})
</script>
@@ -0,0 +1,647 @@
<template>
<div style="margin-bottom: 30px;">
<div>注意</div>
<div>这里加载的是当前<strong>项目地址</strong>下面的输出文件夹</div>
</div>
<n-form style="margin-top: 10px; margin-bottom: 30px;" :model="formValue">
<div style="display: flex;">
<n-form-item label="选择srt地址">
<n-input style="width: 300px; margin-right: 5px;" v-model:value="formValue.srt_path" type="text"
placeholder="SRT字幕地址" />
<n-button color="#e5a84b" @click="SelectSrtFile">
<n-icon :size="20">
<folder-open />
</n-icon>
</n-button>
</n-form-item>
<n-form-item style="margin-left: 10px;" label="选择配音">
<n-input style="width: 300px; margin-right: 5px;" v-model:value="formValue.audio_path" type="text"
placeholder="配音地址(mp3或wav" />
<n-button color="#e5a84b" @click="SelectMusicFile">
<n-icon :size="20">
<folder-open />
</n-icon>
</n-button>
</n-form-item>
<!-- <n-form-item style="flex: 1;" label="选择字幕样式">
<n-select v-model:value="formValue.draft_srt_style" filterable placeholder="选择样式" :options="options" />
</n-form-item> -->
<n-form-item style="margin-left: 10px;" label="选择背景音乐文件夹(随机匹配,自动合成视频必选)">
<n-select style="width: 300px;" v-model:value="formValue.background_music" filterable placeholder="选择样式"
:options="backgroundMusicOptions" />
</n-form-item>
<!-- <n-form-item style="flex: 1; margin-left: 20px;" label="选择温馨提示(不要就选无,可以选择随机)">
<n-select v-model:value="formValue.friendly_reminder" filterable placeholder="选择样式"
:options="friendlyReminderOptions" />
</n-form-item> -->
<n-form-item style="margin-left: 10px;">
<n-button type="info" @click="SaveVideoSrtAndAudioMessage">保存</n-button>
</n-form-item>
</div>
<n-form-item label="注意:高清不会管是不是已经高清过,都会再次高清">
<n-button type="primary" @click="ExportAllDraft">一键导出所有草稿</n-button>
<n-button style="margin-left: 40px;" type="primary" @click="HdAllImages">一键高清所有图片</n-button>
<n-button style="margin-left: 40px;" type="primary" @click="VideoGenerate">一键生成所有视频</n-button>
</n-form-item>
</n-form>
<n-data-table :columns="columns" :data="data" :pagination="pagination" :bordered="false" />
</template>
<script>
import { defineComponent, ref, onMounted, onUnmounted, h, toRaw } from "vue";
import { NButton, useMessage, NInput, NDataTable, NSelect, NForm, NFormItem, useDialog, NPopover, NTag, NTabPane, NTabs, NIcon } from "naive-ui"
import { DEFINE_STRING } from "../../../../define/define_string";
import OneKeyMatrix from "../Backstep/OneKeyMatrix.vue"
import { FolderOpenSharp as FolderOpen } from '@vicons/ionicons5'
const buttonArr = [
{
title: "重绘界面",
key: 'reDraw'
},
{
title: "生成草稿",
key: "addClipDraft"
},
{
title: "高清",
key: "Improve"
},
{
title: "生成视频",
key: "GenerateVideo"
},
{
title: "打开文件夹",
key: "OpenFolder"
}
]
function statusAndMessage(row) {
if (!row.status.startsWith("video")) {
if (row.status != "ok") {
return {
tagLabel: "生图未完成",
color: { color: "#eccc68", textColor: '#ffffff' }
}
} else {
return {
tagLabel: "生图完成(未高清)",
color: { color: "#2ed573", textColor: '#ffffff' }
}
}
} else {
if (row.status == "video_improvied") {
return {
tagLabel: "高清完成",
color: { color: "#70a1ff", textColor: '#ffffff' }
}
} else if (row.status == "video_improving") {
return {
tagLabel: "高清中",
color: { color: "#fedc5e", textColor: '#ffffff' }
}
} else if (row.status == "video_improvie_error") {
return {
tagLabel: "高清失败",
color: { color: "#d23918", textColor: '#ffffff' }
}
}
else if (row.status == "video_queue") {
return {
tagLabel: "合成队列中",
color: { color: "#55efc4", textColor: '#ffffff' }
}
} else if (row.status == "video_error") {
return {
tagLabel: "合成失败",
color: { color: "#ff4757", textColor: '#ffffff' }
}
} else if (row.status == "video_ok") {
return {
tagLabel: "合成完成",
color: { color: "#1dd1a1", textColor: '#ffffff' }
}
} else {
return {
tagLabel: "状态错误",
color: { color: "#d2dae2", textColor: '#ffffff' }
}
}
}
}
export default defineComponent({
components: {
NButton, NDataTable, NInput, NSelect, NForm, NFormItem, NPopover, NTag, NTabPane, NTabs, OneKeyMatrix, NIcon, FolderOpen
},
setup() {
let image_folder = ref("");
let message = useMessage();
let dialog = useDialog();
let data = ref([]);
let options = ref([]);
let backgroundMusicOptions = ref([]);
let friendlyReminderOptions = ref([]);
let formValue = ref({
srt_path: null,
audio_path: null,
draft_srt_style: '0',
background_music: null,
friendly_reminder: '0'
})
/**
* 创建行
* @param {执行的方法} param0
*/
const createColumns = ({
showRegenerateWindow,
AddClipDraft,
ImproveResolution,
GenerateVideo,
OpenFolder
}) => {
return [
{
title: "文件夹",
key: "folder",
width: 200
},
{
title: "操作",
key: "options",
width: 600,
render(row) {
const btn = buttonArr.map(item => {
return h(
NButton, {
style: "margin-right : 10px",
strong: true,
type: "info",
size: "medium",
onClick: () => {
if (item.key == "reDraw") {
showRegenerateWindow(row);
} else if (item.key == "addClipDraft") {
AddClipDraft(row);
} else if (item.key == "Improve") {
// 高清
ImproveResolution(row);
} else if (item.key == "GenerateVideo") {
// 生成视频
GenerateVideo(row);
} else if (item.key == "OpenFolder") {
// 打开对应的文件夹
OpenFolder(row);
}
}
}, {
default: () => item.title
}
)
});
btn.push(h(
NButton, {
style: "margin-right : 10px",
strong: true,
type: "warning",
size: "medium",
onClick: async () => {
await StopTask(row);
}
}, {
default: () => "停止任务"
}
))
return btn;
},
},
{
title: "状态",
key: "status",
render(row) {
return h(NPopover, {
trigger: 'hover',
disabled: (() => {
if (row.status == "video_error") {
return false;
} else {
return true
}
})()
},
{
default: () => (() => {
if (row.status == "wait") {
return "warning"
} else if (row.status == "queue") {
return "info"
} else if (row.status == "ok") {
return "success"
} else if (row.status == "error") {
return row.errorMessage
}
})(),
trigger: () => h(
NTag,
{
color: (() => statusAndMessage(row))().color
},
{
default: (() => statusAndMessage(row).tagLabel)
}
)
})
}
}
]
}
/**
* 停止当前行的任务
*/
async function StopTask(row) {
// 判断当前的状态是不是高清中和生成视频中
if (row.status == "video_queue") {
await window.api.DeleteBackTask([DEFINE_STRING.QUEUE_BATCH.AUTO_VIDEO_GENERATE_SINGLE, row.folder, "default"], async (value) => {
if (value.code == 0) {
message.error(value.message);
return;
}
message.success("停止成功");
// 修改状态为 高清完成
// 修改状态
await window.api.ModifyGenerateTaskStatus(['out_folder', row.folder, 'video_improvied'], (value) => {
if (value.code == 0) {
message.error(value.message);
return;
}
})
})
} else if (row.status == "video_improving") {
await window.api.DeleteBackTask([DEFINE_STRING.QUEUE_BATCH.LOCAL_IMAGE_IMPROVE, row.folder, "default"], async (value) => {
if (value.code == 0) {
message.error(value.message);
return;
}
message.success("停止成功");
// 修改状态
await window.api.ModifyGenerateTaskStatus(['out_folder', row.folder, 'ok'], (value) => {
if (value.code == 0) {
message.error(value.message);
return;
}
})
})
}
else {
message.error("当前状态不是高清中或者生成视频中,无法停止");
}
await InitData();
}
/**
* 高清文件夹,只是加入队列
* @param {行数据} row
*/
async function ImproveResolution(row) {
await window.api.ImproveResolution([row.folder], (value) => {
if (value.code == 1) {
message.success("高清任务加入队列成功,请勿关闭软件");
} else {
message.error(`Error Message ${value.message}`);
}
})
}
/**
* 将生成视频加入到队列中(目前一次最多三个)
* @param {行数据} row
*/
async function GenerateVideo(row) {
let validateRes = await validateMustProperty();
if (!validateRes) {
return
}
await window.api.AutoGenerateVideo([[row.folder], toRaw(formValue.value)], (value) => {
if (value.code == 0) {
message.error(value.message);
return;
}
message.success(row.folder + "添加到队列成功");
})
}
/**
* 一键生成所有的视频(加入队列中)
*/
async function VideoGenerate() {
let validateRes = await validateMustProperty();
if (!validateRes) {
return
}
let f = [];
for (let i = 0; i < data.value.length; i++) {
const item = data.value[i];
f.push(item.folder);
}
// await GenerateVideo(toRaw(item));
await window.api.AutoGenerateVideo([[...f], toRaw(formValue.value)], (value) => {
if (value.code == 0) {
message.error(value.message);
return;
}
message.success("所有任务添加到队列成功");
})
}
function OpenFolder(row) {
window.api.OpenFolder(row.folder);
}
/**
* 高清所有文件夹,将所有需要高清的全部加入队列中
*/
async function HdAllImages() {
dialog.warning({
title: "确认是不是要全部高清",
content: "确认是不是全部高清!分辨率太高,电脑会卡到爆的哦",
positiveText: "确定",
negativeText: "取消",
maskClosable: false,
onPositiveClick: async () => {
let arr = [];
for (let item of data.value) {
arr.push(item.folder); // 这里应该可以正常访问和操作每个元素
}
// 一次高清五个
await window.api.ImproveResolution(arr, (value) => {
console.log(value);
if (value.code == 1) {
message.success("所有的高清任务加入队列成功,请勿关闭软件");
} else {
message.error(`Error Message ${value.message}`);
}
})
}
})
}
/**
* 打开新的窗口
* @param {行数据} row
*/
async function showRegenerateWindow(row) {
window.api.showNewWindow(["ReDrawImage", window.config.project_path + "/tmp/" + row.folder]);
}
/**
* 验证当前表单的必填项
*/
async function validateMustProperty(isBackgroundMusic = true) {
let validateData = toRaw(formValue.value);
if (validateData.srt_path == null || validateData.srt_path == "") {
message.error("SRT地址必填");
return false;
}
if (validateData.audio_path == null || validateData.audio_path == "") {
message.error("配音地址必填");
return false;
}
if (validateData.draft_srt_style == null || validateData.draft_srt_style == "") {
message.error("草稿样式必选");
return false;
}
if (isBackgroundMusic) {
if (validateData.background_music == null || validateData.background_music == "") {
message.error("背景音乐必填");
return false;
}
}
if (validateData.friendly_reminder == null || validateData.friendly_reminder == "") {
message.error("友情提示必填");
return false;
}
return true;
}
/**
* 生成剪映草稿
* @param {行数据} row
*/
async function AddClipDraft(row) {
let validateRes = await validateMustProperty(false);
if (!validateRes) {
return
}
await window.api.addDraft([row.folder, toRaw(formValue.value)], (value) => {
console.log(value)
if (value.code == 0) {
message.error(value.message);
return;
} else {
message.success(`添加成功,草稿名字:${value.draft_name}`);
}
})
}
/**
* 一键保存所有的草稿
*/
async function ExportAllDraft() {
let validateRes = await validateMustProperty(false);
if (!validateRes) {
return
}
for (let i = 0; i < data.value.length; i++) {
const element = data.value[i];
await window.api.addDraft([element.folder, toRaw(formValue.value)], (value) => {
if (value.code == 0) {
message.error(value.message);
return;
} else {
message.success(`添加成功,草稿名字:${value.draft_name}`);
}
})
}
}
/**
* 选择对应的字幕文件
*/
async function SelectSrtFile() {
window.api.SelectFile(['srt'], (value) => {
if (value.code == 0) {
message.error(value.message);
return;
}
formValue.value.srt_path = value.value;
});
}
/**
* 选择对应的字幕文件
*/
async function SelectMusicFile() {
window.api.SelectFile(['mp3', 'wav'], (value) => {
if (value.code == 0) {
message.error(value.message);
return;
}
formValue.value.audio_path = value.value;
});
}
async function InitData() {
// 获取对应的输出文件
await window.api.getSubFolderList(["start", "output_crop"], (value) => {
if (value.code == 0) {
message.error(value.message);
return;
}
data.value = value.data;
})
}
onMounted(async () => {
// 获取对应的输出文件
await InitData();
// // 初始化获取字体样式
// await window.api.getTextStyleList((value) => {
// if (value.code == 0) {
// message.error(value.message);
// return;
// }
// for (let i = 0; i < value.value.length; i++) {
// const item = value.value[i];
// let obj = {
// label: item.name,
// value: item.id,
// }
// options.value.push(obj);
// }
// // 设置默认选项
// if (options.value.length > 0) {
// formValue.value.draft_srt_style = options.value.filter(item => item.value == "1")[0].value;
// }
// })
// 获取初始化的背景音乐列表
await window.api.GetBackgroundMusicConfigList((value) => {
if (value.code == 0) {
message.error(value.message);
return;
}
for (let i = 0; i < value.value.length; i++) {
const element = value.value[i];
let obj = {
label: element.name,
value: element.id,
}
backgroundMusicOptions.value.push(obj);
}
})
// 获取温馨提示列表
// await window.api.getFriendlyReminderList((value) => {
// if (value.code == 0) {
// message.error(value.message);
// return;
// }
// value.value.forEach(item => {
// let obj = {
// label: item.name,
// value: item.id
// };
// friendlyReminderOptions.value.push(obj);
// });
// // 设置默认选项
// if (friendlyReminderOptions.value.length > 0) {
// formValue.value.friendly_reminder = friendlyReminderOptions.value.filter(item => item.value == "1")[0].value;
// }
// })
// 监听状态更新事件
window.api.setEventListen([DEFINE_STRING.VIDEO_GENERATE_STATUS_REFRESH], (value) => {
debugger;
console.log(value)
// 修改当前状态
let index = data.value.findIndex(item => item.folder == value.out_folder);
if (index < 0) {
message.error("数据错误");
return;
}
// 获取指定的数据进行状态更新
data.value[index].status = value.status;
})
// 获取设置的配置
// 获取上一次选择的字幕,配音。未年检
await window.api.GetVideoGenerateConfig((value) => {
if (value.code == 0) {
message.error("获取初始化数据失败");
return;
}
formValue.value.srt_path = value.data.srt_path;
formValue.value.audio_path = value.data.audio_path;
formValue.value.background_music = value.data.background_music;
})
})
onUnmounted(() => {
window.api.removeEventListen(DEFINE_STRING.VIDEO_GENERATE_STATUS_REFRESH);
})
/**
* 保存视频的基础信息
*/
async function SaveVideoSrtAndAudioMessage() {
await window.api.SaveVideoSrtAndAudioMessage(JSON.stringify(toRaw(formValue.value)), (value) => {
if (value.code == 0) {
message.error(value.message);
return;
}
message.success("保存成功");
})
}
return {
image_folder,
columns: createColumns({
showRegenerateWindow,
AddClipDraft,
ImproveResolution,
GenerateVideo,
OpenFolder
}),
data,
options,
backgroundMusicOptions,
friendlyReminderOptions,
formValue,
SelectSrtFile,
SelectMusicFile,
HdAllImages,
ExportAllDraft,
VideoGenerate,
SaveVideoSrtAndAudioMessage
}
}
})
</script>
@@ -0,0 +1,45 @@
<template>
<div>
<n-button @click="addDraft">添加草稿 </n-button>
<n-button @click="downloadModel">下载模型 </n-button>
<n-code style="padding: 0;" :code="code" language="js" word-wrap />
</div>
</template>
<script>
import { defineComponent, ref, onMounted } from "vue";
import { NButton, useMessage, NSpin, NSpace, NSelect, NDivider, NInput, NCode } from "naive-ui"
import { DEFINE_STRING } from "../../../../define/define_string.js"
export default defineComponent({
components: {
NButton, NSpin, NSpace, NSelect, NDivider, NInput, NCode
},
setup() {
let code = ref("");
async function addDraft() {
await window.api.addDraft(['D:\\AI 推文\\番茄小说\\如画室友\\03\\tmp\\output_crop', "D:\\AI 推文\\番茄小说\\如画室友\\03\\1705237224836_gbyfbbGz.srt"], (value) => {
console.log(value)
})
}
onMounted(() => {
window.api.setEventListen([DEFINE_STRING.GET_FRAME_RETUN], (value) => {
debugger;
code.value = code.value + "\n" + value.data;
})
})
async function downloadModel() {
window.api.DownloadModel();
}
return {
addDraft,
downloadModel,
code
}
}
})
</script>
@@ -0,0 +1,122 @@
<template>
<n-spin :show="show">
<template #description>
正在对齐
</template>
<n-space vertical>
<n-select v-model:value="selectedValue" filterable placeholder="选择草稿" :options="options" />
<n-button round type="primary" @click="AlginDraftImg">开始对齐草稿内单条对齐</n-button>
<n-button round type="primary" @click="AlginDraftImgToText">多行对齐提供内容</n-button>
<n-button round type="primary" @click="AlginDraftImgToTextAll">一键对齐</n-button>
</n-space>
</n-spin>
<n-divider />
<!-- <pre>{{ items }}</pre> -->
<n-input v-model:value="inputValue" type="textarea" placeholder="自动调整尺寸" :autosize="{
minRows: 3
}" />
</template>
<script>
import { defineComponent, ref, onMounted } from "vue";
import { NButton, useMessage, NSpin, NSpace, NSelect, NDivider, NInput } from "naive-ui"
import { DEFINE_STRING } from "../../../../define/define_string.js"
export default defineComponent({
components: {
NButton, NSpin, NSpace, NSelect, NDivider, NInput
},
setup() {
let show = ref(false);
let options = ref([]);
let items = ref([]);
let selectedValue = ref(null);
let message = useMessage()
let inputValue = ref("")
// 单句对齐
function AlginDraftImg() {
if (selectedValue.value == null || selectedValue.value == "") {
message.error("请选择需要对齐的草稿");
return;
}
show.value = true;
window.api.alginDraftImg(selectedValue.value, (type, value) => {
items.value.push(value)
console.log(value)
if (type == DEFINE_STRING.PYTHON_CLOSE) {
show.value = false;
message.success("字幕对齐成功")
}
})
}
/**
* 按照内容对齐
*/
function AlginDraftImgToText() {
if (selectedValue.value == null || selectedValue.value == "" || inputValue.value == null || inputValue.value == "") {
message.error("请选择需要对齐的草稿和输入分镜内容");
return;
}
// 文案内容
let text_value = inputValue.value.split("\n")
window.api.alginDraftImgToText([selectedValue.value, text_value], (value) => {
if (value.code == 0) {
message.error(value.message);
return;
}
message.success("对齐成功");
})
}
/**
* 一键全部对齐
*/
function AlginDraftImgToTextAll() {
if (inputValue.value == null || inputValue.value == "") {
message.error("请输入分镜内容");
return;
}
debugger;
// 文案内容
let text_value = inputValue.value.split("\n")
// 获取当前项目下面地所有草稿
console.log(window);
let draft_value = options.value.filter(item => item.value.startsWith(window.config.project_name));
for (let i = 0; i < draft_value.length; i++) {
const element = draft_value[i];
window.api.alginDraftImgToText([element.value, text_value], (value) => {
if (value.code == 0) {
message.error(value.message);
return;
}
message.success(element.value + "对齐成功");
})
}
}
onMounted(() => {
window.api.getDraftFileList((value) => {
value.forEach(element => {
let obj = {
label: element,
value: element
}
options.value.push(obj)
});
})
})
return {
show,
options,
selectedValue,
AlginDraftImg,
AlginDraftImgToText,
AlginDraftImgToTextAll,
items,
inputValue
}
}
})
</script>
@@ -0,0 +1,83 @@
<template>
<div style="height: 200px;">
<div style="height: 40px;"></div>
<div style="display: flex;">
<n-input :disabled="true" v-model:value="machineId" type="text" placeholder="唯一码" />
<n-popover trigger="click">
<template #trigger>
<n-button text style="width: 24px; font-size: 24px; color: skyblue; display: flex; heigh">
<n-icon>
<InformationCircle />
</n-icon>
</n-button>
</template>
<span>VXxiangbie88</span>
</n-popover>
</div>
<div style="color: red; margin: 10px">如未激活将上面的码给管理员激活
<n-popover trigger="hover" raw :show-arrow="false">
<template #trigger>
<n-tag type="info">
客服微信
</n-tag>
</template>
<n-image width="250" src="http://qiniuyun.upsurging.xyz/LAI/a41237ef53a18b6c7fba5d966e9a28f.jpg"
preview-disabled />
</n-popover>
</div>
<div style="margin: 10px;">详细的教程文档<span class="url_class" @click="OpenTeachDoc">教程文档</span></div>
<div style="margin: 10px;">问题/需求收集表<span class="url_class" @click="OpenQueDoc">问题/需求收集表</span></div>
</div>
</template>
<script>
import { defineComponent, ref, h, onMounted, toRaw } from "vue";
import { useMessage, NInput, NButton, NIcon, NPopover, NImage, NTag } from "naive-ui"
import { InformationCircle } from '@vicons/ionicons5'
export default defineComponent({
components: {
NInput, NButton, NIcon, InformationCircle, NPopover, NImage, NTag
},
setup() {
let machineId = ref("");
let message = useMessage();
onMounted(async () => {
// 获取机械码
await window.api.GetMachineId((value) => {
if (value.code == 0) {
message.error(value.message);
return;
}
machineId.value = value.value;
})
})
function OpenTeachDoc() {
window.api.OpenUrl("https://pvwu1oahp5m.feishu.cn/docx/CAjGdTDlboJ3nVx0cQccOuNHnvd?from=from_copylink");
}
function OpenQueDoc() {
window.api.OpenUrl("https://pvwu1oahp5m.feishu.cn/sheets/G5s3sX0KahH1XQtWDazcF70anUb?from=from_copylink");
}
return {
machineId,
OpenQueDoc,
OpenTeachDoc
}
}
})
</script>
<style>
.url_class {
color: blue;
}
.url_class:hover {
color: brown;
cursor: pointer;
}
</style>
@@ -0,0 +1,461 @@
<template>
<div style="display: flex">
<div style="flex: 5; margin-right: 20px">
<n-form ref="formRef" :model="prompt" :rules="rules" :size="size">
<n-form-item label="提示词" path="prompt">
<n-input type="textarea" v-model:value="prompt.prompt" placeholder="提示词" />
</n-form-item>
</n-form>
<n-form
ref="formRef"
inline
:label-width="80"
:model="prompt"
:rules="rules"
:size="size"
>
<n-form-item label="重绘幅度" path="denoising_strength">
<n-input-number
:step="0.05"
:precision="2"
v-model:value="prompt.denoising_strength"
placeholder="重绘幅度"
/>
</n-form-item>
<n-form-item label="种子" path="seed">
<n-input v-model:value="prompt.seed" placeholder="种子" />
</n-form-item>
<n-form-item label="生图模式" path="model">
<n-select
style="width: 150px"
v-model:value="prompt.model"
:options="modelOption"
placeholder="种子"
/>
<n-checkbox
style="margin-left: 20px"
v-model:checked="prompt.adetailer"
label="修脸"
></n-checkbox>
</n-form-item>
<n-form-item>
<n-button type="info" @click="GenerateAll">全部重绘</n-button>
</n-form-item>
<n-form-item label="图片显示大小">
<n-slider
style="width: 120px"
:min="150"
:max="300"
v-model:value="image_width"
:step="10"
/>
</n-form-item>
</n-form>
</div>
<div style="flex: 1; width: 150px; height: 150px">
<n-image style="width: 150px; height: 150px" :src="prompt.oldImage" />
<div style="display: flex; justify-content: left; align-items: center">
<span style="font-size: 18px">{{ prompt.name }}</span>
</div>
</div>
</div>
<div class="image-container" style="display: flex">
<n-image-group show-toolbar-tooltip :theme-overrides="imageGroupThemeOverrides">
<div style="display: flex; flex-wrap: wrap">
<div
v-for="(image, index) in images"
style="margin: 0 10px 5px 10px"
:key="image.id"
>
<n-image
@contextmenu.prevent="handleContextMenu"
:width="image_width"
:height="image_height"
:src="image.img_path"
alt="图片描述"
lazy
style="display: block"
/>
<n-button
:disabled="image.disabled"
style="width: 100%"
type="info"
@click="ReGenerateImage(image)"
>重绘</n-button
>
</div>
</div>
</n-image-group>
</div>
<div
style="margin-top: 10px; display: flex; justify-content: center; align-items: center"
>
<n-button type="error" style="margin-right: 20px" @click="GetLast"
>上一组F1</n-button
>
<n-button type="info" @click="GetNext">下一组F2</n-button>
</div>
<n-dropdown
placement="bottom-start"
trigger="manual"
:x="x"
:y="y"
:options="dropOption"
:show="showDropdownRef"
:on-clickoutside="onClickoutside"
@select="handleSelect"
/>
</template>
<script>
import {
defineComponent,
ref,
onMounted,
watch,
computed,
onUnmounted,
nextTick,
toRaw,
} from "vue";
import {
NImage,
NButton,
NForm,
NFormItem,
NInput,
useMessage,
NInputNumber,
NSelect,
NCheckbox,
useThemeVars,
NImageGroup,
NSlider,
NDropdown,
} from "naive-ui";
import { DEFINE_STRING } from "../../../../define/define_string";
export default defineComponent({
components: {
NImage,
NButton,
NForm,
NFormItem,
NInput,
NInputNumber,
NSelect,
NCheckbox,
NImageGroup,
NSlider,
NDropdown,
},
props: {
ImageInitData: {
required: true,
},
},
setup(props) {
let message = useMessage();
debugger;
let images = ref(
window.ImageInitData.filter((item) => item.id == window.currentImageId)[0]
.output_list
);
let prompt = ref(
window.ImageInitData.filter((item) => item.id == window.currentImageId)[0]
);
const showDropdownRef = ref(false);
const x = ref(0);
const y = ref(0);
let current_img = ref("");
let modelOption = ref([
{
label: "文生图",
value: "txt2img",
},
{
label: "图生图",
value: "img2img",
},
]);
let dropOption = [
{
label: "保存到封面",
key: "savaCover",
},
{
label: "替换参考图",
key: "replaceImage",
},
];
let image_width = ref(150);
let image_height = ref(150 * (prompt.value.height / prompt.value.width));
// 监听myRef的变化
const stopWatch = watch(image_width, (newValue, oldValue) => {
image_height.value = newValue * (prompt.value.height / prompt.value.width);
// 根据myRef的新值执行操作...
});
function handleFunc(event) {
if (event.key == "F1") {
// 数字 112 是 F1 键的 keyCode
GetLast();
// 阻止默认操作
event.preventDefault();
// 这里调用您的函数
} else if (event.key == "F2") {
GetNext();
// 阻止默认操作
event.preventDefault();
}
}
onMounted(() => {
// 重新计算 images,判断是不是disabled
for (let i = 0; i < images.value.length; i++) {
const element = images.value[i];
if (
prompt.value.oldImage.replaceAll("\\", "/") ==
element.img_path.replaceAll("\\", "/")
) {
images.value[i].disabled = true;
}
}
if (!window.isWatch) {
window.api.setEventListen(
[DEFINE_STRING.REGENERATE_IMAGE_RETUN, window.id],
(value) => {
if (value.type != 1) {
return;
}
// 修改图片的数据源
if (value.code == 0) {
message.error(value.message);
return;
}
let index = window.ImageInitData.findIndex(
(item) => item.id == window.currentImageId
);
images.value = window.ImageInitData[index].output_list;
let filteArr = images.value.filter((item) => item.id == value.id);
filteArr[0].img_path =
filteArr[0].img_path.split("?")[0] + `?time=${new Date().getTime()}`;
message.success(JSON.stringify(value));
}
);
window.isWatch = true;
}
// 监听事件
window.addEventListener("keydown", handleFunc);
});
onUnmounted(() => {
window.removeEventListener("keydown", handleFunc);
stopWatch();
});
/**
* 重绘图片
*/
async function ReGenerateImage(data) {
debugger;
console.log(data);
let obj = {
id: data.id,
denoising_strength: prompt.value.denoising_strength,
image: data.img_path.split("?")[0],
name: prompt.value.name,
negative_prompt: prompt.value.negative_prompt,
prompt: prompt.value.prompt,
sampler_name: prompt.value.sampler_name,
seed: -1,
timedata: new Date().getTime(),
image_style: data.image_style,
lora: data.lora,
image_style_list: toRaw(data.image_style_list),
model: prompt.value.model,
adetailer: prompt.value.adetailer,
};
// 开始重新生成
await window.api.ReGenerateImage([window.id, obj, 1], (value) => {
console.log(value);
});
}
/**
* 一键全部重绘
*/
async function GenerateAll() {
for (let i = 0; i < images.value.length; i++) {
const element = images.value[i];
await ReGenerateImage(element);
}
}
/**
* 获取上一组
*/
function GetLast() {
debugger;
let index = window.ImageInitData.findIndex(
(item) => item.id == window.currentImageId
);
if (index > 0) {
images.value = window.ImageInitData[index - 1].output_list;
prompt.value = window.ImageInitData[index - 1];
window.currentImageId = prompt.value.id;
} else {
message.error("没有上一组了");
}
}
/**
* 获取下一组
*/
function GetNext() {
debugger;
let index = window.ImageInitData.findIndex(
(item) => item.id === window.currentImageId
);
if (index + 1 >= window.ImageInitData.length) {
// 已经是最后一个元素
message.error("当前已经是最后一个了");
} else {
// 返回下一个对象
images.value = window.ImageInitData[index + 1].output_list;
prompt.value = window.ImageInitData[index + 1];
window.currentImageId = prompt.value.id;
}
}
return {
images,
ReGenerateImage,
GetLast,
GetNext,
prompt,
modelOption,
current_img,
GenerateAll,
image_width,
image_height,
imageGroupThemeOverrides: computed(() => {
const {
popoverColor,
boxShadow2,
textColor2,
borderRadius,
} = useThemeVars().value;
const themeOverrides = {
toolbarColor: popoverColor,
toolbarBoxShadow: boxShadow2,
toolbarIconColor: textColor2,
toolbarBorderRadius: borderRadius,
};
return themeOverrides;
}),
handleContextMenu(e) {
e.preventDefault();
let img_src = decodeURI(e.target.src);
if (img_src) {
current_img.value = img_src
.split("?")[0]
.replace(/^file:\/\/\//, "")
.replace(/^\//, "");
nextTick().then(() => {
showDropdownRef.value = true;
x.value = e.clientX;
y.value = e.clientY;
});
} else {
message.error("文件路径错误");
return;
}
},
x: x,
y: y,
dropOption,
showDropdownRef,
onClickoutside() {
showDropdownRef.value = false;
},
handleSelect(key) {
showDropdownRef.value = false;
if (key == "savaCover") {
// 将指定的文件写入写入到封面中
window.api.DownloadImageFile(
[toRaw(current_img.value), "cover", window.id],
(value) => {
if (value.code == 1) {
message.success(toRaw(current_img.value) + "保存封面成功");
return;
}
message.error("保存失败:" + value.message);
}
);
} else if (key == "replaceImage") {
// 替换参考图
// 先替换界面。然后修改配置文件
prompt.value.old_old_image = prompt.value.oldImage;
prompt.value.oldImage = current_img.value;
// 修改配置文件
let arg = {
name: prompt.value.name,
prompt: prompt.value.prompt,
denoising_strength: prompt.value.denoising_strength,
model: prompt.value.model,
adetailer: prompt.value.adetailer,
modify_old: prompt.value.oldImage,
};
window.api.ModifyInpurCropJson(arg, (value) => {
if (value.code == 0) {
message.error(value.message);
return;
}
message.success("替换参考图成功");
// 修改image中的disable
for (let i = 0; i < images.value.length; i++) {
const element = images.value[i];
if (
prompt.value.oldImage.replaceAll("\\", "/") ==
element.img_path.replaceAll("\\", "/")
) {
images.value[i].disabled = true;
} else {
images.value[i].disabled = false;
}
}
});
}
},
};
},
});
</script>
<style>
.image {
display: flex;
flex-direction: column;
align-items: center;
}
.image img {
/* 固定高度 */
margin: 10px 15px 5px 15px;
/* 间隔 */
}
/* .btn {
width: 100%;
margin-bottom: 5px;
} */
</style>
@@ -0,0 +1,120 @@
<template>
<div style="margin-top: 10px;">
<div style="color: red;">注意删除所有是删除当前查找到的就是下面显示的tag</div>
<div style="margin-top: 5px; display: flex; margin-bottom: 10px;">
<n-input v-model:value="inputTag" placeholder="输入不想要的标签,多个以英文逗号隔开"></n-input>
<n-button style="width: 60px;margin-left: 5px;" type="info" @click="AddTags">添加</n-button>
</div>
<div style="margin-top: 5px; display: flex; margin-bottom: 10px;">
<n-input v-model:value="searchTag" placeholder="输入搜索的值,多个以英文逗号隔开"></n-input>
<n-button style="width: 60px;margin-left: 5px;" type="info" @click="SearchTags">搜索</n-button>
<n-button style="width: 100px;margin-left: 5px;" type="info" @click="DeleteTags">删除所有</n-button>
</div>
<n-tag style="margin: 5px;" v-for="(tag, index) in show_tags" :key="index" type="success" closable
@close="handleClose(tag)">{{ tag }}</n-tag>
</div>
</template>
<script>
import { defineComponent, ref, onMounted, h, toRaw } from "vue";
import { NButton, useMessage, NInput, NTag } from "naive-ui"
export default defineComponent({
components: {
NButton, NInput, NTag
},
setup() {
let inputTag = ref("");
let searchTag = ref("");
let show_tags = ref([]);
let tags = ref([]);
let message = useMessage();
onMounted(async () => {
// 初始化获取提示词
window.api.GetBadPrompt((value) => {
if (value.code == 0) {
message.error(value.message);
return;
}
if (value.value != null) {
tags.value = value.value.split(',');
show_tags.value = value.value.split(',');
}
})
})
/**
* 添加标签
*/
async function AddTags() {
if (inputTag.value == null || inputTag.value == "") {
message.error("请输入内容");
return;
}
let newTag = inputTag.value.replaceAll('', ',').split(',');
let mergeTag = [...newTag, ...toRaw(tags.value)];
tags.value = [...new Set(mergeTag)]
show_tags.value = [...new Set(mergeTag)]
inputTag.value = "";
}
/**
* 删除
*/
async function handleClose(tag) {
debugger;
// 删除对应的数据
tags.value = tags.value.filter(item => item != tag);
show_tags.value = show_tags.value.filter(item => item != tag);
await SearchTags();
}
/**
* 查找筛选值(返回符合条件的tag)
*/
async function SearchTags() {
if (searchTag.value == null || searchTag.value == "") {
show_tags.value = tags.value;
return;
}
let s_ts = searchTag.value.replaceAll('', ',').split(',');
let temp_tag_list = [];
for (let i = 0; i < s_ts.length; i++) {
const element = s_ts[i];
let t = show_tags.value.filter(item => item.includes(element));
let t2 = [...temp_tag_list, ...t];
temp_tag_list = [...new Set(t2)]
}
show_tags.value = temp_tag_list;
}
/**
* 删除所选的tag(全部)
*/
async function DeleteTags() {
debugger
let d_l = toRaw(show_tags.value);
for (let i = 0; i < d_l.length; i++) {
const element = d_l[i];
// tags.value = tags.value.filter(item => item != element);
// show_tags.value = show_tags.value.filter(item => item != element);
await handleClose(element)
}
// await SearchTags();
}
return {
tags,
inputTag,
AddTags,
handleClose,
SearchTags,
DeleteTags,
show_tags,
searchTag
}
}
})
</script>
@@ -0,0 +1,213 @@
<template>
<div style="position: relative;">
<n-input placeholder="新加tag,多个用逗号隔开" size="tiny" @keyup="AddTag" v-model:value="input_data"></n-input>
<span v-for="(tag, index) in data.chinese_prompt">
<n-tag @click="TagClickEdit(tag, index)" size="small" :key="index" type="success" closable
@close="handleClose(tag)">
<template #default>
<n-input v-if="tag.edit" size="tiny" @keyup="ModifyTag($event, tag, index)" style="width: 70px;"
v-model:value="tag.dst">{{ tag.dst }}</n-input>
<span
style="max-width: 130px; min-width: 10px; display: inline-block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;"
:title="tag.dst" v-else>{{ tag.dst }}</span>
</template>
</n-tag>
<n-button size="tiny" type="info" quaternary @click="AddTagInPosition(index)">
<template #icon>
<n-icon>
<add />
</n-icon>
</template>
</n-button>
</span>
</div>
</template>
<script>
import { h, defineComponent, onMounted, ref, toRaw, watch, onUnmounted } from "vue";
import { NButton, NDataTable, useMessage, NInput, useDialog, NTag, NIcon } from "naive-ui";
import { isEmpty } from "lodash";
import { Add } from "@vicons/ionicons5"
export default defineComponent({
components: {
NButton, NDataTable, NInput, NTag, Add, NIcon
},
props: ["initData", "type", "translateMode"],
setup(props) {
const message = useMessage();
let input_data = ref("");
let data = ref(props.initData);
let type = ref(props.type);
let translateMode = ref(props.translateMode);
// 修改prompt
async function ModifyPrompt() {
// 修改prompt
let res = "";
for (let i = 0; i < data.value.chinese_prompt.length; i++) {
const element = data.value.chinese_prompt[i];
res += element.src + ',';
}
data.value.prompt = res;
}
onMounted(async () => {
console.log(123)
// 处理数据
});
/**
* 添加 Tag 的 方法
* @param {*} e
*/
async function AddTag(e) {
if (e.key.toLowerCase() == "enter") {
// 添加
if (input_data.value == "" || input_data.value == null) {
message.error("请输入标签");
return;
}
if (!data.value.chinese_prompt) {
message.error("添加标签前请先英翻中");
return;
}
let tags = input_data.value.replaceAll('', ',');
// 自动翻译
if (translateMode.value) {
// 翻译单个。马上返回
await window.api.TranslateReturnNow([tags, 'zh', 'en', true], (value) => {
debugger
console.log(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.chinese_prompt.push(element);
}
ModifyPrompt();
message.success("添加成功");
input_data.value = "";
})
} else {
let tags = input_data.value.split(',');
for (let i = 0; i < tags.length; i++) {
const element = tags[i];
if (!isEmpty(element)) {
data.value.chinese_prompt.push(
{
"src": "",
"dst": element
},
);
}
}
}
}
}
/**
* 关闭tag的方法
* @param {要删除的tag} tag
*/
async function handleClose(tag) {
debugger
// 删除对应的数据
data.value.chinese_prompt = data.value.chinese_prompt.filter(item => item.src != tag.src);
ModifyPrompt();
await SearchTags();
}
/**
* 查找筛选值(返回符合条件的tag)
*/
async function SearchTags() {
}
/**
* 可以编辑标签
* @param {*} e
*/
async function TagClickEdit(tag, index) {
console.log(tag, index);
data.value.chinese_prompt[index]["edit"] = true;
// 将其他的数据变成false
for (let i = 0; i < data.value.chinese_prompt.length; i++) {
if (i != index) {
data.value.chinese_prompt[i]["edit"] = false;
}
}
ModifyPrompt();
}
/**
* 修改标签的数据
*/
async function ModifyTag(e, tag, index) {
if (e.key.toLowerCase() == "enter") {
if (tag.dst == "" || tag.dst == null) {
message.error("请输入标签");
return;
}
if (tag.dst.includes(',') || tag.dst.includes('')) {
message.error("单个标签不能有逗号");
return;
}
// 实时翻译一下
let tags = tag.dst.replaceAll('', ',');
// 自动翻译
if (translateMode.value) {
// 翻译单个。马上返回
await window.api.TranslateReturnNow([tags, 'zh', 'en', false], (value) => {
console.log(value)
if (value.code == 0) {
message.error(value.message);
return;
}
data.value.chinese_prompt[index] = value.data[0];
ModifyPrompt();
message.success("修改成功");
input_data.value = "";
})
}
data.value.chinese_prompt[index]["edit"] = false;
ModifyPrompt();
}
}
/**
* 添加数据到指定的位置
*/
async function AddTagInPosition(index) {
// 先判断是不是有没有添加的
let ii = data.value.chinese_prompt.findIndex(item => item.edit == true);
if (ii >= 0) {
message.error("请先保存当前添加的标签");
return;
}
data.value.chinese_prompt.splice(index + 1, 0, { "src": "", "dst": "", edit: true });
}
return {
AddTag,
handleClose,
SearchTags,
type,
translateMode,
TagClickEdit,
ModifyTag,
input_data,
data,
AddTagInPosition
};
}
});
</script>
@@ -0,0 +1,59 @@
<template>
<n-form label-width="auto" :model="wmSetting" label-placement="left" style="margin-top: 50px;">
<n-form-item v-if="showTextLabel" :disable="true" style=" margin-right: 20px;" label="水印字体(要换行输入\N"
path="fontName">
<n-input style="width: 400px;" v-model:value="wmSetting.showText" />
</n-form-item>
<n-form-item style=" margin-right: 20px;" label="字体" path="fontName">
<n-input style="width: 300px;" v-model:value="wmSetting.fontName" :options="fontNameOptions" filterable />
</n-form-item>
<n-form-item style=" margin-right: 20px;" label="字号(80差不多)">
<n-input-number style="width: 200px;" v-model:value="wmSetting.fontSize" />
</n-form-item>
<n-form-item style=" margin-right: 20px;" label="颜色">
<n-color-picker style="width: 200px;" v-model:value="wmSetting.fontColor" :show-alpha="false" />
</n-form-item>
<n-form-item style=" margin-right: 20px;" label="透明度">
<n-input-number style="width: 200px;" v-model:value="wmSetting.transparent" />
</n-form-item>
<n-form-item style="margin-right: 20px;" label="位置">
<n-input-number style="width: 60px; margin-right: 10px;" v-model:value="wmSetting.positionX" placeholder="X"
:show-button="false" />
<n-input-number style="width: 60px;" v-model:value="wmSetting.positionY" placeholder="Y"
:show-button="false" />
</n-form-item>
</n-form>
</template>
<script>
import { defineComponent, ref, h, onMounted, toRaw } from "vue";
import { NForm, NFormItem, NInput, NInputNumber, NColorPicker, NSelect, NButton } from "naive-ui"
export default defineComponent({
components: {
NForm, NFormItem, NInput, NInputNumber, NColorPicker, NSelect, NButton
},
props: ["initData", "type"],
setup(props) {
let showTextLabel = ref(false);
let fontNameOptions = ref([]);
let wmSetting = ref(props.initData);
onMounted(() => {
if (props.type == "watermarkConfig") {
showTextLabel.value = true;
}
})
return {
wmSetting,
fontNameOptions,
showTextLabel
}
}
})
</script>
@@ -0,0 +1,94 @@
<template>
<div>
<div style="color: red; margin-bottom: 5px;">
添加ADetailer配置只要选择修脸修手都会调用<br>
</div>
<n-dynamic-input v-model:value="ADetailer" :on-create="onCreate">
<template #create-button-default>
添加ADetailer数据
</template>
<template #default="{ value }">
<div style="display: flex; align-items: center; width: 100%">
<n-input v-model:value="value.ad_model" placeholder="输入模型名称"> </n-input>
<n-input-number v-model:value="value.ad_confidence" style="margin-right: 12px; width: 160px" />
</div>
</template>
</n-dynamic-input>
<n-button type="primary" style="margin-top: 10px;" @click="SaveADetailerConfig">保存</n-button>
</div>
</template>
<script>
import { defineComponent, ref, h, onMounted, toRaw } from "vue";
import { useMessage, NDynamicInput, NInput, NInputNumber, NButton } from "naive-ui"
export default defineComponent({
components: {
NDynamicInput, NInput, NInputNumber, NButton
},
setup() {
let ADetailer = ref([]);
let message = useMessage();
onMounted(async () => {
await window.api.GetADetailerList((value) => {
if (value.code == 0) {
message.error(value.message);
return;
}
if (value.data != null) {
ADetailer.value = value.data;
}
})
})
/**
* 添加的状态
*/
function onCreate() {
return {
ad_model: null,
ad_confidence: 0.3
}
}
/**
* 保存修脸配置
*/
async function SaveADetailerConfig() {
// 判断添加的数据是不是都有值
let isValue = true;
let obj = [];
for (let i = 0; i < ADetailer.value.length; i++) {
const element = ADetailer.value[i];
if (element.ad_model == null || element.ad_model == "" || element.ad_confidence == null) {
isValue = false;
}
obj.push({
ad_confidence: element.ad_confidence,
ad_model: element.ad_model
});
}
if (!isValue) {
message.error("添加的项,值不能为空");
return;
}
await window.api.SaveADetailerConfig(toRaw(obj), (value) => {
if (value.code == 0) {
window.api.showGlobalMessageDialog(value);
return;
}
window.api.showGlobalMessageDialog(value);
})
}
return {
onCreate,
ADetailer,
SaveADetailerConfig
}
}
})
</script>
@@ -0,0 +1,36 @@
<template>
<div>
<n-select v-model:value="draf_name" filterable placeholder="选择草稿" :options="options"/>
</div>
</template>
<script>
import { defineComponent, ref, onMounted } from "vue";
import { NSelect } from "naive-ui"
export default defineComponent({
components: {
NSelect
},
setup() {
let draf_name = ref(null);
let options = ref([]);
onMounted(async () => {
await window.api.getDraftFileList((value) => {
value.forEach(element => {
let obj = {
label: element,
value: element
}
options.value.push(obj)
});
})
})
return {
draf_name,
options,
}
}
})
</script>
@@ -0,0 +1,154 @@
<template>
<n-layout id="layout_height" has-sider style="height: 300px;">
<n-layout-sider bordered :width="240" :native-scrollbar="false">
<n-menu @update:value="SelectMenu" :options="menuOptions" v-model:value="selectedKeyRef" />
</n-layout-sider>
<n-layout :native-scrollbar="false" style="margin-top: 30px;">
<div style="margin-left: 20px;">
<div>选择的风格</div>
<ShowImageTag :selectStyle="selectStyle"></ShowImageTag>
<div style="margin-top: 5px;">
<div class="image-container" style="display: flex;">
<n-image-group show-toolbar-tooltip>
<div style="display: flex; flex-wrap: wrap;">
<div v-for="(image, index) in style_image_list " style="margin: 0 5px 5px 5px;"
:key="image.id">
<n-image @contextmenu.prevent="handleContextMenu($event, image)" :width="150"
:height="150" :src="image.image" alt="图片描述" lazy style="display: block;" />
</div>
</div>
</n-image-group>
</div>
</div>
</div>
</n-layout>
</n-layout>
<n-dropdown style="z-index: 100;" trigger="manual" :x="x" :y="y" :options="dropOption" :show="showDropdownRef"
:on-clickoutside="onClickoutside" @select="handleSelect" />
</template>
<script>
import { defineComponent, ref, onMounted, watch, nextTick } from "vue";
import { NDropdown, NImage, NLayout, NLayoutContent, NLayoutSider, NMenu, NSpace, NTag, NTooltip, messageDark, useMessage } from "naive-ui"
import ShowImageTag from "./ShowImageTag.vue";
export default defineComponent({
components: {
NSpace, NLayout, NLayoutSider, NLayoutContent, NMenu, NTag, NTooltip, NImage, NDropdown, ShowImageTag
},
props: ["selectStyle", "height"],
setup(props) {
let current_menu = null;
let selectedKeyRef = ref(null);
let selectStyle = ref(props.selectStyle ? props.selectStyle : []);
let style_image_list = ref([]);
let message = useMessage();
let showDropdownRef = ref(false);
let current_img = ref(null);
let menuOptions = ref([]);
const x = ref(0);
const y = ref(0);
let dropOption = [{
label: "选择风格",
key: "selectStyle"
}];
// 监听props.selectStyle修改
watch(() => props.selectStyle, (value) => {
selectStyle.value = value;
if (!selectStyle.value) {
selectStyle.value = [];
}
});
onMounted(async () => {
setLayoutHeight("layout_height");
await window.api.GetImageStyleMenu((value) => {
debugger
if (value.code == 0) {
message.error(value.message);
return;
}
menuOptions.value = value.data;
current_menu = value.data[0].id;
});
await getStyleImageSubList();
selectedKeyRef.value = current_menu;
})
/**
* 菜单选中事件
*/
async function SelectMenu(key, item) {
current_menu = key;
await getStyleImageSubList();
}
async function getStyleImageSubList() {
await window.api.getStyleImageSubList(current_menu, (value) => {
debugger;
console.log(value)
if (value.code == 0) {
message.error(value.message);
return;
}
style_image_list.value = value.data;
});
}
// 将指定ID的元素的的高度设置到和当前组件一样高
function setLayoutHeight(id) {
let layout_height = document.getElementById(id);
layout_height.style.height = props.height - 40 + "px";
}
async function closeTag(image) {
debugger;
let index = selectStyle.value.findIndex(item => item.id == image.id);
if (index != -1) {
selectStyle.value.splice(index, 1);
}
}
return {
selectStyle,
menuOptions,
SelectMenu,
style_image_list,
selectedKeyRef,
showDropdownRef,
dropOption,
current_img,
x: x,
y: y,
closeTag,
handleContextMenu(e, image) {
e.preventDefault();
current_img.value = image;
nextTick().then(() => {
showDropdownRef.value = true;
x.value = e.clientX;
y.value = e.clientY;
});
},
onClickoutside() {
showDropdownRef.value = false;
},
handleSelect(key) {
debugger
showDropdownRef.value = false;
if (key == "selectStyle") {
// 将当前的风格添加到选择的风格中
if (current_img.value) {
selectStyle.value.push(current_img.value);
}
}
}
}
}
})
</script>
@@ -0,0 +1,57 @@
<template>
<div style="margin-top: 10px; min-height: 40px;">
<n-tooltip trigger="hover" v-for="item in selectStyle" placement="bottom-start">
<template #trigger>
<n-tag @close="closeTag(item)" :key="item.id" type="success"
style="margin-right: 10px; margin-bottom: 10px" closable>
{{ item.id.split("-")[0] }}
</n-tag>
</template>
<div>
<n-image :src="item.image" :width="200" :preview-disabled="true"></n-image>
</div>
</n-tooltip>
</div>
</template>
<script>
import { defineComponent, ref, onMounted, watch, nextTick } from "vue";
import { NImage, NTag, NTooltip, useMessage } from "naive-ui"
export default defineComponent({
components: {
NTag, NTooltip, NImage
},
props: ["selectStyle"],
setup(props) {
let selectStyle = ref(props.selectStyle ? props.selectStyle : []);
let message = useMessage();
// 监听props.selectStyle修改
watch(() => props.selectStyle, (value) => {
selectStyle.value = value;
if (!selectStyle.value) {
selectStyle.value = [];
}
});
onMounted(async () => {
})
async function closeTag(image) {
debugger;
let index = selectStyle.value.findIndex(item => item.id == image.id);
if (index != -1) {
selectStyle.value.splice(index, 1);
}
}
return {
selectStyle,
closeTag,
}
}
})
</script>
+327
View File
@@ -0,0 +1,327 @@
<template>
<n-space vertical>
<n-layout has-sider style="height: 100vh; position: relative;">
<n-layout-sider bordered collapse-mode="width" :collapsed-width="64" :width="220" :collapsed="collapsed"
show-trigger @collapse="collapsed = true" @expand="collapsed = false">
<n-menu :collapsed="collapsed" :collapsed-width="64" :collapsed-icon-size="22" :options="menuOptions"
:render-icon="renderMenuIcon" :expand-icon="expandIcon" />
</n-layout-sider>
<n-layout-content content-style="padding:30px">
<!-- <Setting></Setting> -->
<router-view></router-view>
</n-layout-content>
</n-layout>
</n-space>
</template>
<script>
import { ref, h, onMounted, defineComponent, toRaw } from "vue"
import { RouterLink } from "vue-router"
import { NMenu, NSpace, NLayout, NLayoutSider, NLayoutContent, NIcon, useDialog, useNotification, useMessage, NSwitch, NButton } from "naive-ui";
import { BookmarkOutline, CaretDownOutline } from "@vicons/ionicons5";
import CheckMachineId from "../Components/CheckMachineId.vue"
import axios from 'axios';
import { DEFINE_STRING } from "../../../../define/define_string.js"
import ShowMessage from './ShowMessage.vue'
export default defineComponent({
components: {
NMenu, NSpace, NLayout, NLayoutSider, NLayoutContent, NIcon, ShowMessage, NSwitch, NButton
},
setup() {
let collapsed = ref(false);
let dialog = useDialog();
let machineRef = ref();
let message = useMessage();
let notification = useNotification();
function renderMenuIcon(option) {
if (option.key === "sheep-man")
return true;
if (option.key === "food")
return null;
return h(NIcon, null, { default: () => h(BookmarkOutline) });
}
onMounted(async () => {
// 加载版本
await window.api.GetVersion((value) => {
document.title = "LAITool " + value;
})
window.api.setEventListen([DEFINE_STRING.SHOW_MESSAGE_DIALOG], (value) => {
let message = value.message;
let type = "success";
let title = "成功"
if (value.code == 0) {
type = "error";
title = "成功但失败"
} else if (value.code == 2) {
type = "warning",
title = "警告"
}
dialog.create({
type: type,
title: title,
showIcon: true,
content: message,
style: `width : 400px;`,
maskClosable: false,
positiveText: "确定",
})
})
window.api.setEventListen([DEFINE_STRING.SHOW_MAIN_NOTIFICATION], (value) => {
let message = value.message;
let type = "success";
let title = "成功"
if (value.code == 0) {
type = "error";
title = "失败"
} else if (value.code == 2) {
type = "warning";
title = "警告"
}
notification.create({
type: type,
title: title,
content: message,
keepAliveOnHover: true,
duration: 2500,
})
})
// 弹窗,获取机器码
dialog.create({
showIcon: false,
content: () => h(CheckMachineId, { ref: machineRef }),
style: `width : 400px; height: 300px`,
maskClosable: false,
positiveText: "确定",
onClose: () => {
// 关闭程序
window.api.QuitApp();
return false;
},
onEsc: () => {
// 关闭程序
window.api.QuitApp();
return false;
},
onPositiveClick: async () => {
debugger
await new Promise(async (resolve, reject) => {
await window.api.CheckMachineId(toRaw(machineRef.value.machineId), (value) => {
if (value.code == 0) {
message.error(value.message);
reject();
} else {
resolve();
}
});
});
}
})
window.api.getSettingDafultData(async (value) => {
window.config = value;
})
})
function expandIcon(value) {
return h(NIcon, null, { default: () => h(CaretDownOutline) });
}
const menuOptions = [
{
label: () => h(
RouterLink,
{
to: {
name: "sdoriginal",
}
},
{
default: () => "原创生图"
}
),
key: "sdoriginal",
},
{
label: "反推矩阵",
key: "backward_matrix",
children: [
{
label: () => h(
RouterLink,
{
to: {
name: "getframe",
}
},
{ default: () => "抽帧" }
),
key: "backward_frame"
},
{
label: () => h(
RouterLink,
{
to: {
name: "copywriting",
}
},
{ default: () => "文案洗稿" }
),
key: "copywriting"
},
{
label: () => h(
RouterLink,
{
to: {
name: "pushBackPrompt",
}
},
{ default: () => "反推提示词" }
),
key: "push_back"
},
{
label: () => h(
RouterLink,
{
to: {
name: "regenerate",
}
},
{ default: () => "批次生成" }
),
key: "regenerate"
},
{
label: () => h(
RouterLink,
{
to: {
name: "VideoGenerate",
}
},
{ default: () => "重绘/视频合成" }
),
key: "VideoGenerate"
}
]
},
// {
// label: "剪辑",
// key: "clip_options",
// children: [
// {
// label: () => h(
// RouterLink,
// {
// to: {
// name: "align_draft",
// }
// },
// { default: () => "剪映草稿对齐" }
// ),
// key: "align_draft"
// },
// {
// label: () => h(
// RouterLink,
// {
// to: {
// name: "add_draft",
// }
// },
// { default: () => "添加剪映草稿" }
// ),
// key: "add_draft"
// }
// ]
// },
{
label: "设置",
key: "setting",
children: [
{
label: () => h(
RouterLink,
{
to: {
name: "global_setting",
}
},
{ default: () => "通用设置" }
),
key: "global_setting"
},
{
label: () => h(
RouterLink,
{
to: {
name: "clip_setting",
}
},
{ default: () => "剪映设置" }
),
key: "clip_setting"
},
{
label: () => h(
RouterLink,
{
to: {
name: "videogeneratesetting",
}
},
{ default: () => "生成视频设置" }
),
key: "videogeneratesetting"
},
{
label: () => h(
RouterLink,
{
to: {
name: "sd_setting",
}
},
{ default: () => "SD设置" }
),
key: "sd_setting"
},
{
label: () => h(
RouterLink,
{
to: {
name: "mj_setting",
}
},
{ default: () => "MJ设置" }
),
key: "mj_setting"
}
],
}
];
return {
renderMenuIcon,
menuOptions,
expandIcon,
collapsed,
}
}
})
</script>
@@ -0,0 +1,744 @@
<template>
<div id="menu_div"
style="padding : 5px 0 5px 0; position: fixed; left: 0; top: 0; backgroundColor: white; height: 30px; width: 100%; z-index: 100; display: flex; opacity: 1;">
<n-select style="width: 200px" v-model:value="output_folder" :options="select_options" />
<n-button type="info" style="margin:0 10px 0 10px; width: 80px" @click="RefreshImageData">刷新</n-button>
<n-input placeholder="修改前" v-model:value="modeifyBefore" style="width: 200px"></n-input>
<div style="display: flex; margin: 0 10px 0 10px; color: black;align-items: center;">
<span><strong id="menu_strong">替换为</strong></span>
</div>
<n-input placeholder="修改后(为空则删除,并没有替换后台数据,点击后面的一键修改)" v-model:value="modeifyAfter"
style="width: 200px"></n-input>
<n-button type="info" style="width: 100px; margin-left: 10px;" @click="ReplacePrompt">替换</n-button>
<n-input placeholder="添加数据(只能输入英文,逗号分割)" v-model:value="globalPromptInput" style="width: 220px"></n-input>
<n-popover trigger="hover">
<template #trigger>
<n-button type="info" style="width: 100px; margin-left: 10px;"
@click="AddTranslateGlobalPrompt">翻译后添加</n-button>
</template>
<span>翻译后添加会有中文标签提示词和英文提示词</span>
</n-popover>
<n-popover trigger="hover">
<template #trigger>
<n-button type="info" style="width: 100px; margin-left: 10px;" @click="AddGlobalPrompt">添加提示词</n-button>
</template>
<span>直接添加提示词没有进行翻译没有中文标签需自己手动全部翻译</span>
</n-popover>
<n-button type="info" style="width: 150px; margin-left: 10px; margin-right: 10px;"
@click="ModifyAllSDPrompt">一键应用所有修改</n-button>
</div>
<n-data-table style="margin-top: 30px;" :columns="columns" :data="data"> </n-data-table>
</template>
<script>
import { ref, h, onMounted, defineComponent, onUnmounted, toRaw } from "vue"
import { NDataTable, NImage, useMessage, NButton, NInput, NSelect, NInputNumber, useDialog, NModal, NCheckbox, NSwitch, NPopover, NConfigProvider, darkTheme } from "naive-ui";
import { DEFINE_STRING } from "../../../../define/define_string";
import GenerateAllImages from "../Components/GenerateAllImages.vue"
import ModifyPromptChinese from "../Components/ModifyPromptChinese.vue"
const buttonArr = [
{
title: "重绘",
key: 'reDraw',
type: "primary",
}
,
{
title: "重绘所有",
key: "reDrawSelect",
type: "warning",
}
,
{
title: "修改应用全部",
key: "modifyJson",
type: "info",
}
]
export default defineComponent({
components: {
NDataTable, NImage, NButton, NInput, NSelect, NInputNumber, NModal, NCheckbox, NSwitch, ModifyPromptChinese, NPopover, NConfigProvider
},
setup() {
const containerRef = ref < HTMLElement | undefined > (undefined)
let message = useMessage();
let dialog = useDialog();
let output_folder = ref("");
let select_options = ref([]);
let controlHeight = ref(0);
let modeifyBefore = ref("");
let modeifyAfter = ref("");
let data = ref([]);
let multiple_value = ref([]);
let ImageInitData = ref(null);
let projectFolderList = ref([]);
let dialogWidth = ref(0);
let isTagMode = ref(false);
let promptChineseRef = ref(null)
let zhAutoTranslate = ref(false);
let globalPromptInput = ref(null);
let model = ref("");
let modelOption = [{
label: "文生图",
value: "txt2img"
}, {
label: "图生图",
value: "img2img"
}];
/**
* 刷新数据
*/
async function RefreshImageData() {
debugger
data.value = [];
console.log(window)
await window.api.RefreshImageData([window.id, output_folder.value], (value) => {
if (value.code != 1) {
message.error(value.message);
return;
}
value.value.forEach(item => {
value.value.loading = false;
});
data.value = value.value;
})
}
/**
* 替换当前词
*/
async function ReplacePrompt() {
if (modeifyBefore.value == "") {
return;
}
// 循环data里面的所有的提示词,修改
for (let i = 0; i < data.value.length; i++) {
const element = data.value[i];
let prompt = element.prompt;
prompt = prompt.replaceAll(modeifyBefore.value, modeifyAfter.value);
data.value[i].prompt = prompt;
}
}
/**
* 一键应用所有的修改
*/
async function ModifyAllSDPrompt() {
dialog.warning({
title: "确认",
content: "确定修改?后面执行的所有的重绘,都将被修改!",
positiveText: "确定",
negativeText: "取消",
maskClosable: false,
onPositiveClick: async () => {
let count = 0;
// 循环修改全部
for (let i = 0; i < data.value.length; i++) {
const element = data.value[i];
let prompt = "";
let chinese_prompt = [];
if (element.chinese_prompt) {
for (let i = 0; i < element.chinese_prompt.length; i++) {
const item = element.chinese_prompt[i];
prompt += item.src + ',';
chinese_prompt.push(item);
}
} else {
prompt = element.prompt;
chinese_prompt = null;
}
let arg = {
name: element.name,
prompt: prompt,
denoising_strength: element.denoising_strength,
model: element.model,
adetailer: element.adetailer,
chinese_prompt: chinese_prompt
}
await window.api.ModifyInpurCropJson(JSON.stringify(arg), (value) => {
if (value.code != 1) {
message.error(value.message);
return;
}
count++;
});
}
if (count >= data.value.length) {
message.success("修改成功")
}
else {
message.error(`${count + 1}条修改错误`);
return;
}
}
})
}
const createColumns = ({ ReGenerate, ModifyInputJson, GenerateAllImageDialog, TranslatePrompt }) => {
return [
{
title: '名字',
key: 'name',
width: 120
},
{
title: '原图像',
key: 'old_image',
width: 200,
render(row) {
return h(NImage, {
src: "file:\\" + row.oldImage + `?time=${row.timedata}`,
alt: 'Image',
height: '150'
});
}
},
{
title: '图像',
key: 'new_image',
width: 200,
render(row) {
return h(NImage, {
src: "file:\\" + row.image + `?time=${row.timedata}`,
alt: 'Image',
width: '150'
});
}
},
{
title: "操作",
key: "options",
width: 100,
render(row) {
const btn = buttonArr.map(item => {
return h(
NButton, {
strong: true,
size: "small",
type: item.type,
style: "margin : 5px",
onClick: () => {
if (item.key == "reDraw") {
ReGenerate(row);
} else if (item.key == "modifyJson") {
ModifyInputJson(row);
} else if (item.key == "reDrawSelect") {
GenerateAllImageDialog(row);
}
}
}, {
default: () => item.title
}
)
});
return h('div', {
style: "display : flex; flex-wrap : wrap"
}, btn)
},
},
{
title(row) {
return h("span", {}, [
h("span", {
style: "margin-right : 10px; width : 80px "
}, "正向翻译"),
h(NSwitch, {
railStyle: ({
focused,
checked
}) => {
const style = {};
if (checked) {
style.background = "#f3a694";
if (focused) {
style.boxShadow = "0 0 0 2px #f3a694";
}
} else {
style.background = "#775039";
if (focused) {
style.boxShadow = "0 0 0 2px #775039";
}
}
return style;
},
onUpdateValue: (value) => {
zhAutoTranslate.value = value;
},
value: zhAutoTranslate
}, {
checked: "自动翻译",
unchecked: "手动翻译",
}),
h(NButton, {
size: "tiny",
color: "#e18a3b",
style: "margin-left : 5px",
onClick: async () => {
// 只翻译chinese_prompt为null的
let filterData = data.value.filter(item => item.chinese_prompt == null);
await TranslatePrompt(JSON.stringify(filterData), 'en', 'zh', true);
}
}, {
default: () => "翻译英文"
}),
])
},
key: 'prompt_translate',
render(row, index) {
return h("div", {}, [
h(ModifyPromptChinese, { ref: promptChineseRef, initData: row, type: "zh", translateMode: zhAutoTranslate })
])
}
},
{
title() {
return h("span", {}, [
h("span", {
style: "margin-right : 10px; width : 80px "
}, "正向提示词"),
h(NButton, {
size: "tiny",
color: "#e18a3b",
style: "margin-left : 5px",
onClick: async () => {
TranslateChineseToPrompt();
}
}, {
default: () => "翻译中文"
}),
])
},
key: 'prompt',
width: "300",
render(row, index) {
return h("div", {
style: "position: relative;"
}, [
h(NInput, {
size: "tiny",
style: "margin-top : 20px",
value: row.prompt,
type: "textarea",
autosize: "{ minRows: 3 }",
onUpdateValue(v) {
data.value[index].prompt = v
}
}),
h(NButton, {
size: 'tiny',
color: "#dd7694",
style: "position: absolute; left : 0;top : 0",
onClick: async () => {
await window.api.TranslatePrompt([JSON.stringify([row]), 'en', 'zh', window.id, false], (value) => {
debugger;
console.log(value)
})
}
}, { default: () => "翻译" })
])
}
},
{
title: "参数",
key: "argument",
width: 300,
render(row, index) {
return h('div', { class: "div_1" }, [
h('div', { class: "div_2" }, [
h('label', { class: 'input-label' }, '生图方式'),
h(NSelect, {
options: modelOption,
style: "width : 100px",
value: row.model,
onUpdateValue(v) {
data.value[index].model = v;
}
}),
h(NCheckbox, {
style: "margin-left : 5px ; height : 100%",
checked: row.adetailer,
label: "修脸",
onUpdateChecked(v) {
data.value[index].adetailer = v;
}
}),
]),
h('div', { class: "div_2", style: "margin: 10px 0" }, [
h('label', { class: 'input-label' }, '重绘幅度'),
h(NInputNumber, {
step: "0.05",
style: "width : 100px",
precision: 2,
value: row.denoising_strength,
onUpdateValue(v) {
data.value[index].denoising_strength = v;
}
})
])
]);
}
},
]
}
// 计算一行的提示词
async function ModifyPrompt(data, id = null) {
// 如果index 存在。替换单独
if (id) {
let index = data.value.findIndex(item => item.id == id);
if (index < 0) {
message.error("写入源数据ID错误。请检查");
return;
}
let res = '';
if (data.value[index].chinese_prompt && data.value[index].chinese_prompt.length > 0) {
for (let i = 0; i < data.value[index].chinese_prompt.length; i++) {
const item = data.value[index].chinese_prompt[i];
res += item.src + ',';
}
} else {
res = data.value[index].prompt
}
data.value[index].prompt = res;
} else {
// 修改全部
for (let i = 0; i < data.value.length; i++) {
const element = data.value[i];
let res = "";
if (element.chinese_prompt && element.chinese_prompt.length > 0) {
for (let i = 0; i < element.chinese_prompt.length; i++) {
const item = element.chinese_prompt[i];
res += item.src + ',';
}
} else {
res = element.prompt
}
data.value[i].prompt = res;
}
}
}
onMounted(async () => {
await window.api.getImagePromptList((value) => {
if (value.code != 1) {
message.error(value.message)
return;
}
value.value.forEach(item => {
value.value.loading = false;
});
data.value = value.value;
ModifyPrompt(data);
window.id = value.windowID;
})
window.api.setEventListen([DEFINE_STRING.REGENERATE_IMAGE_RETUN, window.id], (value) => {
if (value.type != 0) {
return;
}
// 修改图片的数据源
if (value.code == 0) {
message.error(value.message);
return;
}
let filteArr = data.value.filter(item => item.id == value.id);
filteArr[0].timedata = new Date().getTime();
filteArr[0].loading = false;
message.success(JSON.stringify(value));
})
window.api.setEventListen([DEFINE_STRING.TRANSLATE_RETURN_REFRESH, window.id], (value) => {
debugger
if (value.code == 0) {
message.error("返回数据错误");
return;
}
debugger
// 直接
let index = data.value.findIndex(item => item.id == value.rowId);
if (index < 0) {
message.error("翻译后修改源数据,对应数据ID未找到");
return;
}
data.value[index].chinese_prompt = value.data;
ModifyPrompt(data, value.rowId)
console.log(value)
})
window.api.getSubFolderList(["start", "output_crop"], (value) => {
select_options.value = [];
for (let i = 0; i < value.data.length; i++) {
const v = value.data[i];
let obj = {
label: v.folder,
value: v.folder
};
select_options.value.push(obj);
}
})
await window.api.getSettingDafultData((value) => {
window.config = value;
zhAutoTranslate.value = value.translation_auto;
let div = document.getElementById("menu_div");
let strong = document.getElementById("menu_strong");
if (value.theme == "dark") {
div.style.backgroundColor = "#24242c";
strong.style.color = "#ccc"
} else {
div.style.backgroundColor = "#fff";
strong.style.color = "#333"
}
})
})
onUnmounted(() => {
window.api.removeEventListen(DEFINE_STRING.TRANSLATE_RETURN_REFRESH);
window.api.removeEventListen(DEFINE_STRING.REGENERATE_IMAGE_RETUN);
})
/**
* 将翻译添加到队列中的方式
* @param {要翻译的数据(数组)} translateData
* @param {原语言(zhen} from
* @param {目标语言(zhen} to
* @param {是否显示弹窗} isShow
*/
async function TranslatePrompt(translateData, from, to) {
console.log(translateData);
await window.api.TranslatePrompt([translateData, from, to, window.id, true], (value) => {
debugger;
console.log(value)
})
}
/**
* 翻译中文到提示词
*/
async function TranslateChineseToPrompt() {
debugger;
// 将当前的prompt和Chinese_prompt中的src全部删除(全部重新翻译)
let fileterData = [];
for (let i = 0; i < data.value.length; i++) {
const element = data.value[i];
//
// 判断chinese_prompt 是不是存在
if (element.chinese_prompt && element.chinese_prompt.length > 0) {
// 删除 prompt 和 chinese_prompt 中的src
data.value[i].prompt = "";
for (let j = 0; j < element.chinese_prompt.length; j++) {
data.value[i].chinese_prompt[j].src = "";
}
// 将修改后的数据添加到fileterData
fileterData.push(data.value[i]);
}
}
console.log(fileterData);
let tmp_str = JSON.stringify(fileterData);
// 开始调用
await window.api.TranslatePrompt([tmp_str, 'zh', 'en', window.id, true], (value) => {
debugger
console.log(value)
})
}
/**
* 显示选择框
* @param {} row
*/
async function GenerateAllImageDialog(row) {
// 处理数据。获取当前的所有的数据
dialogWidth.value = window.innerWidth * 0.8;
window.ImageInitData = data.value;
window.currentImageId = row.id;
dialog.create({
showIcon: false,
content: () => h(GenerateAllImages),
style: `width : ${dialogWidth.value}px`,
maskClosable: false,
})
}
/**
* 将当前的修改直接应用到json文件中
* @param {数据列表的行数据} row
*/
async function ModifyInputJson(row) {
dialog.warning({
title: "确认",
content: "确定修改?后面执行的所有的重绘,都将被修改!",
positiveText: "确定",
negativeText: "取消",
maskClosable: false,
onPositiveClick: () => {
// 修改chinese_prompt 和prompt
let prompt = "";
let chinese_prompt = [];
if (row.chinese_prompt) {
for (let i = 0; i < row.chinese_prompt.length; i++) {
const element = row.chinese_prompt[i];
prompt += element.src + ',';
chinese_prompt.push(element);
}
} else {
prompt = row.prompt;
chinese_prompt = null;
}
let arg = {
name: row.name,
prompt: prompt,
denoising_strength: row.denoising_strength,
model: row.model,
adetailer: row.adetailer,
chinese_prompt: chinese_prompt
}
window.api.ModifyInpurCropJson(JSON.stringify(arg), (value) => {
if (value.code != 1) {
message.error(value.message);
return;
}
message.success("修改成功");
console.log(vaule);
});
}
})
}
function ReGenerate(rowData) {
let obj = {
id: rowData.id,
denoising_strength: rowData.denoising_strength,
image: rowData.image,
name: rowData.name,
negative_prompt: rowData.negative_prompt,
prompt: rowData.prompt,
sampler_name: rowData.sampler_name,
seed: rowData.seed,
timedata: new Date().getTime(),
model: rowData.model,
image_style: rowData.image_style,
lora: rowData.lora,
adetailer: rowData.adetailer,
image_style_list: toRaw(rowData.image_style_list),
}
rowData.loading = true;
// 开始重新生成
window.api.ReGenerateImage([window.id, obj, 0], (value) => {
console.log(value)
})
}
// 直接添加提示词到prompt中
async function AddGlobalPrompt() {
if (globalPromptInput.value == "" || globalPromptInput.value == null) {
message.error("请输入要添加的数据");
return;
}
// 直接添加数据
for (let i = 0; i < data.value.length; i++) {
let add_p = globalPromptInput.value.replaceAll('', ',');
data.value[i].prompt = `${add_p},${data.value[i].prompt}`;
}
message.success("添加成功");
globalPromptInput.value = null;
}
// 添加翻译后的全局配置
async function AddTranslateGlobalPrompt() {
if (globalPromptInput.value == "" || globalPromptInput.value == null) {
message.error("请输入要添加的数据");
return;
}
let tmp_s = globalPromptInput.value.replaceAll("", ",");
// 翻译立即返回
// 翻译单个。马上返回
await window.api.TranslateReturnNow([tmp_s, 'en', 'zh', true], (value) => {
debugger
console.log(value)
if (value.code == 0) {
message.error(value.message);
return;
}
for (let i = 0; i < data.value.length; i++) {
data.value[i].chinese_prompt.unshift(...value.data)
}
ModifyPrompt(data);
message.success("添加成功");
globalPromptInput.value = "";
})
}
return {
data,
ImageInitData,
projectFolderList,
columns: createColumns({
ReGenerate(rowData) {
ReGenerate(rowData)
},
ModifyInputJson,
GenerateAllImageDialog,
TranslatePrompt,
TranslateChineseToPrompt
}),
RefreshImageData,
output_folder,
select_options,
multiple_value,
containerRef,
controlHeight,
modeifyAfter,
modeifyBefore,
ReplacePrompt,
ModifyAllSDPrompt,
modelOption,
isTagMode,
zhAutoTranslate,
AddGlobalPrompt,
AddTranslateGlobalPrompt,
globalPromptInput,
model,
bodyStyle: {
width: '600px'
},
segmented: {
content: 'soft',
footer: 'soft'
},
promptChineseRef,
}
}
})
</script>
<style>
.input-label {
display: flex;
align-items: center;
margin-right: 2px;
}
.div_1 {
display: flex;
flex-wrap: wrap;
align-items: center;
}
.div_2 {
display: flex;
}
</style>
@@ -0,0 +1,44 @@
<template>
<div style="width: 100%; height: auto;">
<!-- <div style="display: flex; justify-content: center;">
<n-image width="200" height="250" src="http://qiniuyun.upsurging.xyz/LAI/1280X1280.PNG" preview-disabled />
<n-image width="250" style="margin-left: 100px;"
src="http://qiniuyun.upsurging.xyz/LAI/%E5%BE%AE%E4%BF%A1%E6%88%AA%E5%9B%BE_20240427225845.png"
preview-disabled />
</div> -->
<div style="font-size: 15px ;display: flex; justify-content: center;">
<div id="showmessage"></div>
</div>
</div>
</template>
<script>
import { ref, h, onMounted, defineComponent, onUnmounted, toRaw } from "vue"
import { NImage, useMessage } from "naive-ui";
import { DEFINE_STRING } from "../../../../define/define_string";
export default defineComponent({
components: {
NImage
},
setup() {
onMounted(async () => {
await window.api.GetShowMessage((value) => {
if (value.code == 0) {
return;
}
let parser = new DOMParser();
let html = parser.parseFromString(value.data, 'text/html');
let div = document.getElementById("showmessage");
div.innerHTML = value.data.shareInfo.note_list[0].html_content
})
})
return {
}
}
})
</script>
@@ -0,0 +1,173 @@
<template>
<div style="width: 500px;">
<n-form ref="formRef" label-placement="top" :model="characterData" :rules="rules">
<n-form-item label="人物名称" path="label">
<n-input v-model:value="characterData.label" placeholder="请输入人物名称" />
</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="请输入人物提示词" />
<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="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" />
</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-input v-model:value="characterData.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="SaveCharacterTag">保存</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 { DEFINE_STRING } from "../../../../../define/define_string";
import { v4 as uuidv4 } from "uuid";
export default defineComponent({
components: {
NImage, NButton, NInput, NForm, NFormItem, NDynamicTags, NInputNumber
},
props: ["currentCharacter", "initFunc", "currentTags"],
setup(props) {
let message = useMessage();
let alias_tags = ref(props.currentTags);
let characterData = ref(props.currentCharacter);
let loading = ref(false);
let formRef = 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,
cref_cw: characterData.value.cref_cw,
lora: characterData.value.lora,
})
})
characterData.value["children"] = children;
debugger;
console.log(characterData.value);
characterData.value['type'] = "character_main";
// 开始保存
await window.mj.SaveTagPropertyData([JSON.stringify(characterData.value), "character_tags"], (value) => {
debugger;
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",
prompt: null,
image_url: null,
cref_cw: 20,
lora: 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;
}
debugger
//
console.log(value.data);
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("必填英文提示词")
};
return {
characterData,
alias_tags,
SaveCharacterTag,
TranslatePrompt,
loading,
rules,
formRef
}
}
})
</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) => {
debugger;
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,141 @@
<template>
<div style="width: 500px;">
<n-form label-placement="top" ref="formRef" :rules="rules" :model="sceneData">
<n-form-item label="场景名称" path="label">
<n-input v-model:value="sceneData.label" placeholder="请输入场景名称" />
</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 } from "naive-ui";
import { v4 as uuidv4 } from "uuid";
export default defineComponent({
components: {
NImage, NButton, NInput, NForm, NFormItem, NDynamicTags, NInputNumber
},
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) => {
debugger;
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,144 @@
<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="请输入风格名称" />
</n-form-item>
<n-form-item label="风格提示词描述(中文)">
<n-input type="textarea" :rows="3" v-model:value="styleData.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="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:风格迁移 cw 值(0-1000">
<n-input-number :show-button="false" v-model:value="styleData.sref_cw" min="0" max="1000"
placeholder="请输入人物风格迁移的CW值" />
</n-form-item>
<n-form-item label="SDLora选择">
<n-input v-model:value="styleData.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="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 } from "naive-ui";
import { DEFINE_STRING } from "../../../../../define/define_string";
import { v4 as uuidv4 } from "uuid";
export default defineComponent({
components: {
NImage, NButton, NInput, NForm, NFormItem, NDynamicTags, NInputNumber
},
props: ["currentStyle", "initFunc"],
setup(props) {
let message = useMessage();
let styleData = ref(props.currentStyle);
let loading = ref(false);
let formRef = 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";
// 直接保存
// 开始保存
await window.mj.SaveTagPropertyData([JSON.stringify(styleData.value), "style_tags"], (value) => {
debugger;
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,
image_url: null,
cref_cw: 20,
lora: 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("必填人物名称"),
prompt: ruleObj("必填英文提示词")
};
return {
styleData,
SaveStyleTag,
loading,
rules,
formRef,
TranslatePrompt
}
}
})
</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) => {
debugger;
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,139 @@
<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: 3,
}" placeholder="自动分析的数据" v-model:value="AnalyzeCharacter"></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>
import { ref, h, onMounted, defineComponent, toRaw } from "vue"
import { NImage, useMessage, NButton, useDialog, NInput, NCard, NTabs, NTabPane, NTree } from "naive-ui";
import { DEFINE_STRING } from "../../../../../define/define_string";
import { v4 as uuidv4 } from "uuid";
import CharacterTags from "./CharacterTags.vue";
export default defineComponent({
components: {
NImage, NButton, NInput, NCard, NTabs, NTabPane, NTree, CharacterTags
},
props: ["initData", "height"],
setup(props) {
let message = useMessage();
let data = ref(props.initData);
let height = ref(props.height);
let AnalyzeCharacter = ref("");
let word = ref("");
let loading = ref(false);
let treeData = ref([{
label: '人物',
key: uuidv4(),
children: [],
suffix: () => h(NButton,
{ text: true, type: "primary" },
{ default: () => "suffix" })
},
{
label: '场景',
key: uuidv4(),
children: [],
suffix: () => h(NButton,
{ text: true, type: "primary" },
{ default: () => "suffix" })
}]);
onMounted(async () => {
let temp_arr = [];
for (let i = 0; i < data.value.length; i++) {
const element = data.value[i];
temp_arr.push(element.after_gpt);
}
word.value = temp_arr.join('\n');
// 判断是不是已经有生成(有有生成显示)
await window.api.GetConfigJson(JSON.stringify(['auto_analyze_character', ""]), (value) => {
if (value.code == 0) {
message.error(value.message);
return;
}
AnalyzeCharacter.value = value.data;
})
})
/**
* 自动分析角色
*/
async function autoAnalyzeCharacter() {
if (word.value == "" || word.value == null) {
message.error("请输入要分析的文案");
return;
}
loading.value = true;
await window.api.AutoAnalyzeCharacter(toRaw(word.value), (value) => {
debugger
loading.value = false;
if (value.code == 0) {
message.error(value.message);
return;
}
AnalyzeCharacter.value = value.data;
})
}
/**
* 保存自动分析的设置
*/
async function SaveAutoAnalyzeCharacter() {
if (AnalyzeCharacter.value == "" || AnalyzeCharacter.value == null) {
message.error("没有分析分析出来的信息");
return;
}
// 保存信息
await window.api.SaveCopywritingInformation([toRaw(AnalyzeCharacter.value), "auto_analyze_character"], (value) => {
if (value.code != 1) {
message.error("未知错误");
return;
}
message.success("保存成功");
})
}
return {
data,
treeData,
word,
height,
loading,
AnalyzeCharacter,
autoAnalyzeCharacter,
SaveAutoAnalyzeCharacter,
}
}
})
</script>
<style scoped>
.card-tabs .n-tabs-nav--bar-type {
padding-left: 4px;
}
</style>
@@ -0,0 +1,354 @@
<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" />
<AddStyleTags v-else-if="currentType.startsWith('style')" :currentStyle="currentStyle"
:initFunc="InitData" />
<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" />
</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 } 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 } from "@vicons/ionicons5"
export default defineComponent({
components: {
NImage, NButton, NInput, NCard, NTabs, NTabPane, NTree, NSplit, AddCharacterTag, AddStyleTags, AddSceneTags, AddPrefixTags, AddSuffixTags, NIcon, Trash
},
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("character");
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) }) });
});
}
})
}
onMounted(async () => {
SetAutoHeight();
await InitData();
})
// 设置标签集的自动高度,传一个外面的高度进来
function SetAutoHeight() {
debugger
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",
prompt: null,
image_url: null,
cref_cw: 50,
lora: null,
}
currentTags.value = [];
currentType.value = "character_main";
}
/**
* 新加场景标签
*/
function AddSceneTags(e) {
if (e) {
e.stopPropagation();
}
currentScene.value = {
label: null,
key: uuidv4(),
type: "scene_main",
prompt: null,
}
currentType.value = "scene_main";
}
/**
* 新加 风格迁移预设
* @param {*} e
*/
function AddStyleTags(e) {
if (e) {
e.stopPropagation();
}
currentStyle.value = {
label: null,
key: uuidv4(),
type: "style_main",
prompt: null,
image_url: null,
sref_cw: 50,
}
}
/**
* 添加前缀
*/
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() {
debugger;
// 执行切换组件和数据
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 {
debugger
console.log(option);
currentType.value = option.type;
// 修改当前属性
if (currentType.value.startsWith("character")) {
// 修改tags的值
currentCharacter.value = option;
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
}
}
})
</script>
@@ -0,0 +1,287 @@
<template>
<div v-if="type == 'title'" style="display: flex">
<span style="margin-right: 5px">参数</span>
<n-select
style="width: 75px"
size="tiny"
:options="characterSelectMoedlOptions"
@update:value="UpdateImageGenerateCategory"
v-model:value="title_character_select_model"
placeholder="选择模式"
></n-select>
<n-button size="tiny" color="#e18a3b" style="margin-left: 5px" @click="RefreshTagData">
<n-icon>
<reload />
</n-icon>
</n-button>
</div>
<div v-else-if="type == 'data'">
<div v-if="title_character_select_model == 'tag'">
<div style="min-height: 65px; overflow-y: auto; width: 190px">
<span>人物</span>
<n-tag
v-for="(character, index) in character_tags"
v-model:checked="character.checked"
checkable
@click="UpdateCharacterTags"
size="small"
style="margin: 0 3px 3px 0"
>
{{ character.label }}
</n-tag>
</div>
<div style="margin-top: 5px; min-height: 65px; overflow-y: auto">
<span>场景</span>
<n-tag
v-for="(scene, index) in scene_tags"
v-model:checked="scene.checked"
checkable
@click="UpdateSceneTags()"
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="character_tags"
placeholder="选择人物"
/>
</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="scene_tags"
placeholder="选择场景"
/>
</div>
</div>
</div>
</template>
<script>
import { ref, h, onMounted, defineComponent, toRaw, watch } from 'vue'
import { NImage, useMessage, NButton, useDialog, NInput, NTag, NSelect, NIcon } from 'naive-ui'
import { DEFINE_STRING } from '../../../../../define/define_string'
import { cloneDeep, debounce, get, once } from 'lodash'
import { Refresh, Reload } from '@vicons/ionicons5'
export default defineComponent({
components: {
NImage,
NButton,
NInput,
NTag,
NSelect,
NIcon,
Reload
},
props: ['type', 'row', 'characterSelectModel', 'tags', 'func'],
setup(props) {
let message = useMessage()
let type = ref(props.type)
let title_character_select_model = ref(props.characterSelectModel)
let row = ref(props.row)
let character_tags = ref([])
let scene_tags = ref([])
let tags = ref(props.tags)
let characterData = ref([])
// 存储当权选择的tag数据
let select_character_tags = ref([])
let select_scene_tags = ref([])
let characterSelectMoedlOptions = ref([])
watch(
() => props.characterSelectModel,
(value) => {
debugger
// InitData(value);
if (value.value == 'drop') {
select_character_tags.value = []
select_scene_tags.value = []
character_tags.value.forEach((item) => {
if (item.checked) select_character_tags.value.push(item.key)
})
scene_tags.value.forEach((item) => {
if (item.checked) select_scene_tags.value.push(item.key)
})
}
},
{ deep: true }
)
watch(
() => props.tags,
async (value) => {
// 刷新数据
await InitCharacterAndSceneData()
},
{ deep: true }
)
watch(
() => props.row,
async (value) => {
row.value = value
},
{ deep: true }
)
onMounted(async () => {
// debugger
// await InitCharacterAndSceneData()
await window.mj.GetTagSelectModel((value) => {
characterSelectMoedlOptions.value = value.data
})
})
// 更行当前行选中的人物角色tag数据
async function UpdateCharacterTags(value, option) {
console.log(value, option)
if (title_character_select_model.value == 'tag') {
row.value.character_tags = []
// 循环当前界面,每次全部更新。
character_tags.value.forEach((item) => {
if (item.checked) {
row.value.character_tags.push(item)
} else {
// 移除
let index = row.value.character_tags.findIndex((tag) => tag.key == item.key)
if (index != -1) {
row.value.character_tags.splice(index, 1)
}
}
})
}
}
// 修改当前的场景信息tags数据
async function UpdateSceneTags() {
row.value.scene_tags = []
scene_tags.value.forEach((item) => {
if (item.checked) {
row.value.scene_tags.push(item)
} else {
// 移除
let index = row.value.scene_tags.findIndex((tag) => tag.key == item.key)
if (index != -1) {
row.value.scene_tags.splice(index, 1)
}
}
})
}
// 渲染tag
const renderTag = ({ option, handleClose }) => {
return h(
NTag,
{
size: 'tiny',
type: 'success',
closable: true,
onClose: (e) => {
e.stopPropagation()
handleClose()
// 移除当前option
let k = option.key
character_tags.value.forEach((item) => {
if (item.key == k) {
item.checked = false
}
})
}
},
{ default: () => option.label }
)
}
/**
* 加载人物场景等信息
* @param {*} tags
*/
async function InitCharacterAndSceneData() {
debugger
// if (!tags) return
character_tags.value = cloneDeep(tags.value.character_tags)
character_tags.value.forEach((item) => {
item.children = null
})
scene_tags.value = cloneDeep(tags.value.scene_tags)
select_character_tags.value = []
select_scene_tags.value = []
// 初始化数据(选择为true
for (let i = 0; row.value.character_tags && i < row.value.character_tags.length; i++) {
const element = row.value.character_tags[i]
let index = character_tags.value.findIndex((tag) => tag.key == element.key)
if (index != -1) {
character_tags.value[index].checked = true
// 将当前选中的数据生成到select_character_tags中
select_character_tags.value.push(character_tags.value[index].key)
}
}
for (let i = 0; row.value.scene_tags && i < row.value.scene_tags.length; i++) {
const element = row.value.scene_tags[i]
let index = scene_tags.value.findIndex((tag) => tag.key == element.key)
if (index != -1) {
scene_tags.value[index].checked = true
// 将当前选中的数据生成到select_scene_tags中
select_scene_tags.value.push(scene_tags.value[index].key)
}
}
}
/**
* 更改属性
* @param {*} value
*/
function UpdateImageGenerateCategory(value) {
title_character_select_model.value = value
// InitData(value);
}
/**
* 刷新标签数据
*/
async function RefreshTagData() {
props.func.refreshTagData()
}
return {
characterData,
row,
character_tags,
select_character_tags,
select_scene_tags,
UpdateCharacterTags,
UpdateSceneTags,
scene_tags,
renderTag,
type,
characterSelectMoedlOptions,
InitCharacterAndSceneData,
UpdateImageGenerateCategory,
title_character_select_model,
RefreshTagData
}
}
})
</script>
@@ -0,0 +1,220 @@
<template>
<div v-if="type == 'title'">
<span style="margin-right: 5px">推理提示词</span>
<n-popover trigger="hover">
<template #trigger>
<n-button color="#7c461e" style="margin-left: 5px" size="tiny" @click="AddPrefix"
>添加前缀</n-button
>
</template>
<span>添加前缀只能添加英文并且通用前缀不为空推理会自动添加前缀</span>
</n-popover>
<n-popover trigger="hover">
<template #trigger>
<n-button color="#e18a3b" style="margin-left: 5px" size="tiny" @click="AddSuffix"
>添加后缀</n-button
>
</template>
<span>添加后缀只能添加英文并且通用后缀不为空推理会自动添加后缀</span>
</n-popover>
<n-popover trigger="hover">
<template #trigger>
<n-button
color="#c0d695"
style="margin-left: 5px"
size="tiny"
@click="SelectGenerateImagesStyle"
>风格</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="#779649"
style="margin-left: 5px"
size="tiny"
@click="TranslateAll('english')"
>一键翻译</n-button
>
</n-dropdown>
</span>
</template>
<span
>将下面的推理提示词或者是手写的提示词翻译成英文只会翻译存在中文的数据下面的翻译中文会将所有的推理提示词翻译为中文全部都会翻译</span
>
</n-popover>
</div>
<div v-else-if="type == 'data'">
<n-dropdown trigger="hover" :options="translate_options" @select="TranslateSelect">
<n-button
size="tiny"
color="#dd7694"
style="margin-right: 5px"
@click="TranslateSelect('english')"
>翻译英文</n-button
>
</n-dropdown>
<n-button size="tiny" color="#a76283" style="margin-right: 5px" @click="SinglePrompt"
>单句推理</n-button
>
<n-button color="#a46244" style="margin-right: 5px" size="tiny" @click="NextGptPrompt"
>下推理</n-button
>
<n-input
size="tiny"
placeholder="请生成或推理提示词"
type="textarea"
v-model:value="row.gpt_prompt"
:autosize="{ minRows: 6, maxRows: 6 }"
/>
</div>
</template>
<script>
import { ref, h, onMounted, defineComponent, toRaw, watch } from 'vue'
import { NButton, NDropdown, NInput, NPopover, useMessage } from 'naive-ui'
import { DEFINE_STRING } from '../../../../../define/define_string'
import { v4 as uuidv4 } from 'uuid'
import { cloneDeep } from 'lodash'
export default defineComponent({
components: {
NButton,
NInput,
NPopover,
NDropdown
},
props: ['type', 'row', 'index', 'func'],
setup(props) {
let message = useMessage()
let type = ref(props.type)
let row = ref(props.row)
let translate_options = ref([{ label: '翻译中文', key: 'chinese' }])
// 监听数据变化
watch(
() => props.row,
(newVal) => {
row.value = newVal
},
{ deep: true }
)
onMounted(async () => {})
/**
* 单句生图
*/
async function SingleGenerateImage() {
await window.api.OriginalSDImageGenerate([JSON.stringify([row]), true, false], (value) => {
if (value.code == 0) {
message.error(value.message)
return
}
message.success('添加生图任务成功')
})
}
function containsChineseOrPunctuation(str) {
return /[\u4e00-\u9fa5]|[\u3000-\u301e\u2013\u2014\u2018\u2019\u201c\u201d\u2026\u203b\uff08\uff09\uff1a\uff1b\uff1f\uff01\uff0c\u3001\uff0e\u3002\uff1f\uff01\u2018\u2019\u201c\u201d]/.test(
str
)
}
/**
* 翻译选择
*/
async function TranslateSelect(key) {
// 将中文翻译成英文
if (key == 'english') {
// 检查是不是有中文
if (containsChineseOrPunctuation(row.value.gpt_prompt)) {
// 开始翻译
let t = [[toRaw(row.value)], 'zh', 'en', false, false]
await window.mj.TranslateReturnNowTask(JSON.stringify(t), (value) => {
if (value.code == 0) {
message.error(value.message)
return
}
message.success('添加翻译任务成功')
})
} else {
message.error('当前翻译的数据里面没有中文')
}
} else if (key == 'chinese') {
debugger
let t = [[toRaw(row.value)], 'en', 'zh', false, false]
await window.mj.TranslateReturnNowTask(JSON.stringify(t), (value) => {
if (value.code == 0) {
message.error(value.message)
return
}
message.success('添加翻译任务成功')
})
}
}
/**
* 翻译所有的数据
* @param {*} key
*/
async function TranslateAll(key) {
props.func.translateAll(key)
}
/**
* 添加前缀
*/
async function AddPrefix() {
props.func.addPrefix()
}
/**
* 添加后缀
*/
async function AddSuffix() {
props.func.addSuffix()
}
/**
* 选择风格
*/
async function SelectGenerateImagesStyle() {
props.func.selectGenerateImagesStyle()
}
/**
* 单句推理
*/
async function SinglePrompt() {
await props.func.singlePrompt(row.value)
}
/**
* 下推理
*/
async function NextGptPrompt() {
await props.func.nextGptPrompt(row.value)
}
return {
type,
row,
SingleGenerateImage,
translate_options,
TranslateSelect,
TranslateAll,
AddPrefix,
AddSuffix,
SelectGenerateImagesStyle,
SinglePrompt,
NextGptPrompt
}
}
})
</script>
@@ -0,0 +1,80 @@
<template>
<div v-if="type == 'title'">
<span style="margin-right: 5px">参数</span>
</div>
<div v-else-if="type.startsWith('data') && type.endsWith('sd')">
<n-checkbox
style="margin-left: 5px; height: 100%"
:checked="row.adetailer"
label="修脸"
@update:checked="onUpdateChecked"
/>
</div>
<div v-else-if="type.startsWith('data') && type.endsWith('mj')"></div>
<div v-else-if="type.startsWith('data') && type.endsWith('d3')"></div>
</template>
<script>
import { ref, h, onMounted, defineComponent, toRaw, watch, computed } from "vue";
import { NButton, NCheckbox, NInput, useMessage } from "naive-ui";
import { DEFINE_STRING } from "../../../../../define/define_string";
import { cloneDeep } from "lodash";
export default defineComponent({
components: {
NButton,
NInput,
NCheckbox,
},
props: ["type", "row", "index", "func"],
setup(props) {
let message = useMessage();
let type = ref(props.type);
let row = ref(props.row);
let input_status = ref("default");
// 重新计算命令
watch(
() => props.type,
(newVal) => {
type.value = newVal;
},
{ deep: true }
);
// 监听数据变化
watch(
() => props.row,
(newVal) => {
row.value = newVal;
},
{ deep: true }
);
watch(
() => props.row.mj_message,
(newVal) => {
if (newVal && newVal["hasBadPrompt"]) {
input_status.value = "error";
}
},
{ deep: true }
);
onMounted(async () => {
if (row.value["mj_message"]?.hasBadPrompt) {
input_status.value = "error";
}
});
async function onUpdateChecked(v) {
debugger;
row.adetailer = v;
}
return {
type,
row,
onUpdateChecked,
};
},
});
</script>
@@ -0,0 +1,221 @@
<template>
<div v-if="type == 'title'">
<span style="margin-right: 5px">提示词命令</span>
<n-button size="tiny" @click="MergePrompt" color="#7c461e">合并命令</n-button>
<n-button
color="#e18a3b"
size="tiny"
style="margin-left: 5px"
@click="MJBadPromptCheck"
>敏感词检查</n-button
>
</div>
<div v-else-if="type == 'data'">
<n-button
size="tiny"
color="#dd7694"
style="margin-right: 5px"
@click="SingleGenerateImage"
>单句生图</n-button
>
<n-button
size="tiny"
color="#a76283"
style="margin-right: 5px"
@click="NextGenerateImage"
>下生图</n-button
>
<n-popover trigger="hover">
<template #trigger>
<n-button
color="#a46244"
style="margin-right: 5px"
size="tiny"
@click="ImportMJImageUrl"
>导入图片</n-button
>
</template>
<span>粘贴MJ里面的图片链接自动下载到对应的行并分割</span>
</n-popover>
<n-input
:status="input_status"
readonly
size="tiny"
placeholder="请生成出图命令或是提示词"
type="textarea"
v-model:value="row.prompt"
:autosize="{ minRows: 6, maxRows: 6 }"
/>
</div>
</template>
<script>
import { ref, h, onMounted, defineComponent, toRaw, watch } from "vue";
import { NButton, NInput, NPopover, useMessage, useDialog } from "naive-ui";
import { DEFINE_STRING } from "../../../../../define/define_string";
import InputDialogContent from "./InputDialogContent.vue";
export default defineComponent({
components: {
NButton,
NInput,
NPopover,
},
props: ["type", "row", "index", "func"],
setup(props) {
let message = useMessage();
let dialog = useDialog();
let type = ref(props.type);
let row = ref(props.row);
let input_status = ref("default");
let image_url_ref = ref(null);
// let input_image_
// 重新计算命令
// 监听数据变化
watch(
() => props.row,
(newVal) => {
row.value = newVal;
},
{ deep: true }
);
watch(
() => props.row.mj_message,
(newVal) => {
if (newVal && newVal["hasBadPrompt"]) {
input_status.value = "error";
}
},
{ deep: true }
);
onMounted(async () => {
if (row.value["mj_message"]?.hasBadPrompt) {
input_status.value = "error";
}
});
/**
* SD单句生图
* @param {*} row 要生图的行数据
*/
async function SingleGenerateImage() {
debugger;
let ca = window.config.image_generate_category;
// 判断当前的生图模式
if (ca == "sd") {
// 生成SD生图
await window.api.OriginalSDImageGenerate(
[JSON.stringify([toRaw(row.value)]), true, false],
(value) => {
if (value.code == 0) {
message.error(value.message);
return;
}
message.success("添加SD生图任务成功");
}
);
} else if (ca == "mj") {
// MJ生图
await window.mj.OriginalMJImageGenerate(
[JSON.stringify([toRaw(row.value)]), true, false],
(value) => {
if (value.code == 0) {
message.error(value.message);
return;
}
message.success("添加MJ生图任务成功");
}
);
} else if (ca == "d3") {
// D3 生图
} else {
message.error("生图的类别错误");
}
}
/**
* 下生图
*/
async function NextGenerateImage() {
props.func.nextGenerateImage(row.value, window.config.image_generate_category);
}
/**
* MJ敏感词检测
*/
async function MJBadPromptCheck() {
props.func.mJBadPromptCheck();
}
/**
* 合并提示词
*/
async function MergePrompt() {
props.func.mergePrompt();
}
/**
* 写入图片的链接,将图片链接下载,并且分割
*/
async function ImportMJImageUrl() {
// 判断当前数据是不是存在
// 处理数据。获取当前的所有的数据
let dialogWidth = 400;
let dialogHeight = 150;
dialog.create({
title: "添加图片链接",
showIcon: false,
closeOnEsc: false,
content: () =>
h(InputDialogContent, {
ref: image_url_ref,
initData: null,
placeholder: "请输入图片链接",
}),
style: `width : ${dialogWidth}px; min-height : ${dialogHeight}px`,
maskClosable: false,
onClose: async () => {
debugger;
let row_image_url = image_url_ref.value.data;
// 下载指定的图片地址并且分割
await window.mj.DownloadImageUrlAndSplit(
JSON.stringify([toRaw(row.value), row_image_url]),
(value) => {
if (value.code == 0) {
message.error(value.message);
return;
}
// 更新当前的数据
row.value.outImagePath = value.data.outImagePath;
row.value.subImagePath = value.data.subImagePath;
row.value.mj_message = value.data.mj_message ? value.data.mj_message : {};
row.value.mj_message.image_click = value.data.image_click;
row.value.mj_message.image_path = value.data.image_path;
row.value.mj_message.progress = 100;
}
);
},
});
}
return {
type,
row,
SingleGenerateImage,
NextGenerateImage,
MJBadPromptCheck,
input_status,
MergePrompt,
ImportMJImageUrl,
image_url_ref,
};
},
});
</script>
@@ -0,0 +1,254 @@
<template>
<div v-if="type == 'title'" style="display: flex">
<span>出图</span>
<n-select
style="width: 70px; margin-left: 5px"
size="tiny"
:options="image_options"
@update:value="UpdateImageGenerateCategory"
v-model:value="select_image_category"
placeholder="选择生图模式"
></n-select>
<n-button
v-if="select_image_category == 'mj'"
color="#7c461e"
size="tiny"
style="margin-left: 5px"
@click="MJGetImage"
>MJ采集图片</n-button
>
</div>
<div v-else-if="type == 'data'">
<div style="height: 22px">进度{{ data.mj_message?.progress }}%</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">
<n-image
class="g-image-class generate-image-show"
v-if="outImagePath"
:src="outImagePath"
fit="cover"
width="120px"
height="120px"
/>
</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">
<n-image
subImage="true"
class="g-image-class"
draggable="true"
:width="width"
style="margin: 1px"
v-for="(image, index) in images"
:key="image.id"
:src="image"
alt="图片描述"
lazy
>
</n-image>
</div>
</n-image-group>
</div>
</div>
</div>
</template>
<script>
import { ref, h, onMounted, defineComponent, onUnmounted, toRaw, watch } from 'vue'
import { NImage, useMessage, NDivider, NImageGroup, NButton, NSelect } from 'naive-ui'
export default defineComponent({
components: {
NImage,
NDivider,
NImageGroup,
NButton,
NSelect
},
props: ['initData', 'type', 'image_generate_category', 'func'],
setup(props) {
let message = useMessage()
let data = ref(props.initData)
let type = ref(props.type)
let images = ref(props.initData.subImagePath)
let outImagePath = ref(props.initData.outImagePath)
let sd_batch_size = ref(null)
let width = ref(null)
let dragTarget = null
let image_options = ref([])
let select_image_category = ref(props.image_generate_category)
watch(
() => select_image_category.value,
(value) => {
select_image_category.value = value
}
)
// 监听 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
}
)
// 监听 props.initData 修改
watch(
() => props.initData,
(value) => {
data.value = value
setNodeAttribute()
},
{ deep: true }
)
onMounted(async () => {
await window.api.InitSDConfig((value) => {
debugger
if (value.code == 0) {
message.error(value.message)
return
}
sd_batch_size.value = value.data.batch_size
width.value = 170 / 3
console.log(width.value)
})
setNodeAttribute()
// 获取生图下拉列表的方式
await window.api.GetImageGenerateCategory((value) => {
if (value.code == 0) {
message.error(value.message)
return
}
image_options.value = value.data
})
})
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
}
// 图片拖拽结束事件
async function imageDragDrop(e) {
debugger
console.log('end', e.target, dragTarget)
// 判断原始数据是不是为空
if (dragTarget == null) {
message.error('只能拖拽到当前行的图片上')
return
}
// 判断结束位置是不是主图片
if (e.target.getAttribute('mainImage') != '1') {
message.error('图片只能拖拽到主图片上')
return
}
let source_img = dragTarget.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 UpdateImageGenerateCategory(value, option) {
// 更新配置文件和缓存
window.config.image_generate_category = value
// 保存文件
await window.api.ModifySampleSetting(
JSON.stringify({
image_generate_category: value
}),
(value) => {
if (value.code == 0) {
message.error(value.message)
return
}
message.success('修改成功')
}
)
}
/**
* MJ采集图片
*/
async function MJGetImage() {
props.func.mJGetImage()
}
return {
data,
type,
image_options,
sd_batch_size,
width,
images,
outImagePath,
imageDragStart,
imageDragDrop,
imageDragOver,
select_image_category,
UpdateImageGenerateCategory,
MJGetImage
}
}
})
</script>
@@ -0,0 +1,70 @@
<template>
<div style="margin-top: 30px">
<n-input
v-model:value="word"
:autosize="{ minRows: 30, maxRows: 30 }"
type="textarea"
placeholder="输入分镜文案"
@input="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 } from 'naive-ui'
import { DEFINE_STRING } from '../../../../../define/define_string'
export default defineComponent({
components: {
NImage,
NButton,
NInput
},
props: ['initData'],
setup(props) {
let message = useMessage()
let rowCount = ref(0)
let data = ref(props.initData)
let word = ref(null)
let word_arr = ref([])
onMounted(async () => {
debugger
console.log(data)
// 拼接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()
})
/**
* 修改数据框时触发的事件
*/
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
}
}
return {
data,
rowCount,
ChangeWordInput,
word,
word_arr
}
}
})
</script>
@@ -0,0 +1,562 @@
<template>
<div id="import_word_and_srt">
<div style="height: 30px; margin-bottom: 5px">
<n-button 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 } from 'vue'
import { NButton, NDataTable, useMessage, NInput, NImage, useDialog } from 'naive-ui'
import { v4 as uuidv4 } from 'uuid'
import EditWord from './EditWord.vue'
const buttonArr = [
{
title: '下对齐',
key: 'AlignNextWord',
color: '#8a2be0'
}
]
export default defineComponent({
components: {
NButton,
NDataTable,
NInput,
NImage
},
props: ['initData', 'menuType', 'height'],
setup(props) {
let hh = props.height
let maxHeight = props.height - 80
let data = ref(props.initData)
const message = useMessage()
let selectKey = ref([])
let dialog = useDialog()
let editWordRef = ref(null)
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() {
debugger
let div = document.getElementById('import_word_and_srt')
div.style.height = hh + 'px'
}
onMounted(async () => {
setHeight()
})
/**
* 重新计算指定索引值的时间
* @param {data value 值的索引} index
*/
function modifyTime(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].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 (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 {
debugger
// 先保存单个文件(然后在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('未知错误')
return
}
message.success('保存成功')
}
)
})
}
}
/**
* 导入字幕并进行分镜输出,显示时间,将分镜给他传进去
*/
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) => {
debugger
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 () => {
debugger
console.log(editWordRef)
debugger
let word_arr = editWordRef.value.word.split('\n')
let new_arr = []
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
}
}
})
</script>
@@ -0,0 +1,29 @@
<template>
<div style="margin-top: 30px">
<n-input :placeholder="placeholder" v-model:value="data"></n-input>
</div>
</template>
<script>
import { ref, h, onMounted, defineComponent, onUnmounted, toRaw } from 'vue'
import { NInput, useMessage } from 'naive-ui'
export default defineComponent({
components: {
NInput
},
props: ['initData', 'placeholder'],
setup(props) {
let message = useMessage()
let data = ref(props.initData)
let placeholder = ref(props.placeholder)
onMounted(async () => {})
return {
data,
placeholder
}
}
})
</script>
@@ -0,0 +1,307 @@
<template>
<div id="prompt-setting" style="margin-top: 30px">
<n-card title="GPT设置" hoverable>
<div class="first_row" style="display: flex">
<div>
<div style="margin-bottom: 3px">gpt模型选择</div>
<n-select
size="small"
placeholder="请选择"
:options="gpt_model_options"
v-model:value="gpt_model"
style="width: 160px"
>
</n-select>
</div>
<div style="margin-left: 10px">
<div style="margin-bottom: 3px">gpt自动推理模式</div>
<n-select
size="small"
placeholder="请选择"
:options="gpt_auto_inference_options"
v-model:value="gpt_auto_inference"
style="width: 180px"
>
</n-select>
</div>
<div style="margin-left: 10px">
<div style="margin-bottom: 3px">选择自定义推理词</div>
<n-select
size="small"
placeholder="请选择"
:options="customize_gpt_options"
v-model:value="customize_gpt_select"
style="width: 150px"
>
</n-select>
</div>
</div>
</n-card>
<n-card title="SD设置" hoverable style="margin-top: 5px">
<div class="first_row" style="display: flex">
<div>
<div style="margin-bottom: 3px">SD生图数量</div>
<n-input-number
style="width: 120px"
v-model:value="sd_batch_size"
:show-button="false"
:min="1"
:max="8"
/>
</div>
<div style="margin-left: 10px">
<div style="margin-bottom: 3px">种子</div>
<n-input-number
style="width: 120px"
v-model:value="sd_seed"
:show-button="false"
:min="-1"
/>
</div>
<div style="margin-left: 10px; display: flex; flex-wrap: wrap; width: 130px">
<div style="margin-bottom: 3px; width: 100%">生图尺寸</div>
<n-input-number
style="width: 50px"
v-model:value="sd_image_width"
:show-button="false"
:min="-1"
/>
<span style="margin: 0 10px; line-height: 35px; font-size: 20px"> * </span>
<n-input-number
style="width: 50px"
v-model:value="sd_image_height"
:show-button="false"
:min="-1"
/>
</div>
</div>
</n-card>
<n-card title="MJ设置" hoverable style="margin-top: 5px">
<div class="first_row" style="display: flex">
<div>
<!-- <div style="margin-bottom: 3px;">铭感词相关</div> -->
<n-button size="small" @click="AddMjBadPrompt" color="#7c461e">添加敏感词</n-button>
</div>
</div>
</n-card>
<n-card title="其他设置" hoverable style="margin-top: 5px">
<div class="first_row" style="display: flex">
<div>
<div style="margin-bottom: 3px">人物场景选择模式</div>
<n-select
size="small"
placeholder="请选择"
:options="character_select_model_options"
v-model:value="character_select_model"
style="width: 120px"
/>
</div>
</div>
</n-card>
<div style="text-align: right; margin-top: 5px">
<n-button type="success" size="medium" @click="SaveSetting">保存</n-button>
</div>
</div>
</template>
<script>
import { ref, h, onMounted, defineComponent, onUnmounted, toRaw } from 'vue'
import {
NImage,
useMessage,
NSelect,
NButton,
NInputNumber,
NInput,
NCard,
useDialog
} from 'naive-ui'
import { DEFINE_STRING } from '../../../../../define/define_string'
import InputDialogContent from './InputDialogContent.vue'
import { v4 as uuidv4 } from 'uuid'
export default defineComponent({
components: {
NImage,
NSelect,
NButton,
NInputNumber,
NInput,
NCard
},
setup(props) {
let message = useMessage()
let dialog = useDialog()
let gpt_model = ref(window.config.gpt_model)
let gpt_auto_inference = ref(window.config.gpt_auto_inference)
let sd_batch_size = ref(null)
let sd_seed = ref(null)
let sd_image_width = ref(null)
let sd_image_height = ref(null)
let bad_prompt_ref = ref(null)
let gpt_model_options = ref([])
let gpt_auto_inference_options = ref([])
let customize_gpt_options = ref([])
let customize_gpt_select = ref(window.config.customize_gpt_prompt)
let character_select_model = ref(window.config.character_select_model)
let character_select_model_options = ref([])
/**
* 初始化GPT的option
*/
async function InitGptOptions() {
await window.api.getGptModelOption('all', (value) => {
if (value.code == 0) {
message.error(value.message)
return
}
gpt_model_options.value = value.data
})
await window.api.getGptAutoInferenceOptions('all', (value) => {
if (value.code == 0) {
message.error(value.message)
return
}
gpt_auto_inference_options.value = value.data
})
await window.api.GetCustomizeGptPrompt('all', (value) => {
if (value.code == 0) {
message.error(value.message)
return
}
customize_gpt_options.value = value.data.map((item) => {
return {
label: item.prompt_name,
value: item.id
}
})
})
await window.mj.GetTagSelectModel((value) => {
character_select_model_options.value = value.data
})
}
onMounted(async () => {
// 加载全局配置
// 获取SD配置
await window.api.InitSDConfig((value) => {
if (value.code == 0) {
message.error(value.message)
return
}
sd_batch_size.value = value.data.batch_size
sd_seed.value = value.data.seed
sd_image_width.value = value.data.width
sd_image_height.value = value.data.height
})
await InitGptOptions()
})
/**
* 保存设置
*/
async function SaveSetting() {
try {
window.config.gpt_model = gpt_model.value
window.config.gpt_auto_inference = gpt_auto_inference.value
window.config.character_select_model = character_select_model.value
// 保存到通用配置文件
let obj = {
gpt_model: gpt_model.value,
gpt_auto_inference: gpt_auto_inference.value,
customize_gpt_prompt: customize_gpt_select.value,
character_select_model: character_select_model.value
}
await window.api.ModifySampleSetting(JSON.stringify(obj), (value) => {
if (value.code == 0) {
throw new Error(value.message)
}
})
// 保存到SD配置文件
let sdObj = {
batch_size: toRaw(sd_batch_size.value),
width: toRaw(sd_image_width.value),
height: toRaw(sd_image_height.value)
}
await window.api.SaveSDConfig(sdObj, (value) => {
if (value.code == 0) {
throw new Error(value.message)
}
})
message.success('保存成功')
} catch (error) {
debugger
message.error('保存失败 ' + error.message)
}
}
async function AddMjBadPrompt() {
// 判断当前数据是不是存在
// 处理数据。获取当前的所有的数据
let dialogWidth = 400
let dialogHeight = 150
dialog.create({
title: '添加MJ敏感词',
showIcon: false,
closeOnEsc: false,
content: () =>
h(InputDialogContent, {
ref: bad_prompt_ref,
initData: null,
placeholder: '请输入MJ敏感词'
}),
style: `width : ${dialogWidth}px; min-height : ${dialogHeight}px`,
maskClosable: false,
onClose: async () => {
debugger
let mj_bad_prompt = bad_prompt_ref.value.data
if (mj_bad_prompt == null || mj_bad_prompt == '') {
return
}
let data = {
id: uuidv4(),
value: mj_bad_prompt.toLowerCase()
}
// 保存到后台
await window.mj.AddMjBadPrompt(data, (value) => {
if (value.code == 0) {
message.error(value.message)
return
}
message.success(value.message)
})
}
})
}
return {
gpt_model,
gpt_model_options,
gpt_auto_inference_options,
gpt_auto_inference,
sd_batch_size,
SaveSetting,
sd_seed,
sd_image_width,
sd_image_height,
customize_gpt_options,
customize_gpt_select,
character_select_model_options,
character_select_model,
AddMjBadPrompt,
bad_prompt_ref
}
}
})
</script>
@@ -0,0 +1,926 @@
<template>
<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 {
ref,
h,
onMounted,
defineComponent,
onUnmounted,
watch,
toRaw,
render,
onBeforeUnmount
} from 'vue'
import {
NDataTable,
NImage,
useMessage,
NInput,
NButton,
NSwitch,
NPopover,
useDialog,
NCheckbox,
NSelect,
NIcon
} from 'naive-ui'
import { DEFINE_STRING } from '../../../../define/define_string'
import ModifyPromptChinese from '../Components/ModifyPromptChinese.vue'
import InputDialogContent from './Components/InputDialogContent.vue'
import DataTableShowGenerateImage from './Components/DataTableShowGenerateImage.vue'
import { cloneDeep, debounce, min } from 'lodash'
import SelectImageStyle from '../Components/SelectImageStyle.vue'
import DataTableCharacterAndScene from './Components/DataTableCharacterAndScene.vue'
import DataTablePromptRow from './Components/DataTablePromptRow.vue'
import DataTableGptPromptRow from './Components/DataTableGptPromptRow.vue'
import DataTableParameterRow from './Components/DataTableParameterRow.vue'
import { Reload } from '@vicons/ionicons5'
export default defineComponent({
components: {
NImage,
NDataTable,
NInput,
NButton,
ModifyPromptChinese,
NSwitch,
NPopover,
NCheckbox,
SelectImageStyle,
Reload,
NIcon
},
props: ['initData', 'prefix_prompt', 'suffix_prompt'],
setup(props) {
console.log('输出文件夹位置', props.initData)
let data = ref(props.initData)
let message = useMessage()
let dialog = useDialog()
let selectKey = ref([])
let promptChineseRef = ref(null)
let zhAutoTranslate = ref(false)
let maxHeight = ref(null)
let prefix_prompt = ref(props.prefix_prompt)
let suffix_prompt = ref(props.suffix_prompt)
let titleCharacterAndSceneRef = ref(null)
let dataCharacterAndSceneRef = ref(null)
let prefix_prompt_ref = ref(null)
let suffix_prompt_ref = ref(null)
let selectStyle = ref([])
let characterSelectModel = ref(
window.config.character_select_model ? window.config.character_select_model : 'tag'
)
let tags = ref({})
let AutoSaveDataJsonDebounced = debounce(AutoSaveDataJson, 3000)
let SaveDataJsonDebounced = debounce(AutoSaveDataJson, 1000)
let image_generate_category = ref(
window.config.image_generate_category ? window.config.image_generate_category : 'sd'
)
watch(
() => props.initData,
async (newVal) => {
data.value = newVal
if (window.config.auto_save) {
console.log('自动保存数据')
AutoSaveDataJsonDebounced()
}
},
{ deep: true }
)
watch(
() => props.prefix_prompt,
(newVal) => {
prefix_prompt.value = newVal
},
{ deep: true }
)
watch(
() => props.suffix_prompt,
(newVal) => {
suffix_prompt.value = newVal
},
{ deep: true }
)
/**
* 自动保存数据到json文件
*/
async function AutoSaveDataJson() {
await window.api.AutoSaveDataJson(JSON.stringify(toRaw(data.value)), (value) => {
if (value.code == 0) {
message.error('数据保存错误,错误信息:' + value.message)
return
}
})
}
const createColumns = ({}) => {
return [
{
type: 'selection',
disabled(row) {
return row.name === 'Edward King 3'
}
},
{
title: '',
key: 'no',
width: 50
},
{
title: '字幕',
key: 'srt',
width: '180',
render(row) {
let tmp
if (row.subValue.length > 0) {
tmp = row.subValue.map((item) =>
h('div', { style: 'display:flex; height : auto; margin: 2px 0' }, [
h(NInput, {
size: 'tiny',
value: item.srt_value
})
])
)
} else {
tmp = h('div', { style: 'display:flex; height : auto; margin: 2px 0' }, [
h(NInput, {
type: 'textarea',
autosize: { minRows: 3, maxRows: 6 },
size: 'tiny',
value: row.after_gpt
})
])
}
return tmp
}
},
{
// 修改title,往里面添加添加一个下拉选择框
title(row, index) {
return h(DataTableCharacterAndScene, {
ref: titleCharacterAndSceneRef,
type: 'title',
row: row,
characterSelectModel: characterSelectModel,
func: {
refreshTagData: async () => {
try {
await InitTags()
message.success('人物角色刷新成功')
} catch (error) {
message.success('人物角色刷新失败,请重试')
}
}
}
})
},
className: 'prompt_row',
key: 'character',
width: '200',
render(row, index) {
return h(DataTableCharacterAndScene, {
ref: dataCharacterAndSceneRef,
type: 'data',
row: row,
tags: tags,
characterSelectModel: characterSelectModel
})
}
},
{
title(row, index) {
return h(DataTableGptPromptRow, {
type: 'title',
row: row,
index: index,
func: {
translateAll: TranslateAll,
addPrefix: AddPrefix,
addSuffix: AddSuffix,
selectGenerateImagesStyle: SelectGenerateImagesStyle
}
})
},
key: 'gpt_prompt',
className: 'prompt_row',
render(row, index) {
return h(DataTableGptPromptRow, {
type: 'data',
row: row,
index: index,
func: {
nextGptPrompt: NextGptPrompt,
singlePrompt: SinglePrompt
}
})
}
},
{
// 提示词命令
title(row, index) {
return h(DataTablePromptRow, {
type: 'title',
row: row,
index: index,
func: {
mJBadPromptCheck: MJBadPromptCheck,
mergePrompt: MergePrompt
}
})
},
key: 'prompt',
width: '300',
className: 'prompt_row',
render(row, index) {
return h(DataTablePromptRow, {
type: 'data',
row: row,
index: index,
func: {
nextGenerateImage: NextGenerateImage
}
})
}
},
{
// 参数
title(row, index) {
return h(DataTableParameterRow, {
type: 'title',
row: row,
index: index,
image_generate_category: image_generate_category
})
},
key: 'parameter',
width: '130',
render(row, index) {
return h(DataTableParameterRow, {
type: `data_${image_generate_category.value}`,
row: row,
index: index
})
}
},
{
// 出图
title(row, index) {
return h(DataTableShowGenerateImage, {
initData: row,
index: index,
type: 'title',
image_generate_category: image_generate_category,
func: {
mJGetImage: MJGetImage
}
})
},
key: 'options',
className: 'prompt_row',
width: '300',
render(row, index) {
return h(DataTableShowGenerateImage, {
initData: row,
index: index,
type: 'data'
})
}
}
]
}
let isSaved = false
// 监听control+s 保存数据
let saveListener = function (event) {
debugger
if (event.ctrlKey && event.key === 's') {
event.preventDefault()
if (!isSaved) {
console.log('User pressed Ctrl+S')
SaveDataJsonDebounced()
isSaved = true
}
} else {
isSaved = false
}
}
async function InitTags() {
await window.mj.GetTagDataByTypeAndProperty(['dynamic', null], (value) => {
if (value.code == 0) {
message.error(value.message)
return
}
tags.value = value.data
})
}
onMounted(async () => {
zhAutoTranslate.value = window.config.translation_auto
await window.api.GetConfigJson(JSON.stringify(['image_style', []]), async (value) => {
debugger
if (value.code == 0) {
message.error(value.message)
return
}
await window.api.GetImageStyleInfomation(JSON.stringify(toRaw(value.data)), (value) => {
if (value.code == 0) {
message.error(value.message)
return
}
selectStyle.value = value.data
})
})
// 判断data中的数据是不是有chinese text
window.api.setEventListen([DEFINE_STRING.TRANSLATE_RETURN_REFRESH, window.id], (value) => {
if (value.code == 0) {
message.error('返回数据错误')
return
}
// 直接
let index = data.value.findIndex((item) => item.id == value.rowId)
if (index < 0) {
message.error('翻译后修改源数据,对应数据ID未找到')
return
}
data.value[index].gpt_prompt = value.data
})
getMaxHeight()
window.api.setEventListen(
[DEFINE_STRING.SD_ORIGINAL_GENERATE_IMAGE_RETURN, window.id],
(value) => {
if (value.code == 0) {
message.error('生成图片错误,错误信息如下:' + value.message)
return
}
// 修改data中的数据
for (let i = 0; i < data.value.length; i++) {
if (data.value[i].id == value.id) {
data.value[i].subImagePath = value.data.subImagePath.map(
(item) => 'file://' + item.replaceAll('\\', '/') + '?time=' + new Date().getTime()
)
data.value[i].outImagePath =
'file://' +
value.data.outImagePath.replaceAll('\\', '/') +
'?time=' +
new Date().getTime()
break
}
}
}
)
window.addEventListener('keydown', saveListener)
let div = document.getElementsByClassName('prompt_row')
for (let i = 0; i < div.length; i++) {
div[i].style.padding = '0px 5px'
}
// 监听MJ信息的监听任务的返回
window.api.setEventListen(
[DEFINE_STRING.DISCORD.MAIN_DISCORD_MESSAGE_CHANGE, window.id],
(value) => {
mainDiscordMessageChanged(value)
}
)
await InitTags()
})
onBeforeUnmount(async () => {
debugger
await AutoSaveDataJson()
})
// 接收Discord的修改信息
async function mainDiscordMessageChanged(value) {
if (value.code == 0) {
message.error('Discord返回错误,错误信息如下:' + value.message)
return
}
// 处理数据
if (value.type == 'created') {
debugger
console.log('接收Discord的创建消息', value)
// 找到对应ID的data数据
let index = data.value.findIndex((item) => item.id == value.id)
if (index < 0) {
message.error('未找到对应的数据')
return
}
delete value.type
delete value.code
data.value[index]['mj_message'] = value
} else if (value.type == 'updated') {
console.log('接收Discord的更新消息', value)
debugger
// 修改对应的数据
let index = data.value.findIndex((item) => item.mj_message?.message_id == value.message_id)
if (index < 0) {
message.error('未找到对应的数据')
return
}
// 修改数据
if (value.image_click != null && value.image_click != '') {
data.value[index]['outImagePath'] = value.image_click
}
delete value.type
delete value.code
data.value[index]['mj_message'] = value
} else if (value.type == 'delete') {
console.log('接收Discord的删除消息', value)
} else if (value.type == 'finished') {
console.log('接收Discord的完成消息', value)
debugger
// 修改对应的数据
let index = data.value.findIndex((item) => item.mj_message?.image_id == value.image_id)
if (index < 0) {
message.error('未找到对应的数据')
return
}
data.value[index]['outImagePath'] = value.image_path
delete value.type
delete value.code
value.progress = 100
value.message_id = value.message_id
data.value[index]['mj_message'] = value
// 返回后端分割图片(传入图片地址,返回分割后的图片地址数组)
await window.mj.ImageSplit(
JSON.stringify([value.image_path, data.value[index].name]),
(value) => {
if (value.code == 0) {
message.error(value.message)
return
}
// 修改数据
data.value[index]['outImagePath'] = value.data.outImagePath
data.value[index]['subImagePath'] = value.data.subImagePath
}
)
} else {
message.error('未知的操作类型')
}
}
onUnmounted(() => {
window.api.removeEventListen(DEFINE_STRING.TRANSLATE_RETURN_REFRESH)
window.api.removeEventListen(DEFINE_STRING.DISCORD.MAIN_DISCORD_MESSAGE_CHANGE)
window.removeEventListener('keydown', saveListener)
})
/**
* 将翻译添加到队列中的方式
* @param {要翻译的数据(数组)} translateData
* @param {原语言(zhen} from
* @param {目标语言(zhen} to
* @param {是否显示弹窗} isShow
*/
async function TranslatePrompt(translateData, from, to) {
console.log(translateData)
await window.api.TranslatePrompt([translateData, from, to, window.id, true], (value) => {
debugger
console.log(value)
})
}
// 计算当前程序界面的高度,减去上下的高度,设置maxHegiht
async function getMaxHeight() {
let height = window.innerHeight
maxHeight.value = height - 150
}
// 监听程序界面的高度变化
window.onresize = function () {
getMaxHeight()
}
// 添加前缀的弹窗
const AddPrefix = () => {
// 判断当前数据是不是存在
// 处理数据。获取当前的所有的数据
let dialogWidth = 400
let dialogHeight = 150
dialog.create({
title: '添加前缀',
showIcon: false,
closeOnEsc: false,
content: () =>
h(InputDialogContent, {
ref: prefix_prompt_ref,
initData: prefix_prompt.value,
placeholder: '请输入前缀'
}),
style: `width : ${dialogWidth}px; min-height : ${dialogHeight}px`,
maskClosable: false,
onClose: async () => {
prefix_prompt.value = prefix_prompt_ref.value.data
// 保存通用前缀
await window.api.SaveCopywritingInformation(
[prefix_prompt_ref.value.data, 'prefix_prompt', false],
(value) => {
if (value.code == 0) {
message.error('保存前缀错误,错误信息如下:' + value.message)
return
}
}
)
}
})
}
/**
* 选择生成图片的风格
*/
async function SelectGenerateImagesStyle() {
// 判断当前数据是不是存在
// 处理数据。获取当前的所有的数据
let dialogWidth = window.innerWidth * 0.8
let dialogHeight = window.innerHeight * 0.9
// ImportWordAndSrt
dialog.create({
showIcon: false,
closeOnEsc: false,
content: () =>
h(SelectImageStyle, {
selectStyle: toRaw(selectStyle.value),
height: dialogHeight
}),
style: `width : ${dialogWidth}px; height : ${dialogHeight}px`,
maskClosable: false,
onClose: async () => {
// 重新加载数据
let tmp_arr = []
for (let i = 0; i < selectStyle.value.length; i++) {
const element = selectStyle.value[i]
tmp_arr.push(element.id)
}
// 将当前的风格保存
await window.api.SaveCopywritingInformation(
[JSON.stringify(tmp_arr), 'image_style', true],
(value) => {
if (value.code == 0) {
message.error(value.message)
return
}
message.success('保存成功')
}
)
}
})
}
// 添加后缀的弹窗
const AddSuffix = () => {
// 判断当前数据是不是存在
// 处理数据。获取当前的所有的数据
let dialogWidth = 400
let dialogHeight = 150
dialog.create({
title: '添加后缀',
showIcon: false,
closeOnEsc: false,
content: () =>
h(InputDialogContent, {
ref: suffix_prompt_ref,
initData: suffix_prompt.value,
placeholder: '请输入后缀'
}),
style: `width : ${dialogWidth}px; min-height : ${dialogHeight}px`,
maskClosable: false,
onClose: async () => {
suffix_prompt.value = suffix_prompt_ref.value.data
await window.api.SaveCopywritingInformation(
[suffix_prompt_ref.value.data, 'suffix_prompt', false],
(value) => {
if (value.code == 0) {
message.error('保存前缀错误,错误信息如下:' + value.message)
return
}
}
)
}
})
}
/**
* 添加MJ敏感词检查
*/
async function MJBadPromptCheck() {
await window.mj.MJBadPromptCheck(JSON.stringify(toRaw(data.value)), (value) => {
if (value.code == 0) {
message.error(value.message)
return
}
debugger
// 先将所有的数据设置为false
for (let i = 0; i < data.value.length; i++) {
data.value[i].mj_message = data.value[i].mj_message ? data.value[i].mj_message : {}
data.value[i].mj_message['hasBadPrompt'] = false
}
// 判断返回的ID数据是不是为空,不为空,修改数据
if (value.data.length > 0) {
let message_text = ''
for (let i = 0; i < value.data.length; i++) {
const element = value.data[i]
message_text += '' + element.no.toString()
let index = data.value.findIndex((item) => item.id == element.id)
if (index < 0) {
message.error('未找到对应的数据')
return
}
data.value[index].mj_message = data.value[index].mj_message
? data.value[index].mj_message
: {}
data.value[index].mj_message['hasBadPrompt'] = true
}
let show_text = `检测到 ${value.data.length} 条敏感词,分别在第 ${message_text.substring(
1
)} 行。有红色框标识。请检查`
window.api.showGlobalMessageDialog({
code: 0,
message: show_text
})
}
})
}
/**
* 将当前和下面的所有的数据生成图片
* @param {*} row
*/
async function NextGenerateImage(row, ca = 'sd') {
let index = data.value.findIndex((item) => item.id == row.id)
let tempData = []
for (let i = index; i < data.value.length; i++) {
const element = data.value[i]
tempData.push(element)
}
// 判断当前的生图模式
if (ca == 'sd') {
// 生成SD生图
await window.api.OriginalSDImageGenerate(
[JSON.stringify(tempData), true, true],
(value) => {
if (value.code == 0) {
message.error(value.message)
return
}
message.success('添加SD生图任务成功')
}
)
} else if (ca == 'mj') {
// MJ生图
await window.mj.OriginalMJImageGenerate([JSON.stringify(tempData), true, true], (value) => {
if (value.code == 0) {
message.error(value.message)
return
}
message.success('添加MJ生图任务成功')
})
} else if (ca == 'd3') {
} else {
message.error('生图的类别错误')
}
}
/**
* 往下继续推理
* @param value 当前行数据
*/
async function NextGptPrompt(row) {
let index = data.value.findIndex((item) => item.id == row.id)
let tempData = []
for (let i = index; i < data.value.length; i++) {
const element = data.value[i]
tempData.push(element)
}
await window.api.GPTPrompt(
[JSON.stringify(tempData), true, JSON.stringify(toRaw(data.value))],
(value) => {
console.log('添加推理任务成功')
if (value.code == 0) {
message.error(value.message)
return
}
message.success('添加推理任务成功')
}
)
}
/**
* 单句推理
* @param row
*/
async function SinglePrompt(row) {
await window.api.GPTPrompt(
[JSON.stringify([toRaw(row)]), false, JSON.stringify(toRaw(data.value))],
(value) => {
console.log('添加推理任务成功')
if (value.code == 0) {
message.error(value.message)
return
}
message.success('添加推理任务成功')
}
)
}
function containsChineseOrPunctuation(str) {
return /[\u4e00-\u9fa5]|[\u3000-\u301e\u2013\u2014\u2018\u2019\u201c\u201d\u2026\u203b\uff08\uff09\uff1a\uff1b\uff1f\uff01\uff0c\u3001\uff0e\u3002\uff1f\uff01\u2018\u2019\u201c\u201d]/.test(
str
)
}
/**
* 翻译传入的数据
* @param {*} key 翻译的类型
*/
async function TranslateAll(key) {
debugger
// 将中文翻译成英文
if (key == 'english') {
let tmp_d = []
for (let i = 0; i < data.value.length; i++) {
const element = cloneDeep(data.value[i])
if (containsChineseOrPunctuation(element.gpt_prompt)) {
tmp_d.push(element)
}
}
// 检查是不是有中文
// 开始翻译
let t = [tmp_d, 'zh', 'en', false, false]
await window.mj.TranslateReturnNowTask(JSON.stringify(t), (value) => {
if (value.code == 0) {
message.error(value.message)
return
}
message.success('添加翻译任务成功')
})
} else if (key == 'chinese') {
debugger
let t = [[...toRaw(data.value)], 'en', 'zh', false, false]
await window.mj.TranslateReturnNowTask(JSON.stringify(t), (value) => {
if (value.code == 0) {
message.error(value.message)
return
}
message.success('添加翻译任务成功')
})
}
}
/**
* 生成提示词
*/
async function MergePrompt() {
debugger
try {
// 判断该当前是不是有风格
// 获取当前的风格ID
let style_str = ''
for (let i = 0; i < selectStyle.value.length; i++) {
const element = selectStyle.value[i]
style_str += element.english_style + ','
}
// 获取当前的前缀和后缀
let prefix = prefix_prompt.value
let suffix = suffix_prompt.value
for (let i = 0; i < data.value.length; i++) {
const element = data.value[i]
// 匹配提示词
// 拼接当前选择的人物
let character_string = ''
element.character_tags?.forEach((item) => {
character_string += item.prompt + ', '
})
// 获取当前的场景提示词
let scene_string = ''
element.scene_tags?.forEach((item) => {
scene_string += item.prompt + ', '
})
// 开始拼接提示词
// 风格 + 前缀 + 人物 + 场景 + 词 + 后缀
let end_prompt = ` ${style_str} ${
prefix ? prefix : ''
}, ${character_string} ${scene_string} ${element.gpt_prompt}, ${suffix ? suffix : ''}`
data.value[i].prompt = end_prompt
}
} catch (error) {
message.error('生成提示词错误,错误信息如下:' + error.message)
}
}
/**
*
*/
async function MJGetImage() {
let pa = []
// MJ采集图片
for (let i = 0; i < data.value.length; i++) {
const item = data.value[i]
// 一般进度大于 50 会出现图片,
if (!item.mj_message) {
return
}
if (item.mj_message.progress && item.mj_message.progress == 100) {
// 判断 image_path 是不是存在。
if (item.mj_message.image_id && !item.mj_message.image_path) {
// 通过当前的image_id获取图片
pa.push(item)
}
}
}
if (pa.length > 0) {
window.mj.GetGeneratedMJImageAndSplit(JSON.stringify(pa), (value) => {
if (value.code == 0) {
message.error(value.message)
return
}
// 修改数据
for (let i = 0; i < value.data.length; i++) {
const element = value.data[i]
// 修改对应的数据
let index = data.value.findIndex((item) => item.id == element.id)
if (index < 0) {
message.error('未找到对应的数据')
return
}
data.value[index].outImagePath = element.outImagePath
data.value[index].subImagePath = element.subImagePath
data.value[index].mj_message.image_click = element.result
data.value[index].mj_message.image_path = element.image_path
}
})
} else {
message.error('没有找到可以自动下载图片的数据,可以手动粘贴链接分割')
return
}
}
return {
data,
columns: createColumns({}),
selectStyle,
rowKey: (row) => row.id,
handleCheck(rowKeys) {
selectKey.value = rowKeys
},
promptChineseRef,
zhAutoTranslate,
TranslatePrompt,
TranslateAll,
maxHeight,
prefix_prompt_ref,
suffix_prompt_ref,
characterSelectModel,
image_generate_category,
titleCharacterAndSceneRef,
dataCharacterAndSceneRef
}
}
})
</script>
@@ -0,0 +1,99 @@
<template>
<div id="menu">
<MenuButton :initData="data" :initDataFunction="InitData" :Character="AnalyzeCharacter" :treeData="TagTreeData">
</MenuButton>
</div>
<div id="data-table" style="margin-top: 10px;">
<DataTable :initData="data" :suffix_prompt="suffix_prompt" :prefix_prompt="prefix_prompt"></DataTable>
</div>
</template>
<script>
import { ref, h, onMounted, defineComponent, onUnmounted, toRaw } from "vue"
import { NImage, useMessage } from "naive-ui";
import { DEFINE_STRING } from "../../../../define/define_string";
import MenuButton from './MenuButton.vue'
import DataTable from './DataTable.vue'
export default defineComponent({
components: {
NImage, DataTable, MenuButton
},
setup() {
let message = useMessage();
let data = ref([]);
let AnalyzeCharacter = ref("");
let TagTreeData = ref([]);
let suffix_prompt = ref(null);
let prefix_prompt = ref(null);
let promptError = true;
async function InitData() {
// 加载
// 初始化加载项目下面的分镜好的文案
// 并判断是不是有洗稿后的文件。一并加载
await window.api.GetProjectWord(value => {
data.value = value.data;
})
if (!data.value) {
data.value = [];
}
// 重新加载数据
// 判断是不是已经有生成(有有生成显示)
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([null, {}]), async (value) => {
debugger
if (value.code == 0) {
message.error(value.message);
return;
}
suffix_prompt.value = value.data.suffix_prompt;
prefix_prompt.value = value.data.prefix_prompt;
})
// 读取data数据对应的prompt文件
for (let i = 0; i < data.value.length; i++) {
const item = data.value[i];
await window.api.GetPromptJson(item.name, (value) => {
debugger
console.log(value);
if (value.code == 0 && promptError) {
message.error(value.message);
promptError = false;
}
data.value[i].prompt = value.data.webui_config.prompt;
data.value[i].chinese_prompt = value.data?.chinese_prompt;
// data.value[i].gpt_prompt = value.data?.gpt_prompt;
data.value[i].adetailer = value.data?.adetailer;
})
}
}
onMounted(async () => {
await InitData();
})
return {
data,
AnalyzeCharacter,
TagTreeData,
prefix_prompt,
suffix_prompt,
InitData,
}
}
})
</script>
@@ -0,0 +1,369 @@
<template>
<div style="display: flex">
<n-button color="#ee7959" @click="ImportWord" size="small">导入字幕/SRT</n-button>
<n-button color="#ee7959" style="margin-left: 10px" @click="GetCharacter" size="small"
>角色分析标签集</n-button
>
<n-dropdown trigger="hover" :options="GptButtonOptions" @select="ButtonSelect">
<n-button color="#ee7959" style="margin-left: 10px" @click="GPTPrompt" size="small"
>一键推理提示词</n-button
>
</n-dropdown>
<n-dropdown trigger="hover" :options="GenerateImageOptions" @select="ButtonSelect">
<n-button color="#ee7959" style="margin-left: 10px" @click="GenerateImageAll" size="small"
>生成所有的图片</n-button
>
</n-dropdown>
<n-dropdown trigger="hover" :options="ResetDataOptions" @select="ButtonSelect">
<n-button color="#ee7959" style="margin-left: 10px" @click="resetALLData" size="small"
>重置数据</n-button
>
</n-dropdown>
<n-button color="#ee7959" style="margin-left: 10px" @click="OpenPromptSetting" size="small"
>设置</n-button
>
<div style="display: flex; align-items: center">
<n-switch
v-model:value="auto_save"
:railStyle="railStyle"
@update:value="AutoSaveUpdate"
style="margin-left: 10px"
>
<template #checked> 自动保存 </template>
<template #unchecked> Ctrl + S </template>
</n-switch>
</div>
</div>
</template>
<script>
import { ref, h, onMounted, defineComponent, toRaw, watch } from 'vue'
import { NImage, useMessage, NButton, useDialog, NPopover, NSwitch, NDropdown } from 'naive-ui'
import { DEFINE_STRING } from '../../../../define/define_string'
import ImportWordAndSrt from './Components/ImportWordAndSrt.vue'
import CharacterAnalyze from './Components/CharacterAnalyze.vue'
import PromptSetting from './Components/PromptSetting.vue'
import { cloneDeep } from 'lodash'
export default defineComponent({
components: {
NImage,
NButton,
CharacterAnalyze,
NPopover,
NSwitch,
NDropdown
},
props: ['initData', 'Character', 'treeData', 'autoSave', 'initDataFunction'],
setup(props) {
let message = useMessage()
let dialog = useDialog()
let data = ref(props.initData)
let AnalyzeCharacter = ref(props.Character)
let tagTreeData = ref(props.treeData)
let auto_save = ref(window.config.auto_save ? window.config.auto_save : false)
watch(
() => props.initData,
(newVal) => {
data.value = newVal
},
{ deep: true }
)
watch(
() => props.Character,
(newVal) => {
AnalyzeCharacter.value = newVal
},
{ deep: true }
)
watch(
() => props.treeData,
(newVal) => {
tagTreeData.value = newVal
},
{ deep: true }
)
onMounted(async () => {
window.api.setEventListen([DEFINE_STRING.GPT_GENERATE_PROMPT_RETURN], (value) => {
console.log(value)
// 修改data中指定的id的某个数据
for (let i = 0; i < data.value.length; i++) {
if (data.value[i].id == value.id) {
data.value[i].gpt_prompt = value.gpt_prompt
break
}
}
})
window.config.auto_save = auto_save.value
})
/**
* 导入字幕的配置
*/
async function ImportWord() {
// 判断当前数据是不是存在
// 处理数据。获取当前的所有的数据
let dialogWidth = window.innerWidth * 0.95
let dialogHeight = window.innerHeight * 0.95
// ImportWordAndSrt
dialog.create({
showIcon: false,
closeOnEsc: false,
content: () => h(ImportWordAndSrt, { initData: data.value, height: dialogHeight }),
style: `width : ${dialogWidth}px; min-height : ${dialogHeight}px`,
maskClosable: false,
onClose: () => {
props.initDataFunction()
}
})
}
/**
* 角色自动分析
*/
async function GetCharacter() {
// 判断当前数据是不是存在
// 处理数据。获取当前的所有的数据
let dialogWidth = window.innerWidth * 0.6
let dialogHeight = window.innerHeight * 0.9
// ImportWordAndSrt
dialog.create({
showIcon: false,
closeOnEsc: false,
content: () => h(CharacterAnalyze, { initData: data.value, 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 GPTPrompt() {
// 将所有的人物添加到后台队列任务中
console.log(data.value)
// 处理数据,只推理prompt为空的数据
let tmpData = cloneDeep(toRaw(data.value))
tmpData = tmpData.filter((item) => {
return item.gpt_prompt == null || item.gpt_prompt == ''
})
if (tmpData.length == 0) {
message.success('没有需要推理的数据')
return
}
await window.api.GPTPrompt(
[JSON.stringify(tmpData), true, JSON.stringify(toRaw(data.value))],
(value) => {
console.log('添加推理任务成功')
if (value.code == 0) {
message.error(value.message)
return
}
message.success('添加推理任务成功')
}
)
}
/**
* 打开新的弹窗
*/
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 GenerateImageAll() {
debugger
console.log(data.value)
let tmpData = cloneDeep(toRaw(data.value))
tmpData = tmpData.filter((item) => {
return item.outImagePath == null || item.outImagePath == ''
})
if (tmpData.length == 0) {
message.success('没有需要批量生成的图片')
return
}
// 判断当前是mj还是sd
let ca = window.config.image_generate_category
if (ca == 'sd') {
// 将所有的人物添加到后台队列任务中
await window.api.OriginalSDImageGenerate([JSON.stringify(tmpData), true, true], (value) => {
console.log('添加生成图片任务成功')
if (value.code == 0) {
message.error(value.message)
return
}
message.success('添加生成图片任务成功')
})
} else if (ca == 'mj') {
// MJ生图
await window.mj.OriginalMJImageGenerate([JSON.stringify(tmpData), true, true], (value) => {
if (value.code == 0) {
message.error(value.message)
return
}
message.success('添加MJ生图任务成功')
})
}
}
let railStyle = ({ focused, checked }) => {
const style = {}
if (checked) {
style.background = '#f3a694'
if (focused) {
style.boxShadow = '0 0 0 2px #f3a694'
}
} else {
style.background = '#775039'
if (focused) {
style.boxShadow = '0 0 0 2px #775039'
}
}
return style
}
// 重置提示词数据
async function ResetPrompt() {
// 循环data删除数据
for (let i = 0; i < data.value.length; i++) {
data.value[i].prompt = ''
data.value[i].chinese_prompt = []
}
}
// 图片数据重置
async function ResetImage() {
// 循环data删除数据
for (let i = 0; i < data.value.length; i++) {
data.value[i].outImagePath = null
data.value[i].subImagePath = []
}
}
/**
* 下拉菜单选择值改变的方法
* @param {*} value
*/
async function ButtonSelect(value) {
if (value == 'stopGptPrompt') {
await window.api.DeleteBackTask(
[DEFINE_STRING.QUEUE_BATCH.SD_ORIGINAL_GPT_PROMPT, null, 'default'],
(value) => {
if (value.code == 0) {
message.error(value.message)
return
}
message.success('停止推理任务成功')
}
)
} else if (value == 'stopGenerateImage') {
await window.api.DeleteBackTask(
[DEFINE_STRING.QUEUE_BATCH.SD_ORIGINAL_GENERATE_IMAGE, null, 'default'],
(value) => {
if (value.code == 0) {
message.error(value.message)
return
}
message.success('停止生成图片任务成功')
}
)
await window.api.DeleteBackTask(
[DEFINE_STRING.QUEUE_BATCH.MJ_ORIGINAL_GENERATE_IMAGE, null, 'default'],
(value) => {
if (value.code == 0) {
message.error(value.message)
return
}
message.success('停止生成图片任务成功')
}
)
} else if (value == 'resetPrompt') {
// 重置提示词
debugger
console.log(data)
await ResetPrompt()
} else if (value == 'resetImage') {
await ResetImage()
}
}
// 重置所有数据
async function resetALLData() {
await ResetPrompt()
await ResetImage()
}
return {
ImportWord,
data,
GetCharacter,
tagTreeData,
GPTPrompt,
OpenPromptSetting,
GenerateImageAll,
auto_save,
railStyle,
AutoSaveUpdate: (value) => {
debugger
auto_save.value = value
window.config.auto_save = value
},
GptButtonOptions: [{ label: '停止推理任务', key: 'stopGptPrompt' }],
GenerateImageOptions: [{ label: '停止生成图片任务', key: 'stopGenerateImage' }],
ResetDataOptions: [
{ label: '重置提示词', key: 'resetPrompt' },
{ label: '重置图片', key: 'resetImage' }
],
ButtonSelect,
resetALLData
}
}
})
</script>
@@ -0,0 +1,310 @@
<template>
<n-card>
<n-tabs class="card-tabs" default-value="background_music_setting" size="large" animated>
<n-tab-pane name="background_music_setting" tab="背景音乐设置">
<div style="margin:10px 0;">
注意
<div style="color: red;">设置背景乐对应的文件夹,选择对应的分类,可以随机匹配音乐</div>
</div>
<BackgroundMusic></BackgroundMusic>
</n-tab-pane>
<n-tab-pane name="keyfarme_setting" tab="关键帧设置">
<KeyFrameSetting></KeyFrameSetting>
</n-tab-pane>
<!-- <n-tab-pane name="text_setting" tab="字幕设置">
<div style="margin:10px 0;">
注意
<div style="color: red;">在剪映里面设置一个字体模板然后选择草稿文件只提取第一个句子点击提取</div>
<div style="color: red;">提取所有的内容显示字体颜色字号偏移量根据视频比例会出现不同</div>
</div>
<n-divider />
<div style="display: flex;">
<n-input placeholder="输入预设名称" v-model:value="textName" @keydown.enter.prevent
:style="{ flex: '4', marginRight: '10px' }"></n-input>
<SelectDraft ref="selectDraft" style="flex: 5;"></SelectDraft>
<n-button round type="primary" @click="getTextStyle" style="flex: 1; margin: 0 0 0 30px ;">提取</n-button>
</div>
<n-divider />
<n-data-table :columns="columns" :data="data" :row-props="rowProps" />
<n-dropdown placement="bottom-start" trigger="manual" :x="x" :y="y" :options="options" :show="showDropdown"
:on-clickoutside="onClickoutside" @select="handleSelect" />
</n-tab-pane>
<n-tab-pane name="friendly_reminder_setting" tab="提示设置">
<div style="margin:10px 0;">
注意
<div style="color: red;">在剪映里面设置一个右上角提示然后选择草稿文件只能设置一个点击提取</div>
</div>
<n-divider />
<div style="display: flex;">
<n-input placeholder="提示名称" v-model:value="friendlyReminderTextName" @keydown.enter.prevent
:style="{ flex: '3', marginRight: '10px' }"></n-input>
<SelectDraft ref="friendlyReminderSelectDraft" style="flex: 5;"></SelectDraft>
<n-button round type="primary" @click="GetFriendlyReminder"
style="flex: 1; margin: 0 0 0 30px ;">提取</n-button>
</div>
<n-divider />
<n-data-table :columns="friendlyReminderColums" :data="friendlyReminderData"
:row-props="FriendlyReminderProps" />
<n-dropdown placement="bottom-start" trigger="manual" :x="x" :y="y" :options="options"
:show="FriendlyReminderShowDropdown" :on-clickoutside="onClickoutside"
@select="handleFriendReminderSelect" />
</n-tab-pane> -->
</n-tabs>
</n-card>
</template>
<script>
import { defineComponent, ref, h, onMounted, nextTick } from "vue";
import { NButton, NInput, NCard, NTabs, NTabPane, NDivider, useMessage, NDataTable, NDropdown } from "naive-ui"
import SelectDraft from "../Components/SelectDraft.vue";
import BackgroundMusic from "./Components/BackgroundMusic.vue"
import KeyFrameSetting from "./Components/KeyFrameSetting.vue"
const createColumns = (type) => {
if (type == "srt") {
return [
{
title: '名字',
key: 'name'
},
{
title: "视频比例",
key: "ratio"
},
{
title: '字体',
key: 'fonts'
},
{
title: '字号',
key: 'font_size'
},
{
title: '字体样式',
key: 'font_style'
},
{
title: '偏移量X',
key: 'transformX'
},
{
title: '偏移量Y',
key: 'transformY'
}
]
}
else if (type == "FR") {
return [
{
title: "名字",
key: "name"
},
{
title: "提示字",
key: "text_value"
},
]
}
}
const options = [
{
label: "编辑",
key: "edit"
},
{
label: () => h("span", { style: { color: "red" } }, "删除"),
key: "delete"
}
];
export default defineComponent({
components: {
NButton, NInput, NCard, NTabs, NTabPane, NDivider, NDataTable, SelectDraft, NDropdown, BackgroundMusic, KeyFrameSetting
},
setup() {
let draf_name = ref("");
let selectDraft = ref(null);
let friendlyReminderSelectDraft = ref(null);
let message = useMessage();
let textName = ref(null);
let friendlyReminderTextName = ref("");
let data = ref([]);
let friendlyReminderData = ref([]);
const xRef = ref(0);
const yRef = ref(0);
const showDropdown = ref(false);
const FriendlyReminderShowDropdown = ref(false);
let selectOption = "";
let FriendLyOption = "";
/**
* 提取字体样式
*/
async function getTextStyle() {
let draft_name = selectDraft.value.draf_name;
if (draft_name == null || draf_name == "" || textName.value == null || textName.value == "") {
message.error("请选择草稿");
return;
}
await window.api.getDraftTextStyle([textName.value, draft_name], (value) => {
if (value.code == 1) {
message.success("提取成功");
getDraftFileList();
} else {
message.error(value.message)
}
})
}
/**
* 获取字幕样式泪列表
*/
async function getDraftFileList() {
data.value = [];
await window.api.getTextStyleList((value) => {
if (value.code == 0) {
message.error(value.message);
return;
}
let tabledatas = value.value.filter(item => item.id != "0" && item.id != "1");
for (let i = 0; i < tabledatas.length; i++) {
const element = tabledatas[i];
let obj = {
id: element.id,
name: element.name,
fonts: element.fonts,
font_size: element.font_size,
font_style: element.style_name,
transformX: element.clip.transform.x,
transformY: element.clip.transform.y,
ratio: element.ratio
}
data.value.push(obj)
}
})
}
/**
* 获取温馨提示列表(右上角)
*/
async function getFriendlyReminderList() {
debugger;
friendlyReminderData.value = [];
await window.api.getFriendlyReminderList((value) => {
friendlyReminderData.value = value.value.filter(item => item.id != "0" && item.id != "1")
})
}
/**
* 提取温馨提示样式
*/
async function GetFriendlyReminder() {
let friendlyReminderSelectDraft_name = friendlyReminderSelectDraft.value.draf_name;
debugger;
console.log(friendlyReminderSelectDraft_name);
if (friendlyReminderTextName.value == "" || friendlyReminderTextName.value == null || friendlyReminderSelectDraft_name == "" || friendlyReminderSelectDraft_name == null) {
message.error("请选择草稿地址和名称");
return;
}
await window.api.GetFriendlyReminder([friendlyReminderSelectDraft_name, friendlyReminderTextName.value], async (value) => {
if (value.code == 0) {
message.error(value.message);
return;
}
message.success("写入成功!!");
await getFriendlyReminderList();
})
}
onMounted(async () => {
await getDraftFileList();
await getFriendlyReminderList();
})
return {
draf_name,
getTextStyle,
selectDraft,
friendlyReminderSelectDraft,
textName,
data,
options,
x: xRef,
y: yRef,
showDropdown,
FriendlyReminderShowDropdown,
friendlyReminderTextName,
getDraftFileList,
GetFriendlyReminder,
friendlyReminderData,
columns: createColumns("srt"),
friendlyReminderColums: createColumns("FR"),
handleSelect: async (value) => {
let id = JSON.parse(selectOption).id;
// 删除对应的
if (value == "delete") {
await window.api.deleteDraftTextStyle(id, async (value) => {
if (value.code == 1) {
message.success("删除成功");
await getDraftFileList();
} else {
message.error(value.message)
}
})
}
showDropdown.value = false;
},
handleFriendReminderSelect: async (value) => {
let id = JSON.parse(FriendLyOption).id;
// 删除对应的
if (value == "delete") {
await window.api.deleteClipStettingFriendlyReminder(id, async (value) => {
if (value.code == 1) {
message.success("删除成功");
await getFriendlyReminderList();
} else {
message.error(value.message)
}
})
}
FriendlyReminderShowDropdown.value = false;
},
onClickoutside() {
showDropdown.value = false;
},
rowProps: (row) => {
return {
onContextmenu: (e) => {
selectOption = JSON.stringify(row, null, 2);
e.preventDefault();
showDropdown.value = false;
nextTick().then(() => {
showDropdown.value = true;
xRef.value = e.clientX;
yRef.value = e.clientY;
});
}
};
},
FriendlyReminderProps: (row) => {
return {
onContextmenu: (e) => {
FriendLyOption = JSON.stringify(row, null, 2);
e.preventDefault();
FriendlyReminderShowDropdown.value = false;
nextTick().then(() => {
FriendlyReminderShowDropdown.value = true;
xRef.value = e.clientX;
yRef.value = e.clientY;
});
}
};
}
}
}
})
</script>
@@ -0,0 +1,274 @@
<template>
<div>
<n-tabs class="card-tabs" default-value="signin" 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="signin" tab="GPT服务商">
<n-form ref="formRef" :model="gpt_select_value" :rules="rules">
<n-form-item label="选择自定义的GPT服务商">
<n-select style="width: 300px;" :options="gpt_options"
v-model:value="gpt_select_key"></n-select>
<n-button color="#e18a3b" style="margin-left: 10px;" @click="EditOption">编辑</n-button>
<n-button color="#984f31" style="margin-left: 10px;" @click="AddOption">添加</n-button>
<n-button type="error" style="margin-left: 10px;" @click="DeleteOption">删除</n-button>
</n-form-item>
<n-form-item path="label" label="GPT 名称">
<n-input v-model:value="gpt_select_value.label"></n-input>
</n-form-item>
<n-form-item path="value" label="GPT请求网址(写全)">
<n-input v-model:value="gpt_select_value.value"></n-input>
</n-form-item>
<div style="text-align: right;">
<n-button type="primary" @click="SaveGptOption">保存</n-button>
</div>
</n-form>
</n-tab-pane>
<n-tab-pane name="signup" tab="GPT模型">
<n-form ref="modelFormRef" :model="gpt_model_select_value" :rules="modelRules">
<n-form-item label="选择自定义的GPT模型">
<n-select style="width: 300px;" :options="gpt_model_options"
v-model:value="gpt_model_select_key"></n-select>
<n-button color="#e18a3b" style="margin-left: 10px;" @click="EditModelOption">编辑</n-button>
<n-button color="#984f31" style="margin-left: 10px;" @click="AddModelOption">添加</n-button>
<n-button type="error" style="margin-left: 10px;" @click="DeleteModelOption">删除</n-button>
</n-form-item>
<n-form-item path="label" label="GPT 模型名称">
<n-input v-model:value="gpt_model_select_value.label"></n-input>
</n-form-item>
<n-form-item path="value" label="GPT 模型值">
<n-input v-model:value="gpt_model_select_value.value"></n-input>
</n-form-item>
<div style="text-align: right;">
<n-button type="primary" @click="SaveGptModelOption">保存</n-button>
</div>
</n-form>
</n-tab-pane>
</n-tabs>
</div>
</template>
<script>
import { ref, toRaw, onMounted, defineComponent } from "vue"
import { NForm, NFormItem, NInput, NInputNumber, useDialog, NButton, useMessage, NSelect, NTabs, NTabPane } from "naive-ui"
import { cloneDeep } from "lodash";
export default defineComponent({
components: {
NForm, NFormItem, NInput, NInputNumber, NButton, NSelect, NTabs, NTabPane
},
setup() {
let message = useMessage();
let gpt_model_options = ref([]);
let gpt_options = ref([]);
let gpt_select_value = ref({});
let gpt_select_key = ref("");
let gpt_model_select_value = ref({});
let gpt_model_select_key = ref("");
let formRef = ref(null);
let modelFormRef = ref(null);
/**
* 加载GPT的配置信息
*/
async function InitGptOptions() {
await window.api.getGptBusinessOption("dynamic", (value) => {
if (value.code == 0) {
message.error(value.message);
return;
}
if (value.data.length == 0) {
message.error("请先添加自定义GPT服务商");
return;
}
gpt_options.value = value.data;
gpt_select_key.value = value.data[0].value;
gpt_select_value.value = {
label: null,
value: null
};
})
await window.api.getGptModelOption("dynamic", (value) => {
if (value.code == 0) {
message.error(value.message);
return;
}
if (value.data.length == 0) {
message.error("请先添加自定义GPT模型");
return;
}
gpt_model_options.value = value.data;
gpt_model_select_key.value = value.data[0].value;
gpt_model_select_value.value = {
label: null,
value: null
}
})
}
onMounted(async () => {
await InitGptOptions();
})
/**
* 保存当前的GPT配置信息
*/
async function SaveGptOption(e) {
e.preventDefault();
formRef.value?.validate(async (errors) => {
if (errors) {
message.error("请检查必填字段");
return
}
// 保存逻辑
await window.api.SaveDynamicGptOption([JSON.stringify(toRaw(gpt_select_value.value)), "gpt_options"], async (value) => {
if (value.code == 0) {
message.error(value.message);
return;
}
message.success("添加成功");
await InitGptOptions();
})
});
}
/**
* 将当前选中的GPT配置信息,写入下面已编辑
*/
async function EditOption() {
let temp = gpt_options.value.find(x => x.value == gpt_select_key.value);
gpt_select_value.value = cloneDeep(temp);
}
/**
* 将当前选中的GPT模型配置信息,写入下面已编辑
*/
async function EditModelOption() {
let temp = gpt_model_options.value.find(x => x.value == gpt_model_select_key.value);
gpt_model_select_value.value = cloneDeep(temp);
}
/**
* 删除当前选中的GPT服务商信息
*/
async function DeleteOption() {
let temp = gpt_options.value.find(x => x.value == gpt_select_key.value);
let id = temp.id;
await window.api.DeleteDynamicGptOption([id, "gpt_options"], async (value) => {
if (value.code == 0) {
message.error(value.message);
return;
}
message.success("删除成功");
await InitGptOptions();
})
}
/**
* 删除当前选中的GPT服务商信息
*/
async function DeleteModelOption() {
let temp = gpt_model_options.value.find(x => x.value == gpt_model_select_key.value);
let id = temp.id;
await window.api.DeleteDynamicGptOption([id, "gpt_model_options"], async (value) => {
if (value.code == 0) {
message.error(value.message);
return;
}
message.success("删除成功");
await InitGptOptions();
})
}
/**
* 保存当前的GPT配置信息
*/
async function SaveGptModelOption(e) {
e.preventDefault();
modelFormRef.value?.validate(async (errors) => {
if (errors) {
message.error("请检查必填字段");
return
}
// 保存逻辑
await window.api.SaveDynamicGptOption([JSON.stringify(toRaw(gpt_model_select_value.value)), "gpt_model_options"], async (value) => {
if (value.code == 0) {
message.error(value.message);
return;
}
message.success("添加成功");
await InitGptOptions();
})
});
}
/**
* 添加一个GPT服务商,只是清楚当前的数据
*/
async function AddOption() {
gpt_select_value.value = {
label: null,
value: null
};
}
/**
* 添加一个GPT服务商,只是清楚当前的数据
*/
async function AddModelOption() {
gpt_model_select_value.value = {
label: null,
value: null
};
}
let ruleObj = (errorMessage) => {
return [{
required: true,
validator(rule, value) {
if (value == null || value == "")
return new Error(errorMessage);
return true;
},
trigger: ["input", "blur"]
}]
}
let rules = {
label: ruleObj("必填GPT名称"),
value: ruleObj("必填GPT请求网址"),
};
let modelRules = {
label: ruleObj("必填GPT名称"),
value: ruleObj("必填GPT请求网址"),
};
return {
gpt_model_options,
gpt_options,
gpt_select_value,
gpt_model_select_value,
gpt_select_key,
gpt_model_select_key,
SaveGptOption,
EditOption,
DeleteOption,
AddOption,
SaveGptModelOption,
rules,
modelRules,
formRef,
modelFormRef,
EditModelOption,
AddModelOption,
DeleteModelOption
}
}
})
</script>
<style scoped>
.card-tabs .n-tabs-nav--bar-type {
padding-left: 4px;
}
</style>
@@ -0,0 +1,298 @@
<template>
<div style="height: 50px; display: flex; align-items: center;"> <n-select placeholder="选择编辑或是删除的数据"
style="width: 300px;" v-model:value="customize_gpt_select" :options="customize_gpt_options"></n-select>
<n-button size="small" style="margin: 0 10px 0 10px" color="#e18a3b"
@click="EditCurrentSelectPrompt">编辑</n-button>
<n-button size="small" style="margin: 0 10px 0 0px" color="#779649" @click="AddGptPrompt">新增</n-button>
<n-button size="small" style="margin: 0 10px 0 0px" type="error" @click="DeleteGptPrompt">删除</n-button>
</div>
<div id="add-gpt-prompts" style="overflow-y: auto; padding-right: 20px;">
<n-form ref="promptRef" :model="prompt_data" :rules="rules">
<n-form-item label="提示词预设名字">
<n-input v-model:value="prompt_data.prompt_name" placeholder="输入预设名字"></n-input>
</n-form-item>
<n-form-item label="提示词角色预设">
<n-input type="textarea" style="height: 150px;" :autosize="true"
v-model:value="prompt_data.prompt_roles" placeholder="输入提示词角色预设"></n-input>
</n-form-item>
<div v-for="(example, index) in prompt_data.prompt_example">
<n-form-item :label="'案例输入 ' + (index + 1)">
<n-input type="textarea" style="height: 60px ;margin-right: 10px;" :autosize="true"
v-model:value="example.user_content" placeholder="输入案例输入"></n-input>
<n-button color="#e18a3b" @click="GenerateGptExampleOut(example, index)">生成输出</n-button>
<n-button tertiary type="error" style="font-size: 24px" @click="DeleteExampleOut(example, index)">
<n-icon>
<trash />
</n-icon>
</n-button>
</n-form-item>
<n-form-item :label="'案例输出 ' + (index + 1)">
<n-input type="textarea" style="height: 60px" :autosize="true"
v-model:value="example.assistant_content" placeholder="输入案例输出或是测试生成"></n-input>
</n-form-item>
</div>
</n-form>
</div>
<div style="float: right; margin: 30px;">
<n-button color="#e18a3b" @click="AddExamplePrompt">添加案例输出</n-button>
<n-checkbox style="margin: 0 15px 0 15px;" v-model:checked="save_example">是否保存案例</n-checkbox>
<n-button type="info" @click="SaveGptPrompt">保存</n-button>
<!-- Your content here -->
</div>
</template>
<script>
import { ref, toRaw, onMounted, defineComponent } from "vue"
import { NForm, NFormItem, NInput, NInputNumber, useDialog, NButton, useMessage, NSelect, NTabs, NTabPane, NCheckbox, NIcon } from "naive-ui"
import { cloneDeep, has, isEmpty } from "lodash";
import { Trash } from "@vicons/ionicons5"
export default defineComponent({
components: {
NForm, NFormItem, NInput, NInputNumber, NButton, NSelect, NTabs, NTabPane, NCheckbox, NIcon, Trash
},
props: ["height"],
setup(props) {
let message = useMessage();
let hh = props.height;
let save_example = ref(true);
let prompt_data = ref({
prompt_name: null,
prompt_roles: null,
prompt_example: []
})
// 获取的所有的数据
let customize_gpt_all_data = ref([]);
// 下拉的options
let customize_gpt_options = ref([]);
// 选择的数据
let customize_gpt_select = ref(null);
// 设置窗体的高度
function setHeight() {
let div = document.getElementById("add-gpt-prompts");
div.style.height = hh - 145 - 50 + "px";
}
// 初始化数据
async function InitData() {
await window.api.GetCustomizeGptPrompt("dynamic", (value) => {
if (value.code == 0) {
message.error(value.message);
return;
}
if (value.data.length == 0) {
message.error("请先添加自定义提示词数据");
return;
}
prompt_data.value = {
prompt_name: null,
prompt_roles: null,
prompt_example: []
}
customize_gpt_all_data.value = value.data;
customize_gpt_select.value = value.data[0].id;
customize_gpt_options.value = value.data.map((item) => {
return {
label: item.prompt_name,
value: item.id
}
})
})
}
onMounted(async () => {
setHeight();
await InitData();
})
/**
* 生成案例输出
* @param {*} input 输入的数据
* @param {*} index 输入的数据的索引
*/
async function GenerateGptExampleOut(input, index) {
console.log(input, index);
if (!input.user_content) {
message.error("请填写案例输入");
return;
}
await window.api.GenerateGptExampleOut(JSON.stringify(toRaw(prompt_data.value)), (value) => {
debugger;
console.log(value);
if (value.code == 0) {
message.error(value.message);
return;
}
prompt_data.value.prompt_example[index].assistant_content = value.data;
})
}
/**
* 添加提示词案例输出数据
*/
async function AddExamplePrompt() {
debugger;
let last_data = prompt_data.value.prompt_example[prompt_data.value.prompt_example.length - 1];
if (last_data != null) {
if (isEmpty(last_data.user_content)) {
message.error("上一个案例输入没有填写");
return;
}
if (isEmpty(last_data.assistant_content)) {
message.error("上一个案例输出没有生成或填写");
return;
}
}
prompt_data.value.prompt_example.push({
user_content: null,
assistant_content: null
})
}
// 删除案例输出
async function DeleteExampleOut(example, index) {
debugger;
if (index > prompt_data.value.prompt_example.length - 1) {
message.error("删除的案例不存在");
return;
}
prompt_data.value.prompt_example.splice(index, 1);
}
// 保存提示词
async function SaveGptPrompt() {
// 判断是不是要保存案例输出
let tmp_data = cloneDeep(prompt_data.value);
if (!save_example.value) {
tmp_data.prompt_example = [];
}
// 检查数据是不是存在
if (has(tmp_data, "prompt_name") && isEmpty(tmp_data.prompt_name)) {
message.error("请填写提示词预设名字");
return;
}
if (has(tmp_data, "prompt_roles") && isEmpty(tmp_data.prompt_roles)) {
message.error("请填写提示词角色预设");
return;
}
for (let i = 0; i < tmp_data.prompt_example.length; i++) {
if (isEmpty(tmp_data.prompt_example[i].user_content)) {
message.error("案例输入" + (i + 1) + "没有填写");
return;
}
if (isEmpty(tmp_data.prompt_example[i].assistant_content)) {
message.error("案例输出" + (i + 1) + "没有填写");
return;
}
}
// 保存逻辑
await window.api.SaveDynamicGptOption([JSON.stringify(tmp_data), "customize_gpt_prompt"], async (value) => {
if (value.code == 0) {
message.error(value.message);
return;
}
message.success("添加成功");
})
await InitData();
}
/**
* 编辑当前选中的提示词设置
*/
async function EditCurrentSelectPrompt() {
if (isEmpty(customize_gpt_select.value)) {
message.error("请选择要编辑的数据");
return;
}
let current_data = customize_gpt_all_data.value.find((item) => item.id == customize_gpt_select.value);
if (isEmpty(current_data)) {
message.error("当前选择的数据不存在");
return;
}
prompt_data.value = current_data;
}
/**
* 新增提示词
*/
async function AddGptPrompt() {
prompt_data.value = {
prompt_name: null,
prompt_roles: null,
prompt_example: [{
user_content: null,
assistant_content: null
}]
}
}
/**
* 删除选中的提示词
*/
async function DeleteGptPrompt() {
if (isEmpty(customize_gpt_select.value)) {
message.error("请选择要删除的数据");
return;
}
await window.api.DeleteDynamicGptOption([toRaw(customize_gpt_select.value), "customize_gpt_prompt"], async (value) => {
if (value.code == 0) {
message.error(value.message);
return;
}
message.success("删除成功");
})
await InitData();
}
return {
hh,
prompt_data,
save_example,
GenerateGptExampleOut,
AddExamplePrompt,
DeleteExampleOut,
SaveGptPrompt,
customize_gpt_options,
customize_gpt_select,
customize_gpt_all_data,
EditCurrentSelectPrompt,
AddGptPrompt,
DeleteGptPrompt
}
}
})
</script>
<style>
/* Customize the scrollbar */
::-webkit-scrollbar {
width: 5px;
}
/* Track */
::-webkit-scrollbar-track {
background: #f1f1f1;
}
/* Handle */
::-webkit-scrollbar-thumb {
background: #ccc;
}
/* Handle on hover */
::-webkit-scrollbar-thumb:hover {
background: #ccc;
}
</style>
@@ -0,0 +1,160 @@
<template>
<n-form ref="formRef" inline :label-width="100" :model="formValue" :size="large">
<n-form-item label="背景音乐文件夹信息" path="formValue.name">
<n-input style="width: 150px;" v-model:value="formValue.name" placeholder="输入名称" />
<n-input style="width: 500px;" v-model:value="formValue.folder_path" placeholder="输入文件夹地址" />
<n-button attr-type="button" type="primary" @click="SelectFolder">选择文件夹</n-button>
<n-button style="margin-left: 50px;" attr-type="button" type="info"
@click="AddBackgroundMusic">保存</n-button>
</n-form-item>
</n-form>
<n-data-table :columns="background_music_columns" :data="data" :pagination="pagination" :bordered="false" />
</template>
<script>
import { defineComponent, ref, h, onMounted, nextTick } from "vue";
import { NButton, useMessage, NDataTable, NForm, NFormItem, NInput } from "naive-ui"
const buttonArr = [
{
title: "删除",
key: "delete"
}
]
const createColumns = ({ Delete }) => {
return [
{
title: "名称",
key: "name"
},
{
title: "文件夹位置",
key: "folder_path"
},
{
title: "操作",
key: "options",
render(row) {
const btn = buttonArr.map(item => {
return h(
NButton, {
strong: true,
size: "small",
type: "error",
tertiary: true,
onClick: () => {
if (item.key == "edit") {
// Edit(row);
} else if (item.key == "delete") {
Delete(row);
}
}
}, {
default: () => item.title
}
)
});
return btn;
}
}
]
}
export default defineComponent({
components: {
NButton, NDataTable, NForm, NFormItem, NInput
},
setup() {
let data = ref([]);
let message = useMessage();
let formValue = ref({
name: "",
id: "",
folder_path: ""
})
async function SelectFolder() {
await window.api.selectFolder(null, (value) => {
if (value.length > 0) {
formValue.value.folder_path = value[0];
}
})
}
async function Delete(row) {
await window.api.deleteClipSetting(row.id, async (value) => {
if (value.code == 0) {
message.error(value.message);
return;
}
message.success("删除成功");
await GetBackgroundMusicConfigList();
})
}
async function GetBackgroundMusicConfigList() {
data.value = [];
await window.api.GetBackgroundMusicConfigList((value) => {
if (value.code == 0) {
message.error(value.message);
return;
}
data.value = value.value;
})
}
async function AddBackgroundMusic() {
if (formValue.value.name == "" || formValue.value.folder_path == "") {
message.error("请填入名称和文件夹位置");
return;
}
// 判断当前名称是不是存在
for (let i = 0; i < data.value.length; i++) {
const d = data.value[i];
if (d.name == formValue.value.name) {
message.error("名称已存在");
return;
}
}
//添加音乐文件夹
await window.api.AddBackgroundMusicFolder([formValue.value.name, formValue.value.folder_path], async (value) => {
if (value.code == 0) {
window.api.showGlobalMessageDialog(value);
return;
}
formValue.value.name = "";
formValue.value.folder_path = "";
await GetBackgroundMusicConfigList();
window.api.showGlobalMessageDialog(value);
});
}
onMounted(async () => {
await GetBackgroundMusicConfigList();
})
return {
bodyStyle: {
width: "600px"
},
segmented: {
content: "soft",
footer: "soft"
},
formValue,
data,
background_music_columns: createColumns({
Delete
}),
message,
AddBackgroundMusic,
SelectFolder
}
}
})
</script>
@@ -0,0 +1,185 @@
<template>
<div style="display: flex; height: 40px; line-height: 40px;">
<div style="margin-bottom: 10px; width: 100px;">选择打帧方式</div>
<n-select v-model:value="key_frame_data.key_frame" :options="key_frame_options" style="width: 140px;" />
<div style="width: 170px;">
<n-tooltip trigger="hover">
<template #trigger>
<n-checkbox style="padding-left: 20px;" v-model:checked="key_frame_data.isFixedSpeed">
匀速关键帧
</n-checkbox>
</template>
勾选这个图片的时长小于这个会匀速关键帧
</n-tooltip>
</div>
<span>关键帧时间</span>
<div>
<n-input-number :show-button="false" :step="0.1" :min="0" :max="10"
v-model:value="key_frame_data.key_frame_time" style="width: 100px;" />
</div>
<div>
<n-button style="margin-left: 30px;" type="info" @click="SaveKeyFrameSetting">保存</n-button>
</div>
</div>
<n-card style="margin-top: 10px;" title="上下关键帧设置">
<div style="display: flex; height: 40px; line-height: 40px;">
<div>
上关键帧位置
</div>
<n-input-number :show-button="false" v-model:value="key_frame_data.up_down_key_frame.start_position"
style="width: 100px; margin-right: 10px;" />
<div>
下关键帧位置
</div>
<n-input-number :show-button="false" v-model:value="key_frame_data.up_down_key_frame.end_position"
style="width: 100px; margin-right: 10px;"></n-input-number>
<div>
缩放大小
</div>
<n-input-number :show-button="false" v-model:value="key_frame_data.up_down_key_frame.default_scale"
style="width: 100px; margin-right: 10px;"></n-input-number>
</div>
</n-card>
<n-card title="左右关键帧设置" style="margin-top: 10px;">
<div style="display: flex; height: 40px; line-height: 40px;">
<div>
左关键帧位置
</div>
<n-input-number :show-button="false" v-model:value="key_frame_data.left_right_key_frame.start_position"
style="width: 100px; margin-right: 10px;" />
<div>
右关键帧位置
</div>
<n-input-number :show-button="false" v-model:value="key_frame_data.left_right_key_frame.end_position"
style="width: 100px; margin-right: 10px;"></n-input-number>
<div>
缩放大小
</div>
<n-input-number :show-button="false" v-model:value="key_frame_data.left_right_key_frame.default_scale"
style="width: 100px; margin-right: 10px;"></n-input-number>
</div>
</n-card>
<n-card title="缩放关键帧设置" style="margin-top: 10px;">
<div style="display: flex; height: 40px; line-height: 40px;">
<div>
开始缩放大小
</div>
<n-input-number :show-button="false" v-model:value="key_frame_data.scale_key_frame.start_position"
style="width: 100px; margin-right: 10px;" />
<div>
结束缩放大小
</div>
<n-input-number :show-button="false"
v-model:value="key_frame_data.scale_key_frame.end_position"></n-input-number>
</div>
</n-card>
</template>
<script>
import { defineComponent, ref, h, onMounted, toRaw } from "vue";
import { useMessage, NSelect, NCheckbox, NInputNumber, NCard, NButton, NTooltip } from "naive-ui"
export default defineComponent({
components: {
NSelect, NCheckbox, NInputNumber, NCard, NButton, NTooltip
},
setup() {
let message = useMessage();
let key_frame_options = ref([]);
let key_frame_data = ref({
key_frame: null,
isFixedSpeed: false,
key_frame_time: 4,
up_down_key_frame: {
default_scale: 100,
start_position: 0,
end_position: 0
},
left_right_key_frame: {
default_scale: 100,
start_position: 0,
end_position: 0
},
scale_key_frame: {
default_scale: 100,
start_position: 100,
end_position: 100
}
});
onMounted(async () => {
await window.api.GetKeyFrameConfigData((value) => {
if (value.code == 0) {
message.error("获取关键帧配置失败");
return;
}
if (value.data) {
key_frame_data.value = value.data;
}
})
await window.api.GetKeyFrameOptions((value) => {
if (value) {
key_frame_options.value = value.data;
} else {
message.error("获取关键帧操作列表失败");
}
})
})
/**
* 关键帧设置保存
*/
async function SaveKeyFrameSetting() {
console.log(key_frame_data.value);
// 简单的判断
if (key_frame_data.value.key_frame == "") {
message.error("请选择关键帧方式");
return;
}
if (key_frame_data.value.key_frame && key_frame_data.value.key_frame == "KFTypePositionY") {
if (key_frame_data.value.up_down_key_frame.start_position == null || key_frame_data.value.up_down_key_frame.end_position == null) {
message.error("请输入上下关键帧位置");
return;
}
}
if (key_frame_data.value.key_frame && key_frame_data.value.key_frame == "KFTypePositionX") {
if (key_frame_data.value.left_right_key_frame.start_position == null || key_frame_data.value.left_right_key_frame.end_position == null) {
message.error("请输入左右关键帧位置");
return;
}
}
if (key_frame_data.value.key_frame && key_frame_data.value.key_frame == "KFTypeScale") {
if (key_frame_data.value.scale_key_frame.start_position == null || key_frame_data.value.scale_key_frame.end_position == null) {
message.error("请输入缩放关键帧位置");
return;
}
}
await window.api.SaveKeyFrameSetting(JSON.stringify(toRaw(key_frame_data.value)), (value) => {
if (value.code == 0) {
message.error(value.message);
return;
}
message.success("保存成功");
})
}
return {
key_frame_data,
key_frame_options,
SaveKeyFrameSetting
}
}
})
</script>
@@ -0,0 +1,347 @@
<template>
<n-button type="info" round @click="OpenDiscordWindow">打开登录MJ</n-button>
<n-button type="info" round @click="GetChannelRobots" style="margin-left: 10px;">获取频道机器人</n-button>
<n-button type="info" round @click="SaveMjSetting" style="margin-left: 10px;">保存MJ配置</n-button>
<n-divider />
<n-form ref="formRef" label-placement="top" :model="mjSetting" :rules="rules">
<div style="display: flex;">
<n-form-item label="服务器ID" style="width: 400px;" path="serviceID">
<n-input v-model:value="mjSetting.serviceID" placeholder="登录MJ后切换服务器自动获取" />
</n-form-item>
<n-form-item label="频道ID" style="width: 400px; margin-left: 10px" path="channelID">
<n-input v-model:value="mjSetting.channelID" placeholder="登录MJ后切换频道自动获取" />
</n-form-item>
</div>
<div style="display: flex;">
<n-form-item label="MJ机器ID" path="mj.botId">
<n-input v-model:value="mjSetting.mj.botId" placeholder="MJ机器ID" />
</n-form-item>
<n-form-item label="MJ命令ID" style="margin-left: 10px" path="mj.commandId">
<n-input v-model:value="mjSetting.mj.commandId" placeholder="MJ命令ID" />
</n-form-item>
<n-form-item label="MJ命令名称" style="margin-left: 10px" path="mj.commandName">
<n-input v-model:value="mjSetting.mj.commandName" placeholder="MJ命令名称" />
</n-form-item>
<n-form-item label="MJ版本ID" style="margin-left: 10px" path="mj.versionId">
<n-input v-model:value="mjSetting.mj.versionId" placeholder="MJ版本ID" />
</n-form-item>
</div>
<div style="display: flex;">
<n-form-item label="NIJI机器ID" path="niji.botId">
<n-input v-model:value="mjSetting.niji.botId" placeholder="NIJI机器ID" />
</n-form-item>
<n-form-item label="NIJI命令ID" style="margin-left: 10px" path="niji.commandId">
<n-input v-model:value="mjSetting.niji.commandId" placeholder="NIJI命令ID" />
</n-form-item>
<n-form-item label="NIJI命令名称" style="margin-left: 10px" path="niji.commandName">
<n-input v-model:value="mjSetting.niji.commandName" placeholder="NIJI命令名称" />
</n-form-item>
<n-form-item label="NIJI版本ID" style="margin-left: 10px" path="niji.versionId">
<n-input v-model:value="mjSetting.niji.versionId" placeholder="NIJI版本ID" />
</n-form-item>
</div>
<n-form-item label="用户token" style="width: 810px;" path="token">
<n-input v-model:value="mjSetting.token" placeholder="登录MJ后切换服务器自动获取" />
</n-form-item>
<n-form-item label="用户Agent" style="width: 810px;" path="userAgent.userAgent">
<n-input v-model:value="mjSetting.userAgent.userAgent" placeholder="登录MJ后切换服务器自动获取(可自定义)"
style="margin-right: 10px;" />
<n-tooltip trigger="hover" style="background-color: aliceblue; color: black;">
<template #trigger>
<n-checkbox v-model:checked="mjSetting.userAgent.customize" style="width: 100px;">
自定义
</n-checkbox>
</template>
开启自定义需要手动填写userAgent可以填入浏览器的userAgent
</n-tooltip>
</n-form-item>
<div style="display: flex; align-items: center;">
<n-form-item label="选择生图机器人" path="select_robot" style="width: 200px;">
<n-select :options="select_robot_options" v-model:value="mjSetting.select_robot"></n-select>
</n-form-item>
<n-button color="#e18a3b" style="margin-left : 3px" @click="RefreshRobotOptions">
<n-icon size="large">
<reload></reload>
</n-icon>
</n-button>
</div>
</n-form>
</template>
<script>
import { defineComponent, ref, h, onMounted, nextTick, watch, toRaw } from "vue";
import { NButton, useMessage, NDataTable, NForm, NFormItem, NInput, NDivider, NCheckbox, NSelect, NTooltip, NIcon } from "naive-ui"
import { DEFINE_STRING } from "../../../../define/define_string";
import { Reload } from "@vicons/ionicons5"
import { isEmpty } from "lodash";
export default defineComponent({
components: {
NButton, NDataTable, NForm, NFormItem, NInput, NDivider, NCheckbox, NSelect, NTooltip, NIcon, Reload
},
setup() {
let message = useMessage();
let formRef = ref(null);
let mjSetting = ref({
serviceID: null,
channelID: null,
mj: {
botId: null,
commandId: null,
commandName: null,
versionId: null
},
niji: {
botId: null,
commandId: null,
commandName: null,
versionId: null
},
token: null,
userAgent: {
userAgent: null,
customize: false
},
select_robot: null,
});
function checkNull(obj) {
for (let key in obj) {
if (obj[key] == undefined) {
return false;
}
}
return true;
}
function modifyOption(value) {
// 遍历mjSetting.mj,判断里面的属性数据是不是都存在,如果存在则添加到 select_robot_options 中
select_robot_options.value = [];
let isObj = checkNull(value.mj);
if (isObj) {
select_robot_options.value.push({
label: "MJ",
value: value.mj.botId
})
}
isObj = checkNull(value.niji);
if (isObj) {
select_robot_options.value.push({
label: "NIJI",
value: value.niji.botId
})
}
// 判断当前的select_robot是不是在select_robot_options中,如果不在则设置为null
if (select_robot_options.value.findIndex((item) => { return item.value == mjSetting.value.select_robot }) == -1)
mjSetting.value.select_robot = null;
}
// 监听数据改变,生成下拉的options
watch(mjSetting.value, (value) => {
mjSetting.value = value;
// 计算下拉的options
modifyOption(value)
})
let select_robot_options = ref([])
// 初始化现在设置的数据
async function InitData() {
// 获取当前mj配置信息
await window.api.GetDefineConfigJsonByProperty(JSON.stringify(["img_base", "mj_config", false, null]), (value) => {
debugger
console.log(value);
if (value.code == 0) {
message.error(value.message);
return;
}
// 判断是不是有数据
if (value.data) {
mjSetting.value = value.data;
modifyOption(value.data);
}
})
}
onMounted(async () => {
// 监听修改当前界面的数据,后续可以将数据保存到本地配置
window.api.setEventListen([DEFINE_STRING.DISCORD.OPERATE_REFRASH_DISCORD_URL], (value) => {
if (value.code == 0) {
message.error(value.message);
return;
}
// 保存数据
console.log(value);
if (value.type == DEFINE_STRING.DISCORD_SIMPLE_DATA_TYPE.URL) {
mjSetting.value.serviceID = value.data.serviceID;
mjSetting.value.channelID = value.data.channelID;
} else if (value.type == DEFINE_STRING.DISCORD_SIMPLE_DATA_TYPE.TOKEN) {
mjSetting.value.token = value.data.authorization;
if (!mjSetting.value.userAgent.customize) {
mjSetting.value.userAgent.userAgent = value.data.userAgent;
}
} else {
message.error("未知的数据类型");
}
})
await InitData();
})
/**
* 打开discord窗口
*/
async function OpenDiscordWindow() {
await window.api.OpenDiscordWindow((value) => {
if (value.code == 0) {
message.error(value.message);
return;
}
});
}
/**
* 获取频道机器人
*/
async function GetChannelRobots() {
// 判断是不是有数据(服务器ID,频道ID,用户token,用户Agent
if (isEmpty(mjSetting.value.serviceID) &&
isEmpty(mjSetting.value.channelID) &&
isEmpty(mjSetting.value.token) &&
isEmpty(mjSetting.value.userAgent.userAgent)) {
message.error("请先填写或获取完整的数据(服务器ID,频道ID,用户token,用户Agent)");
return;
}
const headers = {
'authorization': mjSetting.value.token
}
await fetch(`https://discord.com/api/v9/guilds/${mjSetting.value.serviceID}/application-command-index`, {
method: 'GET',
headers: headers,
})
.then(response => response.json())
.then(data => {
debugger
// 修改配置信息(判断是不是niji或者是mj)
// 填充mjniji数据
if (data.applications && data.application_commands) {
let mj_inedx = data.applications.findIndex((item) => {
return item.name.toLowerCase().startsWith("midjourney");
})
if (mj.index != -1) {
mjSetting.value.mj.botId = data.applications[mj_inedx].id;
let f = data.application_commands.find((a) => { return a.name == "imagine" && a.application_id == data.applications[mj_inedx].id })
mjSetting.value.mj.commandId = f?.id;
mjSetting.value.mj.commandName = "imagine";
mjSetting.value.mj.versionId = f?.version;
}
let niji_inedx = data.applications.findIndex((item) => {
return item.name.toLowerCase().startsWith("niji");
})
if (mj.index != -1) {
mjSetting.value.niji.botId = data.applications[niji_inedx].id;
let f = data.application_commands.find((a) => { return a.name == "imagine" && a.application_id == data.applications[niji_inedx].id })
mjSetting.value.niji.commandId = f?.id;
mjSetting.value.niji.commandName = "imagine";
mjSetting.value.niji.versionId = f?.version;
}
}
})
.catch(error => {
window.api.showGlobalNotificationDialog({
code: 0,
message: "获取频道机器人请求失败,错误信息如下: " + error.message.toString()
})
return
})
}
/**
* 保存MJ配置
*/
async function SaveMjSetting(e) {
e.preventDefault();
formRef.value?.validate(async (errors) => {
if (errors) {
message.error("请检查必填字段");
return
}
await window.api.SaveDefineConfigJsonByProperty(JSON.stringify(["img_base", "mj_config", toRaw(mjSetting.value), false]), (value) => {
debugger
console.log(value);
if (value.code == 0) {
message.error(value.message);
return;
}
message.success(value.message);
})
});
}
/**
* 刷新数据
*/
async function RefreshRobotOptions() {
modifyOption(mjSetting.value);
}
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 = {
"serviceID": ruleObj("必填人物名称"),
"channelID": ruleObj("必填英文提示词"),
"mj.botId": ruleObj("必填英文提示词"),
"mj.commandId": ruleObj("必填英文提示词"),
"mj.commandName": ruleObj("必填英文提示词"),
"mj.versionId": ruleObj("必填英文提示词"),
"niji.botId": ruleObj("必填英文提示词"),
"niji.commandId": ruleObj("必填英文提示词"),
"niji.commandName": ruleObj("必填英文提示词"),
"niji.versionId": ruleObj("必填英文提示词"),
"token": ruleObj("必填英文提示词"),
"userAgent.userAgent": ruleObj("必填英文提示词"),
"select_robot": ruleObj("必填英文提示词"),
};
return {
OpenDiscordWindow,
mjSetting,
select_robot_options,
GetChannelRobots,
SaveMjSetting,
RefreshRobotOptions,
rules,
formRef
}
}
})
</script>
@@ -0,0 +1,132 @@
<template>
<n-tabs type="line" animated size="large">
<n-tab-pane name="normal setting" tab="基础设置">
<n-form ref="formRef" :label-width="80" :model="formValue">
<n-form-item label="SD请求地址" path="webui_api_url">
<n-input v-model:value="formValue.webui_api_url" placeholder="输入SD地址" />
</n-form-item>
<n-form-item label="出图方式(文生图、图生图)" path="type">
<n-select v-model:value="formValue.type" :options="modelOption" placeholder="输入采样方法" />
</n-form-item>
<n-form-item label="正向提示词(和推导出来的tag进行拼接)" path="prompt">
<n-input v-model:value="formValue.prompt" placeholder="输入正向提示词" />
</n-form-item>
<n-form-item label="反向提示词" path="negative_prompt">
<n-input v-model:value="formValue.negative_prompt" placeholder="输入反向提示词" />
</n-form-item>
<div style="display: flex">
<n-form-item label="CFG Scale" style="margin-right: 30px;">
<n-input-number :min="0" :max="30" :step="0.05" v-model:value="formValue.cfg_scale"
placeholder="输入重绘幅度" />
</n-form-item>
<n-form-item label="重绘幅度" path="denoising_strength">
<n-input-number :precision="2" :step="0.05" v-model:value="formValue.denoising_strength"
placeholder="输入重绘幅度" />
<n-checkbox style="margin-left: 30px;" size="large" v-model:checked="formValue.adetailer"
label="是否开启修脸/修手" />
</n-form-item>
</div>
<n-form-item label="采样方式" path="sampler_name">
<n-input v-model:value="formValue.sampler_name" placeholder="输入模型" />
</n-form-item>
<div style="display: flex;">
<n-form-item label="迭代步数" path="steps">
<n-input-number style="width: 150px;" v-model:value="formValue.steps" placeholder="输入迭代步数" />
</n-form-item>
<n-form-item label="风格词权重" path="style_weight" style="margin-left: 10px;">
<n-input-number :min="1" :max="4" :step="0.05" :precision="2" style="width: 150px;"
v-model:value="formValue.style_weight" placeholder="输入风格词权重" />
</n-form-item>
</div>
<n-form-item label="图片分辨率" path="resolution">
<n-input-number :show-button="false" v-model:value="formValue.width" placeholder="输入迭代步数" />
<span style="margin: 0 10px;"> * </span>
<n-input-number :show-button="false" v-model:value="formValue.height" placeholder="输入迭代步数" />
</n-form-item>
<n-form-item>
<n-button attr-type="button" type="primary" @click="SaveSDConfig">
保存设置
</n-button>
</n-form-item>
</n-form>
</n-tab-pane>
<n-tab-pane name="ADetailer setting" tab="ADetailer 设置">
<SDADetailerSetting />
</n-tab-pane>
</n-tabs>
</template>
<script>
import { defineComponent, ref, h, onMounted, toRaw } from "vue";
import { NForm, NFormItem, NInput, NButton, useMessage, NInputNumber, NSelect, NTabs, NTabPane, NCheckbox } from "naive-ui"
import SDADetailerSetting from "../Components/SDADetailerSetting.vue"
export default defineComponent({
components: {
NForm, NFormItem, NInput, NButton, NInputNumber, NSelect, NTabs, NTabPane, SDADetailerSetting, NCheckbox
},
setup() {
let formValue = ref({
webui_api_url: window.config.webui_api_url,
type: null,
prompt: null,
negative_prompt: null,
denoising_strength: 1,
sampler_name: null,
steps: 15,
width: 0,
height: 0,
adetailer: false,
style_weight: 1,
cfg_scale: 1
})
let modelOption = ref([{
label: "文生图",
value: "txt2img"
}, {
label: "图生图",
value: "img2img"
}]);
onMounted(async () => {
await window.api.InitSDConfig((value) => {
if (value.code == 0) {
message.error(value.message);
return;
}
formValue.value = value.data;
})
})
let message = useMessage();
/**
* 保存SD设置
*/
async function SaveSDConfig() {
// 保存SD配置
await window.api.SaveSDConfig(toRaw(formValue.value), (value) => {
if (value.code == 0) {
window.api.showGlobalMessageDialog(value);
return;
} else if (value.code == 1) {
window.api.showGlobalMessageDialog(value);
window.api.getSettingDafultData((value) => {
window.config = value;
})
return
} else {
window.api.showGlobalMessageDialog({ code: 0, message: "未知错误" });
}
})
}
return {
formValue,
SaveSDConfig,
modelOption,
}
}
})
</script>
@@ -0,0 +1,379 @@
<template>
<NSpace vertical>
<n-spin :show="show">
<template #description>
保存中
</template>
<n-form ref="formRef" :model="formValue" :rules="rules">
<n-form-item path="draft_path" label="剪映草稿地址">
<n-input v-model:value="formValue.draft_path" @keydown.enter.prevent />
</n-form-item>
<n-form-item path="project_path" label="项目地址(帧,图片等输出的地址)">
<div>
<n-input style="width: 400px;" v-model:value="formValue.project_path" @keydown.enter.prevent />
</div>
<n-button color="#e5a84b" @click="SelectProjectFolder" style="margin-left: 10px;">
<n-icon :size="20">
<folder-open />
</n-icon>
</n-button>
</n-form-item>
<n-form-item path="project_name" label="项目名称">
<n-input v-model:value="formValue.project_name" @keydown.enter.prevent />
</n-form-item>
<n-form-item path="system_setting" label="系统设置">
<n-form-item path="task_number" label="后台并行任务数(量力而行)">
<n-input-number v-model:value="formValue.task_number" @keydown.enter.prevent :min="1" :max="10"
placeholder="输入后台并行任务数" />
</n-form-item>
<n-form-item label="系统主题" style="margin-left: 20px;">
<n-switch :rail-style="railStyle" v-model:value="formValue.theme" size="medium"
checked-value="dark" unchecked-value="light" @update:value="ChangeMode">
<template #checked>
</template>
<template #unchecked>
</template>
</n-switch>
</n-form-item>
<n-form-item label="人物场景选择模式" style="margin-left: 20px;">
<n-select size="small" placeholder="请选择" :options="character_select_model_options"
v-model:value="formValue.character_select_model" style="width: 120px;" />
</n-form-item>
</n-form-item>
<n-form-item path="gpt_setting" label="GPT设置">
<n-form-item path="gpt_business" label="GPT接口服务商">
<n-select v-model:value="formValue.gpt_business" :options="gpt_options" placeholder="GPT接口服务商"
style="width: 200px;" />
</n-form-item>
<n-button type="info" @click="openGptBuyUrl">
购买
</n-button>
<n-form-item style="width: 300px; margin-left: 30px;" path="gpt_key" label="GPT Key">
<n-input type="password" show-password-on="mousedown" v-model:value="formValue.gpt_key"
@keydown.enter.prevent placeholder="GPT Key" />
</n-form-item>
<n-form-item style="margin-left: 30px;" path="gpt_model" label="GPT模型">
<n-select v-model:value="formValue.gpt_model" :options="gpt_model_options" placeholder="GPT模型"
style="width: 160px;" />
</n-form-item>
<n-form-item style="margin-left: 30px;" path="gpt_count" label="推理上下文行数">
<n-input-number v-model:value="formValue.gpt_count" placeholder="GPT模型" style="width: 150px;"
:show-button="false" :min="1" :max="100" />
</n-form-item>
<n-button color="#e18a3b" @click="addGptOption" style="margin-left: 10px;">
添加GPT
</n-button>
<n-button color="#779649" @click="TestGPTConnection" :loading="loading" style="margin-left: 10px;">
测试链接
</n-button>
<n-button color="#ba5b49" @click="AddGptPrompt" style="margin-left: 10px;">
添加GPT提示词
</n-button>
</n-form-item>
<n-form-item path="translation_setting" label="翻译设置">
<n-form-item path="translation_business" label="翻译服务商">
<n-select v-model:value="formValue.translation_business" :options="translation_options"
@update:value="SwitchTranslate" placeholder="翻译服务商" style="width: 200px;" />
</n-form-item>
<n-form-item path="translation_app_id" label="APP ID" style="width: 300px; margin-left: 30px;">
<n-input type="password" show-password-on="mousedown"
v-model:value="formValue.translation_app_id" @keydown.enter.prevent placeholder="APP ID" />
</n-form-item>
<n-form-item path="translation_secret" label="产品密钥" style="width: 300px; margin-left: 30px;">
<n-input type="password" show-password-on="mousedown"
v-model:value="formValue.translation_secret" placeholder="请输入产品密钥"></n-input>
</n-form-item>
<n-form-item path="translation_secret" label="自动翻译" style="width: 300px; margin-left: 30px;">
<n-checkbox v-model:checked="formValue.translation_auto" placeholder="请输入产品密钥"></n-checkbox>
</n-form-item>
</n-form-item>
<n-form-item>
<n-button
:disabled="formValue.draft_path == null || formValue.draft_path == '' || formValue.project_path == null || formValue.project_path == ''"
round type="primary" @click="handleValidateButtonClick">
保存
</n-button>
</n-form-item>
</n-form>
</n-spin>
</NSpace>
</template>
<script>
import { ref, toRaw, onMounted, defineComponent, h } from "vue"
import { NForm, NFormItem, NInput, NInputNumber, useDialog, NButton, NSpace, NSpin, useMessage, NSelect, NCheckbox, NSwitch, NIcon } from "naive-ui"
import AddGptOption from "./Components/AddGptOption.vue";
import { FolderOpenSharp as FolderOpen } from '@vicons/ionicons5'
import AddGptPrompts from './Components/AddGptPrompts.vue'
export default defineComponent({
components: {
NForm, NFormItem, NInput, NInputNumber, NButton, NSpace, NSpin, NSelect, NCheckbox, NSwitch, FolderOpen, NIcon
},
setup() {
let formRef = ref(null);
let message = useMessage()
let formValue = ref({
draft_path: window.config.draft_path,
project_path: window.config.project_path,
project_name: window.config.project_name,
gpt_business: window.config.gpt_business,
gpt_key: window.config.gpt_key,
gpt_model: window.config.gpt_model,
gpt_count: window.config.gpt_count,
task_number: window.config.task_number,
translation_business: window.config.translation_business,
translation_app_id: window.config.translation_app_id,
translation_secret: window.config.translation_secret,
translation_auto: window.config.translation_auto,
theme: window.config.theme,
character_select_model: window.config.character_select_model
});
let show = ref(false)
let gpt_options = ref([]);
let gpt_model_options = ref([]);
let character_select_model_options = ref([])
let dialog = useDialog();
let loading = ref(false);
/**
* 加载GPT的配置信息
*/
async function InitGptOptions() {
debugger;
await window.api.getGptBusinessOption("all", (value) => {
if (value.code == 0) {
message.error(value.message);
return;
}
gpt_options.value = value.data;
})
await window.api.getGptModelOption("all", (value) => {
if (value.code == 0) {
message.error(value.message);
return;
}
gpt_model_options.value = value.data;
})
await window.mj.GetTagSelectModel(value => {
character_select_model_options.value = value.data;
})
}
onMounted(async () => {
await InitGptOptions();
})
let translation_options = [{
label: "百度翻译",
value: "https://fanyi-api.baidu.com/api/trans/vip/translate"
}, {
label: "火山引擎",
value: "https://translate.volcengineapi.com?"
},
{
label: "腾讯翻译",
value: "https://tmt.tencentcloudapi.com"
},
{
label: "阿里翻译",
value: "https://mt.cn-hangzhou.aliyuncs.com"
}
];
// 切换翻译服务商事件
function SwitchTranslate(value, option) {
// 将之前的数据全部删除
formValue.value.translation_app_id = null;
formValue.value.translation_secret = null;
}
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 = {
draft_path: ruleObj("必填剪映草稿地址"),
project_path: ruleObj("必填项目地址"),
project_name: ruleObj("必填项目名称"),
task_number: ruleObj("必填后台并行任务数"),
gpt_business: ruleObj("必填GPT接口服务商"),
gpt_key: ruleObj("必填GPT Key"),
gpt_model: ruleObj("必填GPT模型"),
gpt_count: ruleObj("必填自动推理上下文行数"),
translation_secret: ruleObj("必填产品密钥"),
translation_app_id: ruleObj("必填APP ID"),
translation_business: ruleObj("必填翻译服务商"),
};
/**
* 保存配置信息
*/
function handleValidateButtonClick(e) {
e.preventDefault();
formRef.value?.validate((errors) => {
if (errors) {
message.error("请检查必填字段");
return
}
// 保存文件
window.api.ModifySampleSetting(JSON.stringify(toRaw(formValue.value)), (value) => {
if (value.code == 1) {
window.api.getSettingDafultData((value) => {
window.config = value;
})
window.api.showGlobalMessageDialog({ code: 1, message: value.message })
} else {
window.api.showGlobalMessageDialog({ code: 0, message: value.message })
}
})
});
}
let railStyle = (focused,
checked) => {
const style = {};
if (checked) {
style.background = "#f3a694";
if (focused) {
style.boxShadow = "0 0 0 2px #f3a694";
}
} else {
style.background = "#775039";
if (focused) {
style.boxShadow = "0 0 0 2px #775039";
}
}
return style;
}
/**
* 打开购买GPT的地址
*/
async function openGptBuyUrl() {
window.api.openGptBuyUrl(toRaw(formValue.value.gpt_business));
}
/**
* 切换主题
* @param {*} value 主题名称
*/
async function ChangeMode(value) {
debugger
console.log(value)
const isDarkMode = await window.darkMode.toggle(value)
}
/**
* 添加GPT地址或者模型
*/
async function addGptOption() {
// 判断当前数据是不是存在
// 处理数据。获取当前的所有的数据
let dialogWidth = 600;
let dialogHeight = 450;
// ImportWordAndSrt
dialog.create({
showIcon: false,
closeOnEsc: false,
title: "添加GPT地址或者模型",
content: () => h(AddGptOption, {}),
style: `width : ${dialogWidth}px; min-height : ${dialogHeight}px`,
maskClosable: false,
onClose: async () => {
// 刷新界面数据
await InitGptOptions();
}
})
}
/**
* 测试当前GPT是不是可以链接成功
*/
async function TestGPTConnection() {
loading.value = true;
// 测试链接
await window.api.TestGPTConnection(JSON.stringify(formValue.value), (value) => {
loading.value = false;
if (value.code == 0) {
window.api.showGlobalMessageDialog({ code: 0, message: "GPT链接失败,错误信息: " + value.message })
return;
}
window.api.showGlobalMessageDialog({ code: 1, message: "gpt链接测试成功,可以正常使用!" })
})
}
/**
* 选择项目文件夹
*/
async function SelectProjectFolder() {
await window.api.selectFolder({ default_paht: toRaw(formValue.value).project_path }, (value) => {
if (value.length > 0) {
formValue.value.project_path = value[0]
}
})
}
/**
* 添加GPT提示词预设
*/
async function AddGptPrompt() {
// 判断当前数据是不是存在
// 处理数据。获取当前的所有的数据
let dialogWidth = 600;
let dialogHeight = window.innerHeight * 0.9;
// ImportWordAndSrt
dialog.create({
showIcon: false,
closeOnEsc: false,
title: "添加GPT提示词预设",
content: () => h(AddGptPrompts, { height: dialogHeight }),
style: `width : ${dialogWidth}px; height : ${dialogHeight}px; padding-right : 3px `,
maskClosable: false,
onClose: () => {
}
})
}
return {
formRef,
ChangeMode,
openGptBuyUrl,
railStyle,
handleValidateButtonClick,
rules,
SwitchTranslate,
translation_options,
gpt_model_options,
gpt_options,
show,
formValue,
addGptOption,
TestGPTConnection,
loading,
SelectProjectFolder,
AddGptPrompt,
character_select_model_options,
}
}
})
</script>
@@ -0,0 +1,520 @@
<template>
<div style="color: red;">注意<br>所有的配置都是基于1440 * 1080会根据分辨率自动计算匹配</div>
<n-tabs type="line" animated default-value="GeneralSettings">
<n-tab-pane name="GeneralSettings" tab="通用设置">
<n-form ref="formRef" :model="generalSetting" :rules="rules" label-placement="left" label-width="auto"
require-mark-placement="right-hanging" :size="size">
<n-form-item label="视频分辨率" path="video_resolution">
<n-input-number :show-button="false" v-model:value="generalSetting.video_resolution_x"
placeholder="视频X" />
<div style="font-size: 30px; margin: 0 20px;">*</div>
<n-input-number :show-button="false" v-model:value="generalSetting.video_resolution_y"
placeholder="视频Y" />
</n-form-item>
<n-tooltip trigger="hover" placement="top-start" style="background-color: aliceblue;" :show-arrow="false">
<template #trigger>
<n-form-item label="图片关键帧" path="keyframe">
<n-select style="width: 150px; margin-right: 10px;"
v-model:value="generalSetting.keyframe.name" :options="keyframeOptions"></n-select>
<div style="margin-left: 10px;">上下</div>
<n-input-number :show-button="false" style="width: 100px;"
v-model:value="generalSetting.keyframe.up_down" placeholder="上下偏移量" />
<div style="margin-left: 10px;">左右</div>
<n-input-number :show-button="false" style="width: 100px;"
v-model:value="generalSetting.keyframe.left_right" placeholder="上下偏移量" />
<!-- <n-tooltip trigger="hover">
<template #trigger>
<div style="display: flex; margin-left: 10px;">
<div style="margin-left: 10px; margin: auto;">大小(0-4)</div>
<n-input-number :min="0" :max="4" :step="0.1" :show-button="false" style="width: 100px;"
v-model:value="generalSetting.keyframe.scale" placeholder="放大缩小倍率" />
</div>
</template>
注意放大倍率会先将图片全部铺满然后在进行放大防止出现黑边
</n-tooltip> -->
</n-form-item>
</template>
<div style="color: red;">设置的值越大图片移动的速度越快若是图片的尺寸不够也会被放的越大</div>
</n-tooltip>
<n-form-item label="帧率" path="frameRate">
<n-input-number v-model:value="generalSetting.frameRate" placeholder="输入帧率" />
</n-form-item>
<n-form-item label="码率" path="bitRate">
<n-input-number v-model:value="generalSetting.bitRate" placeholder="输入视频码率" />
</n-form-item>
<n-form-item label="配音声量大小" path="audioSoundSize">
<n-input-number v-model:value="generalSetting.audioSoundSize" placeholder="配音声量大小" />
</n-form-item>
<n-form-item label="背景音乐声量大小" path="backgroundMusicSoundSize">
<n-input-number v-model:value="generalSetting.backgroundMusicSoundSize" placeholder="背景音乐声量大小" />
</n-form-item>
<div style="display: flex;">
<n-button round type="primary" @click="SaveGeneralSetting">
保存
</n-button>
</div>
</n-form>
</n-tab-pane>
<n-tab-pane name="AssSetting" tab="字幕设置">
<div style="color: red;"> 注意 当前有些字体读取不能准确获取可以在后面的下面的编辑列表中进行简单的修改</div>
<!-- 字体 字号 颜色 透明度 位置 -->
<n-form :model="assSetting" style="display: flex; flex-wrap: wrap;">
<n-form-item style=" margin-right: 20px;" label="字体" path="fontName">
<n-select style="width: 300px;" v-model:value="assSetting.fontName" :options="fontNameOptions"
filterable />
<n-button secondary color="#8a2be2" @click="GetSystemInstallFontName"> 更新字体列表 </n-button>
</n-form-item>
<n-form-item style=" margin-right: 20px;" label="字号(80差不多)">
<n-input-number style="width: 200px;" v-model:value="assSetting.fontSize" />
</n-form-item>
<n-form-item style=" margin-right: 20px;" label="颜色">
<n-color-picker style="width: 200px;" v-model:value="assSetting.fontColor" :show-alpha="false" />
</n-form-item>
<n-form-item style=" margin-right: 20px;" label="透明度(100不透明)">
<n-input-number style="width: 200px;" v-model:value="assSetting.transparent" />
</n-form-item>
<n-form-item style="margin-right: 20px;" label="位置">
<n-input-number style="width: 60px; margin-right: 10px;" v-model:value="assSetting.positionX"
placeholder="X" :show-button="false" />
<n-input-number style="width: 60px;" v-model:value="assSetting.positionY" placeholder="Y"
:show-button="false" />
</n-form-item>
</n-form>
<div>
<n-button type="info" @click="SaveAssConfig"> 保存信息 </n-button>
</div>
<n-data-table :columns="assSettingColumns" :data="generalSetting.assConfig" :pagination="false"
:bordered="false" />
</n-tab-pane>
<n-tab-pane name="WaterMarkSetting" tab="水印设置">
<div style="color: red;"> 注意 当前有些字体读取不能准确获取可以在后面的下面的编辑列表中进行简单的修改</div>
<!-- 字体 字号 颜色 透明度 位置 -->
<n-form :model="wmSetting" style="display: flex; flex-wrap: wrap;">
<n-form-item style=" margin-right: 20px;" label="水印字体(要换行输入\N" path="fontName">
<n-input style="width: 400px;" v-model:value="wmSetting.showText" />
</n-form-item>
<n-form-item style=" margin-right: 20px;" label="字体" path="fontName">
<n-select style="width: 300px;" v-model:value="wmSetting.fontName" :options="fontNameOptions"
filterable />
<n-button secondary color="#8a2be2" @click="GetSystemInstallFontName"> 更新字体列表 </n-button>
</n-form-item>
<n-form-item style=" margin-right: 20px;" label="字号(30差不多)">
<n-input-number style="width: 200px;" v-model:value="wmSetting.fontSize" />
</n-form-item>
<n-form-item style=" margin-right: 20px;" label="颜色">
<n-color-picker style="width: 200px;" v-model:value="wmSetting.fontColor" :show-alpha="false" />
</n-form-item>
<n-form-item style=" margin-right: 20px;" label="透明度(100不透明)">
<n-input-number style="width: 200px;" v-model:value="wmSetting.transparent" />
</n-form-item>
<n-form-item style="margin-right: 20px;" label="位置(XY">
<n-input-number style="width: 60px; margin-right: 10px;" v-model:value="wmSetting.positionX"
placeholder="X" :show-button="false" />
<n-input-number style="width: 60px;" v-model:value="wmSetting.positionY" placeholder="Y"
:show-button="false" />
</n-form-item>
</n-form>
<div>
<n-button type="info" @click="SaveWaterMarkConfig"> 保存信息 </n-button>
</div>
<n-data-table :columns="wmColumns" :data="generalSetting.watermarkConfig" :pagination="false"
:bordered="false" />
</n-tab-pane>
<n-tab-pane name="backgroundSetting" tab="背景音乐设置">
<div style="margin:10px 0;">
注意
<div style="color: red;">设置背景乐对应的文件夹,选择对应的分类,可以随机匹配音乐</div>
</div>
<BackgroundMusic></BackgroundMusic>
</n-tab-pane>
</n-tabs>
</template>
<script>
import { defineComponent, ref, h, onMounted, toRaw } from "vue";
import { NForm, NFormItem, NInput, NButton, useMessage, NCard, NTabs, NTabPane, NInputNumber, NSelect, NColorPicker, NDataTable, useDialog, NTooltip } from "naive-ui"
import ModifyVideoAssSetting from '../Components/ModifyVideoAssSetting.vue'
import BackgroundMusic from './Components/BackgroundMusic.vue'
/**
* 创建字幕设置表格的列表
*/
const createAssSettingColumns = ({
DeleteAssSetting,
EditAssSetting,
DeleteWaterMarkSetting
}, isWater = false) => {
let columnData = [];
if (isWater) {
columnData.push(
{
title: "显示值",
key: "showText"
})
}
let return_data = columnData.concat([
{
title: "字体名称",
key: "fontName"
},
{
title: "字号",
key: "fontSize"
},
{
title: "字体颜色",
key: "fontColor"
},
{
title: "透明度",
key: "transparent"
},
{
title: "字体颜色",
key: "fontColor",
render(row) {
return h(NColorPicker, {
disabled: true,
value: row.fontColor
})
}
},
{
title: "位置X",
key: "positionX",
},
{
title: "位置Y",
key: "positionY",
},
]);
if (isWater) {
return_data.push({
title: "Action",
key: "actions",
render(row) {
return [h(
NButton,
{
strong: true,
style: "margin-right:5px",
tertiary: true,
type: 'info',
size: "small",
onClick: () => EditAssSetting(row, "watermarkConfig")
},
{ default: () => "编辑" }
), h(
NButton,
{
strong: true,
tertiary: true,
type: 'error',
size: "small",
onClick: () => DeleteWaterMarkSetting(row)
},
{ default: () => "删除" }
)]
}
})
} else {
return_data.push({
title: "Action",
key: "actions",
render(row) {
return [h(
NButton,
{
strong: true,
style: "margin-right:5px",
tertiary: true,
type: 'info',
size: "small",
onClick: () => EditAssSetting(row, "assConfig")
},
{ default: () => "编辑" }
), h(
NButton,
{
strong: true,
tertiary: true,
type: 'error',
size: "small",
onClick: () => DeleteAssSetting(row)
},
{ default: () => "删除" }
)];
}
})
}
return return_data;
}
export default defineComponent({
components: {
NForm, NFormItem, NInput, useDialog,
NInputNumber, NButton, NCard, NTabPane, NTabs,
NSelect, NColorPicker, NDataTable, BackgroundMusic, NTooltip
},
setup() {
let modifyRef = ref(null)
let dialog = useDialog();
let assSetting = ref({
fontName: null,
fontSize: 0,
fontColor: "#3B7114",
transparent: 0,
positionX: 0,
positionY: 0
})
let wmSetting = ref({
showText: null,
fontName: null,
fontSize: 0,
fontColor: "#3B7114",
transparent: 0,
positionX: 0,
positionY: 0
})
let generalSetting = ref({
video_resolution_x: 0,
video_resolution_y: 0,
keyframe: {
name: null,
up_down: 0,
left_right: 0,
scale: 0
},
frameRate: 0,
bitRate: 0,
audioSoundSize: 0,
backgroundMusicSoundSize: 0,
assConfig: [],
watermarkConfig: []
})
let message = useMessage();
let fontNameOptions = ref([]);
let keyframeOptions = ref([]);
/**
* 获取视频设置的基本信息
*/
async function GetVideoConfigMessage() {
// 获取基础数据
await window.api.GetVideoConfigMessage((value) => {
if (value.code == 0) {
message.error(value.message);
}
else if (value.code == 1) {
// 基础设置
generalSetting.value = value.data;
generalSetting.value.keyframe = generalSetting.value.keyframe ? generalSetting.value.keyframe : {}
// 字体下拉列表显示值
fontNameOptions.value = value.data.font_name_list
//
} else {
message.error("未知错误");
}
})
await window.api.GetKeyFrameOptions(value => {
if (value.code == 0) {
message.error(value.message);
return;
}
keyframeOptions.value = value.data.filter(item => item.value != "KFTypeScale");
})
}
onMounted(async () => {
// 获取基础数据
await GetVideoConfigMessage();
})
/**
* 保存基本配置
*/
async function SaveGeneralSetting() {
debugger
await window.api.SaveGeneralSetting(toRaw(generalSetting.value), (value) => {
console.log(value);
if (value.code == 0) {
window.api.showGlobalMessageDialog(value);
}
else if (value.code == 1) {
window.api.showGlobalMessageDialog(value);
} else {
window.api.showGlobalMessageDialog({ code: 0, message: "未知错误" });
}
})
}
/**
* 获取当前系统已经安装了的字体文件
*/
async function GetSystemInstallFontName() {
await window.api.GetSystemInstallFontName(async (value) => {
debugger;
console.log(value);
if (value.code == 0) {
message.error(value.message);
} else if (value.code == 1) {
message.success("保存成功");
// 刷新数据
await GetVideoConfigMessage();
} else {
message.error("未知错误")
}
})
}
/**
* 保存字幕设置
*/
async function SaveAssConfig() {
window.api.SaveAssConfig(["assConfig", toRaw(assSetting.value)], async (value) => {
debugger
console.log(value);
if (value.code == 1) {
// 刷新数据,将上面的数据清零。
await GetVideoConfigMessage();
// message.success("保存成功");
window.api.showGlobalMessageDialog(value);
} else if (value.code == 0) {
// message.error(value.message);
window.api.showGlobalMessageDialog(value);
} else {
// message.error("未知错误");
window.api.showGlobalMessageDialog({ code: 0, message: "未知错误" })
}
})
}
/**
* 删除对应行的字幕设置
* @param {行数据} row
*/
async function DeleteAssSetting(row) {
await window.api.DeleteVideoConfig(['assConfig', row.id], async (value) => {
debugger;
console.log(value)
if (value.code == 1) {
message.success("删除成功");
await GetVideoConfigMessage();
return
}
message.error(value.message);
})
}
/**
* 保存水印设置
*/
async function SaveWaterMarkConfig() {
window.api.SaveAssConfig(["watermarkConfig", toRaw(wmSetting.value)], async (value) => {
console.log(value);
if (value.code == 1) {
// 刷新数据,将上面的数据清零。
await GetVideoConfigMessage();
window.api.showGlobalMessageDialog(value);
} else if (value.code == 0) {
window.api.showGlobalMessageDialog(value);
} else {
window.api.showGlobalMessageDialog({ value: 0, message: "未知错误" })
}
})
}
/**
* 删除指定的行
*/
async function DeleteWaterMarkSetting(row) {
debugger
await window.api.DeleteVideoConfig(['watermarkConfig', row.id], async (value) => {
debugger;
console.log(value)
if (value.code == 1) {
message.success("删除成功");
await GetVideoConfigMessage();
return
}
message.error(value.message);
})
}
/**
* 编辑数据
*/
async function EditAssSetting(row, type) {
// ModifyVideoAssSetting
dialog.create({
showIcon: false,
content: () => h(ModifyVideoAssSetting, { ref: modifyRef, initData: row, type: type }),
style: `width : 700px;`,
maskClosable: false,
positiveText: "确定",
negativeText: "取消",
onPositiveClick: async () => {
debugger;
let ss = modifyRef.value.wmSetting
console.log(ss)
await window.api.SaveAssConfig([type, toRaw(modifyRef.value.wmSetting)], async (value) => {
debugger;
console.log(value);
if (value.code == 0) {
message.error(value.message);
return;
}
message.success("修改成功")
// 刷新数据,将上面的数据清零。
await GetVideoConfigMessage();
})
},
onNegativeClick: async () => {
await GetVideoConfigMessage();
},
onClose: async () => {
await GetVideoConfigMessage();
},
onEsc: async () => {
await GetVideoConfigMessage();
}
})
}
return {
generalSetting,
SaveGeneralSetting,
assSetting,
wmSetting,
GetSystemInstallFontName,
fontNameOptions,
SaveAssConfig,
SaveWaterMarkConfig,
modifyRef,
keyframeOptions,
assSettingColumns: createAssSettingColumns({
DeleteAssSetting,
EditAssSetting,
DeleteWaterMarkSetting
}),
wmColumns: createAssSettingColumns({ DeleteAssSetting, EditAssSetting, DeleteWaterMarkSetting }, true)
}
}
})
</script>
+37
View File
@@ -0,0 +1,37 @@
import { createApp } from 'vue'
import { createRouter, createWebHashHistory } from 'vue-router';
import App from './App.vue'
import { Home } from '@vicons/ionicons5';
const app = createApp(App);
const routes = [
{
path: "/",
component: () => import('./components/Home/Home.vue'),
children: [
{ path: "/global_setting", name: "global_setting", component: () => import('./components/Setting/Setting.vue') },
{ path: "/clip_setting", name: "clip_setting", component: () => import('./components/Setting/ClipSetting.vue') },
{ path: "/getframe", name: "getframe", component: () => import('./components/Backstep/GetFrame.vue') },
{ path: "/pushBackPrompt", name: "pushBackPrompt", component: () => import('./components/Backstep/PushBackPrompt.vue') },
{ path: "/regenerate", name: "regenerate", component: () => import('./components/Backstep/ReGenerate.vue') },
{ path: "/align_draft", name: "align_draft", component: () => import('./components/Clip/AlignDraft.vue') },
{ path: "/add_draft", name: "add_draft", component: () => import('./components/Clip/AddDraft.vue') },
{ path: "/VideoGenerate", name: "VideoGenerate", component: () => import('./components/Backstep/VideoGenerate.vue') },
{ path: "/sd_setting", name: "sd_setting", component: () => import('./components/Setting/SDSetting.vue') },
{ path: "/copywriting", name: "copywriting", component: () => import('./components/Backstep/CopyWriting.vue') },
{ path: "/videogeneratesetting", name: "videogeneratesetting", component: () => import('./components/Setting/VideoGenerateSetting.vue') },
{ path: "/ShowMessage", name: "ShowMessage", component: () => import('./components/Home/ShowMessage.vue') },
{ path: "/sdoriginal", name: "sdoriginal", component: () => import('./components/Original/MainPage.vue') },
{ path: "/mj_setting", name: "mj_setting", component: () => import('./components/Setting/MJSetting.vue') },
]
},
{ path: "/ReDrawImage", component: () => import('./components/Home/ReDrawImageWD.vue'), },
]
const router = createRouter({
history: createWebHashHistory(),
routes
})
app.use(router);
app.mount('#app')
+7
View File
@@ -0,0 +1,7 @@
@media (prefers-color-scheme: dark) {
body { background: #333; color: white; }
}
@media (prefers-color-scheme: light) {
body { background: #ddd; color: black; }
}