liyongli 2 년 전
부모
커밋
a8c10d126f

+ 1 - 1
src/App.vue

@@ -20,7 +20,7 @@ export default {
 html,body{
     width: 100%;
     height: 100%;
-    min-width: 1805px;
+    /* min-width: 1805px; */
 }
 #app {
   font-family: Avenir, Helvetica, Arial, sans-serif;

+ 50 - 0
src/api/index.js

@@ -312,3 +312,53 @@ export function getComponentsList(ori) {
     method: 'GET',
   });
 }
+
+/**
+ * 获取绘画风格
+ */
+export function getPaintingStyle(ori) {
+  return ajax({
+    api: '/get/modifiers',
+    data: ori.data,
+    type: 'ajax',
+    base: 'easyDiffusion',
+    method: 'GET',
+  });
+}
+
+/**
+ * 生成绘画
+ */
+export function createDraw(ori) {
+  return ajax({
+    noload: true,
+    api: '/render',
+    data: ori.data,
+    type: 'ajax',
+    base: 'easyDiffusion',
+    method: 'POST',
+  });
+}
+
+/**
+ * 查询生成绘画
+ */
+export function queryDraw(ori) {
+  return ajax({
+    noload: true,
+    api: '/image/stream/' + ori.data,
+    type: 'ajax',
+    base: 'easyDiffusion',
+    method: 'POST',
+  });
+}
+export function ChartGpt(data) {
+  return ajax({
+    noLoad: true,
+    url: 'completions',
+    method: 'POST',
+    urlType: 'chat',
+    errorToast: '当前访问人数过多,请重试。',
+    data,
+  });
+}

BIN
src/assets/img/bg.png


+ 4 - 1
src/config/index.js

@@ -4,6 +4,9 @@ export default {
     base:{
         origin: "https://open.sxtvs.net",
         json: "https://cxzx.smcic.net",
-        GaoDeMap: "https://restapi.amap.com"
+        GaoDeMap: "https://restapi.amap.com",
+        easyDiffusion: "https://diffusion.sxtvs.net",
+        chat:"https://gpt.smcic.net/",
+        ws: "ws://10.30.162.36:7860/queue/join"
     }
 }

+ 15 - 0
src/router/easyDiffusion.js

@@ -0,0 +1,15 @@
+export default [
+    {
+      path: '/easyDiffusion',
+      name: 'easyDiffusion',
+      component: () =>
+        import(/* webpackChunkName: "easyDiffusion" */ '../view/easyDiffusion/index.vue'),
+    },
+    {
+      path: '/qh',
+      name: 'qh',
+      component: () =>
+        import(/* webpackChunkName: "qh" */ '../view/qh/index.vue'),
+    },
+  ];
+  

+ 2 - 0
src/router/index.js

@@ -1,6 +1,7 @@
 import { createRouter, createWebHashHistory } from 'vue-router';
 
 import allMedia from "./allMedia";
+import easyDiffusion from "./easyDiffusion";
 
 import NotFound from "../errorPage/404.vue";
 
@@ -15,6 +16,7 @@ const router = createRouter({
         import(/* webpackChunkName: "Home" */ '../view/onlineText.vue'),
     },
     ...allMedia,
+    ...easyDiffusion,
     {
         path: "/:pathMatch(.*)*",
         name: "NotFound",

+ 7 - 6
src/utils/request.js

@@ -24,7 +24,7 @@ function ajax(longRange) {
 
     xhttp.onreadystatechange = function () {
       if (this.readyState !== 4) return;
-      loading.close();
+      !longRange.noload && loading.close();
       var data = JSON.parse(this.responseText || '{}');
       if (this.status !== 200) {
         // 网络错误
@@ -144,11 +144,12 @@ function headerFunc(headers = {}) {
 }
 
 export default function (longRange) {
-  loading = ElLoading.service({
-    lock: true,
-    text: 'Loading',
-    background: 'rgba(0, 0, 0, 0.1)',
-  });
+  !longRange.noload &&
+    (loading = ElLoading.service({
+      lock: true,
+      text: 'Loading',
+      background: 'rgba(0, 0, 0, 0.1)',
+    }));
   if (longRange.type !== 'ajax' && window.fetch) return fetch(longRange);
   return ajax(longRange);
 }

+ 55 - 0
src/utils/socket.js

@@ -0,0 +1,55 @@
+import config from '@/config/index';
+
+let websocket = null; //全局WebSocket对象
+let lockReconnect = false; // 网络断开重连
+let wsCreateHandler = null; // 创建连接
+
+export function createSocket(fn) {
+    try {
+      if ('WebSocket' in window)
+        websocket = new window.WebSocket(config.base.ws);
+      else if ('MozWebSocket' in window)
+        websocket = new window.MozWebSocket(config.base.ws);
+      else reconnect();
+    } catch {
+      reconnect();
+      return;
+    }
+
+    websocket.onmessage = function (data) {
+      fn && fn({
+        ws: websocket,
+        data: JSON.parse(data.data)
+      })
+    };
+
+    websocket.onopen = function (event) {
+      // websocket.send("连接成功");
+      console.log('服务已连接', event);
+    };
+    websocket.onclose = function (event) {
+      console.log('服务连接关闭', event);
+    };
+    websocket.onerror = function (event) {
+      console.log(event, '连接出错');
+    };
+}
+
+/**
+ *  异常处理
+ * 处理可以检测到的异常,并尝试重新连接
+ */
+function reconnect() {
+  if (lockReconnect) {
+    return;
+  }
+  console.log('reconnect');
+  lockReconnect = true;
+  // 没链接上会一直连接,设置延迟,避免过多请求
+  wsCreateHandler && clearTimeout(wsCreateHandler);
+  wsCreateHandler = setTimeout(function () {
+    console.log('-----websoket异常-------');
+    createSocket();
+    lockReconnect = false;
+  }, 1000);
+}

+ 64 - 0
src/view/easyDiffusion/components/image.vue

@@ -0,0 +1,64 @@
+<template>
+  <div class="image">
+    <el-icon :size="25" class="close" @click="close"><CircleClose /></el-icon>
+    <img :src="file.fileResult" style="width: 100%; border-radius: 6px" />
+    <div class="right">
+      <label for="file" class="dark label" style="">
+        <el-icon><FolderOpened /></el-icon>
+        选择
+      </label>
+      <p style="height: 1em"></p>
+      <span class="dark label" @click="draw">
+        <el-icon><EditPen /></el-icon>
+        绘制
+      </span>
+    </div>
+  </div>
+</template>
+
+<script setup>
+import { defineProps, defineEmits } from 'vue';
+defineProps({
+  file: Object,
+});
+const emit = defineEmits(['close', 'draw']);
+
+const close = () => {
+  emit('close');
+};
+const draw = () => {
+  emit('draw');
+};
+</script>
+
+<style scoped>
+.image {
+  width: 100px;
+  position: relative;
+  margin-top: 1em;
+  margin-bottom: 1em;
+}
+.close {
+  position: absolute;
+  top: -10px;
+  right: -10px;
+  background-color: #000;
+  border-radius: 50%;
+  font-weight: 500;
+}
+
+.label {
+  background-color: #202225;
+  padding: 3px 8px;
+  font-size: 0.9em;
+  border-radius: 3px;
+}
+
+.right {
+  position: absolute;
+  width: 6em;
+  text-align: center;
+  top: 3em;
+  right: -7em;
+}
+</style>

+ 529 - 0
src/view/easyDiffusion/components/imageEdit.vue

@@ -0,0 +1,529 @@
+<template>
+  <div class="edit" @mouseup="mouseup">
+    <div class="main">
+      <h1 class="head">
+        图像编辑器
+        <el-icon class="closeMain" @click="close"><Close /></el-icon>
+      </h1>
+      <el-row>
+        <el-col :span="6" style="padding-left: 32px">
+          <h4>工具</h4>
+          <el-row>
+            <el-col :span="12">
+              <span
+                @click="select[0] = 0"
+                :class="{ dark: true, active: select[0] === 0 }"
+              >
+                <el-icon><EditPen /></el-icon>
+                绘制
+              </span>
+            </el-col>
+            <el-col :span="12">
+              <span
+                @click="select[0] = 1"
+                :class="{ dark: true, active: select[0] === 1 }"
+              >
+                <el-icon><EditPen /></el-icon>
+                橡皮擦
+              </span>
+            </el-col>
+          </el-row>
+          <el-row style="margin-top: 1em">
+            <el-col :span="12">
+              <span
+                @click="select[0] = 2"
+                :class="{ dark: true, active: select[0] === 2 }"
+              >
+                <el-icon><EditPen /></el-icon>
+                填充
+              </span>
+            </el-col>
+            <el-col :span="12">
+              <span
+                @click="select[0] = 3"
+                :class="{ dark: true, active: select[0] === 3 }"
+              >
+                <el-icon><EditPen /></el-icon>
+                取色器
+              </span>
+            </el-col>
+          </el-row>
+          <h4>颜色</h4>
+          <el-color-picker v-model="select[1]" size="large" />
+          <div style="padding: 10px 0">
+            <div
+              :class="{ colorItm: true, active: select[1] === item }"
+              v-for="(item, i) in colors"
+              :key="i"
+              :style="'background-color:' + item"
+              @click="() => colorSelect(item)"
+            >
+              <el-icon color="#000" class="colorIcon" v-if="select[1] === item">
+                <Select />
+              </el-icon>
+            </div>
+          </div>
+          <h4>画笔尺寸</h4>
+          <div>
+            <div
+              class="pen"
+              :style="{ width: item + 'px', height: item + 'px' }"
+              :class="{ active: select[2] === item }"
+              @click="select[2] = item"
+              v-for="(item, i) in width"
+              :key="'width' + i"
+            ></div>
+          </div>
+          <h4>不透明度</h4>
+          <div class="racGroup">
+            <div class="rav">
+              <div
+                :class="{ item: true, btm0: true, active: select[3] === 1 }"
+                @click="() => selectBTM(1)"
+              ></div>
+            </div>
+            <div class="rav">
+              <div
+                :class="{ item: true, btm1: true, active: select[3] === 0.8 }"
+                @click="() => selectBTM(0.8)"
+              ></div>
+            </div>
+            <div class="rav">
+              <div
+                :class="{ item: true, btm2: true, active: select[3] === 0.6 }"
+                @click="() => selectBTM(0.6)"
+              ></div>
+            </div>
+            <div class="rav">
+              <div
+                :class="{ item: true, btm3: true, active: select[3] === 0.4 }"
+                @click="() => selectBTM(0.4)"
+              ></div>
+            </div>
+            <div class="rav">
+              <div
+                :class="{ item: true, btm4: true, active: select[3] === 0.2 }"
+                @click="() => selectBTM(0.2)"
+              ></div>
+            </div>
+          </div>
+          <h4>清晰度</h4>
+          <div class="racGroup">
+            <div class="rav">
+              <div
+                :class="{ item: true, active: select[4] === 0 }"
+                @click="() => selectQXD(0)"
+                style="background-color: #292b2f"
+              ></div>
+            </div>
+            <div class="rav">
+              <div
+                :class="{ item: true, active: select[4] === 1 }"
+                @click="() => selectQXD(1)"
+              >
+                <div class="qxd" style="filter: blur(1px)"></div>
+              </div>
+            </div>
+            <div class="rav">
+              <div
+                :class="{ item: true, active: select[4] === 2 }"
+                @click="() => selectQXD(2)"
+              >
+                <div class="qxd" style="filter: blur(3px)"></div>
+              </div>
+            </div>
+            <div class="rav">
+              <div
+                :class="{ item: true, active: select[4] === 3 }"
+                @click="() => selectQXD(3)"
+              >
+                <div class="qxd" style="filter: blur(6px)"></div>
+              </div>
+            </div>
+            <div class="rav">
+              <div
+                :class="{ item: true, active: select[4] === 4 }"
+                @click="() => selectQXD(4)"
+              >
+                <div class="qxd" style="filter: blur(9px)"></div>
+              </div>
+            </div>
+          </div>
+        </el-col>
+        <el-col :span="12">
+          <div style=" position: relative;width: 512px;height:512px;margin: 0 auto;">
+            <canvas
+              class="canvasBg"
+              :width="512"
+              :height="512"
+              ref="canvasBg"
+            ></canvas>
+            <canvas
+              :width="512"
+              :height="512"
+              @mousedown="mousedownFunc"
+              @mousemove="drawPen"
+              @mouseover="mouseover"
+              @mouseout="mouseout"
+              ref="canvas"
+            ></canvas>
+          </div>
+        </el-col>
+        <el-col :span="6">
+          <h4>操作</h4>
+
+          <span
+            class="dark"
+            style="
+              display: block;
+              margin-bottom: 3px;
+              width: 80%;
+              margin-left: auto;
+              margin-right: auto;
+            "
+            @click="canvas.width = canvas.width"
+          >
+            <el-icon><EditPen /></el-icon>
+            清除
+          </span>
+          <span
+            class="dark"
+            style="
+              display: block;
+              margin-bottom: 3px;
+              width: 80%;
+              margin-left: auto;
+              margin-right: auto;
+            "
+          >
+            <el-icon><EditPen /></el-icon>
+            撤销
+          </span>
+          <span
+            class="dark"
+            style="
+              display: block;
+              margin-bottom: 3px;
+              width: 80%;
+              margin-left: auto;
+              margin-right: auto;
+            "
+          >
+            <el-icon><EditPen /></el-icon>
+            重做
+          </span>
+        </el-col>
+      </el-row>
+    </div>
+  </div>
+</template>
+
+<script setup>
+import { defineProps, defineEmits, ref, onMounted } from 'vue';
+defineProps({
+  file: Object,
+});
+
+let mouseDown = false;
+const colors = [
+  'rgb(234, 153, 153)',
+  'rgb(224, 102, 102)',
+  'rgb(204, 0, 0)',
+  'rgb(153, 0, 0)',
+  'rgb(102, 0, 0)',
+  'rgb(249, 203, 156)',
+  'rgb(246, 178, 107)',
+  'rgb(230, 145, 56)',
+  'rgb(180, 95, 6)',
+  'rgb(120, 63, 4)',
+  'rgb(255, 229, 153)',
+  'rgb(255, 217, 102)',
+  'rgb(241, 194, 50)',
+  'rgb(191, 144, 0)',
+  'rgb(127, 96, 0)',
+  'rgb(182, 215, 168)',
+  'rgb(147, 196, 125)',
+  'rgb(106, 168, 79)',
+  'rgb(56, 118, 29)',
+  'rgb(39, 78, 19)',
+  'rgb(164, 194, 244)',
+  'rgb(109, 158, 235)',
+  'rgb(60, 120, 216)',
+  'rgb(17, 85, 204)',
+  'rgb(28, 69, 135)',
+  'rgb(180, 167, 214)',
+  'rgb(142, 124, 195)',
+  'rgb(103, 78, 167)',
+  'rgb(53, 28, 117)',
+  'rgb(32, 18, 77)',
+  'rgb(213, 166, 189)',
+  'rgb(194, 123, 160)',
+  'rgb(166, 77, 121)',
+  'rgb(116, 27, 71)',
+  'rgb(76, 17, 48)',
+  'rgb(255, 255, 255)',
+  'rgb(192, 192, 192)',
+  'rgb(131, 131, 131)',
+  'rgb(82, 82, 82)',
+  'rgb(0, 0, 0)',
+];
+const canvas = ref(null);
+const canvasBg = ref(null);
+let ctx = undefined;
+let ctxBg = undefined;
+const select = ref([0, 'rgb(241, 194, 50)', 6, 1, 0]);
+const emit = defineEmits(['closemain']);
+const close = () => {
+  emit('closemain');
+};
+
+// 线条集合
+const pathList = [];
+// 粗细
+const width = [6, 14, 16, 24, 30, 40, 48, 64];
+
+const selectBTM = index => {
+  select.value[3] = index;
+};
+
+const selectQXD = index => {
+  select.value[4] = index;
+};
+
+const colorSelect = color => {
+  select.value[1] = color;
+};
+
+const drawPen = ele => {
+  // 绘制
+  if (select.value[0] !== 0 || !mouseDown || !pathList.length) return;
+  pathList[pathList.length - 1].path.push({
+    x: ele.offsetX,
+    y: ele.offsetY,
+  });
+  const len = pathList[pathList.length - 1].path.length;
+  const last = {
+    offsetX: pathList[pathList.length - 1].path[len - 1].x,
+    offsetY: pathList[pathList.length - 1].path[len - 1].y,
+  };
+  ctx.quadraticCurveTo(last.offsetX, last.offsetY, ele.offsetX, ele.offsetY);
+  ctx.stroke();
+};
+
+const mousedownFunc = ele => {
+  // 鼠标按下
+  if (select.value[0] !== 0) return;
+  mouseDown = true;
+  start(ele);
+};
+
+const mouseup = () => {
+  // 鼠标松开
+  if (select.value[0] !== 0) return;
+  mouseDown = false;
+  ctx.closePath();
+  drawBg();
+};
+
+const mouseover = ele => {
+  // 鼠标移入
+  start(ele);
+};
+
+const start = ele => {
+  if (!mouseDown) return;
+  const w = canvas.value.width;
+  canvas.value.width = w;
+  ctx.beginPath();
+  ctx.lineCap = 'round';
+  ctx.lineJoin = 'round';
+  ctx.shadowBlur = select.value[4] / 4 + 1;
+  ctx.shadowColor = select.value[1];
+  ctx.strokeStyle = select.value[1];
+  console.log(select.value[3]);
+  ctx.globalAlpha = select.value[3];
+  ctx.lineWidth = select.value[2];
+  ctx.moveTo(ele.offsetX, ele.offsetY);
+  pathList.push({
+    globalAlpha: select.value[3],
+    shadowBlur: select.value[4] / 4 + 1,
+    strokeStyle: select.value[1],
+    shadowColor: select.value[1],
+    lineWidth: select.value[2],
+    lineCap: 'round',
+    lineJoin: 'round',
+    path: [{ x: ele.offsetX, y: ele.offsetY }],
+  });
+};
+
+const mouseout = () => {
+  // 鼠标移出
+  ctx.closePath();
+  drawBg();
+};
+
+//将线条绘制到背景canvas上
+const drawBg = () => {
+  console.log(pathList, ctxBg);
+};
+
+onMounted(() => {
+  ctx = canvas.value.getContext('2d');
+  ctxBg = canvasBg.value.getContext('2d');
+});
+</script>
+
+<style scoped>
+.edit {
+  position: fixed;
+  z-index: 1;
+  top: 0;
+  left: 0;
+  background-color: #00000060;
+  padding: 1em;
+  width: 100vw;
+  height: 100vh;
+}
+
+.main {
+  width: 100%;
+  height: 100%;
+  border-radius: 25px;
+  background-color: #2f3136;
+}
+h1 {
+  text-align: center;
+  padding: 10px 0;
+}
+h4 {
+  margin: 1em 0;
+}
+h4:first-child {
+  margin-top: 0;
+}
+
+.closeMain {
+  float: right;
+  margin-right: 15px;
+}
+
+.racGroup {
+  display: flex;
+}
+.rav {
+  flex: 1;
+}
+.item {
+  position: relative;
+  width: 32px;
+  height: 32px;
+  border-radius: 16px;
+  background: #292b2f;
+  cursor: pointer;
+  transition: opacity 0.25s;
+  border: 1px solid #292b2f;
+}
+
+.btm0 {
+  background: repeating-conic-gradient(
+      rgba(0, 0, 0, 0) 0%,
+      rgba(0, 0, 0, 0) 25%,
+      rgba(255, 255, 255, 0) 0%,
+      rgba(255, 255, 255, 0) 50%
+    )
+    50% center / 10px 10px;
+}
+.btm1 {
+  background: repeating-conic-gradient(
+      rgba(0, 0, 0, 0.2) 0%,
+      rgba(0, 0, 0, 0.2) 25%,
+      rgba(255, 255, 255, 0.2) 0%,
+      rgba(255, 255, 255, 0.2) 50%
+    )
+    50% center / 10px 10px;
+}
+.btm2 {
+  background: repeating-conic-gradient(
+      rgba(0, 0, 0, 0.4) 0%,
+      rgba(0, 0, 0, 0.4) 25%,
+      rgba(255, 255, 255, 0.4) 0%,
+      rgba(255, 255, 255, 0.4) 50%
+    )
+    50% center / 10px 10px;
+}
+.btm3 {
+  background: repeating-conic-gradient(
+      rgba(0, 0, 0, 0.6) 0%,
+      rgba(0, 0, 0, 0.6) 25%,
+      rgba(255, 255, 255, 0.6) 0%,
+      rgba(255, 255, 255, 0.6) 50%
+    )
+    50% center / 10px 10px;
+}
+.btm4 {
+  background: repeating-conic-gradient(
+      rgba(0, 0, 0, 0.8) 0%,
+      rgba(0, 0, 0, 0.8) 25%,
+      rgba(255, 255, 255, 0.8) 0%,
+      rgba(255, 255, 255, 0.8) 50%
+    )
+    50% center / 10px 10px;
+}
+
+.qxd {
+  background: #292b2f;
+  width: 30px;
+  height: 30px;
+  border-radius: 32px;
+}
+
+.pen {
+  border-radius: 50%;
+  background-color: #292b2f;
+  width: 6px;
+  height: 6px;
+  display: inline-block;
+  margin-right: 5px;
+}
+.colorItm {
+  width: 32px;
+  height: 32px;
+  margin: 0 20px 5px 0;
+  border-radius: 50%;
+  display: inline-block;
+  position: relative;
+}
+.colorIcon {
+  position: absolute;
+  top: 50%;
+  left: 50%;
+  transform: translate(-50%, -50%);
+}
+.dark {
+  background-color: #292b2f;
+  border-radius: 2em;
+  padding: 5px 0;
+  text-align: center;
+  width: 8em;
+  display: inline-block;
+  border: 1px solid #00000000;
+}
+.active {
+  border: 1px solid #3584e4;
+}
+
+.canvas{
+  position: absolute;
+  top: 0;
+  left: 50%;
+  transform: translateX(-50%);
+  background: #ffffff;
+}
+.canvasBg {
+  position: absolute;
+  top: 0;
+  left: 50%;
+  transform: translateX(-50%);
+  background: #ffffff;
+}
+</style>

+ 91 - 0
src/view/easyDiffusion/components/imgCom.vue

@@ -0,0 +1,91 @@
+<template>
+  <div
+    :style="{
+      'margin-right': '8px',
+      'margin-bottom': '8px',
+      width: '7em',
+      height: '10.5em',
+      'font-size': '11pt',
+      display: 'inline-block',
+    }"
+  >
+    <div :class="{imgCom: true, active: isSelect}">
+      <div class="icon">
+        <img style="width: 100%" :src="config.base.easyDiffusion + '/modifier-thumbnails/' + imgUrl" />
+        <div class="mo">
+          <el-icon :size="50"><Minus v-if="isSelect" /><Plus v-else /></el-icon>
+        </div>
+      </div>
+      <div class="centent">
+        <div class="text" v-text="text"></div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script setup>
+import config from '../../../config';
+import { defineProps } from 'vue';
+defineProps({
+  isSelect: Boolean,
+  text: String,
+  imgUrl: String,
+});
+</script>
+
+<style scoped>
+.imgCom {
+  position: absolute;
+  background-color: #16171a;
+  width: 7em;
+  height: 10.5em;
+  border-radius: 5px;
+  overflow: hidden;
+  box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2);
+  transition: all 0.2s;
+}
+.imgCom:hover {
+  transform: translate(-5px, -5px);
+  width: calc(7em + 10px);
+  height: calc(10.5em + 10px);
+}
+.icon {
+  position: relative;
+}
+
+.mo {
+  width: 100%;
+  height: 100%;
+  position: absolute;
+  display: none;
+  top: 0;
+  left: 0;
+  background-color: #00000080;
+}
+.imgCom:hover .mo {
+  display: block;
+}
+.mo .el-icon {
+  position: absolute;
+  left: 50%;
+  top: 50%;
+  transform: translate(-50%, -50%);
+}
+.active {
+  border: 2px solid rgb(179 82 255 / 94%);
+  box-shadow: 0 0px 10px 0 rgb(170 0 229 / 58%);
+}
+.centent {
+  height: 3.5em;
+  font-size: 0.9em;
+  font-weight: 100;
+  text-align: center;
+  position: relative;
+}
+.centent .text {
+  position: absolute;
+  left: 50%;
+  top: 50%;
+  transform: translate(-50%, -50%);
+}
+</style>

+ 307 - 0
src/view/easyDiffusion/components/listItem.vue

@@ -0,0 +1,307 @@
+<template>
+  <div class="listMain">
+    <div style="margin: 0 0 1em 0">
+      <el-button
+        color="#840800"
+        size="small"
+        type="danger"
+        :icon="Delete"
+        @click="dialogVisible = true"
+      >
+        删除全部
+      </el-button>
+      <el-button
+        color="rgb(75,77,81)"
+        size="small"
+        :icon="Download"
+        @click="fileZip = true"
+      >
+        下载图片
+      </el-button>
+
+      <div style="float: right">
+        <!-- <el-button
+          color="rgb(107,119,225)"
+          size="small"
+          :icon="Upload"
+        ></el-button> -->
+        <el-tooltip effect="dark" placement="bottom-start">
+          <template #content>
+            <div class="jin">
+              <div class="bg" ref="jindutiao">
+                <div class="jined" :style="'width:' + jindu / 2 + '%'">
+                  <div class="jinart"></div>
+                </div>
+                <div
+                  style="
+                    position: absolute;
+                    top: 0;
+                    right: 0;
+                    bottom: 0;
+                    left: 0;
+                  "
+                  @mousemove="mousemove"
+                  @mousedown="mousedown"
+                  @mouseout="mouseout"
+                  @mouseup="mouseup"
+                ></div>
+              </div>
+              <div
+                style="
+                  width: 3em;
+                  overflow: hidden;
+                  padding-left: 0.5em;
+                  border-radius: 3px;
+                  display: inline-block;
+                "
+              >
+                <input
+                  type="number"
+                  v-model="jindu"
+                  @input="change"
+                  :max="200"
+                />
+              </div>
+              %
+            </div>
+          </template>
+          <el-button
+            color="rgb(75,77,81)"
+            size="small"
+            :icon="Search"
+          ></el-button>
+        </el-tooltip>
+      </div>
+    </div>
+    <div class="listItem" v-for="(item, index) in downlist" :key="index">
+      <div class="listHead">
+        <h4 style="font-size: 14pt">
+          <el-icon><Grid /></el-icon>
+          {{ item.prompt || '' }}
+
+          <div class="btngroup">
+            <el-button
+              color="rgb(75,77,81)"
+              size="small"
+              :icon="Tools"
+              @click="item => setConfig(item)"
+            >
+              使用这些设置
+            </el-button>
+            <el-button
+              color="#840800"
+              size="small"
+              type="danger"
+              :icon="Delete"
+            >
+              删除
+            </el-button>
+          </div>
+        </h4>
+        <div style="margin: 0.5em 0">
+          <img
+            :src="item.init_image"
+            alt=""
+            style="width: 50px; display: inline-block; vertical-align: middle"
+          />
+          <small style="font-size: 10pt; color: #aaa">
+            尺寸: {{ item.list[0].height }}x{{ item.list[0].width }}
+          </small>
+        </div>
+
+        <div class="imagelist">
+          <div class="imgbg" v-for="(v, i) in item.list" :key="'son' + i">
+            <el-icon
+              @click="() => removeItem(i, index)"
+              :size="30"
+              class="close"
+              ><CircleClose
+            /></el-icon>
+            <el-image
+              :style="{ width: jindu / 2 + 'vh', height: jindu / 2 + 'vh' }"
+              :src="v.image"
+              :zoom-rate="1.2"
+              :preview-src-list="srcList"
+              :initial-index="1"
+              fit="cover"
+            />
+          </div>
+        </div>
+      </div>
+    </div>
+
+    <el-dialog
+      v-model="dialogVisible"
+      class="dark"
+      title="清除此窗口中的所有结果和任务?"
+      width="30%"
+      :center="true"
+    >
+      <template #footer>
+        <span class="dialog-footer">
+          <el-button @click="dialogVisible = false">是的</el-button>
+          <el-button @click="dialogVisible = false"> 取消 </el-button>
+        </span>
+      </template>
+    </el-dialog>
+    <el-dialog
+      v-model="fileZip"
+      class="dark"
+      title="清除此窗口中的所有结果和任务?"
+      width="30%"
+      :center="true"
+    >
+      <small>
+        提示:要跳过此对话框,请使用 Shift
+        键单击或禁用“设置”选项卡中的“确认危险操作”设置。
+      </small>
+      <template #footer>
+        <span class="dialog-footer">
+          <el-button color="#ffffff" @click="fileZip = false">
+            开始下载
+          </el-button>
+        </span>
+      </template>
+    </el-dialog>
+  </div>
+</template>
+
+<script setup>
+import { Delete, Download, Tools, Search } from '@element-plus/icons-vue';
+import { ref, defineProps, defineEmits } from 'vue';
+const jindutiao = ref(null);
+const dialogVisible = ref(false);
+const fileZip = ref(false);
+const jindu = ref(50);
+const emit = defineEmits(['removeItem', 'setconfig']);
+defineProps({
+  downlist: Array,
+});
+const srcList = [
+  'https://fuss10.elemecdn.com/a/3f/3302e58f9a181d2509f3dc0fa68b0jpeg.jpeg',
+  'https://fuss10.elemecdn.com/1/34/19aa98b1fcb2781c4fba33d850549jpeg.jpeg',
+];
+const change = e => {
+  console.log(e.target.value);
+  if (e.target.value > 200) {
+    jindu.value = 200;
+    return;
+  }
+  if (e.target.value < 10) {
+    jindu.value = 10;
+    return;
+  }
+};
+
+const setConfig = item => {
+    console.log(item)
+  emit('setconfig', item);
+};
+
+const removeItem = (i, index) => {
+  console.log(i, index);
+  emit('removeItem', index + '-' + i);
+};
+
+let isdown = false;
+const mousemove = e => {
+  if (!isdown) return;
+  let m = Math.ceil((e.offsetX / jindutiao.value.clientWidth) * 200);
+  m < 10 && (m = 10);
+  jindu.value = m;
+};
+
+const mousedown = () => {
+  isdown = true;
+};
+const mouseup = () => {
+  isdown = false;
+};
+const mouseout = () => {
+  isdown = false;
+};
+</script>
+
+<style scoped>
+.listMain {
+  padding: 1em 0;
+}
+.listItem {
+  border: 1px solid var(--background-color2);
+  margin-bottom: 10pt;
+  padding: 5pt;
+  border-radius: 5pt;
+  box-shadow: 0 20px 28px 0 rgba(0, 0, 0, 0.15),
+    0 6px 20px 0 rgba(0, 0, 0, 0.15);
+}
+
+.listHead {
+  line-height: 1.5em;
+}
+
+.btngroup {
+  float: right;
+}
+
+.jin {
+  height: 1.5em;
+  width: 15em;
+}
+
+.jin .bg {
+  display: inline-block;
+  margin-top: 0.5em;
+  border-radius: 2em;
+  width: 10em;
+  height: 0.6em;
+  background-color: #fff;
+  position: relative;
+}
+
+.jined {
+  border-radius: 2em;
+  height: 0.6em;
+  background-color: #0011cc;
+}
+.jinart {
+  background-color: #0011cc;
+  margin-top: -0.3em;
+  border-radius: 50%;
+  width: 1.2em;
+  height: 1.2em;
+  float: right;
+}
+
+input {
+  background-color: #202225;
+  outline: none;
+  border: none;
+  height: 1.5em;
+  width: 4em;
+  color: #fff;
+}
+
+.imgbg {
+  position: relative;
+  display: inline-block;
+  margin-right: 20px;
+  margin-bottom: 5px;
+}
+
+.close {
+  display: none;
+  position: absolute;
+  right: -15px;
+  top: -15px;
+  z-index: 1;
+  background-color: #202225;
+  border-radius: 50%;
+}
+
+.imgbg:hover .close {
+  display: block;
+}
+.imagelist {
+  padding: 0 1em;
+}
+</style>

+ 408 - 0
src/view/easyDiffusion/index.vue

@@ -0,0 +1,408 @@
+<template>
+  <div class="diffusion">
+    <h1>
+      <img
+        id="logo_img"
+        :src="config.base.easyDiffusion + '/media/images/icon-512x512.png'"
+      />
+      <span style="vertical-align: middle"> Easy Diffusion </span>
+    </h1>
+    <el-row class="body">
+      <el-col :span="6">
+        <div class="main left">
+          <p class="label">输入文字</p>
+          <el-input
+            v-model="textarea"
+            :rows="2"
+            type="textarea"
+            placeholder="请输入配置文字"
+          />
+          <p class="label">初始图片 (img2img) <small>(可选)</small></p>
+          <input
+            type="file"
+            style="position: absolute; left: -100%"
+            name="file"
+            id="file"
+            accept="image/*"
+            @change="selectfile"
+          />
+          <image-ele
+            v-if="fileInput.file"
+            :file="fileInput"
+            @close="closeFunc"
+            @draw="draw"
+          />
+          <label
+            for="file"
+            class="dark"
+            v-if="!fileInput.file"
+            style="
+              background-color: #202225;
+              padding: 3px 8px;
+              font-size: 0.9em;
+              border-radius: 3px;
+            "
+          >
+            <el-icon><FolderOpened /></el-icon>
+            选择
+          </label>
+
+          <span
+            for="file"
+            class="dark"
+            v-if="!fileInput.file"
+            style="
+              background-color: #202225;
+              padding: 3px 8px;
+              font-size: 0.9em;
+              border-radius: 3px;
+              margin-left: 3px;
+            "
+            @click="draw"
+          >
+            <el-icon><EditPen /></el-icon>
+            绘制
+          </span>
+
+          <p class="label" v-if="Object.keys(selectObject).length">
+            图像修改器
+          </p>
+          <img-com
+            v-for="(v, i) in selectObject || {}"
+            :key="i + 'select'"
+            :imgUrl="v.previews[selectType].path"
+            :text="v.modifier"
+            :isSelect="true"
+            @click="() => delectSelect(i)"
+          />
+          <el-button
+            type="primary"
+            size="large"
+            color="#4d5bff"
+            style="display: block; width: 100%; margin-top: 1em"
+            @click="createDr"
+          >
+            生成图片
+          </el-button>
+          <div class="line"></div>
+          <el-collapse>
+            <el-collapse-item>
+              <template #title>
+                <div class="collapseTitle">
+                  <el-icon size="11pt">
+                    <Plus />
+                  </el-icon>
+                  图像设置
+                </div>
+              </template>
+              <el-row>
+                <el-col :span="6">图片数量</el-col>
+                <el-col :span="18">
+                  <el-input-number
+                    class="dark"
+                    size="small"
+                    v-model="imageNum"
+                    :min="1"
+                  />
+                </el-col>
+              </el-row>
+            </el-collapse-item>
+          </el-collapse>
+          <br />
+          <el-collapse>
+            <el-collapse-item name="1">
+              <template #title>
+                <div class="collapseTitle">
+                  <el-icon size="11pt">
+                    <Plus />
+                  </el-icon>
+                  风格配置 (艺术风格、标签等)
+                </div>
+              </template>
+              <el-row>
+                <el-col :span="4">图像样式</el-col>
+                <el-col :span="8">
+                  <el-select
+                    v-model="selectType"
+                    class="dark"
+                    popper-class="dark"
+                    size="small"
+                    placeholder="Select"
+                  >
+                    <el-option label="人脸" :value="0" />
+                    <el-option label="实景" :value="1" />
+                  </el-select>
+                </el-col>
+              </el-row>
+              <el-collapse
+                v-model="activeNamesItem"
+                v-for="(item, index) in list"
+                :key="index"
+              >
+                <el-collapse-item :name="index">
+                  <template #title>
+                    <div class="collapseTitle">
+                      <el-icon size="11pt">
+                        <Plus v-if="activeNamesItem != index" />
+                        <Minus v-else />
+                      </el-icon>
+                      {{ item.category }}
+                    </div>
+                  </template>
+                  <img-com
+                    v-for="(v, i) in item.modifiers || []"
+                    :key="index + i"
+                    :imgUrl="v.previews[selectType].path"
+                    :text="v.modifier"
+                    :isSelect="!!selectObject[index + '-' + i]"
+                    @click="() => selectTypeFunc(v, i, index)"
+                  />
+                </el-collapse-item>
+              </el-collapse>
+            </el-collapse-item>
+          </el-collapse>
+        </div>
+      </el-col>
+      <el-col :span="18">
+        <div class="main right">
+          <listItem @setconfig="setconfig" @removeItem="removeItem" :downlist="downlist" />
+        </div>
+      </el-col>
+    </el-row>
+    <image-edit v-if="showImageEdit" :file="fileInput" @closemain="draw" />
+  </div>
+</template>
+
+<script setup>
+import imgCom from './components/imgCom.vue';
+import imageEle from './components/image.vue';
+import imageEdit from './components/imageEdit.vue';
+import listItem from './components/listItem.vue';
+import { getPaintingStyle, createDraw, queryDraw } from '@/api/index';
+import config from '../../config';
+import { ref } from 'vue';
+const textarea = ref('');
+const fileInput = ref({});
+const activeNamesItem = ref(-1);
+const list = ref([]);
+const selectObject = ref({});
+const showImageEdit = ref(false);
+const selectType = ref(1);
+const imageNum = ref(1);
+const ajaxList = [];
+const downlist = ref([]);
+let l = [];
+
+getPaintingStyle({}).then(r => {
+  list.value = r || [];
+});
+
+const createDr = () => {
+  const active_tags = [];
+  for (const key in selectObject.value) {
+    if (!Object.hasOwnProperty.call(selectObject.value, key)) continue;
+    active_tags.push(selectObject.value[key].modifier);
+  }
+  ajaxList.push({
+    data: {
+      prompt: textarea.value || undefined,
+      init_image: fileInput.value.fileResult,
+      active_tags,
+    },
+    imageNum: imageNum.value || 1,
+    status: 0,
+  });
+  ajax();
+};
+
+const ajax = () => {
+  if (!ajaxList.length || ajaxList[0].status > ajaxList[0].imageNum) return;
+  createDraw({
+    data: ajaxList[0].data,
+  })
+    .then(r => {
+      let t = setInterval(() => {
+        queryDraw({
+          data: r.task,
+        }).then(res => {
+          if (res.status === 'succeeded') {
+            l.push({
+              image: res.output[0].data,
+              ...res.render_request,
+              ...res.task_data,
+            });
+            ajaxList[0].status += 1;
+            if (ajaxList[0].imageNum - ajaxList[0].status <= 0) {
+                const tags = JSON.parse(JSON.stringify(selectObject.value));
+              downlist.value.push({ list: l, tags ,...ajaxList[0].data });
+              ajaxList.shift();
+              l = [];
+              // 当前图已经生成结束
+            }
+            window.clearInterval(t);
+            if (!ajaxList.length) return;
+            return ajax();
+          }
+        });
+      }, 1000);
+    })
+    .catch(() => {
+      ajaxList[0].status = 0;
+    });
+};
+
+const selectTypeFunc = (type, i, index) => {
+  if (!selectObject.value[index + '-' + i])
+    selectObject.value[index + '-' + i] = JSON.parse(JSON.stringify(type));
+  else delete selectObject.value[index + '-' + i];
+};
+
+const delectSelect = index => {
+  delete selectObject.value[index];
+};
+
+const selectfile = file => {
+  const F = file.target.files[0] || undefined;
+  if (!F) {
+    closeFunc();
+    return;
+  }
+  var reader = new FileReader();
+  reader.readAsDataURL(F); //发起异步请求
+  reader.onload = function () {
+    //读取完成后,数据保存在对象的result属性中
+    file.target.value = '';
+    fileInput.value.fileResult = this.result;
+    fileInput.value.file = F;
+  };
+};
+
+const closeFunc = () => {
+  fileInput.value.fileResult = undefined;
+  fileInput.value.file = undefined;
+};
+
+const draw = () => {
+  showImageEdit.value = !showImageEdit.value;
+};
+
+const removeItem = indexs => {
+  const i = indexs.split('-');
+  if (i.length === 1 || downlist.value[i[0]].list.length == 1) {
+    downlist.value.splice(i[0], 1);
+  } else {
+    downlist.value[i[0]].list.splice(i[1], 1);
+  }
+};
+
+const setconfig = item => {
+    console.log(item)
+}
+</script>
+
+<style scoped>
+.diffusion {
+  background-color: #202225;
+  color: #eee;
+  width: 100vw;
+  height: 100%;
+}
+
+.diffusion > h1 {
+  padding: 12px 0;
+  font-size: 2em;
+}
+
+.body {
+  background-color: #36393f;
+  width: 100%;
+  height: 100%;
+  overflow-y: auto;
+  padding-bottom: 2em;
+}
+
+#logo_img {
+  width: 32px;
+  height: 32px;
+}
+
+.main {
+  padding: 0 16px 16px 16px;
+}
+
+.label {
+  font-size: 10pt;
+  font-weight: 700;
+  height: 1.5em;
+  line-height: 1.5em;
+  padding-left: 3px;
+  margin-top: 12px;
+}
+
+.line {
+  margin: 16px 0;
+  height: 1px;
+  background-color: #202225;
+}
+
+.collapseTitle {
+  padding-left: 5px;
+  font-weight: 700;
+  font-size: 11pt;
+}
+
+.right {
+  padding: 1em;
+  width: 100%;
+}
+</style>
+
+<style>
+.diffusion .el-textarea__inner {
+  background-color: #202225;
+  border: 2px solid #2f3136;
+  box-shadow: none;
+  color: #fff;
+}
+.diffusion .el-textarea__inner:focus {
+  outline: 2px solid #4d5bff;
+}
+.diffusion .el-collapse-item__header,
+.diffusion .el-collapse,
+.diffusion .el-collapse-item__wrap,
+.el-popper.is-light,
+.el-select__popper.el-popper .el-popper__arrow::before {
+  background-color: #2f3136;
+  color: #eee;
+  border-color: #2f3136;
+}
+.dark,
+.dark * {
+  background-color: #202225;
+  color: #eee;
+  border-color: #2f3136;
+}
+.dark .el-select-dropdown__item.hover,
+.dark .el-select-dropdown__item:hover {
+  background-color: #ffffff60;
+}
+.dark .el-select-dropdown__item.hover span {
+  background-color: #ffffff00;
+}
+.el-popper.is-light,
+.el-select__popper.el-popper .el-popper__arrow::before {
+  box-shadow: 0 0 5px #ffffff80;
+}
+.diffusion .el-collapse-item__content {
+  color: #eee;
+  padding-left: 15px;
+}
+.diffusion .el-collapse {
+  border-radius: 5px;
+}
+
+.diffusion .el-input__wrapper {
+  box-shadow: 0 0 0 1px #202225 inset;
+  background-color: #202225;
+}
+</style>

+ 344 - 0
src/view/qh/index.vue

@@ -0,0 +1,344 @@
+<template>
+  <h2 style="text-align: center; margin: 18px 0;font-size: 25px;font-family: 'Source Sans Pro', 'ui-sans-serif', 'system-ui', sans-serif;">ChatGLM</h2>
+  <div class="chatbg tool">
+    <div
+      style="float: left; font-size: 12px;padding: 4px 8px;border-right: 1px solid #e5e7eb;border-bottom: 1px solid #e5e7eb;border-bottom-right-radius: 7px;"
+    >
+        <el-icon><ChatDotRound /></el-icon>
+      Chatbot
+    </div>
+    <div style="clear: both"></div>
+    <div class="chatGptChat" ref="chatEle">
+      <div v-for="(item, index) in chat" :key="index">
+        <div v-if="item.type === 'robot'" class="chat">
+          <div class="cahtText">
+            <textShow :text="item.text" />
+          </div>
+        </div>
+        <div v-if="item.type === 'user'" class="chat chatRight">
+          <div class="cahtText" v-text="item.text"></div>
+        </div>
+        <div style="clear: both"></div>
+      </div>
+    </div>
+  </div>
+  <div class="tool">
+    <el-row :gutter="15">
+      <el-col :span="16">
+        <el-input
+          v-model="inputText"
+          :rows="10"
+          type="textarea"
+          placeholder="请输入您的问题"
+        />
+        <el-button
+          style="
+            width: 100%;
+            color: #ea580c;
+            font-weight: 600;
+            font-size: 16px;
+            margin-top: 1em;
+          "
+          :loading="load"
+          color="#fdba74"
+          size="large"
+          @click="saveText"
+        >
+          提交
+        </el-button>
+      </el-col>
+      <el-col :span="8">
+        <el-button
+          style="width: 100%; margin-bottom: 1em"
+          color="#f3f4f6"
+          size="large"
+          @click="chat = []"
+        >
+          清除历史记录
+        </el-button>
+        <div class="tools">
+          <div class="toolsItem">
+            最大长度
+            <el-input-number
+              style="float: right"
+              v-model="num"
+              :min="0"
+              :max="4096"
+              @change="change"
+              controls-position="right"
+              size="small"
+            />
+            <input
+              :value="0"
+              type="range"
+              id="num"
+              style="width: 100%"
+              @input="input"
+            />
+          </div>
+          <div class="line"></div>
+          <div class="toolsItem">
+            Top P
+            <el-input-number
+              style="float: right"
+              v-model="num1"
+              :min="0"
+              :max="1"
+              :step="0.01"
+              @change="change1"
+              controls-position="right"
+              size="small"
+            />
+            <input
+              :value="0"
+              type="range"
+              id="num1"
+              style="width: 100%"
+              @input="input1"
+            />
+          </div>
+          <div class="line"></div>
+          <div class="toolsItem">
+            感情度
+            <el-input-number
+              style="float: right"
+              v-model="num2"
+              :min="0"
+              :max="1"
+              :step="0.01"
+              @change="change2"
+              controls-position="right"
+              size="small"
+            />
+            <input
+              :value="0"
+              type="range"
+              id="num2"
+              style="width: 100%"
+              @input="input2"
+            />
+          </div>
+        </div>
+      </el-col>
+    </el-row>
+  </div>
+</template>
+<script setup>
+import { createSocket } from '@/utils/socket';
+import { ChatDotRound } from '@element-plus/icons-vue';
+import { ref, nextTick, onMounted } from 'vue';
+
+import textShow from './textShow.vue';
+
+const chat = ref([]);
+const load = ref(false);
+const chatEle = ref(null);
+const inputText = ref('');
+const num = ref(2048);
+const num1 = ref(0.7);
+const num2 = ref(0.95);
+let chatlist = ref([]);
+
+function saveText() {
+  load.value = true;
+  nextTick(() => {
+    // 滚动到最底层
+    if (chatEle.value.scrollHeight > chatEle.value.clientHeight) {
+      chatEle.value.scrollTop = chatEle.value.scrollHeight;
+    }
+  });
+  chat.value.push({
+    text: inputText.value,
+    type: 'user',
+  });
+  chat.value.push({
+    text: '',
+    type: 'robot',
+  });
+  const idnex = chatlist.value.length === 0 ? 0 : chatlist.value.length - 1;
+  const t = inputText.value;
+  inputText.value = '';
+  createSocket(ws => {
+    const data = ws.data || {};
+    let session_hash = Math.random().toString(36).substring(2);
+    if (data.msg == 'send_hash')
+      ws.ws.send(
+        JSON.stringify({
+          fn_index: idnex,
+          session_hash,
+        })
+      );
+    if (data.msg == 'send_data')
+      ws.ws.send(
+        JSON.stringify({
+          fn_index: idnex,
+          data: [t, chatlist.value, num.value, num1.value, num2.value, null],
+          event_data: null,
+          session_hash,
+        })
+      );
+    if (data.msg == 'process_generating') {
+      const li = data.output.data[0] || [];
+      const olist = li[li.length - 1];
+      chat.value[chat.value.length - 1].text = olist[1];
+    }
+    if (data.msg == 'process_completed') {
+      chatlist.value = data.output.data[0] || [];
+      load.value = false;
+    }
+  });
+}
+
+const input = e => {
+  num.value = Math.floor((e.target.value / 100) * 4096);
+};
+
+const change = () => {
+  document.querySelector('#num').value =
+    ((num.value / 4096) * 100).toFixed(0) - 0;
+};
+const input1 = e => {
+  num1.value = e.target.value / 100;
+};
+
+const change1 = () => {
+  document.querySelector('#num1').value = num1.value * 100;
+};
+const input2 = e => {
+  num2.value = e.target.value / 100;
+};
+
+const change2 = () => {
+  document.querySelector('#num2').value = num2.value * 100;
+};
+onMounted(() => {
+  change();
+  change1();
+  change2();
+});
+</script>
+<style>
+.chatbg {
+  min-height: 3em;
+  border-radius: 8px;
+  overflow: hidden;
+  border: 1px solid #e5e7eb;
+}
+.chatGptChat {
+  padding-top: 5px;
+  box-sizing: border-box;
+  font-weight: 500;
+  margin: 1em auto 0 auto;
+  width: 100%;
+  height: auto;
+  max-height: 480px;
+  position: relative;
+  overflow-y: auto;
+  padding: 0 1em;
+}
+
+.chatGptChat .van-field {
+  background-color: #eee;
+  position: fixed;
+  padding-bottom: 1em;
+  bottom: 0;
+  left: 0;
+}
+
+.chatGptChat .van-field .van-field__control {
+  background-color: #ffffff;
+  padding: 0.4em;
+}
+
+.chatGptChat .chat {
+  padding: 8px 12px;
+  background-color: #f4f4f680;
+  border-radius: 22px;
+  border: 1px solid #e5e7eb;
+  width: 98%;
+  border-bottom-left-radius: 0;
+  margin-bottom: 1em;
+}
+
+.chatGptChat .chat .cahtText {
+  text-align: left;
+  line-height: 1.5em;
+  border-radius: 5px;
+  padding: 0.5em;
+  display: inline-block;
+  max-width: calc(100% - 40px);
+  vertical-align: middle;
+}
+
+.chatGptChat .chat .van-image {
+  vertical-align: top;
+}
+
+.chatGptChat .chat .cahtText:not(:last-child),
+.chatGptChat .chat .van-image:not(:last-child) {
+  margin-right: 5px;
+}
+.chatGptChat .chatRight {
+  border-color: #fdba74;
+  background-color: #fff7ed;
+  border-bottom-right-radius: 0;
+  border-bottom-left-radius: 22px;
+  float: right;
+}
+
+.tool {
+  width: 100%;
+  margin: 1em auto 0 auto;
+}
+
+@media (min-width: 640px) {
+  .chatGptChat,
+  .tool {
+    max-width: 608px;
+  }
+}
+
+@media (min-width: 768px) {
+  .chatGptChat,
+  .tool {
+    max-width: 736px;
+  }
+}
+
+@media (min-width: 1024px) {
+  .chatGptChat,
+  .tool {
+    max-width: 992px;
+  }
+}
+
+@media (min-width: 1280px) {
+  .chatGptChat,
+  .tool {
+    max-width: 1248px;
+  }
+}
+
+@media (min-width: 1536px) {
+  .chatGptChat,
+  .tool {
+    max-width: 1504px;
+  }
+}
+
+.tools {
+  border-radius: 8px;
+  border: 1px solid #e5e7eb;
+  line-height: 1.5em;
+}
+.toolsItem {
+  padding: 10px 12px;
+  font-size: 14px;
+  font-weight: 400;
+  color: #6b7280;
+}
+.line {
+  height: 1px;
+  background-color: #e5e7eb;
+}
+</style>

+ 20 - 0
src/view/qh/textShow.vue

@@ -0,0 +1,20 @@
+<template>
+  <div class="text" v-html="text"></div>
+</template>
+<script setup>
+import { defineProps } from 'vue';
+
+defineProps({
+  text: String,
+});
+
+// const li = prop.text.replace(/\n/g, '^').split('');
+// let index = 0;
+// const EleText = ref(li[0] || '');
+// let t = window.setInterval(() => {
+//   if (index++ >= li.length) return window.clearInterval(t);
+//   if (li[index] === '^') EleText.value += '<br />';
+//   else EleText.value += li[index] || '';
+// }, 50);
+</script>
+<style lang="scss"></style>