Compare commits
7 Commits
540229cd9b
...
3942f894b0
Author | SHA1 | Date |
---|---|---|
朱浩 | 3942f894b0 | |
朱浩 | ec8f7b5727 | |
yangws | 1a447b30fb | |
小杨 | 1c9a3762ef | |
lyc | ea88aa959d | |
lyc | d534340f88 | |
lyc | ad2c37be82 |
|
@ -43,6 +43,11 @@ export default defineConfig({
|
|||
changeOrigin: true,
|
||||
rewrite: (p) => p.replace(/^\/baidubce/, '')
|
||||
},
|
||||
'/parth': {
|
||||
target: 'https://zwapi.xfyun.cn', // 第三方API的地址
|
||||
changeOrigin: true, // 改变请求的起源
|
||||
rewrite: (path) => path.replace(/^\/parth/, '') // 重写路径
|
||||
},
|
||||
},
|
||||
},
|
||||
plugins: [vue(), WindiCSS()],
|
||||
|
|
|
@ -10,12 +10,12 @@
|
|||
<li class="flex" :class="[activeId == menu.path ? 'active-li' : '', menu.disabled ? 'disabled' : '']"
|
||||
v-for="menu in headerMenus" :key="menu.id" @click="clickMenu(menu)">
|
||||
<div class="icon-box">
|
||||
|
||||
|
||||
|
||||
|
||||
<svg class="icon iconfont" aria-hidden="true">
|
||||
<use :xlink:href="menu.icon"></use>
|
||||
</svg>
|
||||
|
||||
|
||||
</div>
|
||||
<span class="text">{{ menu.name }}</span>
|
||||
</li>
|
||||
|
@ -80,8 +80,8 @@ import { updateUserInfo } from '@/api/system/user'
|
|||
import logoIco from '@/assets/images/logo.png'
|
||||
import { listEvaluation } from '@/api/classManage/index'
|
||||
import { sessionStore } from '@/utils/store'
|
||||
import Chat from '@/utils/chat' // im 登录初始化
|
||||
if (!Chat.imChat) Chat.init()
|
||||
// import Chat from '@/utils/chat' // im 登录初始化
|
||||
// if (!Chat.imChat) Chat.init()
|
||||
|
||||
let homeTitle = ref(import.meta.env.VITE_APP_TITLE)
|
||||
const { ipcRenderer } = window.electron || {}
|
||||
|
@ -145,7 +145,7 @@ function handleCommand(command) {
|
|||
break
|
||||
case 'logout':
|
||||
logout()
|
||||
Chat?.logout() // im 退出登录
|
||||
// Chat?.logout() // im 退出登录
|
||||
break
|
||||
default:
|
||||
break
|
||||
|
@ -161,7 +161,9 @@ function logout() {
|
|||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
})
|
||||
.then(() => {
|
||||
.then(async () => {
|
||||
const Chat = (await import('@/utils/chat')).default
|
||||
if (!!Chat.imChat) Chat.logout()
|
||||
userStore
|
||||
.logOut()
|
||||
.then(() => {
|
||||
|
@ -172,8 +174,7 @@ function logout() {
|
|||
// router.replace('/login')
|
||||
ipcRenderer && ipcRenderer.send('openLoginWindow')
|
||||
})
|
||||
})
|
||||
.catch(() => { })
|
||||
}).catch(()=>{})
|
||||
}
|
||||
|
||||
const emits = defineEmits(['setLayout'])
|
||||
|
@ -183,10 +184,10 @@ function setLayout() {
|
|||
// 切换学科
|
||||
const changeSubject = async (command) =>{
|
||||
let sessionSubject = {
|
||||
bookList: null,
|
||||
curBook: null,
|
||||
curNode: null,
|
||||
defaultExpandedKeys: [],
|
||||
bookList: null,
|
||||
curBook: null,
|
||||
curNode: null,
|
||||
defaultExpandedKeys: [],
|
||||
subjectTree: []
|
||||
}
|
||||
sessionStore.set( 'subject', sessionSubject)
|
||||
|
|
|
@ -0,0 +1,80 @@
|
|||
import axios from "axios";
|
||||
import { getSignature } from "./index";
|
||||
|
||||
let appId = "01ec9aa3";
|
||||
let secret = "M2QxMDAxMjYyYTEzODMwMGRkZTQ4NmUy";
|
||||
let timestamp = Math.floor(Date.now() / 1000);
|
||||
let signature = getSignature(appId, secret, timestamp);
|
||||
|
||||
export function text2Text(data) {
|
||||
return axios({
|
||||
url: "v1/chat/completions",
|
||||
method: "post",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
Authorization: "Bearer yjCwlZCeUtBYvjHAQZdk:FtWNmJSWcZMCmTBQZfoH",
|
||||
Accept: "*/*",
|
||||
},
|
||||
data: {
|
||||
model: "4.0Ultra",
|
||||
messages: data,
|
||||
stream: false,
|
||||
},
|
||||
});
|
||||
}
|
||||
export function uploadDoc(data) {
|
||||
return axios({
|
||||
url: "openapi/v1/file/upload",
|
||||
method: "post",
|
||||
headers: {
|
||||
"Content-Type": "multipart/form-data",
|
||||
appId: appId,
|
||||
timestamp: timestamp,
|
||||
signature: signature,
|
||||
},
|
||||
data: data,
|
||||
});
|
||||
}
|
||||
export function queryDocStatus(data) {
|
||||
return axios({
|
||||
url: "openapi/v1/file/status",
|
||||
method: "post",
|
||||
headers: {
|
||||
"Content-Type": "application/form-data",
|
||||
appId: appId,
|
||||
timestamp: timestamp,
|
||||
signature: signature,
|
||||
},
|
||||
data: data
|
||||
});
|
||||
}
|
||||
export function chatByDoc(fileId, data) {
|
||||
const wsUrl = `wss://chatdoc.xfyun.cn/openapi/chat?fileId=${fileId}&appId=${appId}×tamp=${timestamp}&signature=${signature}`;
|
||||
|
||||
const ws = new WebSocket(wsUrl);
|
||||
|
||||
ws.onopen = () => {
|
||||
const messageBody = {
|
||||
fileIds: [fileId],
|
||||
messages: data,
|
||||
chatExtends: {
|
||||
wikiPromptTpl:
|
||||
"请将以下内容作为已知信息:\n<wikicontent>\n请根据以上内容回答用户的问题。\n问题:<wikiquestion>\n回答:",
|
||||
wikiFilterScore: 0.82,
|
||||
temperature: 0.5,
|
||||
sparkWhenWithoutEmbedding: false,
|
||||
},
|
||||
};
|
||||
|
||||
ws.send(JSON.stringify(messageBody));
|
||||
};
|
||||
|
||||
ws.onmessage = (event) => {
|
||||
const response = JSON.parse(event.data);
|
||||
console.log("WebSocket 消息:", response);
|
||||
};
|
||||
|
||||
ws.onerror = (error) => {
|
||||
console.error("WebSocket 错误:", error);
|
||||
};
|
||||
}
|
|
@ -20,9 +20,9 @@ export class Chat {
|
|||
}
|
||||
/**
|
||||
* 初始化 获取IM签名
|
||||
* @param {*} isInit : 是否初始化IM
|
||||
* @param {*} isInit : 是否初始化IM
|
||||
* @param {*} isLogin : 是否登录IM
|
||||
* @param {*} callback: 监听消息回调函数
|
||||
* @param {*} callback: 监听消息回调函数
|
||||
* @returns Promise<ImChat>
|
||||
*/
|
||||
async init(isInit = true, isLogin = true, callback) {
|
||||
|
@ -69,7 +69,6 @@ export class Chat {
|
|||
async logout() {
|
||||
if (!this.imChat) return
|
||||
await this.imChat?.logout()
|
||||
imChat = null
|
||||
this.imChat = null
|
||||
}
|
||||
// 发群消息
|
||||
|
@ -95,4 +94,4 @@ export class Chat {
|
|||
}
|
||||
}
|
||||
|
||||
export default new Chat()
|
||||
export default new Chat()
|
||||
|
|
|
@ -0,0 +1,33 @@
|
|||
import CryptoJS from 'crypto-js';
|
||||
|
||||
// 生成签名的主方法
|
||||
export function getSignature(appId, secret, ts) {
|
||||
try {
|
||||
const auth = md5(appId + ts);
|
||||
return hmacSHA1Encrypt(auth, secret);
|
||||
} catch (error) {
|
||||
console.error('Error generating signature:', error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// MD5 加密
|
||||
function md5(cipherText) {
|
||||
try {
|
||||
return CryptoJS.MD5(cipherText).toString(); // 使用 CryptoJS 进行 MD5 加密
|
||||
} catch (error) {
|
||||
console.error('Error in MD5 hashing:', error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// HMAC-SHA1 加密
|
||||
function hmacSHA1Encrypt(encryptText, encryptKey) {
|
||||
try {
|
||||
// 使用 CryptoJS 进行 HMAC-SHA1 加密,并转换为 Base64 格式
|
||||
return CryptoJS.HmacSHA1(encryptText, encryptKey).toString(CryptoJS.enc.Base64);
|
||||
} catch (error) {
|
||||
console.error('Error in HMAC-SHA1 encryption:', error);
|
||||
return null;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,78 @@
|
|||
import axios from "axios";
|
||||
import { getSignature } from "./index";
|
||||
import { ElMessage } from "element-plus";
|
||||
|
||||
let appId = "01ec9aa3";
|
||||
let secret = "M2QxMDAxMjYyYTEzODMwMGRkZTQ4NmUy";
|
||||
let timestamp = Math.floor(Date.now() / 1000);
|
||||
let signature = getSignature(appId, secret, timestamp);
|
||||
|
||||
const instance = axios.create({
|
||||
baseURL: "",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
appId: appId,
|
||||
timestamp: timestamp,
|
||||
signature: signature,
|
||||
},
|
||||
});
|
||||
|
||||
const createOutline = async (data) => {
|
||||
console.log("createOutline data:", data);
|
||||
try {
|
||||
const response = await instance.post(
|
||||
"/parth/api/aippt/createOutline",
|
||||
data
|
||||
);
|
||||
console.log("createOutline response:", response);
|
||||
if (response.code == 81002) {
|
||||
ElMessage.error("并发数量超过限制");
|
||||
}
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
console.error("请求失败:", error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
const getBackGround = async () => {
|
||||
try {
|
||||
const response = await instance.get("/parth/api/aippt/themeList");
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
console.error("请求失败:", error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
const createPPT = async (data) => {
|
||||
try {
|
||||
const response = await instance.post("/parth/api/aippt/create", data);
|
||||
console.log("createOutline response:", response);
|
||||
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
console.error("请求失败:", error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
const createByOutline = async (data) => {
|
||||
try {
|
||||
const response = await instance.post("/parth/api/aippt/createByOutline", data);
|
||||
console.log("createByOutline response:", response);
|
||||
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
console.error("请求失败:", error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
const getProgress = async (id) => {
|
||||
try {
|
||||
const response = await instance.get(`/parth/api/aippt/progress?sid=${id}`);
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
console.error("请求失败:", error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
export { instance, createOutline, getBackGround, createPPT, getProgress, createByOutline };
|
|
@ -32,6 +32,8 @@ function getColor(index) {
|
|||
// 初始化图表
|
||||
function initChart() {
|
||||
const myChart = echarts.init(chartRef.value);
|
||||
const total = dataList.value.reduce((acc, cur) => acc + cur.value, 0); // 计算总数
|
||||
|
||||
const options = {
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
|
@ -70,7 +72,11 @@ function initChart() {
|
|||
label: {
|
||||
show: true,
|
||||
position: 'top',
|
||||
formatter: '{c}人',
|
||||
formatter: params => {
|
||||
const value = dataList.value[params.dataIndex].value;
|
||||
const percentage = ((value / total) * 100).toFixed(2); // 计算百分比并保留两位小数
|
||||
return `${percentage}%`; // 显示为百分比形式
|
||||
},
|
||||
color: '#333',
|
||||
fontSize: 12
|
||||
}
|
||||
|
|
|
@ -0,0 +1,697 @@
|
|||
<template>
|
||||
<div class="ai-container">
|
||||
<el-steps style="max-width:100% " :active="activeStep" align-center>
|
||||
<el-step title="开始创作" />
|
||||
<el-step title="输入主题" />
|
||||
<el-step title="编辑大纲" />
|
||||
<el-step title="选择模板" />
|
||||
<el-step title="制作PPT" />
|
||||
</el-steps>
|
||||
<div class="card-box">
|
||||
<el-card class="card1" v-if="activeStep == 0">
|
||||
<el-tabs v-model="activeName" type="card" class="demo-tabs">
|
||||
<el-tab-pane label="输入主题与要求" name="first">
|
||||
<div style="padding: 20px;">输入主题</div>
|
||||
<el-input type="textarea" v-model="inputTheme" :rows="3" placeholder="在此输入您的PPT主题..."
|
||||
@keydown.enter.exact.prevent="addMessage" @keydown.enter.shift.exact.prevent="inputTheme += '\n'" />
|
||||
<div style="padding: 20px;">具体生成要求</div>
|
||||
<el-input type="textarea" v-model="inputRequire" :rows="3" placeholder="请输入对生成大纲的具体要求,比如要包含那些内容"
|
||||
@keydown.enter.exact.prevent="addMessage" @keydown.enter.shift.exact.prevent="inputRequire += '\n'" />
|
||||
<div>
|
||||
<el-button style="margin:15px 0" type="primary" @click="addMessage">生成大纲→</el-button>
|
||||
</div>
|
||||
</el-tab-pane>
|
||||
<!-- <el-tab-pane label="上传文件并解析" name="second">
|
||||
<el-upload action="#" :on-change="onFileChange" :before-upload="beforeUpload" :show-file-list="false">
|
||||
<el-button type="primary">点击上传并解析文件</el-button>
|
||||
<text>(支持 doc/docx、pdf、md、txt 格式文档,不超过 20M,不超过 100W 字符)</text>
|
||||
</el-upload>
|
||||
<br/>
|
||||
<div v-if="enableButton">{{docName}}文档已解析完毕</div>
|
||||
<br/>
|
||||
<div style="padding: 20px;">输入主题</div>
|
||||
<textarea style="width:50vw" v-model="inputTheme" :rows="3" placeholder="在此输入您的PPT主题..."
|
||||
@keydown.enter.shift.exact.prevent="inputTheme += '\n'">
|
||||
</textarea>
|
||||
<div style="padding: 20px;">具体生成要求</div>
|
||||
<textarea style="width:50vw; margin:20px" v-model="docRequire" :rows="3"
|
||||
placeholder="请输入对生成大纲的具体要求,比如要包含那些内容" @keydown.enter.shift.exact.prevent="inputRequire += '\n'">
|
||||
</textarea>
|
||||
<div>
|
||||
<el-button style="padding:15px" type="primary" :disabled="!enableButton"
|
||||
@click="chatDoc">生成大纲→</el-button>
|
||||
</div>
|
||||
</el-tab-pane> -->
|
||||
</el-tabs>
|
||||
</el-card>
|
||||
<el-card class="card2" v-if="activeStep == 1">
|
||||
<div class="paragraphs">
|
||||
{{ outputText }}
|
||||
</div>
|
||||
</el-card>
|
||||
<el-card class="card3" v-if="activeStep == 2">
|
||||
<div class="outline">
|
||||
<el-scrollbar height="250px">
|
||||
<el-row :gutter="20" class="outline-row">
|
||||
<el-col :span="8">
|
||||
<div v-for="item in firstArray" :key="item" class="item-with-dash">{{ item }}</div>
|
||||
</el-col>
|
||||
<el-col :span="16">
|
||||
<div v-for="(item, index) in secondArray" :key="index">
|
||||
<el-input v-model="item.value"></el-input>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-scrollbar>
|
||||
</div>
|
||||
<el-input type="textarea" v-model="fixRequire" :rows="3" placeholder="与AI对话,告诉AI您想如何修改"
|
||||
@keydown.enter.exact.prevent="fixOutline" @keydown.enter.shift.exact.prevent="inputRequire += '\n'" />
|
||||
<br />
|
||||
<el-button v-if="fixRequire.length > 0" style="padding:15px" type="primary"
|
||||
@click="fixOutline()">发送修改</el-button>
|
||||
<el-button style="margin-top:15px" type="primary" @click="combineOutline()">下一步→</el-button>
|
||||
</el-card>
|
||||
<el-card v-if="activeStep == 3">
|
||||
<div style="padding-bottom: 10px">ppt模板选择</div>
|
||||
<div class="themes">
|
||||
<div v-for="item in backGroundList" :key="item.key" :style="{
|
||||
padding: '20px',
|
||||
paddingRight: '30px',
|
||||
paddingLeft: '30px',
|
||||
margin: '10px',
|
||||
backgroundColor: getBackgroundColor(item.key),
|
||||
borderRadius: '10px',
|
||||
borderBlock: '10px solid #e6e6e6'
|
||||
}" @click="chooseBackground(item.key)" @mouseenter="changeCursor('pointer')" @mouseleave="changeCursor('default')">
|
||||
{{ item.name }}
|
||||
<br />
|
||||
<img style="width: 150px; height: auto" :src="item.thumbnail" alt="" />
|
||||
</div>
|
||||
</div>
|
||||
<el-row class="el-row">
|
||||
<el-col :span="6" class="el-col">
|
||||
<div class="grid-content-1">
|
||||
<div>演讲备注</div>
|
||||
<el-switch v-model="outlineData.is_card_note" />
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="6" class="el-col">
|
||||
<div class="grid-content-2">
|
||||
<div>生成封面</div>
|
||||
<el-switch v-model="outlineData.is_cover_img" />
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="6" class="el-col">
|
||||
<div class="grid-content-1">
|
||||
<div>自动配图</div>
|
||||
<el-switch v-model="outlineData.is_figure" />
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="6" class="el-col">
|
||||
<div class="grid-content-2">
|
||||
<div>PPT作者名:</div>
|
||||
<el-input v-model="outlineData.author" style="width: 50%" />
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<div>
|
||||
<el-button style="margin-bottom: 5px;" type="primary" @click="outlineCreatePPT()">生成PPT</el-button>
|
||||
</div>
|
||||
</el-card>
|
||||
<el-card v-if="activeStep == 4">
|
||||
<el-progress :percentage="30" type="circle" v-if="percentage == 30"></el-progress>
|
||||
<el-progress :percentage="70" type="circle" v-if="percentage == 70"></el-progress>
|
||||
<el-progress :percentage="100" type="circle" v-if="percentage == 100"></el-progress>
|
||||
</el-card>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted } from "vue";
|
||||
import { ElMessage } from 'element-plus'
|
||||
import {
|
||||
getBackGround,
|
||||
createPPT,
|
||||
getProgress,
|
||||
} from "@/utils/ppt-request.js";
|
||||
import { uploadDoc, queryDocStatus } from "@/utils/aichat.js";
|
||||
import CryptoJS from "crypto-js"
|
||||
|
||||
import { getSignature } from "@/utils/index.js";
|
||||
|
||||
let appId = "01ec9aa3";
|
||||
let secret = "M2QxMDAxMjYyYTEzODMwMGRkZTQ4NmUy";
|
||||
let apikey = "39d05b269fa229f431a56c21794a8ea5"
|
||||
let timestamp = Math.floor(Date.now() / 1000);
|
||||
let signature = getSignature(appId, secret, timestamp);
|
||||
const { ipcRenderer } = window.electron || {}
|
||||
|
||||
|
||||
const outputText = ref(""); // 用于展示的大纲数据
|
||||
const stagingData = ref([]); //储存的对话数据,用于多轮对话
|
||||
const stagOutputText = ref(""); // 暂存大纲用于拆分
|
||||
let extractedParts = ref([]) // 初步拆分
|
||||
|
||||
let firstArray = ref([]); //大纲的大纲等级数字部分
|
||||
let secondArray = ref([]); //大纲的文字部分
|
||||
|
||||
|
||||
const backGroundList = ref([]);
|
||||
|
||||
const inputTheme = ref(""); // 输入的主题
|
||||
const inputRequire = ref("") // 输入的需求
|
||||
const activeStep = ref(0); // 上方进度条
|
||||
const fixRequire = ref(""); // 对话修改大纲
|
||||
const combined = ref('') // 修改完毕的大纲数据,准备传入ppt生成模型
|
||||
|
||||
const treeData = ref([]);
|
||||
const status = ref("init");
|
||||
|
||||
const percentage = ref(0);
|
||||
|
||||
const activeName = ref("first");
|
||||
|
||||
const getBackground = () => {
|
||||
treeData.value = [];
|
||||
getBackGround().then((res) => {
|
||||
console.log(res);
|
||||
backGroundList.value = res.data;
|
||||
});
|
||||
};
|
||||
|
||||
const getBackgroundColor = (key) => {
|
||||
return outlineData.value.theme === key ? '#83e2b6' : '#f5f5f5';
|
||||
};
|
||||
|
||||
const outlineData = ref({
|
||||
query: '', // 用户要求(最多8000字)
|
||||
theme: 'auto', // ppt生成主题
|
||||
author: 'AIX平台',
|
||||
is_card_note: false, // 是否自动生成ppt演讲备注
|
||||
is_cover_img: false, // 是否自动生成封面
|
||||
is_figure: false, // 是否自动配图
|
||||
}
|
||||
)
|
||||
|
||||
// 将输入数据或返回数据存入记忆中
|
||||
function updateStagingData(role, newData) {
|
||||
stagingData.value.push({ role: role, content: newData });
|
||||
}
|
||||
//大纲直接生成ppt
|
||||
const outlineCreatePPT = () => {
|
||||
const newOutlineData = { ...outlineData.value, };
|
||||
newOutlineData.query = combined.value;
|
||||
|
||||
createPPT(newOutlineData).then((res) => {
|
||||
console.log(res, "正在生成中");
|
||||
activeStep.value = 4
|
||||
|
||||
const checkProgress = () => {
|
||||
getProgress(res.data.sid).then((response) => {
|
||||
percentage.value = response.data.process;
|
||||
if (response.data && response.data.pptUrl && response.data.pptUrl.length > 4) {
|
||||
console.log('PPT',response)
|
||||
//TODO window.location.href = response.data.pptUrl;
|
||||
ElMessage.success("生成成功");
|
||||
} else {
|
||||
const sleepTime = 2000;
|
||||
let remainingTime = sleepTime;
|
||||
const intervalId = setInterval(() => {
|
||||
remainingTime -= 100;
|
||||
if (remainingTime <= 0) {
|
||||
clearInterval(intervalId);
|
||||
checkProgress();
|
||||
}
|
||||
}, 100);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
checkProgress();
|
||||
})
|
||||
};
|
||||
|
||||
//初次对话
|
||||
const addMessage = () => {
|
||||
const themeValue = inputTheme.value;
|
||||
const requireValue = inputRequire.value;
|
||||
firstArray.value = []
|
||||
secondArray.value = []
|
||||
extractedParts.value = []
|
||||
stagOutputText.value = ''
|
||||
const combinedString = `请帮我生成一个ppt大纲,主题为:${themeValue}。具体内容要求为:${requireValue}。注意,用三个等级大纲展示,如1. 1.1 1.1.2 2. 2.1这种类型,且按照这种顺序,不要有完全相同数字等级的大纲,不要有目录`
|
||||
updateStagingData("user", combinedString);
|
||||
connectWebSocket(stagingData.value);
|
||||
activeStep.value = 1
|
||||
};
|
||||
|
||||
//修改大纲时和ai对话
|
||||
const fixOutline = () => {
|
||||
outputText.value = '';
|
||||
firstArray.value = [];
|
||||
secondArray.value = [];
|
||||
extractedParts.value = []
|
||||
stagOutputText.value = ''
|
||||
|
||||
const fixValue = fixRequire.value;
|
||||
updateStagingData('user', fixValue)
|
||||
activeStep.value = 1
|
||||
if (enableButton.value) {
|
||||
uploadAndAskMainContent(stagingData.value)
|
||||
} else { connectWebSocket(stagingData.value); }
|
||||
fixRequire.value = ''
|
||||
};
|
||||
|
||||
// 大纲的拆分
|
||||
function extractAndRemove() {
|
||||
let startIndex = -1;
|
||||
let endIndex = -1;
|
||||
|
||||
for (let i = 0; i < stagOutputText.value.length; i++) {
|
||||
const char = stagOutputText.value[i];
|
||||
if (!isNaN(parseInt(char))) {
|
||||
startIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (startIndex !== -1) {
|
||||
for (let j = startIndex; j < stagOutputText.value.length; j++) {
|
||||
const char2 = stagOutputText.value[j];
|
||||
if (char2 === '\n') {
|
||||
endIndex = j;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let extractedPart = '';
|
||||
if (startIndex !== -1 && endIndex !== -1) {
|
||||
extractedPart = stagOutputText.value.slice(startIndex, endIndex).replace(/\n/g, '');
|
||||
extractedParts.value.push(extractedPart);
|
||||
stagOutputText.value = stagOutputText.value.replace(extractedPart, '');
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// 开始大纲拆分
|
||||
function startExtraction() {
|
||||
stagOutputText.value = outputText.value
|
||||
while (extractAndRemove()) { }
|
||||
|
||||
//将拆分好的大纲的大纲等级和文字部分拆分到两个数组中
|
||||
extractedParts.value.forEach(item => {
|
||||
const parts = item.split(' ');
|
||||
if (parts.length === 2) {
|
||||
firstArray.value.push(parts[0]);
|
||||
secondArray.value.push({ value: parts[1] });
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 拼接修改完毕的大纲
|
||||
function combineOutline() {
|
||||
let tempCombined = '';
|
||||
for (let i = 0; i < Math.max(firstArray.value.length, secondArray.value.length); i++) {
|
||||
tempCombined += firstArray.value[i] || '';
|
||||
tempCombined += secondArray.value[i] ? secondArray.value[i].value : '';
|
||||
tempCombined += i < Math.max(firstArray.value.length, secondArray.value.length) - 1 ? ',' : '';
|
||||
}
|
||||
|
||||
combined.value = tempCombined;
|
||||
fixRequire.value = ''
|
||||
activeStep.value = 3
|
||||
}
|
||||
|
||||
|
||||
let ttsWS
|
||||
function connectWebSocket(data) {
|
||||
outputText.value = ""; //清楚展示部分内容
|
||||
status.value = "ttsing";
|
||||
return getWebsocketUrl().then((url) => {
|
||||
ttsWS = new WebSocket(url);
|
||||
ttsWS.onopen = () => {
|
||||
webSocketSend(ttsWS, data);
|
||||
};
|
||||
ttsWS.onmessage = (e) => {
|
||||
result1(e.data);
|
||||
};
|
||||
ttsWS.onerror = (e) => {
|
||||
status.value = "error";
|
||||
console.log("WebSocket error:", e);
|
||||
};
|
||||
ttsWS.onclose = () => {
|
||||
status.value = "init";
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
function getWebsocketUrl() {
|
||||
return new Promise((resolve, reject) => {
|
||||
var apiKey = apikey;
|
||||
var apiSecret = secret;
|
||||
var url = "wss://spark-api.xf-yun.com/v4.0/chat";
|
||||
|
||||
var host = "spark-api.xf-yun.com";
|
||||
var date = new Date().toGMTString();
|
||||
var algorithm = "hmac-sha256";
|
||||
var headers = "host date request-line";
|
||||
var signatureOrigin = `host: ${host}\ndate: ${date}\nGET /v4.0/chat HTTP/1.1`;
|
||||
var signatureSha = CryptoJS.HmacSHA256(signatureOrigin, apiSecret);
|
||||
var signature = CryptoJS.enc.Base64.stringify(signatureSha);
|
||||
|
||||
var authorizationOrigin = `api_key="${apiKey}", algorithm="${algorithm}", headers="${headers}", signature="${signature}"`;
|
||||
var authorization = CryptoJS.enc.Base64.stringify(
|
||||
CryptoJS.enc.Utf8.parse(authorizationOrigin)
|
||||
);
|
||||
|
||||
url = `${url}?authorization=${authorization}&date=${date}&host=${host}`;
|
||||
console.log(url);
|
||||
resolve(url);
|
||||
});
|
||||
}
|
||||
function webSocketSend(ws, data) {
|
||||
const params = {
|
||||
header: {
|
||||
app_id: appId,
|
||||
},
|
||||
parameter: {
|
||||
chat: {
|
||||
domain: "4.0Ultra",
|
||||
temperature: 0.5,
|
||||
max_tokens: 1024,
|
||||
},
|
||||
},
|
||||
payload: {
|
||||
message: {
|
||||
text: data,
|
||||
},
|
||||
},
|
||||
};
|
||||
ws.send(JSON.stringify(params));
|
||||
}
|
||||
|
||||
function result1(resultData) {
|
||||
let jsonData = JSON.parse(resultData);
|
||||
outputText.value += jsonData.payload.choices.text[0].content;
|
||||
const div = document.querySelector('.paragraphs');
|
||||
if (div) {
|
||||
div.scrollTop = div.scrollHeight;
|
||||
}
|
||||
if (jsonData.payload && jsonData.payload.usage) {
|
||||
startExtraction() // 返回完毕后开始拆分大纲
|
||||
console.log(firstArray.value, secondArray.value)
|
||||
activeStep.value = 2
|
||||
updateStagingData("assistant", outputText.value) //返回数据存入记忆池
|
||||
}
|
||||
if (jsonData.header.code !== 0) {
|
||||
alert(`提问失败: ${jsonData.header.code}:${jsonData.header.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const selectedFile = ref(null);
|
||||
|
||||
const maxSize = 1024 * 1024 * 20;
|
||||
const allowedTypes = [
|
||||
"application/pdf",
|
||||
"application/msword",
|
||||
"application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
||||
"text/markdown",
|
||||
"text/plain",
|
||||
];
|
||||
const fileType = "wiki";
|
||||
const parseType = "AUTO";
|
||||
const upfileId = ref('');
|
||||
const docName = ref('')
|
||||
|
||||
const docTheme = ref('') // 文档生成ppt的主题
|
||||
const docRequire = ref('') // 文档生成要求
|
||||
|
||||
const onFileChange = (file) => {
|
||||
console.log(file);
|
||||
if (!allowedTypes.includes(file.raw.type)) {
|
||||
console.error("不支持的文件类型");
|
||||
return;
|
||||
}
|
||||
if (file.size > maxSize) {
|
||||
console.error("文件过大");
|
||||
return;
|
||||
}
|
||||
docName.value = file.raw.name
|
||||
selectedFile.value = file.raw;
|
||||
uploadFile();
|
||||
};
|
||||
const beforeUpload = (file) => {
|
||||
return false;
|
||||
};
|
||||
const uploadFile = async () => {
|
||||
if (!selectedFile.value) {
|
||||
console.error("请先选择文件");
|
||||
return;
|
||||
}
|
||||
|
||||
const formData = new FormData();
|
||||
formData.append("file", selectedFile.value);
|
||||
formData.append("fileType", fileType);
|
||||
formData.append("parseType", parseType);
|
||||
|
||||
for (const pair of formData.entries()) {
|
||||
console.log(pair[0], pair[1]);
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await uploadDoc(formData);
|
||||
const upfileData = new FormData();
|
||||
upfileId.value = response.data.data.fileId;
|
||||
upfileData.append("fileIds", upfileId.value);
|
||||
askdoc(upfileData);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
};
|
||||
|
||||
//文档状态查询
|
||||
const askdoc = async (data) => {
|
||||
const response = await queryDocStatus(data);
|
||||
if (response.data.data && response.data.data.length > 0) {
|
||||
let foundVectored = false;
|
||||
for (const item of response.data.data) {
|
||||
if (item.fileStatus === 'vectored') {
|
||||
foundVectored = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (foundVectored) {
|
||||
enableButton.value = true
|
||||
} else {
|
||||
const sleepTime = 2000;
|
||||
let remainingTime = sleepTime;
|
||||
const intervalId = setInterval(() => {
|
||||
remainingTime -= 100;
|
||||
if (remainingTime <= 0) {
|
||||
clearInterval(intervalId);
|
||||
askdoc(data);
|
||||
}
|
||||
}, 100);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
//文档对话
|
||||
function chatDoc() {
|
||||
const docthemeValue = docTheme.value;
|
||||
const docrequireValue = docRequire.value;
|
||||
firstArray.value = []
|
||||
secondArray.value = []
|
||||
extractedParts.value = []
|
||||
stagOutputText.value = ''
|
||||
const combinedString = `请帮我生成一个ppt大纲,主题为:${docthemeValue}。具体内容要求为:${docrequireValue}。注意,用三个等级大纲展示,如1. 1.1 1.1.2 2. 2.1这种类型,且按照这种顺序,不要有完全相同数字等级的大纲,不要有目录`
|
||||
updateStagingData("user", combinedString);
|
||||
activeStep.value = 1
|
||||
uploadAndAskMainContent(stagingData.value);
|
||||
}
|
||||
|
||||
function chatByDoc(fileId, data) {
|
||||
const wsUrl = `wss://chatdoc.xfyun.cn/openapi/chat?fileId=${fileId}&appId=${appId}×tamp=${timestamp}&signature=${signature}`;
|
||||
|
||||
const ws = new WebSocket(wsUrl);
|
||||
|
||||
ws.onopen = () => {
|
||||
const messageBody = {
|
||||
fileIds: [fileId],
|
||||
messages: data,
|
||||
chatExtends: {
|
||||
wikiPromptTpl:
|
||||
"请将以下内容作为已知信息:\n<wikicontent>\n请根据以上内容回答用户的问题。\n问题:<wikiquestion>\n回答:",
|
||||
wikiFilterScore: 0.82,
|
||||
temperature: 0.5,
|
||||
sparkWhenWithoutEmbedding: false,
|
||||
},
|
||||
};
|
||||
|
||||
ws.send(JSON.stringify(messageBody));
|
||||
};
|
||||
|
||||
ws.onmessage = (event) => {
|
||||
const response = JSON.parse(event.data);
|
||||
result2(response)
|
||||
console.log("WebSocket 消息:", response);
|
||||
};
|
||||
|
||||
ws.onerror = (error) => {
|
||||
console.error("WebSocket 错误:", error);
|
||||
};
|
||||
}
|
||||
|
||||
function result2(resultData) {
|
||||
const div = document.querySelector('.paragraphs');
|
||||
if (div) {
|
||||
div.scrollTop = div.scrollHeight;
|
||||
}
|
||||
if (resultData.status == 1) {
|
||||
outputText.value += resultData.content
|
||||
}
|
||||
if (resultData.status == 2) {
|
||||
startExtraction() // 返回完毕后开始拆分大纲
|
||||
activeStep.value = 2
|
||||
updateStagingData("assistant", outputText.value) //返回数据存入记忆池
|
||||
}
|
||||
if (resultData.code !== 0) {
|
||||
alert(`提问失败: ${jsonData.header.code}:${jsonData.header.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
//文档上传问题查询
|
||||
const uploadAndAskMainContent = async (data) => {
|
||||
try {
|
||||
// const formData = new FormData();
|
||||
// formData.append(
|
||||
// "url",
|
||||
// "https://bjcdn.openstorage.cn/xinghuo/chatdocs/2024-09-13/dad35a4f-e7c1-4efc-b6df-cae763cb984b/39052b09-d154-419f-9832-20884adeb2f41726226211177.pdf"
|
||||
// );
|
||||
// formData.append("fileName", "test.pdf");
|
||||
// formData.append("appId", "2ff2cc26");
|
||||
// formData.append("secret", "YTMyZWFiOGVlYTc5ZGM5NGIwOTU3NWMx");
|
||||
// // formData.append("fileType", "wiki");
|
||||
// // formData.append("parseType", "AUTO");
|
||||
|
||||
// const uploadResp = await uploadDoc(formData);
|
||||
// const fileId = uploadResp.data.data.fileId;
|
||||
const fileId = upfileId.value;
|
||||
chatByDoc(fileId, data);
|
||||
|
||||
} catch (error) {
|
||||
console.error("上传或提问过程中发生错误:", error);
|
||||
}
|
||||
};
|
||||
const enableButton = ref(false);
|
||||
|
||||
const chooseBackground = (data) => {
|
||||
outlineData.value.theme = data
|
||||
}
|
||||
|
||||
const changeCursor = (cursorStyle) => {
|
||||
document.documentElement.style.cursor = cursorStyle;
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
connectWebSocket("");
|
||||
getBackground();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.ai-container {
|
||||
width: 100%;
|
||||
background-color: #f5f7f6;
|
||||
padding: 20px
|
||||
}
|
||||
|
||||
.card-box {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.card1 {
|
||||
padding: 0;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.paragraphs {
|
||||
white-space: pre-wrap;
|
||||
text-align: left;
|
||||
max-height: 60vh;
|
||||
overflow-y: auto;
|
||||
border: 1px solid #409EFF;
|
||||
padding: 10px;
|
||||
margin: 5px
|
||||
}
|
||||
|
||||
.themes {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
height: 250px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.outline {
|
||||
white-space: pre-wrap;
|
||||
text-align: left;
|
||||
|
||||
border: 1px solid #409EFF;
|
||||
padding: 10px;
|
||||
outline-style: none;
|
||||
/* margin: 5px */
|
||||
}
|
||||
|
||||
.outline-row {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.outline-row>.el-col {
|
||||
display: flex;
|
||||
flex-direction: column
|
||||
}
|
||||
|
||||
.outline-row>.el-col>div,
|
||||
.outline-row>.el-col>div>.el-input {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 3px;
|
||||
}
|
||||
|
||||
.item-with-dash {
|
||||
margin-left: 100px
|
||||
}
|
||||
|
||||
.item-with-dash::after {
|
||||
content: "";
|
||||
border-bottom: 1px dashed #000;
|
||||
flex-grow: 1;
|
||||
margin-left: 4px;
|
||||
}
|
||||
|
||||
.grid-content-1 {
|
||||
border-radius: 4px;
|
||||
background-color: #c2dbf3;
|
||||
}
|
||||
|
||||
.grid-content-2 {
|
||||
border-radius: 4px;
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
.el-row {
|
||||
padding: 20px
|
||||
}
|
||||
:deep(.el-card__body){
|
||||
padding: 10px 15px;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,33 @@
|
|||
<template>
|
||||
<div>
|
||||
<el-dialog class="ppt-dialog" v-model="model" :show-close="false" width="900" destroy-on-close :top="'3vh'">
|
||||
<template #header="{ close, titleId, titleClass }">
|
||||
<div class="dialog-header">
|
||||
<h4 :id="titleId" :class="titleClass">生成PPT(试验版)</h4>
|
||||
<i class="iconfont icon-guanbi" @click="close"></i>
|
||||
</div>
|
||||
</template>
|
||||
<AiPpt/>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import AiPpt from './ai-ppt.vue';
|
||||
const model = defineModel()
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
:deep(.ppt-dialog){
|
||||
-webkit-app-region: no-drag;
|
||||
}
|
||||
.dialog-header{
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
.icon-guanbi {
|
||||
font-size: 20px;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -13,6 +13,10 @@
|
|||
<div class="create-btn-title"><el-icon><Plus /></el-icon><label>创建PPT</label></div>
|
||||
<div class="create-btn-info">传统课件</div>
|
||||
</div>
|
||||
<div class="center-create-btn" style="background-color: #909399" @click="pptDialog = true">
|
||||
<div class="create-btn-title"><el-icon><Plus /></el-icon><label>生成PPT</label></div>
|
||||
<div class="create-btn-info">试验版</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="prepare-center-body">
|
||||
<kj-list-item
|
||||
|
@ -133,6 +137,7 @@
|
|||
></reserv>
|
||||
<!-- 上课配置 -->
|
||||
<class-start ref="calssRef" @close="closeChange"/>
|
||||
<PptDialog v-model="pptDialog"/>
|
||||
</template>
|
||||
<script setup>
|
||||
import { Check,Plus } from '@element-plus/icons-vue'
|
||||
|
@ -150,6 +155,7 @@ import { useToolState } from '@/store/modules/tool'
|
|||
import MoveFile from '@/components/move-file/index.vue'
|
||||
import FileListItem from '@/views/prepare/container/file-list-item.vue'
|
||||
import KjListItem from '@/views/prepare/container/kj-list-item.vue'
|
||||
import PptDialog from './container/ppt-dialog.vue'
|
||||
import { getSmarttalkPage, moveSmarttalk, creatAPT } from '@/api/file'
|
||||
import { toTimeText } from '@/utils/date'
|
||||
import { ElMessage } from 'element-plus'
|
||||
|
@ -226,6 +232,7 @@ export default {
|
|||
isOpenHomework: false,
|
||||
// 当前上课课程
|
||||
activeClass: null,
|
||||
pptDialog: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
|
|
Loading…
Reference in New Issue