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

chore: 新增AI生成图片

上级 599d1118
...@@ -44,3 +44,8 @@ export function getAIUsage(params: { marketing_material_id: string }) { ...@@ -44,3 +44,8 @@ export function getAIUsage(params: { marketing_material_id: string }) {
export function postAIChat(data: { marketing_material_id: string; context: string; type: number; chart_id: string | null }) { 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) 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)
}
...@@ -10,6 +10,9 @@ import IconComputer from './IconComputer.vue' ...@@ -10,6 +10,9 @@ import IconComputer from './IconComputer.vue'
import IconUser from './IconUser.vue' import IconUser from './IconUser.vue'
import IconAI from './IconAI.vue' import IconAI from './IconAI.vue'
import xss from 'xss' import xss from 'xss'
import { uploadFileByUrl } from '@/utils/upload'
const emit = defineEmits(['submit'])
const form = defineModel() const form = defineModel()
...@@ -30,13 +33,17 @@ const welcomeMessage = computed(() => { ...@@ -30,13 +33,17 @@ const welcomeMessage = computed(() => {
const content = ref('') const content = ref('')
const route = useRoute() const route = useRoute()
const { usages, messages, post, isLoading } = useChat({ experiment_id: route.query.experiment_id, marketing_material_id: form.value.id }) const { usages, messages, post, isLoading } = useChat({
experiment_id: route.query.experiment_id,
marketing_material_id: form.value.id,
fileType: form.value.type
})
onMounted(() => { onMounted(() => {
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 })
} // }
}) })
watch(welcomeMessage, () => { watch(welcomeMessage, () => {
...@@ -55,7 +62,7 @@ async function postMessage() { ...@@ -55,7 +62,7 @@ async function postMessage() {
if (!content.value) return if (!content.value) return
console.log(content.value) console.log(content.value)
messages.value.push({ role: 'user', content: content.value }) messages.value.push({ role: 'user', content: content.value })
post({ context: content.value, type: '1' }) post({ content: content.value, type: '1' })
content.value = '' content.value = ''
} }
...@@ -65,26 +72,26 @@ async function handleSend(event) { ...@@ -65,26 +72,26 @@ async function handleSend(event) {
await postMessage() await postMessage()
} }
async function handleSendType(type, context) { async function handleSendType(type, content) {
const userName = useUserStore().user.name const userName = useUserStore().user.name
context = xss(context, { 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:
context = `我是${userName},请帮我创作一个文本内容,${context.replace('你将以在线AI 的方式创作一个文本内容,', '')}` content = `我是${userName},请帮我创作一个文本内容,${content.replace('你将以在线AI 的方式创作一个文本内容,', '')}`
break break
case 3: case 3:
context = `我是${userName},请帮我润色一个文本内容,${context.replace('你将以在线AI 的方式创作一个文本内容,', '')}` content = `我是${userName},请帮我润色一个文本内容,${content.replace('你将以在线AI 的方式创作一个文本内容,', '')}`
break break
case 4: case 4:
context = `我是${userName},请帮我扩写一个文本内容,${context.replace('你将以在线AI 的方式创作一个文本内容,', '')}` content = `我是${userName},请帮我扩写一个文本内容,${content.replace('你将以在线AI 的方式创作一个文本内容,', '')}`
break break
} }
post({ type, context }) post({ type, content })
} }
const chatRef = ref() const chatRef = ref()
...@@ -109,6 +116,12 @@ function handleCopy(content) { ...@@ -109,6 +116,12 @@ function handleCopy(content) {
function parseHtml(content) { function parseHtml(content) {
return content.replaceAll('\n', '<br/>') return content.replaceAll('\n', '<br/>')
} }
// 保存图片
async function handleSave(message) {
const url = await uploadFileByUrl(message.image_url)
form.value.content = url
emit('submit', form.value)
}
</script> </script>
<template> <template>
...@@ -120,21 +133,44 @@ function parseHtml(content) { ...@@ -120,21 +133,44 @@ function parseHtml(content) {
<IconAI v-else /> <IconAI v-else />
</div> </div>
<div class="chat-message-main"> <div class="chat-message-main">
<div class="chat-message-content" v-html="parseHtml(item.content)"></div> <div class="chat-message-content">
<div class="chat-message-extra" v-if="item.role !== 'user'"> <div v-if="item.type === 'image'">
<el-button size="small" type="primary" @click="handleCopy(item.content)">复制</el-button> <img :src="item.image_url" />
<el-button size="small" type="primary" @click="handleSendType(5, item.input || item.content)" v-if="item.role == 'bot'" </div>
>刷新({{ usages.ai_refresh_count }}/{{ usages.ai_refresh_max_count }})</el-button <div v-else v-html="parseHtml(item.content)"></div>
> </div>
<el-button size="small" type="primary" @click="handleSendType(2, item.content)" <div class="chat-message-extra">
>AI创作({{ usages.ai_creation_count }}/{{ usages.ai_creation_max_count }})</el-button <!-- 文本 -->
> <template v-if="form.type == 1 && item.role !== 'user'">
<el-button size="small" type="primary" @click="handleSendType(3, item.content)" <el-button size="small" type="primary" @click="handleCopy(item.content)">复制</el-button>
>AI润色({{ usages.ai_polish_count }}/{{ usages.ai_polish_max_count }})</el-button <el-button size="small" type="primary" @click="handleSendType(5, item.input || item.content)" v-if="item.role == 'bot'"
> >刷新({{ usages.ai_refresh_count }}/{{ usages.ai_refresh_max_count }})</el-button
<el-button size="small" type="primary" @click="handleSendType(4, item.content)" >
>AI扩写({{ usages.ai_expand_count }}/{{ usages.ai_expand_max_count }})</el-button <el-button size="small" type="primary" @click="handleSendType(2, item.content)"
> >AI创作({{ usages.ai_creation_count }}/{{ usages.ai_creation_max_count }})</el-button
>
<el-button size="small" type="primary" @click="handleSendType(3, item.content)"
>AI润色({{ usages.ai_polish_count }}/{{ usages.ai_polish_max_count }})</el-button
>
<el-button size="small" type="primary" @click="handleSendType(4, item.content)"
>AI扩写({{ usages.ai_expand_count }}/{{ usages.ai_expand_max_count }})</el-button
>
</template>
<!-- 图片 -->
<template v-if="form.type == 2">
<template v-if="item.role == 'system'">
<el-button size="small" type="primary" @click="handleCopy(item.content)">复制</el-button>
<el-button size="small" type="primary" @click="handleSendType(99, item.content)"
>生成({{ usages.ai_generate_image_count }}/{{ usages.ai_generate_image_max_count }})</el-button
>
</template>
<template v-if="item.role == 'bot'">
<el-button size="small" type="primary" @click="handleSendType(99, item.content)"
>重画({{ usages.ai_generate_image_count }}/{{ usages.ai_generate_image_max_count }})</el-button
>
<el-button size="small" type="primary" @click="handleSave(item)">保存</el-button>
</template>
</template>
</div> </div>
</div> </div>
</div> </div>
...@@ -195,14 +231,19 @@ function parseHtml(content) { ...@@ -195,14 +231,19 @@ function parseHtml(content) {
.chat-message-content { .chat-message-content {
max-width: 100%; max-width: 100%;
word-break: break-word; word-break: break-word;
* {
max-width: 100%;
}
} }
.user .chat-message-main { .user .chat-message-main {
color: #fff; color: #fff;
background-color: var(--main-color); background-color: var(--main-color);
} }
.chat-message-extra { .chat-message-extra:not(:empty) {
margin-top: 20px; margin-top: 20px;
padding-top: 12px;
border-top: 1px solid #edeff1;
} }
.dot-flashing { .dot-flashing {
......
...@@ -17,13 +17,6 @@ const rules = ref({ ...@@ -17,13 +17,6 @@ const rules = ref({
name: [{ required: true, message: '请输入内容名称' }] name: [{ required: true, message: '请输入内容名称' }]
}) })
watch(
() => form.value.type,
() => {
form.value.way = '2'
}
)
async function handleValidate() { async function handleValidate() {
await formRef.value.validate() await formRef.value.validate()
} }
...@@ -39,7 +32,7 @@ async function handleNext() { ...@@ -39,7 +32,7 @@ async function handleNext() {
<template #header>基础信息</template> <template #header>基础信息</template>
<el-form label-suffix=":" label-width="130" :model="form" :rules="rules" ref="formRef" :disabled="action === 'view'"> <el-form label-suffix=":" label-width="130" :model="form" :rules="rules" ref="formRef" :disabled="action === 'view'">
<el-form-item label="营销内容类型" prop="type"> <el-form-item label="营销内容类型" prop="type">
<el-radio-group v-model="form.type" :disabled="action === 'update'"> <el-radio-group v-model="form.type" :disabled="action === 'update'" @change="form.way = '2'">
<el-radio v-for="item in materialType" :key="item.id" :value="item.value">{{ item.label }}</el-radio> <el-radio v-for="item in materialType" :key="item.id" :value="item.value">{{ item.label }}</el-radio>
</el-radio-group> </el-radio-group>
</el-form-item> </el-form-item>
...@@ -48,7 +41,7 @@ async function handleNext() { ...@@ -48,7 +41,7 @@ async function handleNext() {
</el-form-item> </el-form-item>
<el-form-item label="创作方式" prop="way"> <el-form-item label="创作方式" prop="way">
<el-radio-group v-model="form.way"> <el-radio-group v-model="form.way">
<el-radio v-for="item in materialMethodList" :key="item.value" :value="item.value" :disabled="item.value == 1 && form.type != 1">{{ <el-radio v-for="item in materialMethodList" :key="item.value" :value="item.value" :disabled="item.value == 1 && !['1', '2'].includes(form.type)">{{
item.label item.label
}}</el-radio> }}</el-radio>
</el-radio-group> </el-radio-group>
......
...@@ -39,7 +39,7 @@ async function handleSubmit() { ...@@ -39,7 +39,7 @@ async function handleSubmit() {
<template> <template>
<div class="three"> <div class="three">
<el-card shadow="never" v-if="form.way == 1"> <el-card shadow="never" v-if="form.way == 1">
<AIChat v-model="form" v-if="form.id"></AIChat> <AIChat v-model="form" @submit="handleSubmit" v-if="form.id"></AIChat>
</el-card> </el-card>
<el-card shadow="never"> <el-card shadow="never">
<template #header>内容创作</template> <template #header>内容创作</template>
......
import { fetchEventSource } from '@fortaine/fetch-event-source' import { fetchEventSource } from '@fortaine/fetch-event-source'
import { getAIUsage } from '../api' import { getAIUsage, postGenerateImage } from '../api'
import type { Message } from '../types' import type { Message } from '../types'
import { ElMessage } from 'element-plus' import { ElMessage } from 'element-plus'
...@@ -32,13 +32,22 @@ export function useChat(options: any) { ...@@ -32,13 +32,22 @@ export function useChat(options: any) {
}) })
async function post(data: any) { async function post(data: any) {
if (options.fileType == 1) {
await generateText(data)
} else {
await generateImage(data)
}
}
// 生成文本
async function generateText(data: any) {
isLoading.value = true isLoading.value = true
await fetchEventSource('/api/lab/v1/experiment/marketing-ai/sky-agents-chat', { await fetchEventSource('/api/lab/v1/experiment/marketing-ai/sky-agents-chat', {
method: 'POST', method: 'POST',
headers: { headers: {
'Content-Type': 'application/json' 'Content-Type': 'application/json'
}, },
body: JSON.stringify({ ...options, ...data, chart_id: chatId.value }), body: JSON.stringify({ ...options, ...data, context: data.content, chart_id: chatId.value }),
async onopen(response) { async onopen(response) {
if (response.ok) { if (response.ok) {
return return
...@@ -77,5 +86,22 @@ export function useChat(options: any) { ...@@ -77,5 +86,22 @@ export function useChat(options: any) {
}) })
} }
return { usages, chatId, messages, post, isLoading } // 生成图片
async function generateImage(data: any) {
isLoading.value = true
try {
const res = await postGenerateImage({ ...options, ...data })
if (res.data.detail.image_url) {
messages.value.push({ type: 'image', role: 'bot', ...res.data.detail })
} else {
ElMessage.error(res.data.detail.failure_reason)
}
fetchUsages()
} catch (error) {
console.log(error)
}
isLoading.value = false
}
return { usages, chatId, messages, post, generateImage, isLoading }
} }
import axios from 'axios'
import md5 from 'blueimp-md5' import md5 from 'blueimp-md5'
import { getSignature, uploadFile } from '@/api/base' import { getSignature, uploadFile } from '@/api/base'
...@@ -17,3 +18,8 @@ export async function upload(blob: Blob) { ...@@ -17,3 +18,8 @@ export async function upload(blob: Blob) {
await uploadFile(params) await uploadFile(params)
return params.url return params.url
} }
export async function uploadFileByUrl(url: string) {
const res = await axios.get(url, { responseType: 'blob' })
return upload(res.data)
}
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论