|
@@ -0,0 +1,421 @@
|
|
|
+<template>
|
|
|
+ <div class="ai-chat-container">
|
|
|
+ <div class="chat-body" ref="chatBody">
|
|
|
+ <div class="message ai-message">
|
|
|
+ <div class="message-header">
|
|
|
+ <div class="message-header-img">
|
|
|
+ <avatar :user="{
|
|
|
+ portrait:'https://file-node.oss-cn-shanghai.aliyuncs.com/youji/f9617c7f80da485cb3cc72b6accc62ed'
|
|
|
+ }"></avatar>
|
|
|
+ </div>
|
|
|
+ <div class="message-header-label">AI网站生成助手</div>
|
|
|
+ </div>
|
|
|
+ <div class="message-content">
|
|
|
+ <p>您好!我是AI网站生成助手,请点击按钮生成企业官网。</p>
|
|
|
+ <el-button size="mini" @click="visible = true" :disabled="messageList.length > 0"
|
|
|
+ style="margin-top: 10px;">
|
|
|
+ 点击生成企业官网
|
|
|
+ </el-button>
|
|
|
+ </div>
|
|
|
+ <div class="message-time">
|
|
|
+ {{ formatTime(messageList.length === 0? new Date():messageList[0].date) }}
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div v-for="(message, index) in messageList" :key="index">
|
|
|
+ <div class="message user-message">
|
|
|
+ <div class="message-header">
|
|
|
+ <div class="message-header-img">
|
|
|
+ <avatar :user="user"></avatar>
|
|
|
+ </div>
|
|
|
+ <div class="message-header-label">{{user.userName}}</div>
|
|
|
+ </div>
|
|
|
+ <div class="message-content" v-html="returnUserInputs(message.inputs)"></div>
|
|
|
+ <div class="message-time">
|
|
|
+ {{ formatTime(message.date) }}
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="message ai-message" v-if="!loading || message.status !== 'running'"
|
|
|
+ style="margin-top: 15px;">
|
|
|
+ <div class="message-header">
|
|
|
+ <div class="message-header-img">
|
|
|
+ <avatar :user="{
|
|
|
+ portrait:'https://file-node.oss-cn-shanghai.aliyuncs.com/youji/f9617c7f80da485cb3cc72b6accc62ed'
|
|
|
+ }"></avatar>
|
|
|
+ </div>
|
|
|
+ <div class="message-header-label">AI网站生成助手</div>
|
|
|
+ </div>
|
|
|
+ <div class="message-content">
|
|
|
+ <div v-if="message.status === 'error'">
|
|
|
+ <p>服务器繁忙,请稍后重试</p>
|
|
|
+ <el-button size="mini" @click="reload(message)" :disabled="index !== messageList.length - 1"
|
|
|
+ style="margin-top: 10px;">
|
|
|
+ 点击重试
|
|
|
+ </el-button>
|
|
|
+ </div>
|
|
|
+ <div class="ai-website-boxs" v-else>
|
|
|
+ <div class="ai-website-box" v-html="extractContentBetween(message.outputs)"></div>
|
|
|
+ <div class="ai-website-mask">
|
|
|
+ <el-button size="mini" type="primary" @click="preview(message)">
|
|
|
+ 预览
|
|
|
+ </el-button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="message-time">
|
|
|
+ {{ formatTime(message.endDate) }}
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div v-if="loading" class="typing-indicator">
|
|
|
+ <div class="typing-dot"></div>
|
|
|
+ <div class="typing-dot"></div>
|
|
|
+ <div class="typing-dot"></div>
|
|
|
+ <span style="margin-left: 10px;">思考中...</span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="input-container">
|
|
|
+ <el-input type="textarea" v-model="newMessage" placeholder="给AI发送消息" resize="none" rows="2"
|
|
|
+ :autosize="{ minRows: 2, maxRows: 6 }" :disabled="!htmlCode">
|
|
|
+ </el-input>
|
|
|
+ <div class="input-button">
|
|
|
+ <el-button type="primary" size="mini" @click="sendMessage"
|
|
|
+ :disabled="!newMessage.trim() || loading || !htmlCode">
|
|
|
+ 发送
|
|
|
+ </el-button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <el-dialog :close-on-click-modal="false" title="生成网站" :visible.sync="visible" width="900px"
|
|
|
+ :append-to-body="true">
|
|
|
+ <website-form v-if="visible" @callback="callback"></website-form>
|
|
|
+ </el-dialog>
|
|
|
+ </div>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script>
|
|
|
+ import websiteForm from '@/components/work/common/websiteForm.vue'
|
|
|
+ import {
|
|
|
+ getAIDataStatus,
|
|
|
+ getAIDataList,
|
|
|
+ createAIData
|
|
|
+ } from '@/api/ai'
|
|
|
+ export default {
|
|
|
+ components: {
|
|
|
+ websiteForm
|
|
|
+ },
|
|
|
+ data() {
|
|
|
+ return {
|
|
|
+ messageList: [],
|
|
|
+ newMessage: '',
|
|
|
+ loading: false,
|
|
|
+ darkMode: false,
|
|
|
+ visible: false,
|
|
|
+ timer: null,
|
|
|
+ formData: {
|
|
|
+ site_name: '网站名称',
|
|
|
+ site_type: '网站类型',
|
|
|
+ color_scheme: '布局风格',
|
|
|
+ layout_style: '主色调',
|
|
|
+ sections: '主要模块',
|
|
|
+ logo_url: 'LOGO'
|
|
|
+ },
|
|
|
+ user: {},
|
|
|
+ htmlCode: ''
|
|
|
+ }
|
|
|
+ },
|
|
|
+ mounted() {
|
|
|
+ this.user = this.$store.getters.user;
|
|
|
+ this.scrollToBottom();
|
|
|
+ this.init()
|
|
|
+ },
|
|
|
+ watch: {
|
|
|
+ messageList: {
|
|
|
+ deep: true,
|
|
|
+ handler() {
|
|
|
+ this.$nextTick(() => {
|
|
|
+ this.scrollToBottom();
|
|
|
+ });
|
|
|
+ }
|
|
|
+ }
|
|
|
+ },
|
|
|
+ beforeDestroy() {
|
|
|
+ this.clearTimer();
|
|
|
+ },
|
|
|
+ methods: {
|
|
|
+ init() {
|
|
|
+ getAIDataList({
|
|
|
+ userId: this.$store.getters.user.userId
|
|
|
+ }).then(res => {
|
|
|
+ if (res.state) {
|
|
|
+ this.messageList = res.data;
|
|
|
+ if (this.messageList.length > 0) {
|
|
|
+ let data = this.messageList[this.messageList.length - 1];
|
|
|
+ if (data.status === 'running') this.initStaus(data.simpleUUID);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ })
|
|
|
+ },
|
|
|
+ initStaus(simpleUUlD) {
|
|
|
+ this.loading = true;
|
|
|
+ this.timer = setInterval(() => {
|
|
|
+ getAIDataStatus(simpleUUlD).then(res => {
|
|
|
+ if (res.state) {
|
|
|
+ let data = res.data;
|
|
|
+ if (!data) return;
|
|
|
+ if (data.status === 'succeeded' || data.status === 'error') {
|
|
|
+ this.clearTimer();
|
|
|
+ this.loading = false;
|
|
|
+ this.messageList[this.messageList.length - 1] = data;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ })
|
|
|
+ }, 10000)
|
|
|
+ },
|
|
|
+ returnUserInputs(obj) {
|
|
|
+ if (!obj) return '';
|
|
|
+ let html = '';
|
|
|
+ let objs = JSON.parse(obj);
|
|
|
+ if (objs['site_name']) {
|
|
|
+ for (let key in objs) {
|
|
|
+ html += this.formData[key] + ':' + objs[key] + "<br />";
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ html = objs.adjustments;
|
|
|
+ }
|
|
|
+ return html;
|
|
|
+ },
|
|
|
+ clearTimer() {
|
|
|
+ if (!this.timer) return;
|
|
|
+ clearInterval(this.timer);
|
|
|
+ this.timer = null;
|
|
|
+ },
|
|
|
+ formatTime(date) {
|
|
|
+ return new Date(date).toLocaleTimeString([], {
|
|
|
+ hour: '2-digit',
|
|
|
+ minute: '2-digit'
|
|
|
+ });
|
|
|
+ },
|
|
|
+ scrollToBottom() {
|
|
|
+ const container = this.$refs.chatBody;
|
|
|
+ if (container) {
|
|
|
+ container.scrollTop = container.scrollHeight;
|
|
|
+ }
|
|
|
+ },
|
|
|
+ callback(type) {
|
|
|
+ this.visible = false;
|
|
|
+ if (type === 'init') this.init();
|
|
|
+ },
|
|
|
+ sendMessage() {
|
|
|
+ createAIData(3, {
|
|
|
+ detailed_html: this.htmlCode,
|
|
|
+ adjustments: this.newMessage
|
|
|
+ }).then(this.successFunc);
|
|
|
+ },
|
|
|
+ reload(message) {
|
|
|
+ if (!this.htmlCode) {
|
|
|
+ createAIData(3, JSON.parse(message.inputs)).then(this.successFunc);
|
|
|
+ } else {
|
|
|
+ createAIData(1, JSON.parse(message.inputs)).then(this.successFunc);
|
|
|
+ }
|
|
|
+ },
|
|
|
+ successFunc(res) {
|
|
|
+ if (res.state) {
|
|
|
+ this.newMessage = '';
|
|
|
+ this.$message.success('操作成功');
|
|
|
+ this.init();
|
|
|
+ }
|
|
|
+ },
|
|
|
+ extractContentBetween(html) {
|
|
|
+ if (!html) return '';
|
|
|
+ // 使用正则表达式匹配 ```html 和 ``` 之间的内容
|
|
|
+ let regex = /```html([\s\S]*?)```/;
|
|
|
+ let match = html.match(regex);
|
|
|
+ let htmls = match && match[1] ? match[1].trim() : ''
|
|
|
+ return htmls;
|
|
|
+ },
|
|
|
+ preview(message) {
|
|
|
+ this.htmlCode = this.extractContentBetween(message.outputs);
|
|
|
+ this.$emit('previewWebSite', this.htmlCode);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+</script>
|
|
|
+
|
|
|
+<style scoped lang="scss">
|
|
|
+ .ai-chat-container {
|
|
|
+ width: 100%;
|
|
|
+ max-width: 900px;
|
|
|
+ height: 100%;
|
|
|
+ background: white;
|
|
|
+ border-radius: 16px;
|
|
|
+ box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);
|
|
|
+ overflow: hidden;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ }
|
|
|
+
|
|
|
+ .ai-website-boxs {
|
|
|
+ width: 185px;
|
|
|
+ height: 108px;
|
|
|
+ overflow: hidden;
|
|
|
+ cursor: pointer;
|
|
|
+ position: relative;
|
|
|
+
|
|
|
+ &:hover .ai-website-mask {
|
|
|
+ display: flex;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .ai-website-mask {
|
|
|
+ background: rgba(0, 0, 0, 0.1);
|
|
|
+ position: absolute;
|
|
|
+ top: 0;
|
|
|
+ left: 0;
|
|
|
+ right: 0;
|
|
|
+ bottom: 0;
|
|
|
+ display: none;
|
|
|
+ justify-content: center;
|
|
|
+ align-items: center;
|
|
|
+ }
|
|
|
+
|
|
|
+ .ai-website-box {
|
|
|
+ width: 1850px;
|
|
|
+ height: 1080px;
|
|
|
+ overflow: hidden;
|
|
|
+ transform: scale(0.1);
|
|
|
+ transform-origin: 0 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* 聊天内容区域 */
|
|
|
+ .chat-body {
|
|
|
+ flex: 1;
|
|
|
+ padding: 20px;
|
|
|
+ overflow-y: auto;
|
|
|
+ background: #f8fafc;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ gap: 15px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .message {
|
|
|
+ max-width: 90%;
|
|
|
+ padding: 12px 16px;
|
|
|
+ border-radius: 18px;
|
|
|
+ position: relative;
|
|
|
+ animation: fadeIn 0.3s ease;
|
|
|
+ line-height: 1.5;
|
|
|
+ }
|
|
|
+
|
|
|
+ .user-message {
|
|
|
+ background: $--color-primary;
|
|
|
+ color: white;
|
|
|
+ margin-left: auto;
|
|
|
+ border-bottom-right-radius: 4px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .ai-message {
|
|
|
+ background: #ffffff;
|
|
|
+ border: 1px solid #e9ecef;
|
|
|
+ margin-right: auto;
|
|
|
+ border-bottom-left-radius: 4px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .message-header {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ margin-bottom: 5px;
|
|
|
+ font-weight: 500;
|
|
|
+ }
|
|
|
+
|
|
|
+ .message-header-img {
|
|
|
+ width: 30px;
|
|
|
+ height: 30px;
|
|
|
+ border-radius: 30px;
|
|
|
+ overflow: hidden;
|
|
|
+ margin-right: 5px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .message-header-label {
|
|
|
+ flex: 1;
|
|
|
+ width: 0;
|
|
|
+ overflow: hidden;
|
|
|
+ }
|
|
|
+
|
|
|
+ .message-time {
|
|
|
+ font-size: 0.7rem;
|
|
|
+ opacity: 0.7;
|
|
|
+ margin-top: 5px;
|
|
|
+ text-align: right;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* 加载指示器 */
|
|
|
+ .typing-indicator {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ padding: 12px 16px;
|
|
|
+ background: white;
|
|
|
+ border: 1px solid #e9ecef;
|
|
|
+ border-radius: 18px;
|
|
|
+ width: fit-content;
|
|
|
+ margin-bottom: 15px;
|
|
|
+ border-bottom-left-radius: 4px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .typing-dot {
|
|
|
+ width: 8px;
|
|
|
+ height: 8px;
|
|
|
+ background: #6c757d;
|
|
|
+ border-radius: 50%;
|
|
|
+ margin: 0 3px;
|
|
|
+ animation: typing 1.4s infinite;
|
|
|
+ }
|
|
|
+
|
|
|
+ .typing-dot:nth-child(1) {
|
|
|
+ animation-delay: 0s;
|
|
|
+ }
|
|
|
+
|
|
|
+ .typing-dot:nth-child(2) {
|
|
|
+ animation-delay: 0.2s;
|
|
|
+ }
|
|
|
+
|
|
|
+ .typing-dot:nth-child(3) {
|
|
|
+ animation-delay: 0.4s;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* 输入区域 */
|
|
|
+ .input-container {
|
|
|
+ padding: 10px;
|
|
|
+ background: #f8f9fa;
|
|
|
+ border-top: 1px solid #e9ecef;
|
|
|
+ }
|
|
|
+
|
|
|
+ .input-button {
|
|
|
+ margin-top: 10px;
|
|
|
+ text-align: right;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* 动画 */
|
|
|
+ @keyframes fadeIn {
|
|
|
+ from {
|
|
|
+ opacity: 0;
|
|
|
+ transform: translateY(10px);
|
|
|
+ }
|
|
|
+
|
|
|
+ to {
|
|
|
+ opacity: 1;
|
|
|
+ transform: translateY(0);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ @keyframes typing {
|
|
|
+
|
|
|
+ 0%,
|
|
|
+ 60%,
|
|
|
+ 100% {
|
|
|
+ transform: translateY(0);
|
|
|
+ }
|
|
|
+
|
|
|
+ 30% {
|
|
|
+ transform: translateY(-5px);
|
|
|
+ }
|
|
|
+ }
|
|
|
+</style>
|