前端文生图代码
<script setup>
import {ref} from 'vue'
import {
QuestionFilled
} from '@element-plus/icons-vue'
import {useRouter} from 'vue-router'
const router = useRouter()
import {userStore} from '../stores/user.js';
const ui = userStore()
const textAigc = () => {
router.push('/textAigc')
}
const imgaigc = () => {
router.push("/imgaigc")
}
const Loginjump = async () => {
if (ui.userinfo.username === 'GunChan') {
await router.push('/title/manage')
} else {
await router.push('/aigc/manage')
}
}
//定义数据模型
const textmodel = ref(
{
"enable_hr": false,
"denoising_strength": 0.78,
"firstphase_width": 0,
"firstphase_height": 0,
"hr_scale": 2,
"hr_upscaler": null,
"hr_second_pass_steps": 0,
"hr_resize_x": 0,
"hr_resize_y": 0,
"prompt": "",
"seed": -1,
"subseed": -1,
"subseed_strength": 0,
"seed_resize_from_h": -1,
"seed_resize_from_w": -1,
"sampler_name": "DPM++ 2S a",
"batch_size": 1,
"n_iter": 1,
"steps": 20,
"cfg_scale": 7,
"width": 512,
"height": 512,
"restore_faces": false,
"tiling": false,
"do_not_save_samples": false,
"do_not_save_grid": false,
"negative_prompt": "nsfw,naked,nude,topless,(worst quality:2),",
"eta": 0,
"s_churn": 0,
"s_tmax": 0,
"s_tmin": 0,
"s_noise": 1,
"sampler_index": "DPM++ 2S a",
"script_name": null,
"send_images": true,
"save_images": false,
"task_id": "lsh4ksce44q5egvtchy0m3jmjkoypz6l"
}
)
const re = ref('');
const imageData = ref(null);
const errorMessage = ref('');
const fetchImageData = async (text) => {
console.log(text.value)
console.log(text)
const keys = Object.entries(text.value);
keys.forEach(([key, value]) => {
re[key] = value;
});
console.log(re);
try {
const response = await fetch('http://localhost:8080/sdapi/v1/txt2img', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization':' Basic MTIzNDU2OjEyMzQ1Ng=='
},
body: JSON.stringify(re)
});
if (!response.ok) {
throw new Error('获取图片数据失败1');
}
console.log(response)
return response.json();
} catch (error) {
throw new Error('获取图片数据失败2');
}
};
const processTextAndFetchImage = async () => {
console.log(textmodel)
textmodel.value.sampler_index=textmodel.value.sampler_name
try {
const imageDataValue = await fetchImageData(textmodel);
console.log(imageDataValue)
const cleanedImage = imageDataValue.images[0].replace(/["\[\]]/g, '');
console.log(cleanedImage)
imageData.value = `data:image/png;base64,${cleanedImage}`;
} catch (error) {
errorMessage.value = '生成图片失败1';
console.error(error);
}
};
const con = () => {
console.log()
};
const radio1 = ref('New York')
const value1 = ref(0)
const formatTooltip = (val) => {
return val;
}
// const download = () => {
// const link = document.createElement('a');
// link.herf = imageData.value;
// console.log(imageData.value)
// link.download = 'downloaded_image.jpg'; // 设置下载时的默认文件名
// document.body.appendChild(link);
// link.click();
// document.body.removeChild(link);
// }
const download = () => {
console.log(typeof imageData.value)
console.log(imageData.value)
const base64Data = imageData.value.replace(/^data:image\/(png|jpg|jpeg);base64,/, '');
const blob = base64toBlob(base64Data, 'image/png');
const url = window.URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = 'downloaded_image.png';
document.body.appendChild(a);
a.click();
window.URL.revokeObjectURL(url);
document.body.removeChild(a);
}
const base64toBlob = (base64Data, contentType) => {
const sliceSize = 512;
const byteCharacters = atob(base64Data);
const byteArrays = [];
for (let offset = 0; offset < byteCharacters.length; offset += sliceSize) {
const slice = byteCharacters.slice(offset, offset + sliceSize);
const byteNumbers = new Array(slice.length);
for (let i = 0; i < slice.length; i++) {
byteNumbers[i] = slice.charCodeAt(i);
}
const byteArray = new Uint8Array(byteNumbers);
byteArrays.push(byteArray);
}
return new Blob(byteArrays, { type: contentType });
}
const random =() =>{
const randomNumber = Math.floor(Math.random() * 9999) + 1;
textmodel.value.seed = randomNumber;
}
const showbox = ref(false)
const showbox1 = ref(false)
const showbox2 = ref(false)
const showbox3 = ref(false)
</script>
<template>
<body>
<el-container>
<!-- 头部区域-->
<el-header>
<div class="container">
<!-- 导航栏 -->
<nav class="nav__container">
<div class="logo">
<img src="../assets/logo.png" alt="Logo" class="logo-image" />
<router-link to="/IndexView" class="logo-text">壮锦艺坊</router-link>
</div>
<!-- 导航菜单 -->
<div class="nav__menu" id="nav-menu">
<ul class="nav__list">
<li class="nav__item">
<router-link to="/IndexView" class="nav__link" active-class="active-link">首页</router-link>
</li>
<li class="nav__item">
<router-link to="/Zhuangview" class="nav__link" active-class="active-link">壮锦背景</router-link>
</li>
<li class="nav__item">
<router-link to="/ZhuangPattern" class="nav__link" active-class="active-link">壮锦纹样</router-link>
</li>
<li class="nav__item">
<router-link to="/textAigc" class="nav__link" active-class="active-link">AIGC创作</router-link>
</li>
<li class="nav__item">
<router-link to="/ProductList" class="nav__link" active-class="active-link">在线定制</router-link>
</li>
</ul>
</div>
<div class="user-profile">
<img src="../assets/01.png" alt="Avatar" class="avatar" />
<a class="nav__link" @click="Loginjump">用户名</a>
</div>
</nav>
</div>
</el-header>
<el-container>
<!-- 左部区域-->
<el-aside class="aside">
<el-row class="login-page">
<el-col :span="6" :offset="3" class="form">
<el-button class="imgaigc" @click="textAigc">文生图</el-button>
<el-button class="imgaigc" @click="imgaigc">图生图</el-button>
<!-- 表单 -->
<el-form ref="form" size="large" autocomplete="off" >
<el-form-item>
<h4>正向提示词</h4>
<el-icon size="large" class="question1" @click="showbox= true;"><QuestionFilled /></el-icon>
<div class="text-box">
<el-dialog v-model="showbox" type="text" >正向提示词:正向提示词是用来规定生成图片时,图片所呈现的相关特征。添加<lora:xiu:1>可以使用壮锦的Lora模型</el-dialog>
</div>
</el-form-item>
<el-form-item prop="prompt" class="textInput">
<textarea class="text__ai" placeholder="请输入正向提示词(英文)" v-model="textmodel.prompt"
type="textarea"></textarea>
</el-form-item>
<el-form-item>
<h4>反向提示词</h4>
<el-icon size="large" class="question1" @click="showbox1= true;"><QuestionFilled /></el-icon>
<div class="text-box">
<el-dialog v-model="showbox1" type="text" >反向提示词:反向提示词是用来规定生成图片时,图片所避免的特征。</el-dialog>
</div>
</el-form-item>
<el-form-item prop="prompt" class="textInput1">
<textarea class="text__ai" placeholder="请输入反向提示词(英文)" v-model="textmodel.negative_prompt"
type="textarea"></textarea>
</el-form-item>
<!-- 按钮 -->
<el-form-item>
<el-button class="button" type="primary" auto-insert-space @click="processTextAndFetchImage">生成
</el-button>
</el-form-item>
</el-form>
</el-col>
</el-row>
<!-- 图像算法-->
<div class="radio_div">
<h6>图像采样算法</h6>
<el-icon size="large" class="question2" @click="showbox2= true;"><QuestionFilled /></el-icon>
<div class="text-box">
<el-dialog v-model="showbox2" type="text" >图像采样算法:不同的图像采样算法会对生成图片产生轻微的影响。</el-dialog>
</div>
<el-radio-group v-model="textmodel.sampler_name">
<el-radio label="DPM++ 2M Karras" value="DPM++ 2M Karras" border/>
<el-radio label="DPM++ 2S a" value="DPM++ 2S a" border/>
<el-radio label="DPM++ 2M" value="DPM++ 2M" border/>
<el-radio label="DPM++ 2S a Karras" value="DPM++ 2S a Karras" border/>
<el-radio label="DPM2 a Karras" value="DPM2 a Karras" border/>
<el-radio label="DPM++ SDE Karras" value="DPM++ SDE Karras" border/>
</el-radio-group>
</div>
<div class="slider">
<h5>图片分辨率宽度</h5>
<el-slider v-model="textmodel.width" :format-tooltip="formatTooltip" :min="0" :max="1024" :step="1" show-input/>
<h5>图片分辨率高度</h5>
<el-slider v-model="textmodel.height" :format-tooltip="formatTooltip" :min="0" :max="1024" :step="1" show-input/>
</div>
<div class="seed_div">
<h4>随机种子 </h4>
<el-icon size="large" class="question3" @click="showbox3= true;"><QuestionFilled /></el-icon>
<div class="text-box">
<el-dialog v-model="showbox3" type="text" >随机种子:随机数种子控制了图像生成的底层形状,类似于画画时最开始的线稿。当遇到喜欢的图片风格时可以保存当前种子。当种子为-1时,为随机种子。</el-dialog>
</div>
<el-input class="input_div"v-model="textmodel.seed"/>
<img class="touzi" src="../assets/骰子.png"@click="random">
</div>
<!-- <div >-->
<!-- <h4>fenbianlv </h4>-->
<!-- <el-input v-model="value"/>-->
<!-- <el-input v-model="value"/>-->
<!-- </div>-->
</el-aside>
<!-- 显示区域-->
<el-container>
<el-main class="main">
<!-- 展示生成的图片或返回的消息 -->
<div class="img_ai_div">
<img v-if="imageData" :src="imageData" alt="Generated Image" id="aigenerate" class="img_ai">
<img v-else src="../assets/img/397x300/02-397x300.png"alt="备用" class="img_ai">
</div>
<div v-if="errorMessage" class="error">{{ errorMessage }}</div>
<div>
<el-button class="download" @click="download">下载</el-button>
</div>
</el-main>
</el-container>
</el-container>
</el-container>
</body>
</template>
<style lang="scss" scoped>
body {
font-family: "Roboto", sans-serif;
background-color: #f8f8f8;
color: #333;
margin: 0;
padding: 0;
}
.container {
max-width: 1200px;
margin: 0 auto;
padding: 0 20px;
}
/* 导航栏样式 */
.nav__container {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 80px;
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 20px;
background-color: #fff;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
z-index: 1000;
}
.logo {
display: flex;
align-items: center;
}
.logo-image {
width: 50px;
height: 50px;
border-radius: 50%;
margin-right: 10px;
}
.logo-text {
font-size: 1.5rem;
font-weight: bold;
color: #333;
text-decoration: none;
}
.nav__menu {
display: flex;
align-items: center;
}
.nav__list {
list-style: none;
margin: 0;
padding: 0;
display: flex;
}
.nav__item {
margin: 0 15px;
}
.nav__link {
font-size: 1rem;
color: #333;
text-decoration: none;
transition: color 0.3s ease;
}
.nav__link:hover,
.nav__link.active-link {
color: #b43149;
}
.user-profile {
display: flex;
align-items: center;
}
.avatar {
width: 40px;
height: 40px;
border-radius: 50%;
margin-right: 10px;
}
.username-link {
font-size: 1rem;
color: #333;
text-decoration: none;
transition: color 0.3s ease;
}
.username-link:hover {
color: #b43149;
}
/* 深度设计系统 */
:root {
--primary-color: #4A90E2;
--secondary-color: #6C5CE7;
--success-color: #00C853;
--background-color: #F8F9FC;
--surface-color: #FFFFFF;
--text-primary: #2D3436;
--text-secondary: #636E72;
}
/* 主布局重构 */
.el-container {
padding-top: 20px;
background: var(--background-color);
min-height: 100vh;
}
/* 智能侧边栏 */
.el-aside {
width: 800px !important;
background: var(--surface-color);
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.05);
padding: 24px;
border-radius: 16px;
margin: 20px;
height: calc(100vh - 120px);
overflow-y: auto;
/* 动态表单区域 */
.form-section {
background: rgba(245, 247, 250, 0.6);
border-radius: 12px;
padding: 16px;
margin-bottom: 24px;
.text__ai {
width: 100%;
min-height: 100px;
padding: 12px;
border: 1px solid #E3E8F0;
border-radius: 8px;
background: var(--surface-color);
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
&:focus {
border-color: var(--primary-color);
box-shadow: 0 0 0 3px rgba(74, 144, 226, 0.15);
}
}
}
/* 智能按钮 */
.button {
width: 100%;
height: 48px;
background: linear-gradient(135deg, var(--primary-color), var(--secondary-color));
border-radius: 8px;
font-weight: 600;
letter-spacing: 0.5px;
transition: transform 0.2s ease;
background-color: #4A90E2;
&:active {
transform: scale(0.98);
}
}
}
/* 参数控制面板 */
.control-panel {
background: var(--surface-color);
border-radius: 12px;
padding: 20px;
margin-top: 16px;
.param-group {
margin-bottom: 24px;
h4 {
color: var(--text-primary);
font-size: 14px;
font-weight: 600;
margin-bottom: 12px;
display: flex;
align-items: center;
gap: 8px;
}
/* 现代单选组 */
.el-radio-group {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
gap: 8px;
.el-radio {
margin: 0;
padding: 10px;
border-radius: 8px;
background: #F8FAFF;
border: 1px solid #E4E9F2;
transition: all 0.2s ease;
&.is-checked {
background: var(--primary-color);
border-color: var(--primary-color);
color: white;
}
}
}
}
}
/* 动态生成区域 */
.el-main {
padding: 16px;
display: flex;
flex-direction: column;
gap: 24px;
.generation-area {
flex: 1;
background: var(--surface-color);
border-radius: 16px;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.05);
padding: 24px;
display: flex;
flex-direction: column;
.preview-container {
flex: 1;
background: #F8FAFF;
border-radius: 12px;
position: relative;
overflow: hidden;
img {
width: 100%;
height: 100%;
object-fit: contain;
transition: opacity 0.3s ease;
}
.loading-overlay {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(255, 255, 255, 0.9);
display: flex;
align-items: center;
justify-content: center;
}
}
.action-bar {
margin-top: 24px;
display: flex;
gap: 12px;
.el-button {
flex: 1;
height: 48px;
border-radius: 8px;
font-weight: 600;
&.download-btn {
background: linear-gradient(135deg, var(--success-color), #00B248);
}
&:hover {
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.1);
}
}
}
}
}
/* 高级控件样式 */
.smart-control {
.el-slider {
--slider-height: 6px;
.el-slider__runway {
height: var(--slider-height);
background: #E3E8F0;
}
.el-slider__bar {
height: var(--slider-height);
background: var(--primary-color);
}
.el-slider__button {
width: 16px;
height: 16px;
border: 2px solid var(--primary-color);
background: var(--surface-color);
}
}
.seed-control {
display: flex;
gap: 12px;
align-items: center;
.el-input {
flex: 1;
input {
text-align: center;
font-family: monospace;
}
}
img {
width: 24px;
height: 24px;
cursor: pointer;
transition: all 0.3s ease;
&:hover {
transform: rotate(180deg);
filter: drop-shadow(0 2px 4px rgba(0, 0, 0, 0.1));
}
}
}
}
/* 响应式适应 */
@media (max-width: 1024px) {
.el-container {
flex-direction: column;
}
.el-aside {
width: 100% !important;
height: auto;
margin: 16px;
}
.generation-area {
min-height: 500px;
}
}
@media (max-width: 768px) {
.param-group .el-radio-group {
grid-template-columns: 1fr;
}
.action-bar {
flex-direction: column;
}
}
.el-form-item {
textarea.text__ai {
width: 100%;
min-width: 600px; /* 增加高度 */
max-height: 300px; /* 可以输入更多内容 */
padding: 12px;
border: 1px solid #E3E8F0;
border-radius: 8px;
background: var(--surface-color);
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
&:focus {
border-color: var(--primary-color);
box-shadow: 0 0 0 3px rgba(74, 144, 226, 0.15);
}
}
}
.touzi{
width: 20px;
}
</style>
token.js
import {defineStore} from "pinia";
import {ref} from 'vue'
// 参数一:名字
// 参数二:函数
//
// 返回值
export const userTokenStore = defineStore('token', () => {
//定义状态内容
// 响应式变量
const token = ref('')
// 定义一个函数 修改token数据
const tokenset = (newtoken) => {
token.value = newtoken
}
// 移除token
const tokenremove = () => {
token.value = ''
}
return {token, tokenset, tokenremove}
},
{
persist: true //刷新保留Authorization
});
aigc.js
//定制请求的实例
//导入axios npm install axios
import axios from 'axios';
//定义一个变量,记录公共的前缀 , baseURL
const baseURL = '/api';
const instance = axios.create({baseURL})
//添加响应拦截器
// instance.interceptors.response.use(
// result=>{
// return result.data;
// },
// err=>{
// alert('服务异常');
// return Promise.reject(err);//异步的状态转化成失败的状态
// }
// )
instance.interceptors.request.use(
(config) => {
const token1 = userTokenStore();
if (token1.token) {
config.headers.Authorization = `Bearer ${token1.token}`; // 确保格式正确
}
return config;
},
(err) => {
Promise.reject(err);
}
);
export default instance;
request.js
//定制请求的实例
//导入axios npm install axios
import axios from 'axios';
import {ElMessage} from "element-plus";
//定义一个变量,记录公共的前缀 , baseURL
// const baseURL = 'http://localhost:8080';
const baseURL = '/api';
const instance = axios.create({baseURL})
//添加请求拦截器 其实就是简化Auyhorization的获取方法
import {userTokenStore} from "../stores/token.js";
instance.interceptors.request.use(
(config) => {
//请求前的回调
//添加token
const token1 = userTokenStore();
if (token1.token) {
config.headers.Authorization = token1.token
}
return config;
},
(err) => {
//请求错误的回调
Promise.reject(err)
}
)
//添加响应拦截器
// import {useRouter} from 'vue-router'
// const router = useRouter();
import router from "../router/index.js";
// import router from "../router/";
instance.interceptors.response.use(
result => {
//判断业务状态
if (result.data.code === 0) {
return result.data;
}
//操作失败
// alert(result.data.msg?result.data.msg:'服务异常')
ElMessage.error(result.data.msg ? result.data.msg : '服务异常')
return Promise.reject(result.data);//异步的状态转化成失败的状态
},
err => {
//若401未连接 强制登陆页面
if (err.response.status===401) {
ElMessage.error('未登录,请登录账号')
router.push('/userlogin')
} else {
ElMessage.error('服务异常')
}
// alert('服务异常');
return Promise.reject(err);//异步的状态转化成失败的状态
}
)
export default instance;
SDController
package com.Controller;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.gson.Gson;
import com.google.gson.JsonObject;
import com.pojo.Imgai;
import com.pojo.Result;
import com.pojo.Textai;
import com.pojo.prompt;
import com.service.impl.StableDiffusionImpl;
import lombok.extern.slf4j.Slf4j;
import net.minidev.json.JSONObject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpHeaders;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.charset.StandardCharsets;
@RestController
@RequestMapping("/sdapi")
@Slf4j
public class StableDiffusionController {
private final StableDiffusionImpl stableDiffusionImpl;
@Autowired
public StableDiffusionController(StableDiffusionImpl stableDiffusionImpl) {
this.stableDiffusionImpl = stableDiffusionImpl;
}
// 在你的类中定义一个ObjectMapper实例
private static final ObjectMapper objectMapper = new ObjectMapper();
@PostMapping("/v1/txt2img")
public ResponseEntity txt2img(@RequestBody Textai textai) throws IOException {
// 调用服务类处理文本转图片的逻辑
log.info("text{}",textai);
// 创建 Gson 对象
Gson gson = new Gson();
// 将 prompt 字符串转换为 JSON 对象
String json = gson.toJson(textai);
// 创建HttpURLConnection对象
HttpURLConnection connection = null;
// 设置URL
URL url = new URL("http://127.0.0.1:7860/sdapi/v1/txt2img");
connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("POST");
connection.setRequestProperty("Content-Type", "application/json");
connection.setDoOutput(true);
// 将JSON字符串作为请求体写入连接
try (OutputStream os = connection.getOutputStream()) {
byte[] input = json.toString().getBytes(StandardCharsets.UTF_8);
os.write(input, 0, input.length);
}
// 获取响应
try (BufferedReader br = new BufferedReader(new InputStreamReader(connection.getInputStream(), "utf-8"))) {
StringBuilder response = new StringBuilder();
String responseLine = null;
while ((responseLine = br.readLine()) != null) {
response.append(responseLine.trim());
}
// 处理响应
return ResponseEntity.ok(response);
} finally {
// 关闭连接
connection.disconnect();
}
// 将头部和主体数据包装在ResponseEntity中返回
}
@PostMapping("/v1/img2img")
public ResponseEntity img2img(@RequestBody Imgai imgai) throws IOException {
Gson gson = new Gson();
// 将 prompt 字符串转换为 JSON 对象
String json = gson.toJson(imgai);
log.info("json{}",json);
// 创建HttpURLConnection对象
HttpURLConnection connection = null;
// 设置URL
URL url = new URL("http://127.0.0.1:7860/sdapi/v1/img2img");
connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("POST");
connection.setRequestProperty("Content-Type", "application/json");
connection.setDoOutput(true);
// 将JSON字符串作为请求体写入连接
try (OutputStream os = connection.getOutputStream()) {
byte[] input = json.toString().getBytes(StandardCharsets.UTF_8);
os.write(input, 0, input.length);
}
// 获取响应
try (BufferedReader br = new BufferedReader(new InputStreamReader(connection.getInputStream(), "utf-8"))) {
StringBuilder response = new StringBuilder();
String responseLine = null;
while ((responseLine = br.readLine()) != null) {
response.append(responseLine.trim());
}
// 处理响应
return ResponseEntity.ok(response);
} finally {
// 关闭连接
connection.disconnect();
}
// 将头部和主体数据包装在ResponseEntity中返回
}
}
我在尝试调用秋叶启动器的SD的API到我的网站,我已经在秋叶启动器里设置了用户名和密码,都是123456。
前后端都是我从别人那里得到的,他之前成功调用过实现了在自己网站进行文生图和图生图。
我的登录注册功能可以实现,但是我拷过来后一直报401,我在postman测试http://127.0.0.1:7860/sdapi/v1/txt2img是可以的,搞了好久了还是不行,有没有大佬指点啊