refactor(Agent/Chat): 使用 cozeInfo 替代 botId 获取聊天记录
- 移除了 HistoryChat 组件中的 botId属性 - 使用 cozeInfo.bot_id 替代 botId 获取历史聊天数据
This commit is contained in:
@ -133,7 +133,7 @@ const str: string = join(['a', 'b', 'c'], '~');
|
|||||||
> 命名导出: `Comp/Index.js`
|
> 命名导出: `Comp/Index.js`
|
||||||
|
|
||||||
```js
|
```js
|
||||||
// Comp/Index.js
|
// Comp/index.js
|
||||||
export { CompA, CompB, CompC };
|
export { CompA, CompB, CompC };
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@ -17,10 +17,12 @@
|
|||||||
"ali-oss": "^6.17.1",
|
"ali-oss": "^6.17.1",
|
||||||
"axios": "^1.3.0",
|
"axios": "^1.3.0",
|
||||||
"dayjs": "^1.11.7",
|
"dayjs": "^1.11.7",
|
||||||
|
"dompurify": "^3.2.6",
|
||||||
"echarts": "^5.6.0",
|
"echarts": "^5.6.0",
|
||||||
"html2canvas": "^1.4.1",
|
"html2canvas": "^1.4.1",
|
||||||
"jspdf": "^3.0.1",
|
"jspdf": "^3.0.1",
|
||||||
"lodash-es": "^4.17.21",
|
"lodash-es": "^4.17.21",
|
||||||
|
"marked": "^16.1.1",
|
||||||
"mitt": "^3.0.0",
|
"mitt": "^3.0.0",
|
||||||
"normalize.css": "^8.0.1",
|
"normalize.css": "^8.0.1",
|
||||||
"pinia": "^2.0.29",
|
"pinia": "^2.0.29",
|
||||||
|
|||||||
137
src/components/upload/FileUpload.vue
Normal file
137
src/components/upload/FileUpload.vue
Normal file
@ -0,0 +1,137 @@
|
|||||||
|
<template>
|
||||||
|
<a-upload
|
||||||
|
:custom-request="customRequest"
|
||||||
|
action="/"
|
||||||
|
:limit="limit"
|
||||||
|
:fileList="fileList"
|
||||||
|
@change="onChange"
|
||||||
|
@success="handleSuccess"
|
||||||
|
@error="handleError"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref } from 'vue';
|
||||||
|
import { Message } from '@arco-design/web-vue';
|
||||||
|
import { fetchImageUploadFile, fetchUploadFile } from '@/api/all';
|
||||||
|
import axios from 'axios';
|
||||||
|
|
||||||
|
const fileList = ref([]);
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
modelValue: {
|
||||||
|
type: [Array, String],
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
limit: {
|
||||||
|
type: Number,
|
||||||
|
default: 0, // 0 表示不限制
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const emit = defineEmits(['update:modelValue']);
|
||||||
|
|
||||||
|
const handleSuccess = (fileItem) => {
|
||||||
|
let response = fileItem.response;
|
||||||
|
response = JSON.parse(response);
|
||||||
|
if (response && response.data.file_url) {
|
||||||
|
if (props.limit === 1) {
|
||||||
|
emit('update:modelValue', response.data.file_url);
|
||||||
|
} else {
|
||||||
|
emit('update:modelValue', [...props.modelValue, response.data.file_url]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.modelValue,
|
||||||
|
async (value) => {
|
||||||
|
console.log(value, 'value');
|
||||||
|
if (value) {
|
||||||
|
fileList.value =
|
||||||
|
props.limit == 1
|
||||||
|
? [
|
||||||
|
{
|
||||||
|
name: '',
|
||||||
|
url: props.modelValue as string,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
: (props.modelValue as string[]).map((item) => {
|
||||||
|
return {
|
||||||
|
name: '',
|
||||||
|
url: item,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
fileList.value = [];
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ once: true },
|
||||||
|
);
|
||||||
|
|
||||||
|
let previousFileListLength = 0;
|
||||||
|
//删除图片
|
||||||
|
const onChange = (fileList) => {
|
||||||
|
if (fileList.length < previousFileListLength) {
|
||||||
|
if (props.limit === 1) {
|
||||||
|
if (fileList.length === 0) {
|
||||||
|
emit('update:modelValue', '');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (fileList.length === 0) {
|
||||||
|
emit('update:modelValue', []);
|
||||||
|
} else {
|
||||||
|
let image_data = fileList.map((item) => item.url);
|
||||||
|
emit('update:modelValue', image_data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
previousFileListLength = fileList.length;
|
||||||
|
};
|
||||||
|
|
||||||
|
const beforeUpload = (file, files) => {
|
||||||
|
if (props.limit > 0 && files.length >= props.limit) {
|
||||||
|
Message.warning(`最多只能上传 ${props.limit} 张图片`);
|
||||||
|
return false; // 阻止上传
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleError = (error) => {
|
||||||
|
Message.error('上传失败');
|
||||||
|
console.error(error);
|
||||||
|
};
|
||||||
|
|
||||||
|
const customRequest = async (option) => {
|
||||||
|
const { onProgress, onError, onSuccess, fileItem, name } = option;
|
||||||
|
try {
|
||||||
|
// 1. 获取预签名上传URL
|
||||||
|
const response = await fetchUploadFile({ suffix: getFileExtension(fileItem.file.name) });
|
||||||
|
const preSignedUrl = response?.data?.upload_url;
|
||||||
|
|
||||||
|
if (!preSignedUrl) {
|
||||||
|
throw new Error('未能获取有效的预签名上传地址');
|
||||||
|
}
|
||||||
|
console.log('preSignedUrl', preSignedUrl);
|
||||||
|
// 2. 使用预签名URL上传文件
|
||||||
|
const blob = new Blob([fileItem.file], { type: fileItem.file.type });
|
||||||
|
await axios.put(preSignedUrl, blob, {
|
||||||
|
headers: { 'Content-Type': fileItem.file.type },
|
||||||
|
});
|
||||||
|
|
||||||
|
onSuccess(JSON.stringify(response));
|
||||||
|
} catch (error) {
|
||||||
|
onError(error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
function getFileExtension(filename: string): string {
|
||||||
|
const match = filename.match(/\.([^.]+)$/);
|
||||||
|
return match ? match[1].toLowerCase() : '';
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
/* 添加一些样式 */
|
||||||
|
</style>
|
||||||
@ -38,7 +38,18 @@ export const router = createRouter({
|
|||||||
{
|
{
|
||||||
path: '/agent/chat',
|
path: '/agent/chat',
|
||||||
name: 'Chat',
|
name: 'Chat',
|
||||||
component: () => import('@/views/Agent/chat'),
|
component: () => import('@/views/agent/chat'),
|
||||||
|
meta: {
|
||||||
|
hideSidebar: true,
|
||||||
|
requiresAuth: false,
|
||||||
|
requireLogin: true,
|
||||||
|
id: MENU_GROUP_IDS.WORK_BENCH_ID,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/agent/workFlow',
|
||||||
|
name: 'WorkFlow',
|
||||||
|
component: () => import('@/views/agent/work-flow'),
|
||||||
meta: {
|
meta: {
|
||||||
hideSidebar: true,
|
hideSidebar: true,
|
||||||
requiresAuth: false,
|
requiresAuth: false,
|
||||||
|
|||||||
@ -4,42 +4,31 @@ import { MENU_GROUP_IDS } from '@/router/constants';
|
|||||||
import IconRepository from '@/assets/svg/icon-repository.svg';
|
import IconRepository from '@/assets/svg/icon-repository.svg';
|
||||||
|
|
||||||
const COMPONENTS: AppRouteRecordRaw[] = [
|
const COMPONENTS: AppRouteRecordRaw[] = [
|
||||||
{
|
// {
|
||||||
path: '/agent',
|
// path: '/agent',
|
||||||
name: 'Agent',
|
// name: 'Agent',
|
||||||
redirect: 'agent/index',
|
// redirect: 'agent/index',
|
||||||
meta: {
|
// meta: {
|
||||||
locale: '扣子智能体',
|
// locale: '扣子智能体',
|
||||||
icon: IconRepository,
|
// icon: IconRepository,
|
||||||
requiresAuth: true,
|
// requiresAuth: true,
|
||||||
requireLogin: true,
|
// requireLogin: true,
|
||||||
roles: ['*'],
|
// roles: ['*'],
|
||||||
id: MENU_GROUP_IDS.PROPERTY_ID,
|
// id: MENU_GROUP_IDS.AGENT,
|
||||||
},
|
// },
|
||||||
children: [
|
// children: [
|
||||||
{
|
// {
|
||||||
path: 'index',
|
// path: 'index',
|
||||||
name: 'AgentIndex',
|
// name: 'AgentIndex',
|
||||||
component: () => import('@/views/agent/index'),
|
// component: () => import('@/views/agent/index'),
|
||||||
meta: {
|
// meta: {
|
||||||
requiresAuth: false,
|
// requiresAuth: false,
|
||||||
requireLogin: true,
|
// requireLogin: true,
|
||||||
id: MENU_GROUP_IDS.WORK_BENCH_ID,
|
// },
|
||||||
},
|
// }
|
||||||
},
|
//
|
||||||
{
|
// ],
|
||||||
path: 'chat',
|
// },
|
||||||
name: 'Chat',
|
|
||||||
component: () => import('@/views/agent/chat'),
|
|
||||||
meta: {
|
|
||||||
hideSidebar: true,
|
|
||||||
requiresAuth: false,
|
|
||||||
requireLogin: true,
|
|
||||||
id: MENU_GROUP_IDS.WORK_BENCH_ID,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
];
|
];
|
||||||
|
|
||||||
export default COMPONENTS;
|
export default COMPONENTS;
|
||||||
|
|||||||
@ -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 router = useRouter();
|
||||||
|
|
||||||
// 存储认证令牌
|
// 存储认证令牌
|
||||||
const authToken = ref('pat_tuIM7jubM1hLXaIWzbWg1U15lBe66AlYwu9BkXMQXInh8VdPszRFTwlTPmdziHwg');
|
const authToken = ref('');
|
||||||
|
|
||||||
// 模拟从API获取token
|
// 模拟从API获取token
|
||||||
const fetchToken = async () => {
|
const fetchToken = async () => {
|
||||||
@ -89,9 +89,8 @@ const initChat = async () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const cozeWebSdkConfig = (botId, name, auth) => {
|
const cozeWebSdkConfig = (botId, name, auth) => {
|
||||||
console.log(name, 'title');
|
|
||||||
auth.onRefreshToken = function () {
|
auth.onRefreshToken = function () {
|
||||||
return 'pat_tuIM7jubM1hLXaIWzbWg1U15lBe66AlYwu9BkXMQXInh8VdPszRFTwlTPmdziHwg';
|
return '';
|
||||||
};
|
};
|
||||||
let config = {
|
let config = {
|
||||||
config: {
|
config: {
|
||||||
@ -105,6 +104,9 @@ const cozeWebSdkConfig = (botId, name, auth) => {
|
|||||||
title: name,
|
title: name,
|
||||||
isNeedFunctionCallMessage: true,
|
isNeedFunctionCallMessage: true,
|
||||||
},
|
},
|
||||||
|
footer:{
|
||||||
|
expressionText:"内容由AI生成,无法确保真实准确,仅供参考。",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
auth: auth,
|
auth: auth,
|
||||||
base: {
|
base: {
|
||||||
@ -2,7 +2,7 @@
|
|||||||
<div class="agent-wrap">
|
<div class="agent-wrap">
|
||||||
<a-input
|
<a-input
|
||||||
style="float: right; width: 300px"
|
style="float: right; width: 300px"
|
||||||
v-model="query.title"
|
v-model="query.name"
|
||||||
@blur="getData()"
|
@blur="getData()"
|
||||||
placeholder="搜索智能体"
|
placeholder="搜索智能体"
|
||||||
size="medium"
|
size="medium"
|
||||||
@ -57,7 +57,7 @@ const getData = async () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const query = reactive({
|
const query = reactive({
|
||||||
title: '',
|
name: '',
|
||||||
});
|
});
|
||||||
const goDetail = (type: number, id: number) => {
|
const goDetail = (type: number, id: number) => {
|
||||||
if (type === 1) {
|
if (type === 1) {
|
||||||
@ -4,16 +4,30 @@
|
|||||||
<a-form-item
|
<a-form-item
|
||||||
v-for="(field, index) in formFields"
|
v-for="(field, index) in formFields"
|
||||||
:key="index"
|
:key="index"
|
||||||
:label="field.label"
|
:label="field.props.label"
|
||||||
:field="field.key"
|
:field="field.props.name"
|
||||||
:rules="field.rules"
|
:rules="field.props.rules"
|
||||||
>
|
>
|
||||||
<a-input v-if="field.type === 'input'" v-model="formData[field.key]" :placeholder="field.placeholder" />
|
<a-input
|
||||||
<a-textarea v-if="field.type === 'textarea'" v-model="formData[field.key]" :placeholder="field.placeholder" />
|
allowClear
|
||||||
<ImageUpload v-if="field.type == 'upload'" v-model="formData[field.key]" :limit="1"></ImageUpload>
|
v-if="field.type === 'input'"
|
||||||
|
v-model="formData[field.props.name]"
|
||||||
<a-select v-else-if="field.type === 'select'" v-model="formData[field.key]" :placeholder="field.placeholder">
|
:placeholder="field?.props?.placeholder"
|
||||||
<a-option v-for="(option, optIndex) in field.options" :key="optIndex" :value="option.value">
|
/>
|
||||||
|
<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 }}
|
{{ option.label }}
|
||||||
</a-option>
|
</a-option>
|
||||||
</a-select>
|
</a-select>
|
||||||
@ -25,6 +39,7 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { defineProps, defineEmits } from 'vue';
|
import { defineProps, defineEmits } from 'vue';
|
||||||
import ImageUpload from '@/components/upload/ImageUpload.vue';
|
import ImageUpload from '@/components/upload/ImageUpload.vue';
|
||||||
|
import FileUpload from '@/components/upload/FileUpload.vue';
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
formFields: {
|
formFields: {
|
||||||
@ -45,7 +60,7 @@ const formRef = ref(null);
|
|||||||
const handleSubmit = async () => {
|
const handleSubmit = async () => {
|
||||||
const errors = await formRef.value.validate();
|
const errors = await formRef.value.validate();
|
||||||
if (errors) return;
|
if (errors) return;
|
||||||
|
console.log(props.formFields, 'props.formFields');
|
||||||
emit('submit', props.formData);
|
emit('submit', props.formData);
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
@ -5,11 +5,10 @@
|
|||||||
</div>
|
</div>
|
||||||
<a-menu mode="inline" theme="light">
|
<a-menu mode="inline" theme="light">
|
||||||
<a-menu-item key="1">
|
<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="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>
|
||||||
<a-menu-item key="2">历史对话</a-menu-item>
|
|
||||||
|
|
||||||
</a-menu>
|
</a-menu>
|
||||||
</a-layout-sider>
|
</a-layout-sider>
|
||||||
@ -28,15 +27,8 @@ const props = defineProps({
|
|||||||
default: '',
|
default: '',
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
console.log(props.cozeInfo, 'cozeInfo');
|
|
||||||
const getHistoryChat = async () => {
|
|
||||||
const { code, data } = await getHistoryChat({ botId: props.botId });
|
|
||||||
console.log(data, 'data');
|
|
||||||
// 获取历史对话
|
|
||||||
};
|
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
getHistoryChat();
|
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@ -13,9 +13,8 @@
|
|||||||
</a-layout-sider>
|
</a-layout-sider>
|
||||||
<a-layout-content ref="contentRef" class="content-container">
|
<a-layout-content ref="contentRef" class="content-container">
|
||||||
<a-spin v-if="loading" class="spin-center" tip="生成中。。。" />
|
<a-spin v-if="loading" class="spin-center" tip="生成中。。。" />
|
||||||
<div class="work-res">
|
<div v-if="workFlowRes?.output != ''" class="work-res" v-html="renderedMarkdown"></div>
|
||||||
<span > {{ workFlowRes?.output }}</span>
|
<NoData v-else />
|
||||||
</div>
|
|
||||||
</a-layout-content>
|
</a-layout-content>
|
||||||
</a-layout>
|
</a-layout>
|
||||||
</a-layout>
|
</a-layout>
|
||||||
@ -26,9 +25,11 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { ref, reactive } from 'vue';
|
import { ref, reactive } from 'vue';
|
||||||
import HistoryChat from './components/historyChat.vue';
|
import HistoryChat from './components/historyChat.vue';
|
||||||
import { executeWorkFlow, getWorkFlowInfo } from '@/api/all/agent';
|
|
||||||
import DynamicForm from './components/DynamicForm.vue';
|
import DynamicForm from './components/DynamicForm.vue';
|
||||||
|
import { executeWorkFlow, getWorkFlowInfo } from '@/api/all/agent';
|
||||||
import { useRoute, useRouter } from 'vue-router';
|
import { useRoute, useRouter } from 'vue-router';
|
||||||
|
import { marked } from 'marked';
|
||||||
|
import DOMPurify from 'dompurify';
|
||||||
|
|
||||||
const formFields = ref([]);
|
const formFields = ref([]);
|
||||||
|
|
||||||
@ -49,7 +50,7 @@ const goChatIndex = async () => {
|
|||||||
const loading = ref(false);
|
const loading = ref(false);
|
||||||
|
|
||||||
const cozeInfo = reactive({
|
const cozeInfo = reactive({
|
||||||
title: '',
|
name: '',
|
||||||
description: '',
|
description: '',
|
||||||
icon_url: '',
|
icon_url: '',
|
||||||
workflow_id: '',
|
workflow_id: '',
|
||||||
@ -59,11 +60,22 @@ const getData = async () => {
|
|||||||
Object.assign(cozeInfo, data.info);
|
Object.assign(cozeInfo, data.info);
|
||||||
formFields.value = data.form_config;
|
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) => {
|
const handleSubmit = async (formData) => {
|
||||||
console.log(formData, 'formData');
|
console.log(formData, 'formData');
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const param = { form_data: formData, workflow_id: cozeInfo.workflow_id };
|
const param = { form_data: formData, workflow_id: cozeInfo.workflow_id };
|
||||||
loading.value = true;
|
loading.value = true;
|
||||||
@ -40,7 +40,7 @@ export default defineConfig(({ mode }: ConfigEnv): UserConfig => {
|
|||||||
changeOrigin: true,
|
changeOrigin: true,
|
||||||
rewrite: (path) => path.replace(/^\/api/, ''),
|
rewrite: (path) => path.replace(/^\/api/, ''),
|
||||||
// 目标地址
|
// 目标地址
|
||||||
target: 'https://lingjiapi.lvfunai.com/api',
|
target: 'http://www.lingji.com/api',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user