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

chore: 新增AI生成图片

上级 599d1118
......@@ -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 }) {
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'
import IconUser from './IconUser.vue'
import IconAI from './IconAI.vue'
import xss from 'xss'
import { uploadFileByUrl } from '@/utils/upload'
const emit = defineEmits(['submit'])
const form = defineModel()
......@@ -30,13 +33,17 @@ const welcomeMessage = computed(() => {
const content = ref('')
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(() => {
messages.value.push({ role: 'system', content: welcomeMessage.value })
if (form.value.content) {
messages.value.push({ role: 'bot', content: form.value.content })
}
// if (form.value.content) {
// messages.value.push({ role: 'bot', content: form.value.content })
// }
})
watch(welcomeMessage, () => {
......@@ -55,7 +62,7 @@ async function postMessage() {
if (!content.value) return
console.log(content.value)
messages.value.push({ role: 'user', content: content.value })
post({ context: content.value, type: '1' })
post({ content: content.value, type: '1' })
content.value = ''
}
......@@ -65,26 +72,26 @@ async function handleSend(event) {
await postMessage()
}
async function handleSendType(type, context) {
async function handleSendType(type, content) {
const userName = useUserStore().user.name
context = xss(context, {
content = xss(content, {
whiteList: {}, // 白名单为空,表示过滤所有标签
stripIgnoreTag: true, // 过滤所有非白名单标签的HTML
stripIgnoreTagBody: ['script'] // script标签较特殊,需要过滤标签中间的内容
})
switch (type) {
case 2:
context = `我是${userName},请帮我创作一个文本内容,${context.replace('你将以在线AI 的方式创作一个文本内容,', '')}`
content = `我是${userName},请帮我创作一个文本内容,${content.replace('你将以在线AI 的方式创作一个文本内容,', '')}`
break
case 3:
context = `我是${userName},请帮我润色一个文本内容,${context.replace('你将以在线AI 的方式创作一个文本内容,', '')}`
content = `我是${userName},请帮我润色一个文本内容,${content.replace('你将以在线AI 的方式创作一个文本内容,', '')}`
break
case 4:
context = `我是${userName},请帮我扩写一个文本内容,${context.replace('你将以在线AI 的方式创作一个文本内容,', '')}`
content = `我是${userName},请帮我扩写一个文本内容,${content.replace('你将以在线AI 的方式创作一个文本内容,', '')}`
break
}
post({ type, context })
post({ type, content })
}
const chatRef = ref()
......@@ -109,6 +116,12 @@ function handleCopy(content) {
function parseHtml(content) {
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>
<template>
......@@ -120,21 +133,44 @@ function parseHtml(content) {
<IconAI v-else />
</div>
<div class="chat-message-main">
<div class="chat-message-content" v-html="parseHtml(item.content)"></div>
<div class="chat-message-extra" v-if="item.role !== 'user'">
<el-button size="small" type="primary" @click="handleCopy(item.content)">复制</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(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
>
<div class="chat-message-content">
<div v-if="item.type === 'image'">
<img :src="item.image_url" />
</div>
<div v-else v-html="parseHtml(item.content)"></div>
</div>
<div class="chat-message-extra">
<!-- 文本 -->
<template v-if="form.type == 1 && item.role !== 'user'">
<el-button size="small" type="primary" @click="handleCopy(item.content)">复制</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(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>
......@@ -195,14 +231,19 @@ function parseHtml(content) {
.chat-message-content {
max-width: 100%;
word-break: break-word;
* {
max-width: 100%;
}
}
.user .chat-message-main {
color: #fff;
background-color: var(--main-color);
}
.chat-message-extra {
.chat-message-extra:not(:empty) {
margin-top: 20px;
padding-top: 12px;
border-top: 1px solid #edeff1;
}
.dot-flashing {
......
......@@ -17,13 +17,6 @@ const rules = ref({
name: [{ required: true, message: '请输入内容名称' }]
})
watch(
() => form.value.type,
() => {
form.value.way = '2'
}
)
async function handleValidate() {
await formRef.value.validate()
}
......@@ -39,7 +32,7 @@ async function handleNext() {
<template #header>基础信息</template>
<el-form label-suffix=":" label-width="130" :model="form" :rules="rules" ref="formRef" :disabled="action === 'view'">
<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-group>
</el-form-item>
......@@ -48,7 +41,7 @@ async function handleNext() {
</el-form-item>
<el-form-item label="创作方式" prop="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
}}</el-radio>
</el-radio-group>
......
......@@ -39,7 +39,7 @@ async function handleSubmit() {
<template>
<div class="three">
<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 shadow="never">
<template #header>内容创作</template>
......
import { fetchEventSource } from '@fortaine/fetch-event-source'
import { getAIUsage } from '../api'
import { getAIUsage, postGenerateImage } from '../api'
import type { Message } from '../types'
import { ElMessage } from 'element-plus'
......@@ -32,13 +32,22 @@ export function useChat(options: 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
await fetchEventSource('/api/lab/v1/experiment/marketing-ai/sky-agents-chat', {
method: 'POST',
headers: {
'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) {
if (response.ok) {
return
......@@ -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 { getSignature, uploadFile } from '@/api/base'
......@@ -17,3 +18,8 @@ export async function upload(blob: Blob) {
await uploadFile(params)
return params.url
}
export async function uploadFileByUrl(url: string) {
const res = await axios.get(url, { responseType: 'blob' })
return upload(res.data)
}
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论