refactor(Agent/Chat): 使用 cozeInfo 替代 botId 获取聊天记录
- 移除了 HistoryChat 组件中的 botId属性 - 使用 cozeInfo.bot_id 替代 botId 获取历史聊天数据
This commit is contained in:
@ -1,36 +0,0 @@
|
||||
<template>
|
||||
<a-layout-content style="padding: 24px; background: #fff; min-height: 280px;">
|
||||
<div style="text-align: center; margin-bottom: 20px">
|
||||
<img src="@/assets/chatbot-icon.png" alt="Chatbot Icon" style="width: 60px" />
|
||||
<h3>舆情脉络整理</h3>
|
||||
</div>
|
||||
<a-card :bordered="false">
|
||||
<p>
|
||||
您好,我是舆情脉络整理助手,可以帮您梳理事件发展脉络,提取核心观点,分析情绪倾向,快速生成舆情摘要与应对建议。
|
||||
</p>
|
||||
</a-card>
|
||||
<div style="margin-top: 20px">
|
||||
<a-input-search
|
||||
v-model:value="message"
|
||||
placeholder="发送消息..."
|
||||
enter-button
|
||||
@search="onSearch"
|
||||
>
|
||||
<template #enterButton>
|
||||
<a-button type="primary">
|
||||
<!-- <template #icon><SendOutlined /></template>-->
|
||||
</a-button>
|
||||
</template>
|
||||
</a-input-search>
|
||||
</div>
|
||||
<small style="display: block; text-align: center; margin-top: 10px">
|
||||
内容由AI生成,无法确保真实准确,仅供参考。
|
||||
</small>
|
||||
</a-layout-content>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { defineComponent, ref } from 'vue';
|
||||
// import { SendOutlined } from '@ant-design/icons-vue';
|
||||
|
||||
</script>
|
||||
@ -1,217 +0,0 @@
|
||||
<template>
|
||||
<div>
|
||||
<!-- 聊天容器 -->
|
||||
<div ref="chatContainer" class="coze-chat-container"></div>
|
||||
|
||||
<!-- 加载状态 -->
|
||||
<div v-if="loading" class="loading-state">
|
||||
<div class="spinner"></div>
|
||||
<p>正在加载聊天服务...</p>
|
||||
</div>
|
||||
|
||||
<!-- 错误提示 -->
|
||||
<div v-if="error" class="error-state">
|
||||
<p>⚠️ 聊天服务加载失败</p>
|
||||
<button @click="retry">重新加载</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ref, onMounted, onBeforeUnmount } from 'vue';
|
||||
|
||||
export default {
|
||||
props: {
|
||||
botId: {
|
||||
type: String,
|
||||
default: '7522056630889381923',
|
||||
},
|
||||
title: {
|
||||
type: String,
|
||||
default: 'Coze助手',
|
||||
},
|
||||
token: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
|
||||
setup(props) {
|
||||
const chatContainer = ref(null);
|
||||
const loading = ref(true);
|
||||
const error = ref(false);
|
||||
let chatClient = null;
|
||||
let scriptLoaded = false;
|
||||
|
||||
// 加载SDK脚本
|
||||
const loadSDK = () => {
|
||||
return new Promise((resolve, reject) => {
|
||||
// 检查是否已加载
|
||||
if (window.CozeWebSDK) {
|
||||
resolve();
|
||||
return;
|
||||
}
|
||||
|
||||
// 检查是否正在加载
|
||||
if (document.querySelector('script[src*="coze.cn"]')) {
|
||||
const checkInterval = setInterval(() => {
|
||||
if (window.CozeWebSDK) {
|
||||
clearInterval(checkInterval);
|
||||
resolve();
|
||||
}
|
||||
}, 100);
|
||||
return;
|
||||
}
|
||||
|
||||
// 创建新脚本
|
||||
const script = document.createElement('script');
|
||||
script.src = 'https://lf-cdn.coze.cn/obj/unpkg/flow-platform/chat-app-sdk/1.2.0-beta.10/libs/cn/index.js';
|
||||
script.onload = () => {
|
||||
scriptLoaded = true;
|
||||
resolve();
|
||||
};
|
||||
script.onerror = (err) => {
|
||||
console.error('SDK加载失败:', err);
|
||||
reject(new Error('无法加载聊天SDK'));
|
||||
};
|
||||
document.head.appendChild(script);
|
||||
});
|
||||
};
|
||||
|
||||
// 初始化聊天
|
||||
const initChat = () => {
|
||||
try {
|
||||
if (!window.CozeWebSDK) {
|
||||
throw new Error('SDK未加载');
|
||||
}
|
||||
|
||||
chatClient = new window.CozeWebSDK.WebChatClient({
|
||||
container: chatContainer.value,
|
||||
config: {
|
||||
bot_id: props.botId,
|
||||
},
|
||||
componentProps: {
|
||||
title: props.title,
|
||||
// 可选配置
|
||||
// theme: 'light',
|
||||
// welcome_message: '您好!需要什么帮助?',
|
||||
// input_placeholder: '输入您的问题...'
|
||||
},
|
||||
auth: {
|
||||
type: 'token',
|
||||
token: props.token,
|
||||
onRefreshToken: () => {
|
||||
// 实际项目中应从API获取新token
|
||||
console.log('Token刷新');
|
||||
return props.token;
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
loading.value = false;
|
||||
} catch (err) {
|
||||
console.error('聊天初始化失败:', err);
|
||||
error.value = true;
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
const retry = async () => {
|
||||
error.value = false;
|
||||
loading.value = true;
|
||||
try {
|
||||
await loadSDK();
|
||||
initChat();
|
||||
} catch (err) {
|
||||
console.error('重试失败:', err);
|
||||
error.value = true;
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
onMounted(async () => {
|
||||
try {
|
||||
await loadSDK();
|
||||
initChat();
|
||||
} catch (err) {
|
||||
console.error('初始化失败:', err);
|
||||
error.value = true;
|
||||
loading.value = false;
|
||||
}
|
||||
});
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
if (chatClient && typeof chatClient.destroy === 'function') {
|
||||
chatClient.destroy();
|
||||
}
|
||||
|
||||
if (scriptLoaded) {
|
||||
const scripts = document.querySelectorAll('script[src*="coze.cn"]');
|
||||
scripts.forEach((script) => script.remove());
|
||||
window.CozeWebSDK = undefined;
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
chatContainer,
|
||||
loading,
|
||||
error,
|
||||
retry,
|
||||
};
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.coze-chat-container {
|
||||
height: 600px;
|
||||
width: 100%;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
||||
overflow: hidden;
|
||||
background: #f5f7fa;
|
||||
}
|
||||
|
||||
.loading-state,
|
||||
.error-state {
|
||||
height: 600px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
background: #f9f9f9;
|
||||
border-radius: 12px;
|
||||
border: 1px dashed #ddd;
|
||||
}
|
||||
|
||||
.spinner {
|
||||
border: 4px solid rgba(0, 0, 0, 0.1);
|
||||
border-left-color: #3498db;
|
||||
border-radius: 50%;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
animation: spin 1s linear infinite;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
.error-state button {
|
||||
margin-top: 16px;
|
||||
padding: 8px 16px;
|
||||
background: #3498db;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
transition: background 0.3s;
|
||||
}
|
||||
|
||||
.error-state button:hover {
|
||||
background: #2980b9;
|
||||
}
|
||||
</style>
|
||||
@ -1,217 +0,0 @@
|
||||
<template>
|
||||
<div>
|
||||
<!-- 聊天容器 -->
|
||||
<div ref="chatContainer" class="coze-chat-container"></div>
|
||||
|
||||
<!-- 加载状态 -->
|
||||
<div v-if="loading" class="loading-state">
|
||||
<div class="spinner"></div>
|
||||
<p>正在加载聊天服务...</p>
|
||||
</div>
|
||||
|
||||
<!-- 错误提示 -->
|
||||
<div v-if="error" class="error-state">
|
||||
<p>⚠️ 聊天服务加载失败</p>
|
||||
<button @click="retry">重新加载</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ref, onMounted, onBeforeUnmount } from 'vue';
|
||||
|
||||
export default {
|
||||
props: {
|
||||
botId: {
|
||||
type: String,
|
||||
default: '7522056630889381923',
|
||||
},
|
||||
title: {
|
||||
type: String,
|
||||
default: 'Coze助手',
|
||||
},
|
||||
token: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
|
||||
setup(props) {
|
||||
const chatContainer = ref(null);
|
||||
const loading = ref(true);
|
||||
const error = ref(false);
|
||||
let chatClient = null;
|
||||
let scriptLoaded = false;
|
||||
|
||||
// 加载SDK脚本
|
||||
const loadSDK = () => {
|
||||
return new Promise((resolve, reject) => {
|
||||
// 检查是否已加载
|
||||
if (window.CozeWebSDK) {
|
||||
resolve();
|
||||
return;
|
||||
}
|
||||
|
||||
// 检查是否正在加载
|
||||
if (document.querySelector('script[src*="coze.cn"]')) {
|
||||
const checkInterval = setInterval(() => {
|
||||
if (window.CozeWebSDK) {
|
||||
clearInterval(checkInterval);
|
||||
resolve();
|
||||
}
|
||||
}, 100);
|
||||
return;
|
||||
}
|
||||
|
||||
// 创建新脚本
|
||||
const script = document.createElement('script');
|
||||
script.src = 'https://lf-cdn.coze.cn/obj/unpkg/flow-platform/chat-app-sdk/1.2.0-beta.10/libs/cn/index.js';
|
||||
script.onload = () => {
|
||||
scriptLoaded = true;
|
||||
resolve();
|
||||
};
|
||||
script.onerror = (err) => {
|
||||
console.error('SDK加载失败:', err);
|
||||
reject(new Error('无法加载聊天SDK'));
|
||||
};
|
||||
document.head.appendChild(script);
|
||||
});
|
||||
};
|
||||
|
||||
// 初始化聊天
|
||||
const initChat = () => {
|
||||
try {
|
||||
if (!window.CozeWebSDK) {
|
||||
throw new Error('SDK未加载');
|
||||
}
|
||||
|
||||
chatClient = new window.CozeWebSDK.WebChatClient({
|
||||
container: chatContainer.value,
|
||||
config: {
|
||||
bot_id: props.botId,
|
||||
},
|
||||
componentProps: {
|
||||
title: props.title,
|
||||
// 可选配置
|
||||
// theme: 'light',
|
||||
// welcome_message: '您好!需要什么帮助?',
|
||||
// input_placeholder: '输入您的问题...'
|
||||
},
|
||||
auth: {
|
||||
type: 'token',
|
||||
token: props.token,
|
||||
onRefreshToken: () => {
|
||||
// 实际项目中应从API获取新token
|
||||
console.log('Token刷新');
|
||||
return props.token;
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
loading.value = false;
|
||||
} catch (err) {
|
||||
console.error('聊天初始化失败:', err);
|
||||
error.value = true;
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
const retry = async () => {
|
||||
error.value = false;
|
||||
loading.value = true;
|
||||
try {
|
||||
await loadSDK();
|
||||
initChat();
|
||||
} catch (err) {
|
||||
console.error('重试失败:', err);
|
||||
error.value = true;
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
onMounted(async () => {
|
||||
try {
|
||||
await loadSDK();
|
||||
initChat();
|
||||
} catch (err) {
|
||||
console.error('初始化失败:', err);
|
||||
error.value = true;
|
||||
loading.value = false;
|
||||
}
|
||||
});
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
if (chatClient && typeof chatClient.destroy === 'function') {
|
||||
chatClient.destroy();
|
||||
}
|
||||
|
||||
if (scriptLoaded) {
|
||||
const scripts = document.querySelectorAll('script[src*="coze.cn"]');
|
||||
scripts.forEach((script) => script.remove());
|
||||
window.CozeWebSDK = undefined;
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
chatContainer,
|
||||
loading,
|
||||
error,
|
||||
retry,
|
||||
};
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.coze-chat-container {
|
||||
height: 600px;
|
||||
width: 100%;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
||||
overflow: hidden;
|
||||
background: #f5f7fa;
|
||||
}
|
||||
|
||||
.loading-state,
|
||||
.error-state {
|
||||
height: 600px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
background: #f9f9f9;
|
||||
border-radius: 12px;
|
||||
border: 1px dashed #ddd;
|
||||
}
|
||||
|
||||
.spinner {
|
||||
border: 4px solid rgba(0, 0, 0, 0.1);
|
||||
border-left-color: #3498db;
|
||||
border-radius: 50%;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
animation: spin 1s linear infinite;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
.error-state button {
|
||||
margin-top: 16px;
|
||||
padding: 8px 16px;
|
||||
background: #3498db;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
transition: background 0.3s;
|
||||
}
|
||||
|
||||
.error-state button:hover {
|
||||
background: #2980b9;
|
||||
}
|
||||
</style>
|
||||
@ -27,7 +27,7 @@ import { useRouter } from 'vue-router';
|
||||
const router = useRouter();
|
||||
|
||||
// 存储认证令牌
|
||||
const authToken = ref('pat_tuIM7jubM1hLXaIWzbWg1U15lBe66AlYwu9BkXMQXInh8VdPszRFTwlTPmdziHwg');
|
||||
const authToken = ref('');
|
||||
|
||||
// 模拟从API获取token
|
||||
const fetchToken = async () => {
|
||||
@ -89,9 +89,8 @@ const initChat = async () => {
|
||||
};
|
||||
|
||||
const cozeWebSdkConfig = (botId, name, auth) => {
|
||||
console.log(name, 'title');
|
||||
auth.onRefreshToken = function () {
|
||||
return 'pat_tuIM7jubM1hLXaIWzbWg1U15lBe66AlYwu9BkXMQXInh8VdPszRFTwlTPmdziHwg';
|
||||
return '';
|
||||
};
|
||||
let config = {
|
||||
config: {
|
||||
@ -105,6 +104,9 @@ const cozeWebSdkConfig = (botId, name, auth) => {
|
||||
title: name,
|
||||
isNeedFunctionCallMessage: true,
|
||||
},
|
||||
footer:{
|
||||
expressionText:"内容由AI生成,无法确保真实准确,仅供参考。",
|
||||
},
|
||||
},
|
||||
auth: auth,
|
||||
base: {
|
||||
@ -2,7 +2,7 @@
|
||||
<div class="agent-wrap">
|
||||
<a-input
|
||||
style="float: right; width: 300px"
|
||||
v-model="query.title"
|
||||
v-model="query.name"
|
||||
@blur="getData()"
|
||||
placeholder="搜索智能体"
|
||||
size="medium"
|
||||
@ -57,7 +57,7 @@ const getData = async () => {
|
||||
};
|
||||
|
||||
const query = reactive({
|
||||
title: '',
|
||||
name: '',
|
||||
});
|
||||
const goDetail = (type: number, id: number) => {
|
||||
if (type === 1) {
|
||||
@ -4,16 +4,30 @@
|
||||
<a-form-item
|
||||
v-for="(field, index) in formFields"
|
||||
:key="index"
|
||||
:label="field.label"
|
||||
:field="field.key"
|
||||
:rules="field.rules"
|
||||
:label="field.props.label"
|
||||
:field="field.props.name"
|
||||
:rules="field.props.rules"
|
||||
>
|
||||
<a-input v-if="field.type === 'input'" v-model="formData[field.key]" :placeholder="field.placeholder" />
|
||||
<a-textarea v-if="field.type === 'textarea'" v-model="formData[field.key]" :placeholder="field.placeholder" />
|
||||
<ImageUpload v-if="field.type == 'upload'" v-model="formData[field.key]" :limit="1"></ImageUpload>
|
||||
|
||||
<a-select v-else-if="field.type === 'select'" v-model="formData[field.key]" :placeholder="field.placeholder">
|
||||
<a-option v-for="(option, optIndex) in field.options" :key="optIndex" :value="option.value">
|
||||
<a-input
|
||||
allowClear
|
||||
v-if="field.type === 'input'"
|
||||
v-model="formData[field.props.name]"
|
||||
:placeholder="field?.props?.placeholder"
|
||||
/>
|
||||
<a-textarea
|
||||
v-if="field.type === 'textarea'"
|
||||
style="width: 500px; height: 200px;"
|
||||
v-model="formData[field.props.name]"
|
||||
:placeholder="field?.props?.placeholder"
|
||||
/>
|
||||
<ImageUpload v-if="field.type == 'upload_image'" v-model="formData[field.props.name]" :limit="field.props.limit"></ImageUpload>
|
||||
<FileUpload v-if="field.type == 'upload_file'" v-model="formData[field.props.name]" :limit="field.props.limit"></FileUpload>
|
||||
<a-select
|
||||
v-else-if="field.type === 'select'"
|
||||
v-model="formData[field.props.name]"
|
||||
:placeholder="field.placeholder"
|
||||
>
|
||||
<a-option v-for="(option, optIndex) in field.props.options" :key="optIndex" :value="option.value">
|
||||
{{ option.label }}
|
||||
</a-option>
|
||||
</a-select>
|
||||
@ -25,6 +39,7 @@
|
||||
<script setup>
|
||||
import { defineProps, defineEmits } from 'vue';
|
||||
import ImageUpload from '@/components/upload/ImageUpload.vue';
|
||||
import FileUpload from '@/components/upload/FileUpload.vue';
|
||||
|
||||
const props = defineProps({
|
||||
formFields: {
|
||||
@ -45,7 +60,7 @@ const formRef = ref(null);
|
||||
const handleSubmit = async () => {
|
||||
const errors = await formRef.value.validate();
|
||||
if (errors) return;
|
||||
|
||||
console.log(props.formFields, 'props.formFields');
|
||||
emit('submit', props.formData);
|
||||
};
|
||||
</script>
|
||||
@ -5,11 +5,10 @@
|
||||
</div>
|
||||
<a-menu mode="inline" theme="light">
|
||||
<a-menu-item key="1">
|
||||
<span>{{ cozeInfo.title }}</span>
|
||||
<span>{{ cozeInfo.name }}</span>
|
||||
<span style="color: #8492ff; font-size: 12px">{{ cozeInfo.type == 1 ? '智能体' : '对话式' }}</span>
|
||||
<span style="float: right">{{ cozeInfo.views }}次使用</span>
|
||||
<span style="float: right">{{ cozeInfo.views }}次使用 </span>
|
||||
</a-menu-item>
|
||||
<a-menu-item key="2">历史对话</a-menu-item>
|
||||
|
||||
</a-menu>
|
||||
</a-layout-sider>
|
||||
@ -28,15 +27,8 @@ const props = defineProps({
|
||||
default: '',
|
||||
},
|
||||
});
|
||||
console.log(props.cozeInfo, 'cozeInfo');
|
||||
const getHistoryChat = async () => {
|
||||
const { code, data } = await getHistoryChat({ botId: props.botId });
|
||||
console.log(data, 'data');
|
||||
// 获取历史对话
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
getHistoryChat();
|
||||
});
|
||||
</script>
|
||||
|
||||
@ -13,9 +13,8 @@
|
||||
</a-layout-sider>
|
||||
<a-layout-content ref="contentRef" class="content-container">
|
||||
<a-spin v-if="loading" class="spin-center" tip="生成中。。。" />
|
||||
<div class="work-res">
|
||||
<span > {{ workFlowRes?.output }}</span>
|
||||
</div>
|
||||
<div v-if="workFlowRes?.output != ''" class="work-res" v-html="renderedMarkdown"></div>
|
||||
<NoData v-else />
|
||||
</a-layout-content>
|
||||
</a-layout>
|
||||
</a-layout>
|
||||
@ -26,9 +25,11 @@
|
||||
<script setup>
|
||||
import { ref, reactive } from 'vue';
|
||||
import HistoryChat from './components/historyChat.vue';
|
||||
import { executeWorkFlow, getWorkFlowInfo } from '@/api/all/agent';
|
||||
import DynamicForm from './components/DynamicForm.vue';
|
||||
import { executeWorkFlow, getWorkFlowInfo } from '@/api/all/agent';
|
||||
import { useRoute, useRouter } from 'vue-router';
|
||||
import { marked } from 'marked';
|
||||
import DOMPurify from 'dompurify';
|
||||
|
||||
const formFields = ref([]);
|
||||
|
||||
@ -49,7 +50,7 @@ const goChatIndex = async () => {
|
||||
const loading = ref(false);
|
||||
|
||||
const cozeInfo = reactive({
|
||||
title: '',
|
||||
name: '',
|
||||
description: '',
|
||||
icon_url: '',
|
||||
workflow_id: '',
|
||||
@ -59,11 +60,22 @@ const getData = async () => {
|
||||
Object.assign(cozeInfo, data.info);
|
||||
formFields.value = data.form_config;
|
||||
};
|
||||
const workFlowRes = reactive({});
|
||||
const workFlowRes = reactive({
|
||||
output: '',
|
||||
});
|
||||
|
||||
// 渲染 Markdown 的计算属性
|
||||
const renderedMarkdown = computed(() => {
|
||||
if (workFlowRes?.output) {
|
||||
const rawHtml = marked.parse(workFlowRes.output || '');
|
||||
return DOMPurify.sanitize(rawHtml); // 防止 XSS 攻击
|
||||
}
|
||||
return '';
|
||||
});
|
||||
|
||||
// 提交表单
|
||||
const handleSubmit = async (formData) => {
|
||||
console.log(formData, 'formData');
|
||||
|
||||
try {
|
||||
const param = { form_data: formData, workflow_id: cozeInfo.workflow_id };
|
||||
loading.value = true;
|
||||
Reference in New Issue
Block a user