提交 94155133 authored 作者: 王鹏飞's avatar 王鹏飞

chore: update

上级 ec26e346
...@@ -10,7 +10,7 @@ ...@@ -10,7 +10,7 @@
<body> <body>
<div id="app"></div> <div id="app"></div>
<script type="module" src="/src/main.ts"></script> <script type="module" src="/src/main.ts"></script>
<script src="https://webapp-pub.ezijing.com/plugins/sky-agents/sky-agent.umd.cjs?v=1"></script> <script src="https://webapp-pub.ezijing.com/plugins/sky-agents/sky-agent.umd.cjs?v=2"></script>
<script src="https://webapp-pub.ezijing.com/plugins/tinymce/tinymce.min.js"></script> <script src="https://webapp-pub.ezijing.com/plugins/tinymce/tinymce.min.js"></script>
</body> </body>
</html> </html>
import httpRequest from '@/utils/axios'
// 获取天工AI的使用详情
export function getAIUsage(params: { marketing_material_id: string }) {
return httpRequest.get('/api/lab/v1/experiment/marketing-ai/ai-usage-detail', { params })
}
// 天工AI-聊天
export function postAIChat(data: {
marketing_material_id: string
context: string
type: number
chart_id: string | null
}) {
return httpRequest.post('/api/lab/v1/experiment/marketing-ai/sky-agents-chat', data)
}
// 天工3.0文字生成图片
export function postGenerateImage(data: {
marketing_material_id: string
context: string
type: number
chart_id: string | null
}) {
return httpRequest.post('/api/lab/v1/experiment/marketing-ai/sky-agent3-generate-image', data)
}
import md5 from 'blueimp-md5'
import { useStorage } from '@vueuse/core'
import { fetchEventSource } from '@fortaine/fetch-event-source'
import axios from 'axios'
import { getAIUsage, postGenerateImage } from '@/api/ai'
import { ElMessage } from 'element-plus'
export function useAI(config) {
// AI 配置列表
const options = [
{ label: '文心一言', value: 'yiyan' },
{ label: 'DeepSeek', value: 'deepseek' },
{ label: '通义千问', value: 'qwen' },
{ label: '天工', value: 'tiangong' },
]
const ai = useStorage('ai', 'tiangong')
const messages = ref([])
const isLoading = ref(false)
async function post(data) {
isLoading.value = true
try {
switch (ai.value) {
case 'yiyan':
await yiyan(data)
break
case 'deepseek':
await deepseek(data)
break
case 'qwen':
await qwen(data)
break
case 'tiangong':
await tiangong(data)
break
default:
throw new Error('未找到对应的 AI 配置')
}
} catch (err) {
console.error('AI 请求失败:', err)
} finally {
isLoading.value = false
}
}
// 文心一言
async function yiyan(data) {
// 获取token
const getAccessToken = async () => {
const AK = 'wY7bvMpkWeZbDVq9w3EDvpjU'
const SK = 'XJwpiJWxs5HXkOtbo6tQrvYPZFJAWdAy'
const resp = await axios.post(
'/api/qianfan/oauth/2.0/token?grant_type=client_credentials&client_id=' + AK + '&client_secret=' + SK
)
return resp.data.access_token
}
const resp = await axios.post(
'/api/qianfan/rpc/2.0/ai_custom/v1/wenxinworkshop/chat/eb-instant?access_token=' + (await getAccessToken()),
{
messages: [{ role: 'user', content: data.content }],
}
)
messages.value.push({ role: 'assistant', content: resp.data.result.replaceAll('\n', '<br/>') })
}
// DeepSeek
async function deepseek(data) {
const apiKey = 'sk-f1a6f0a7013241de8393cb2cb108e777'
const resp = await axios.post(
'/api/deepseek/chat/completions',
{
model: 'deepseek-chat',
messages: [{ role: 'user', content: data.content }],
},
{
headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${apiKey}` },
}
)
if (resp.data) {
const [choice = {}] = resp.data.choices
messages.value.push({ role: 'assistant', content: choice.message.content.replaceAll('\n', '<br/>') })
}
}
// 通义千问
async function qwen(data) {
const apiKey = 'sk-afd0fcdb53bf4058b2068b8548820150'
const resp = await axios.post(
'/api/qwen/compatible-mode/v1/chat/completions',
{
model: 'qwen-max',
messages: [{ role: 'user', content: data.content }],
},
{
headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${apiKey}` },
}
)
if (resp.data) {
const [choice = {}] = resp.data.choices
messages.value.push({ role: 'assistant', content: choice.message.content.replaceAll('\n', '<br/>') })
}
}
// 天工
async function tiangong(data) {
const appKey = 'a8701b73637562d33a53c668a90ee3be'
const appSecret = 'e191593f486bb88a39c634f46926762dddc97b9082e192af'
const timestamp = Math.floor(Date.now() / 1000)
const sign = md5(`${appKey}${appSecret}${timestamp}`)
return await fetchEventSource('/api/tiangong/sky-saas-writing/api/v1/chat', {
method: 'POST',
headers: { 'Content-Type': 'application/json', app_key: appKey, sign, timestamp, stream: 'true' },
body: JSON.stringify({
chat_history: [{ role: 'user', content: data.content }],
stream_resp_type: 'update',
}),
async onopen(response) {
console.log(response)
if (response.ok) {
return response
} else {
throw response
}
},
onmessage(res) {
console.log(res.data)
const message = JSON.parse(res.data)
if (message.type !== 1) return
const messageId = message.conversation_id
const messageIndex = messages.value.findIndex((message) => message.id === messageId)
const content = message?.arguments?.[0]?.messages?.[0]?.text || ''
if (messageIndex === -1) {
messages.value.push({ id: messageId, role: 'assistant', content })
} else {
messages.value[messageIndex].content = content
}
isLoading.value = false
},
onerror(err) {
isLoading.value = false
throw err
},
})
}
const usages = ref({
chart_count: 0,
ai_creation_count: 0,
ai_polish_count: 0,
ai_expand_count: 0,
ai_refresh_count: 0,
chart_max_count: 20,
ai_creation_max_count: 5,
ai_polish_max_count: 5,
ai_expand_max_count: 5,
ai_refresh_max_count: 5,
})
async function fetchUsages() {
const res = await getAIUsage(config)
usages.value = res.data.detail
}
async function generateText(data) {
isLoading.value = true
const docAction = {
2: 'write',
3: 'rewrite',
4: 'expand',
5: 'rewrite',
7: 'abbreviate',
8: 'summary',
}
const params = {
content: data.content,
doc_action: docAction[data.type],
full_text: !!(data.type === 2),
}
await fetchEventSource('/api/lab/v1/experiment/marketing-ai/sky-agent3-chat', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
...config,
...data,
api_type: parseInt(data.type) === 1 ? 1 : 2,
context: data.content,
params: params,
}),
async onopen(response) {
if (response.ok) {
return
} else {
throw response
}
},
onmessage(res) {
const message = JSON.parse(res.data)
// 聊天返回内容
if (data.type === '1') {
if (message.code === 0) {
ElMessage.error(message.message)
return
}
const conversationId = message.conversation_id
const messageIndex = messages.value.findIndex((session) => session.conversationId === conversationId)
const content = message?.arguments?.reduce((a, b) => {
a = b?.messages[0]?.text || ''
return a
}, '')
if (messageIndex === -1) {
messages.value.push({ conversationId, role: 'assistant', content, input: data.context })
} else {
if (content) {
messages.value[messageIndex].content = content
}
}
} else {
// 按钮功能返回内容
const requestId = message.request_id
const messageIndex = messages.value.findIndex((session) => session.conversationId === requestId)
if (messageIndex === -1) {
messages.value.push({
conversationId: requestId,
role: 'assistant',
content: message.data?.text || '',
input: data.context,
})
} else {
messages.value[messageIndex].content = message.data?.text
}
}
isLoading.value = false
},
onclose() {
fetchUsages()
isLoading.value = false
},
onerror(err) {
console.log(err)
isLoading.value = false
throw err
},
})
}
// 生成图片
async function generateImage(data) {
isLoading.value = true
try {
const res = await postGenerateImage({ ...config, ...data })
if (res.data.detail.image_url) {
messages.value.push({ type: 'image', role: 'assistant', ...res.data.detail })
} else {
ElMessage.error(res.data.detail.failure_reason)
}
fetchUsages()
} catch (error) {
console.log(error)
}
isLoading.value = false
}
return { ai, options, post, messages, isLoading, usages, fetchUsages, generateText, generateImage }
}
<script setup> <script setup>
import { useClipboard } from '@vueuse/core' import { useClipboard } from '@vueuse/core'
import { useChat } from '../composables/useChat' import { useAI } from '@/composables/useAI'
import { useMapStore } from '@/stores/map' import { useMapStore } from '@/stores/map'
import { useUserStore } from '@/stores/user' import { useUserStore } from '@/stores/user'
import { useConnection } from '../composables/useConnection' import { useConnection } from '../composables/useConnection'
...@@ -11,7 +11,7 @@ import { ...@@ -11,7 +11,7 @@ import {
materialUsageList, materialUsageList,
materialUsersList, materialUsersList,
materialPictureStyleList, materialPictureStyleList,
textPurposeList textPurposeList,
} from '@/utils/dictionary' } from '@/utils/dictionary'
import IconComputer from './IconComputer.vue' import IconComputer from './IconComputer.vue'
import IconUser from './IconUser.vue' import IconUser from './IconUser.vue'
...@@ -31,10 +31,10 @@ const welcomeMessage = computed(() => { ...@@ -31,10 +31,10 @@ const welcomeMessage = computed(() => {
const data = form.value const data = form.value
const way = getNameByValue(data.way, materialMethodList) const way = getNameByValue(data.way, materialMethodList)
const type = getNameByValue(data.type, materialType) const type = getNameByValue(data.type, materialType)
const industry = industryList.value.find(item => item.id == data.industry_id)?.name const industry = industryList.value.find((item) => item.id == data.industry_id)?.name
const personnel = getNameByValue(data.personnel_type, materialUsersList) const personnel = getNameByValue(data.personnel_type, materialUsersList)
const scenario = getNameByValue(data.scenario_type, materialUsageList) const scenario = getNameByValue(data.scenario_type, materialUsageList)
const connection = connectionList.value.find(item => item.id == data.channel)?.type_name const connection = connectionList.value.find((item) => item.id == data.channel)?.type_name
const extendInfo = data.extend_info || {} const extendInfo = data.extend_info || {}
const pictureStyle = getNameByValue(extendInfo.picture_style, materialPictureStyleList) const pictureStyle = getNameByValue(extendInfo.picture_style, materialPictureStyleList)
const textPurpose = getNameByValue(extendInfo.text_use, textPurposeList) const textPurpose = getNameByValue(extendInfo.text_use, textPurposeList)
...@@ -50,13 +50,14 @@ const welcomeMessage = computed(() => { ...@@ -50,13 +50,14 @@ const welcomeMessage = computed(() => {
const content = ref('') const content = ref('')
const route = useRoute() const route = useRoute()
const { usages, messages, post, isLoading } = useChat({
const { ai, options, usages, messages, post, isLoading, fetchUsages, generateText, generateImage } = useAI({
experiment_id: route.query.experiment_id, experiment_id: route.query.experiment_id,
marketing_material_id: form.value.id, marketing_material_id: form.value.id,
fileType: form.value.type
}) })
onMounted(() => { onMounted(() => {
fetchUsages()
messages.value.push({ role: 'system', content: welcomeMessage.value }) messages.value.push({ role: 'system', content: welcomeMessage.value })
// if (form.value.content) { // if (form.value.content) {
// messages.value.push({ role: 'bot', content: form.value.content }) // messages.value.push({ role: 'bot', content: form.value.content })
...@@ -78,7 +79,11 @@ watch(welcomeMessage, () => { ...@@ -78,7 +79,11 @@ watch(welcomeMessage, () => {
async function postMessage() { async function postMessage() {
if (!content.value) return if (!content.value) return
messages.value.push({ role: 'user', content: content.value }) messages.value.push({ role: 'user', content: content.value })
post({ content: content.value, type: '1' }) if (form.value.type == 2) {
generateImage({ content: content.value })
} else {
post({ content: content.value, type: '1' })
}
content.value = '' content.value = ''
} }
...@@ -93,7 +98,7 @@ async function handleSendType(type, content) { ...@@ -93,7 +98,7 @@ async function handleSendType(type, content) {
content = xss(content, { content = xss(content, {
whiteList: {}, // 白名单为空,表示过滤所有标签 whiteList: {}, // 白名单为空,表示过滤所有标签
stripIgnoreTag: true, // 过滤所有非白名单标签的HTML stripIgnoreTag: true, // 过滤所有非白名单标签的HTML
stripIgnoreTagBody: ['script'] // script标签较特殊,需要过滤标签中间的内容 stripIgnoreTagBody: ['script'], // script标签较特殊,需要过滤标签中间的内容
}) })
switch (type) { switch (type) {
case 2: case 2:
...@@ -112,7 +117,11 @@ async function handleSendType(type, content) { ...@@ -112,7 +117,11 @@ async function handleSendType(type, content) {
content = `我是${userName},请帮我总结以下内容:${content.replace('请帮我创作一个', '')}` content = `我是${userName},请帮我总结以下内容:${content.replace('请帮我创作一个', '')}`
break break
} }
post({ type, content }) if (type == 99) {
generateImage({ type, content })
} else {
generateText({ type, content })
}
} }
const chatRef = ref() const chatRef = ref()
...@@ -129,7 +138,7 @@ function handleCopy(content) { ...@@ -129,7 +138,7 @@ function handleCopy(content) {
const html = xss(content, { const html = xss(content, {
whiteList: {}, // 白名单为空,表示过滤所有标签 whiteList: {}, // 白名单为空,表示过滤所有标签
stripIgnoreTag: true, // 过滤所有非白名单标签的HTML stripIgnoreTag: true, // 过滤所有非白名单标签的HTML
stripIgnoreTagBody: ['script'] // script标签较特殊,需要过滤标签中间的内容 stripIgnoreTagBody: ['script'], // script标签较特殊,需要过滤标签中间的内容
}) })
copy(html) copy(html)
} }
...@@ -168,7 +177,7 @@ async function handleSave(message) { ...@@ -168,7 +177,7 @@ async function handleSave(message) {
size="small" size="small"
type="primary" type="primary"
@click="handleSendType(5, item.input || item.content)" @click="handleSendType(5, item.input || item.content)"
v-if="item.role == 'bot'" v-if="item.role == 'assistant'"
>刷新({{ usages.ai_refresh_count }}/{{ usages.ai_refresh_max_count }})</el-button >刷新({{ usages.ai_refresh_count }}/{{ usages.ai_refresh_max_count }})</el-button
> >
<el-button size="small" type="primary" @click="handleSendType(2, item.content)" <el-button size="small" type="primary" @click="handleSendType(2, item.content)"
...@@ -195,7 +204,7 @@ async function handleSave(message) { ...@@ -195,7 +204,7 @@ async function handleSave(message) {
>生成({{ usages.ai_generate_image_count }}/{{ usages.ai_generate_image_max_count }})</el-button >生成({{ usages.ai_generate_image_count }}/{{ usages.ai_generate_image_max_count }})</el-button
> >
</template> </template>
<template v-if="item.role == 'bot'"> <template v-if="item.role == 'assistant'">
<el-button size="small" type="primary" @click="handleSendType(99, item.content)" <el-button size="small" type="primary" @click="handleSendType(99, item.content)"
>重画({{ usages.ai_generate_image_count }}/{{ usages.ai_generate_image_max_count }})</el-button >重画({{ usages.ai_generate_image_count }}/{{ usages.ai_generate_image_max_count }})</el-button
> >
...@@ -213,13 +222,17 @@ async function handleSave(message) { ...@@ -213,13 +222,17 @@ async function handleSave(message) {
</div> </div>
</div> </div>
<div class="chat-footer"> <div class="chat-footer">
<el-select-v2
:options="options"
v-model="ai"
style="width: 100%; margin-bottom: 10px"
v-if="form.type == 1"></el-select-v2>
<el-input <el-input
type="textarea" type="textarea"
:autosize="{ minRows: 1, maxRows: 12 }" :autosize="{ minRows: 1, maxRows: 12 }"
placeholder="发消息" placeholder="发消息"
v-model="content" v-model="content"
@keydown.enter="handleSend" @keydown.enter="handleSend"></el-input>
></el-input>
<el-button text type="primary" @click="handleSend">发送</el-button> <el-button text type="primary" @click="handleSend">发送</el-button>
</div> </div>
</template> </template>
......
...@@ -30,6 +30,26 @@ export default defineConfig(({ mode }) => ({ ...@@ -30,6 +30,26 @@ export default defineConfig(({ mode }) => ({
// cert: fs.readFileSync(path.join(__dirname, './https/ezijing.com.pem')) // cert: fs.readFileSync(path.join(__dirname, './https/ezijing.com.pem'))
// }, // },
proxy: { proxy: {
'/api/tiangong': {
target: 'https://api.singularity-ai.com',
changeOrigin: true,
rewrite: (path) => path.replace(/^\/api\/tiangong/, ''),
},
'/api/deepseek': {
target: 'https://api.deepseek.com',
changeOrigin: true,
rewrite: (path) => path.replace(/^\/api\/deepseek/, ''),
},
'/api/qwen': {
target: 'https://dashscope.aliyuncs.com',
changeOrigin: true,
rewrite: (path) => path.replace(/^\/api\/qwen/, ''),
},
'/api/qianfan': {
target: 'https://aip.baidubce.com',
changeOrigin: true,
rewrite: (path) => path.replace(/^\/api\/qianfan/, ''),
},
// '/api/resource': { // '/api/resource': {
// target: 'http://com-resource-admin-test.ezijing.com/', // target: 'http://com-resource-admin-test.ezijing.com/',
// changeOrigin: true, // changeOrigin: true,
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论