Commit 7fd27d15 authored by nick zheng's avatar nick zheng

feat: 应用增加AI自动配置

parent 69e0b200
......@@ -78,31 +78,54 @@ export function fetchCreateContinueQuestions<T>(payload: { input: string }) {
}
/**
* * @param { agentTitle: 标题 agentDesc: 描述 }
* * @param { payload: { agentTitle: 标题 agentDesc: 描述 }, controller: 控制取消请求 }
* @returns AI生成应用头像
*/
export function fetchCreateAgentAvatar<T>(payload: { agentTitle: string; agentDesc: string }) {
export function fetchCreateAgentAvatar<T>(
payload: { agentTitle: string; agentDesc: string },
controller: AbortController,
) {
return request.post<T>('/agentApplicationInfoRest/createAgentApplicationAvatar.json', payload, {
timeout: 120000,
signal: controller.signal,
})
}
/**
* * @param { agentTitle: 标题 agentDesc: 描述 agentSystem: 指令 }
* * @param { payload: { agentTitle: 标题, agentDesc: 描述, agentSystem: 指令 }, controller: 控制取消请求 }
* @returns AI生成开场白
*/
export function fetchCreatePreamble<T>(payload: { agentTitle: string; agentDesc: string; agentSystem: string }) {
export function fetchCreatePreamble<T>(
payload: { agentTitle: string; agentDesc: string; agentSystem: string },
controller: AbortController,
) {
return request.post<T>('/agentApplicationInfoRest/createPreamble.json', payload, {
timeout: 120000,
signal: controller.signal,
})
}
/**
* * @param { agentTitle: 标题 agentDesc: 描述 }
* * @param { payload: { agentTitle: 标题 agentDesc: 描述 }, controller: 控制取消请求 }
* @returns AI生成推荐问
*/
export function fetchCreateFeaturedQuestions<T>(payload: { agentTitle: string; agentDesc: string }) {
export function fetchCreateFeaturedQuestions<T>(
payload: { agentTitle: string; agentDesc: string },
controller: AbortController,
) {
return request.post<T>('/agentApplicationInfoRest/createFeaturedQuestions.json', payload, {
timeout: 120000,
signal: controller.signal,
})
}
/**
* * @param { payload: { input: 自动配置信息 }, controller: 控制取消请求 }
* @returns 生成应用标题和描述
*/
export function fetchCreateAgentTitleAndDesc<T>(payload: { input: string }, controller: AbortController) {
return request.post<T>('/agentApplicationInfoRest/createAgentTitleAndDesc.json', payload, {
timeout: 120000,
signal: controller.signal,
})
}
<script setup lang="ts">
import { computed } from 'vue'
import { computed, useSlots } from 'vue'
import { modalHeaderStyle, modalContentStyle, modalFooterStyle } from './modal-style'
interface Props {
......@@ -36,9 +36,11 @@ const props = withDefaults(defineProps<Props>(), {
const emit = defineEmits<Emits>()
const slots = useSlots()
const modalBasicStyle = {
width: props.width + 'px',
height: props.height + 'px',
minHeight: props.height + 'px',
borderRadius: props.borderRadius + 'px',
}
......@@ -85,7 +87,9 @@ function handleDetele() {
</div>
<template #footer>
<div class="flex w-full items-center justify-end">
<slot v-if="slots.footer" name="footer" />
<div v-else class="flex w-full items-center justify-end">
<NButton class="h-[32px]! rounded-md! px-6!" @click="handleCloseModal"> {{ cancelBtnText }} </NButton>
<NButton
:loading="btnLoading"
......
<script setup lang="ts">
import { computed, onMounted, reactive, ref, watch } from 'vue'
import { computed, nextTick, onMounted, onUnmounted, reactive, ref, watch } from 'vue'
import { useRouter } from 'vue-router'
import { FormInst, SelectOption, UploadFileInfo } from 'naive-ui'
import { FormInst, InputInst, SelectOption, UploadFileInfo } from 'naive-ui'
import { useThrottleFn } from '@vueuse/core'
import CustomIcon from '@/components/custom-icon/custom-icon.vue'
import UploadPhoto from '@/components/upload-photo/upload-photo.vue'
import AutoConfigModal from './auto-config-modal.vue'
import OptimizeSystemModal from './optimize-system-modal.vue'
import { usePersonalAppConfigStore } from '@/store/modules/personal-app-config'
import { PersonalAppConfigState } from '@/store/types/personal-app-config'
import {
fetchCreateAgentAvatar,
fetchCreateAgentTitleAndDesc,
fetchCreateFeaturedQuestions,
fetchCreatePreamble,
fetchGetDebugApplicationInfo,
fetchGetLargeModelList,
fetchSaveAgentApplication,
} from '@/apis/agent-application'
import { fetchCustomEventSource } from '@/composables/useEventSource'
const router = useRouter()
......@@ -52,6 +55,10 @@ const commConfigExpandedNames = ref<string[]>(['continuousQuestion'])
const isInitGetAgentAppDetail = ref(false)
const isFullScreenLoading = ref(false) // 是否全面加载中
const isShowAutoConfigModal = ref(false) // 是否显示自动配置对话框
const autoConfigBtnLoading = ref(false) // 自动配置按钮加载状态
const isShowOptimizeAgentSystemModal = ref(false) // 是否显示优化角色指令对话框
const generateAgentAvatarLoading = ref(false) // 是否正在生成图片
......@@ -59,6 +66,7 @@ const generatePreambleLoading = ref(false) // 是否正在生成开场白
const generateFeaturedQuestionsLoading = ref(false) // 是否正在生成推荐词
const personalAppFormRef = ref<FormInst | null>(null)
const agentSystemInputRef = ref<InputInst | null>(null)
const personalAppRules = {
baseInfo: {
......@@ -66,6 +74,12 @@ const personalAppRules = {
},
}
let generateAgentTitleAndDescController: AbortController | null = null
let generateAgentAvatarController: AbortController | null = null
let generatePreambleController: AbortController | null = null
let generateFeaturedQuestionsController: AbortController | null = null
let generateAgentSystemController: AbortController | null = null
const personalAppConfig = computed(() => {
return personalAppConfigStore.$state
})
......@@ -74,10 +88,14 @@ const continuousQuestionStatusText = computed(() => {
return personalAppConfig.value.commConfig.continuousQuestionStatus === 'default' ? '默认' : '关闭'
})
const isUpdatePersonalAgentConfig = computed(() => {
return !isInitGetAgentAppDetail.value
})
watch(
() => personalAppConfig.value,
() => {
!isInitGetAgentAppDetail.value && handleUpdatePersonalAppId()
isUpdatePersonalAgentConfig.value && handleUpdatePersonalAppId()
},
{ deep: true, once: true },
)
......@@ -85,7 +103,7 @@ watch(
watch(
() => personalAppConfig.value,
() => {
!isInitGetAgentAppDetail.value && handleSavePersonalAppConfig()
isUpdatePersonalAgentConfig.value && handleSavePersonalAppConfig()
},
{ deep: true },
)
......@@ -99,11 +117,15 @@ onMounted(async () => {
await handleGetLargeModelList()
})
onUnmounted(() => {
handleStopGenerate()
})
const handleSavePersonalAppConfig = useThrottleFn(
async () => {
personalAppConfig.value.baseInfo.agentId && (await handleSaveAgentApplication())
},
2000,
() => (isFullScreenLoading.value ? 6000 : 2000),
true,
)
......@@ -155,6 +177,11 @@ async function handleUpdatePersonalAppId() {
}
}
function handleShowAutoConfigModal() {
isShowAutoConfigModal.value = true
autoConfigBtnLoading.value = false
}
function handleShowOptimizeAgentSystemModal() {
if (!personalAppConfig.value.baseInfo.agentSystem) return
......@@ -179,11 +206,15 @@ function handleUpdateCommConfigExpandedNames(expandedNames: string[]) {
async function handleAIGenerateAgentAvatar() {
generateAgentAvatarLoading.value = true
generateAgentAvatarController = new AbortController()
const res = await fetchCreateAgentAvatar({
const res = await fetchCreateAgentAvatar(
{
agentTitle: personalAppConfig.value.baseInfo.agentTitle,
agentDesc: personalAppConfig.value.baseInfo.agentDesc,
}).finally(() => (generateAgentAvatarLoading.value = false))
},
generateAgentAvatarController,
).finally(() => (generateAgentAvatarLoading.value = false))
if (res.code === 0) {
personalAppConfig.value.baseInfo.agentAvatar = res.data as string
......@@ -195,11 +226,16 @@ async function handleAIGeneratePreamble() {
generatePreambleLoading.value = true
const res = await fetchCreatePreamble<string>({
generatePreambleController = new AbortController()
const res = await fetchCreatePreamble<string>(
{
agentTitle: personalAppConfig.value.baseInfo.agentTitle,
agentDesc: personalAppConfig.value.baseInfo.agentDesc,
agentSystem: personalAppConfig.value.baseInfo.agentSystem,
}).finally(() => (generatePreambleLoading.value = false))
},
generatePreambleController,
).finally(() => (generatePreambleLoading.value = false))
if (res.code === 0) {
try {
......@@ -217,21 +253,119 @@ async function handleAIGenerateFeaturedQuestions() {
commConfigExpandedNames.value.push('featuredQuestions')
generateFeaturedQuestionsLoading.value = true
generateFeaturedQuestionsController = new AbortController()
const res = await fetchCreateFeaturedQuestions<string[]>({
const res = await fetchCreateFeaturedQuestions<string[]>(
{
agentTitle: personalAppConfig.value.baseInfo.agentTitle,
agentDesc: personalAppConfig.value.baseInfo.agentDesc,
}).finally(() => (generateFeaturedQuestionsLoading.value = false))
},
generateFeaturedQuestionsController,
).finally(() => (generateFeaturedQuestionsLoading.value = false))
if (res.code === 0) {
personalAppConfig.value.commConfig.featuredQuestions = res.data
}
}
async function handleSettingAgent(autoConfigInputValue: string) {
autoConfigBtnLoading.value = true
isShowAutoConfigModal.value = false
isFullScreenLoading.value = true
await handleCreateAgentTitleAndDesc(autoConfigInputValue)
.then(() => {
Promise.all([
handleAIGenerateAgentAvatar(),
handleAIGeneratePreamble(),
handleAIGenerateFeaturedQuestions(),
handleAIGenerateAgentSystem(),
]).finally(() => {
isFullScreenLoading.value = false
})
})
.catch(() => {
isFullScreenLoading.value = false
})
}
async function handleCreateAgentTitleAndDesc(input: string) {
generateAgentTitleAndDescController = new AbortController()
const res = await fetchCreateAgentTitleAndDesc<{ agentTitle: string; agentDesc: string }>(
{ input },
generateAgentTitleAndDescController,
)
if (res.code === 0) {
personalAppConfig.value.baseInfo.agentTitle = res.data.agentTitle
personalAppConfig.value.baseInfo.agentDesc = res.data.agentDesc
}
}
function handleSettingAgentSystem(agentSystem: string) {
personalAppConfig.value.baseInfo.agentSystem = agentSystem
isShowOptimizeAgentSystemModal.value = false
}
function handleAIGenerateAgentSystem() {
return new Promise((resolve, reject) => {
personalAppConfig.value.baseInfo.agentSystem = ''
generateAgentSystemController = new AbortController()
fetchCustomEventSource({
path: '/api/rest/agentApplicationInfoRest/createAgentSystem.json',
payload: {
input: personalAppConfigStore.baseInfo.agentSystem,
},
controller: generateAgentSystemController,
onMessage: (data: any) => {
if (data === '[DONE]') {
blockAnswerResponse()
resolve(data)
return
}
if (data) {
useThrottleFn(
() => {
nextTick(() => {
personalAppConfig.value.baseInfo.agentSystem += data
agentSystemInputRef.value!.textareaElRef!.scrollTop =
agentSystemInputRef.value?.textareaElRef?.scrollHeight || 0
})
},
1000,
true,
)()
}
},
onRequestError: () => {
reject()
blockAnswerResponse()
},
onError: () => {
reject()
blockAnswerResponse()
},
onFinally: () => {
generateAgentSystemController = null
},
})
})
}
function blockAnswerResponse() {
generateAgentSystemController?.abort()
}
function handleStopGenerate() {
generateAgentTitleAndDescController?.abort()
generateAgentAvatarController?.abort()
generatePreambleController?.abort()
generateFeaturedQuestionsController?.abort()
generateAgentSystemController?.abort()
isFullScreenLoading.value = false
}
</script>
<template>
......@@ -239,8 +373,18 @@ function handleSettingAgentSystem(agentSystem: string) {
<div
class="flex h-[56px] w-full items-center justify-between border-r border-[#e8e9eb] px-5 text-[#333] shadow-[inset_0_-1px_#e8e9eb]"
>
<div class="flex items-center">
<span class="font-600 mr-4 text-base">应用配置</span>
<div
class="text-theme-color flex cursor-pointer items-center rounded-md border border-[#d4e5ff] bg-[#eef3fe] px-[11px] py-[7px] text-xs hover:opacity-80"
@click="handleShowAutoConfigModal"
>
<div class="mr-2 mt-[-4px] h-[16px] w-[16px] bg-[url(@/assets/svgs/star.svg)] bg-[length:100%_100%]" />
<span>AI自动配置</span>
</div>
</div>
<div>
<NPopover placement="bottom" trigger="click" style="width: 420px">
<template #trigger>
......@@ -474,6 +618,7 @@ function handleSettingAgentSystem(agentSystem: string) {
<div class="flex flex-1 p-1 pl-6">
<NInput
ref="agentSystemInputRef"
v-model:value="personalAppConfig.baseInfo.agentSystem"
type="textarea"
class="prompt-input not-resize text-xs! flex-1"
......@@ -626,6 +771,26 @@ function handleSettingAgentSystem(agentSystem: string) {
</div>
</div>
<div
v-show="isFullScreenLoading"
class="z-999 fixed bottom-0 left-0 right-0 top-0 flex items-center justify-center bg-[#151b2680]"
>
<div class="flex h-14 w-[300px] items-center justify-between rounded-xl bg-white px-5">
<div class="flex items-center">
<div class="mr-2 h-4 w-4 bg-[url(@/assets/images/loading.gif)] bg-[length:100%_100%]" />
<span class="text-theme-color">配置信息生成中...</span>
</div>
<div class="cursor-pointer text-[#5c5f66] hover:opacity-80" @click="handleStopGenerate">停止生成</div>
</div>
</div>
<AutoConfigModal
v-model:is-show-modal="isShowAutoConfigModal"
:btn-loading="autoConfigBtnLoading"
modal-title="AI生成配置信息"
@comfirm="handleSettingAgent"
/>
<OptimizeSystemModal
v-model:is-show-modal="isShowOptimizeAgentSystemModal"
:btn-loading="false"
......
<script setup lang="ts">
import { computed, ref, watch } from 'vue'
import CustomModal from '@/components/custom-modal/custom-modal.vue'
import CustomIcon from '@/components/custom-icon/custom-icon.vue'
interface Props {
isShowModal: boolean
btnLoading: boolean
modalTitle: string
}
interface Emits {
(e: 'update:isShowModal', value: boolean): void
(e: 'comfirm', value: string): void
}
const props = defineProps<Props>()
const emit = defineEmits<Emits>()
const autoConfigInputValue = ref('')
const autoConfigInputType = ref<'random' | 'input'>('random')
const showModal = computed({
get() {
return props.isShowModal
},
set(value: boolean) {
emit('update:isShowModal', value)
},
})
const isDisabledBtn = computed(() => {
return !autoConfigInputValue.value
})
const isRandomBtnLoading = computed(() => {
return props.btnLoading && autoConfigInputType.value === 'random'
})
const isInputBtnLoading = computed(() => {
return props.btnLoading && autoConfigInputType.value === 'input'
})
watch(
() => showModal.value,
() => {
showModal.value && (autoConfigInputValue.value = '')
},
)
function handleCloseModal() {
emit('update:isShowModal', false)
}
function handleComfirm(inputType: 'random' | 'input') {
autoConfigInputType.value = inputType
emit('comfirm', inputType === 'random' ? '' : autoConfigInputValue.value)
}
</script>
<template>
<CustomModal v-model:is-show="showModal" :title="modalTitle" :width="600">
<template #content>
<div class="mb-3 flex h-8 w-full items-center rounded bg-[#FFF4E6] px-4">
<CustomIcon icon="ep:warning-filled" class="mr-2 h-4 w-4 text-[#FFA500]" />
<span>生成结果将覆盖当前的配置内容,请确认是否继续生成</span>
</div>
<NInput
v-model:value="autoConfigInputValue"
type="textarea"
:rows="10"
:disabled="false"
placeholder="请告诉我你想创建一个什么样的应用,大模型将为你自动生成"
class="rounded-lg!"
/>
</template>
<template #footer>
<div class="flex w-full items-center justify-end">
<NButton class="h-[32px]! rounded-md! px-6!" @click="handleCloseModal"> 取 消 </NButton>
<NButton
:loading="isRandomBtnLoading"
class="h-[32px]! rounded-md! px-6! ml-4!"
@click="handleComfirm('random')"
>
随机生成
</NButton>
<NButton
:loading="isInputBtnLoading"
type="primary"
:disabled="isDisabledBtn"
class="h-[32px]! px-6! rounded-md! ml-4!"
@click="handleComfirm('input')"
>
AI生成
</NButton>
</div>
</template>
</CustomModal>
</template>
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment