feat: application
Some checks are pending
sync2gitee / repo-sync (push) Waiting to run

This commit is contained in:
wangdan-fit2cloud 2025-06-03 20:45:12 +08:00
parent b6ef007b45
commit f4f39d8059
8 changed files with 586 additions and 77 deletions

View File

@ -39,10 +39,11 @@ const getApplication: (
* @param
*/
const postApplication: (
wordspace_id: string,
data: ApplicationFormType,
loading?: Ref<boolean>,
) => Promise<Result<any>> = (data, loading) => {
return post(`${prefix}`, data, undefined, loading)
) => Promise<Result<any>> = (wordspace_id, data, loading) => {
return post(`${prefix}/${wordspace_id}/application`, data, undefined, loading)
}
/**

View File

@ -0,0 +1,503 @@
import { type Dict } from '@/api/type/common'
import { type Ref } from 'vue'
import bus from '@/bus'
interface ApplicationFormType {
name?: string
desc?: string
model_id?: string
dialogue_number?: number
prologue?: string
dataset_id_list?: string[]
dataset_setting?: any
model_setting?: any
problem_optimization?: boolean
problem_optimization_prompt?: string
icon?: string | undefined
type?: string
work_flow?: any
model_params_setting?: any
tts_model_params_setting?: any
stt_model_id?: string
tts_model_id?: string
stt_model_enable?: boolean
tts_model_enable?: boolean
tts_type?: string
tts_autoplay?: boolean
stt_autosend?: boolean
}
interface Chunk {
real_node_id: string
chat_id: string
chat_record_id: string
content: string
reasoning_content: string
node_id: string
up_node_id: string
is_end: boolean
node_is_end: boolean
node_type: string
view_type: string
runtime_node_id: string
child_node: any
}
interface chatType {
id: string
problem_text: string
answer_text: string
buffer: Array<String>
answer_text_list: Array<
Array<{
content: string
reasoning_content: string
chat_record_id?: string
runtime_node_id?: string
child_node?: any
real_node_id?: string
}>
>
/**
*
*/
write_ed?: boolean
/**
*
*/
is_stop?: boolean
record_id: string
chat_id: string
vote_status: string
status?: number
execution_details: any[]
upload_meta?: {
document_list: Array<any>
image_list: Array<any>
audio_list: Array<any>
other_list: Array<any>
}
}
interface Node {
buffer: Array<string>
node_id: string
up_node_id: string
node_type: string
view_type: string
index: number
is_end: boolean
}
interface WriteNodeInfo {
current_node: any
answer_text_list_index: number
current_up_node?: any
divider_content?: Array<string>
divider_reasoning_content?: Array<string>
}
export class ChatRecordManage {
id?: any
ms: number
chat: chatType
is_close?: boolean
write_ed?: boolean
is_stop?: boolean
loading?: Ref<boolean>
node_list: Array<any>
write_node_info?: WriteNodeInfo
constructor(chat: chatType, ms?: number, loading?: Ref<boolean>) {
this.ms = ms ? ms : 10
this.chat = chat
this.loading = loading
this.is_stop = false
this.is_close = false
this.write_ed = false
this.node_list = []
}
append_answer(
chunk_answer: string,
reasoning_content: string,
index?: number,
chat_record_id?: string,
runtime_node_id?: string,
child_node?: any,
real_node_id?: string
) {
if (chunk_answer || reasoning_content) {
const set_index = index != undefined ? index : this.chat.answer_text_list.length - 1
let card_list = this.chat.answer_text_list[set_index]
if (!card_list) {
card_list = []
this.chat.answer_text_list[set_index] = card_list
}
const answer_value = card_list.find((item) => item.real_node_id == real_node_id)
const content = answer_value ? answer_value.content + chunk_answer : chunk_answer
const _reasoning_content = answer_value
? answer_value.reasoning_content + reasoning_content
: reasoning_content
if (answer_value) {
answer_value.content = content
answer_value.reasoning_content = _reasoning_content
} else {
card_list.push({
content: content,
reasoning_content: _reasoning_content,
chat_record_id,
runtime_node_id,
child_node,
real_node_id
})
}
}
this.chat.answer_text = this.chat.answer_text + chunk_answer
bus.emit('change:answer', { record_id: this.chat.record_id, is_end: false })
}
get_current_up_node(run_node: any) {
const index = this.node_list.findIndex((item) => item == run_node)
if (index > 0) {
const n = this.node_list[index - 1]
return n
}
return undefined
}
get_run_node() {
if (
this.write_node_info &&
(this.write_node_info.current_node.reasoning_content_buffer.length > 0 ||
this.write_node_info.current_node.buffer.length > 0 ||
!this.write_node_info.current_node.is_end)
) {
return this.write_node_info
}
const run_node = this.node_list.filter(
(item) => item.reasoning_content_buffer.length > 0 || item.buffer.length > 0 || !item.is_end
)[0]
if (run_node) {
const index = this.node_list.indexOf(run_node)
let current_up_node = undefined
if (index > 0) {
current_up_node = this.get_current_up_node(run_node)
}
let answer_text_list_index = 0
if (
current_up_node == undefined ||
run_node.view_type == 'single_view' ||
current_up_node.view_type == 'single_view'
) {
const none_index = this.findIndex(
this.chat.answer_text_list,
(item) => (item.length == 1 && item[0].content == '') || item.length == 0,
'index'
)
if (none_index > -1) {
answer_text_list_index = none_index
} else {
answer_text_list_index = this.chat.answer_text_list.length
}
} else {
const none_index = this.findIndex(
this.chat.answer_text_list,
(item) => (item.length == 1 && item[0].content == '') || item.length == 0,
'index'
)
if (none_index > -1) {
answer_text_list_index = none_index
} else {
answer_text_list_index = this.chat.answer_text_list.length - 1
}
}
this.write_node_info = {
current_node: run_node,
current_up_node: current_up_node,
answer_text_list_index: answer_text_list_index
}
return this.write_node_info
}
return undefined
}
findIndex<T>(array: Array<T>, find: (item: T) => boolean, type: 'last' | 'index') {
let set_index = -1
for (let index = 0; index < array.length; index++) {
const element = array[index]
if (find(element)) {
set_index = index
if (type == 'index') {
break
}
}
}
return set_index
}
closeInterval() {
this.chat.write_ed = true
this.write_ed = true
if (this.loading) {
this.loading.value = false
}
bus.emit('change:answer', { record_id: this.chat.record_id, is_end: true })
if (this.id) {
clearInterval(this.id)
}
const last_index = this.findIndex(
this.chat.answer_text_list,
(item) => (item.length == 1 && item[0].content == '') || item.length == 0,
'last'
)
if (last_index > 0) {
this.chat.answer_text_list.splice(last_index, 1)
}
}
write() {
this.chat.is_stop = false
this.is_stop = false
if (!this.is_close) {
this.is_close = false
}
this.write_ed = false
this.chat.write_ed = false
if (this.loading) {
this.loading.value = true
}
this.id = setInterval(() => {
const node_info = this.get_run_node()
if (node_info == undefined) {
if (this.is_close) {
this.closeInterval()
}
return
}
const { current_node, answer_text_list_index } = node_info
if (current_node.buffer.length > 20) {
const context = current_node.is_end
? current_node.buffer.splice(0)
: current_node.buffer.splice(
0,
current_node.is_end ? undefined : current_node.buffer.length - 20
)
const reasoning_content = current_node.is_end
? current_node.reasoning_content_buffer.splice(0)
: current_node.reasoning_content_buffer.splice(
0,
current_node.is_end ? undefined : current_node.reasoning_content_buffer.length - 20
)
this.append_answer(
context.join(''),
reasoning_content.join(''),
answer_text_list_index,
current_node.chat_record_id,
current_node.runtime_node_id,
current_node.child_node,
current_node.real_node_id
)
} else if (this.is_close) {
while (true) {
const node_info = this.get_run_node()
if (node_info == undefined) {
break
}
this.append_answer(
node_info.current_node.buffer.splice(0).join(''),
node_info.current_node.reasoning_content_buffer.splice(0).join(''),
node_info.answer_text_list_index,
node_info.current_node.chat_record_id,
node_info.current_node.runtime_node_id,
node_info.current_node.child_node,
node_info.current_node.real_node_id
)
if (
node_info.current_node.buffer.length == 0 &&
node_info.current_node.reasoning_content_buffer.length == 0
) {
node_info.current_node.is_end = true
}
}
this.closeInterval()
} else {
const s = current_node.buffer.shift()
const reasoning_content = current_node.reasoning_content_buffer.shift()
if (s !== undefined) {
this.append_answer(
s,
'',
answer_text_list_index,
current_node.chat_record_id,
current_node.runtime_node_id,
current_node.child_node,
current_node.real_node_id
)
}
if (reasoning_content !== undefined) {
this.append_answer(
'',
reasoning_content,
answer_text_list_index,
current_node.chat_record_id,
current_node.runtime_node_id,
current_node.child_node,
current_node.real_node_id
)
}
}
}, this.ms)
}
stop() {
clearInterval(this.id)
this.is_stop = true
this.chat.is_stop = true
if (this.loading) {
this.loading.value = false
}
}
close() {
this.is_close = true
}
open() {
this.is_close = false
this.is_stop = false
}
appendChunk(chunk: Chunk) {
let n = this.node_list.find((item) => item.real_node_id == chunk.real_node_id)
if (n) {
n.buffer.push(...chunk.content)
n.content += chunk.content
if (chunk.reasoning_content) {
n.reasoning_content_buffer.push(...chunk.reasoning_content)
n.reasoning_content += chunk.reasoning_content
}
} else {
n = {
buffer: [...chunk.content],
reasoning_content_buffer: chunk.reasoning_content ? [...chunk.reasoning_content] : [],
reasoning_content: chunk.reasoning_content ? chunk.reasoning_content : '',
content: chunk.content,
real_node_id: chunk.real_node_id,
node_id: chunk.node_id,
chat_record_id: chunk.chat_record_id,
up_node_id: chunk.up_node_id,
runtime_node_id: chunk.runtime_node_id,
child_node: chunk.child_node,
node_type: chunk.node_type,
index: this.node_list.length,
view_type: chunk.view_type,
is_end: false
}
this.node_list.push(n)
}
if (chunk.node_is_end) {
n['is_end'] = true
}
}
append(answer_text_block: string, reasoning_content?: string) {
let set_index = this.findIndex(
this.chat.answer_text_list,
(item) => item.length == 1 && item[0].content == '',
'index'
)
if (set_index <= -1) {
set_index = 0
}
this.chat.answer_text_list[set_index] = [
{
content: answer_text_block,
reasoning_content: reasoning_content ? reasoning_content : ''
}
]
}
}
export class ChatManagement {
static chatMessageContainer: Dict<ChatRecordManage> = {}
static addChatRecord(chat: chatType, ms: number, loading?: Ref<boolean>) {
this.chatMessageContainer[chat.id] = new ChatRecordManage(chat, ms, loading)
}
static appendChunk(chatRecordId: string, chunk: Chunk) {
const chatRecord = this.chatMessageContainer[chatRecordId]
if (chatRecord) {
chatRecord.appendChunk(chunk)
}
}
static append(chatRecordId: string, content: string, reasoning_content?: string) {
const chatRecord = this.chatMessageContainer[chatRecordId]
if (chatRecord) {
chatRecord.append(content, reasoning_content)
}
}
static updateStatus(chatRecordId: string, code: number) {
const chatRecord = this.chatMessageContainer[chatRecordId]
if (chatRecord) {
chatRecord.chat.status = code
}
}
/**
*
* @param chatRecordId id
*/
static write(chatRecordId: string) {
const chatRecord = this.chatMessageContainer[chatRecordId]
if (chatRecord) {
chatRecord.write()
}
}
static open(chatRecordId: string) {
const chatRecord = this.chatMessageContainer[chatRecordId]
if (chatRecord) {
chatRecord.open()
}
}
/**
*
* @param chatRecordId id
* @returns boolean
*/
static close(chatRecordId: string) {
const chatRecord = this.chatMessageContainer[chatRecordId]
if (chatRecord) {
chatRecord.close()
}
}
/**
*
* @param chatRecordId id
* @returns boolean
*/
static stop(chatRecordId: string) {
const chatRecord = this.chatMessageContainer[chatRecordId]
if (chatRecord) {
chatRecord.stop()
}
}
/**
*
* @param chatRecordId id
* @returns boolean
*/
static isClose(chatRecordId: string) {
const chatRecord = this.chatMessageContainer[chatRecordId]
return chatRecord ? chatRecord.is_close && chatRecord.write_ed : false
}
/**
*
* @param chatRecordId id
* @returns
*/
static isStop(chatRecordId: string) {
const chatRecord = this.chatMessageContainer[chatRecordId]
return chatRecord ? chatRecord.is_stop : false
}
/**
* close掉的和stop的数据
*/
static clean() {
for (const key in Object.keys(this.chatMessageContainer)) {
if (this.chatMessageContainer[key].is_close) {
delete this.chatMessageContainer[key]
}
}
}
}
export type { ApplicationFormType, chatType }

8
ui/src/bus/index.ts Normal file
View File

@ -0,0 +1,8 @@
import mitt from "mitt";
const bus: any = {};
const emitter = mitt();
bus.on = emitter.on;
bus.off = emitter.off;
bus.emit = emitter.emit;
export default bus;

View File

@ -1,6 +1,7 @@
export default {
title: 'APP',
createApplication: 'Create APP',
createApplication: 'Create Simple APP',
createWorkFlowApplication: 'Create Workflow APP',
importApplication: 'Import APP',
copyApplication: 'Copy APP',
workflow: 'WORKFLOW',

View File

@ -1,6 +1,7 @@
export default {
title: '应用',
createApplication: '创建应用',
createApplication: '创建简易应用',
createWorkFlowApplication: '创建高级编排应用',
importApplication: '导入应用',
copyApplication: '复制应用',
workflow: '高级编排',

View File

@ -1,6 +1,7 @@
export default {
title: '應用',
createApplication: '建立應用',
createApplication: '建立簡易應用',
createWorkFlowApplication: '建立進階編排應用',
importApplication: '匯入應用',
copyApplication: '複製應用',
workflow: '進階編排',

View File

@ -1,6 +1,10 @@
<template>
<el-dialog
:title="$t('views.application.createApplication')"
:title="
isWorkFlow(applicationForm.type)
? $t('views.application.createWorkFlowApplication')
: $t('views.application.createApplication')
"
v-model="dialogVisible"
width="650"
append-to-body
@ -15,51 +19,26 @@
require-asterisk-position="right"
@submit.prevent
>
<el-form-item :label="$t('views.application.applicationForm.form.appName.label')" prop="name">
<el-form-item :label="$t('views.application.form.appName.label')" prop="name">
<el-input
v-model="applicationForm.name"
maxlength="64"
:placeholder="$t('views.application.applicationForm.form.appName.placeholder')"
:placeholder="$t('views.application.form.appName.placeholder')"
show-word-limit
@blur="applicationForm.name = applicationForm.name?.trim()"
/>
</el-form-item>
<el-form-item :label="$t('views.application.applicationForm.form.appDescription.label')">
<el-form-item :label="$t('views.application.form.appDescription.label')">
<el-input
v-model="applicationForm.desc"
type="textarea"
:placeholder="$t('views.application.applicationForm.form.appDescription.placeholder')"
:placeholder="$t('views.application.form.appDescription.placeholder')"
:rows="3"
maxlength="256"
show-word-limit
/>
</el-form-item>
<el-form-item :label="$t('views.application.applicationForm.form.appType.label')">
<el-radio-group v-model="applicationForm.type" class="card__radio">
<el-row :gutter="16">
<el-col :span="12">
<el-card shadow="never" :class="applicationForm.type === 'SIMPLE' ? 'active' : ''">
<el-radio value="SIMPLE" size="large">
<p class="mb-4">{{ $t('views.application.simple') }}</p>
<el-text type="info">{{
$t('views.application.applicationForm.form.appType.simplePlaceholder')
}}</el-text>
</el-radio>
</el-card>
</el-col>
<el-col :span="12">
<el-card shadow="never" :class="isWorkFlow(applicationForm.type) ? 'active' : ''">
<el-radio value="WORK_FLOW" size="large">
<p class="mb-4">{{ $t('views.application.workflow') }}</p>
<el-text type="info">{{
$t('views.application.applicationForm.form.appType.workflowPlaceholder')
}}</el-text>
</el-radio>
</el-card>
</el-col>
</el-row>
</el-radio-group>
</el-form-item>
<el-form-item
:label="$t('views.document.upload.template')"
v-if="applicationForm.type === 'WORK_FLOW'"
@ -73,7 +52,7 @@
@click="selectedType('blank')"
:class="appTemplate === 'blank' ? 'active' : ''"
>
{{ $t('views.application.applicationForm.form.appTemplate.blankApp') }}
{{ $t('views.application.form.appTemplate.blankApp') }}
</el-card>
</el-col>
<el-col :span="12">
@ -83,7 +62,7 @@
:class="appTemplate === 'assistant' ? 'active' : ''"
@click="selectedType('assistant')"
>
{{ $t('views.application.applicationForm.form.appTemplate.assistantApp') }}
{{ $t('views.application.form.appTemplate.assistantApp') }}
</el-card>
</el-col>
</el-row>
@ -116,21 +95,21 @@ const router = useRouter()
const emit = defineEmits(['refresh'])
// @ts-ignore
const defaultPrompt = t('views.application.applicationForm.form.prompt.defaultPrompt', {
const defaultPrompt = t('views.application.form.prompt.defaultPrompt', {
data: '{data}',
question: '{question}'
question: '{question}',
})
const optimizationPrompt =
t('views.application.applicationForm.dialog.defaultPrompt1', {
question: '{question}'
t('views.application.dialog.defaultPrompt1', {
question: '{question}',
}) +
'<data></data>' +
t('views.application.applicationForm.dialog.defaultPrompt2')
t('views.application.dialog.defaultPrompt2')
const workflowDefault = ref<any>({
edges: [],
nodes: baseNodes
nodes: baseNodes,
})
const appTemplate = ref('blank')
@ -144,7 +123,7 @@ const applicationForm = ref<ApplicationFormType>({
desc: '',
model_id: '',
dialogue_number: 1,
prologue: t('views.application.applicationForm.form.defaultPrologue'),
prologue: t('views.application.form.defaultPrologue'),
dataset_id_list: [],
dataset_setting: {
top_n: 3,
@ -153,13 +132,13 @@ const applicationForm = ref<ApplicationFormType>({
search_mode: 'embedding',
no_references_setting: {
status: 'ai_questioning',
value: '{question}'
}
value: '{question}',
},
},
model_setting: {
prompt: defaultPrompt,
system: t('views.application.applicationForm.form.roleSettings.placeholder'),
no_references_prompt: '{question}'
system: t('views.application.form.roleSettings.placeholder'),
no_references_prompt: '{question}',
},
model_params_setting: {},
problem_optimization: false,
@ -169,26 +148,28 @@ const applicationForm = ref<ApplicationFormType>({
stt_model_enable: false,
tts_model_enable: false,
tts_type: 'BROWSER',
type: 'SIMPLE'
type: 'SIMPLE',
})
const rules = reactive<FormRules<ApplicationFormType>>({
name: [
{
required: true,
message: t('views.application.applicationForm.form.appName.placeholder'),
trigger: 'blur'
}
message: t('views.application.form.appName.placeholder'),
trigger: 'blur',
},
],
model_id: [
{
required: false,
message: t('views.application.applicationForm.form.aiModel.placeholder'),
trigger: 'change'
}
]
message: t('views.application.form.aiModel.placeholder'),
trigger: 'change',
},
],
})
const currentFolder = ref('')
watch(dialogVisible, (bool) => {
if (!bool) {
applicationForm.value = {
@ -196,7 +177,7 @@ watch(dialogVisible, (bool) => {
desc: '',
model_id: '',
dialogue_number: 1,
prologue: t('views.application.applicationForm.form.defaultPrologue'),
prologue: t('views.application.form.defaultPrologue'),
dataset_id_list: [],
dataset_setting: {
top_n: 3,
@ -205,13 +186,13 @@ watch(dialogVisible, (bool) => {
search_mode: 'embedding',
no_references_setting: {
status: 'ai_questioning',
value: '{question}'
}
value: '{question}',
},
},
model_setting: {
prompt: defaultPrompt,
system: t('views.application.applicationForm.form.roleSettings.placeholder'),
no_references_prompt: '{question}'
system: t('views.application.form.roleSettings.placeholder'),
no_references_prompt: '{question}',
},
model_params_setting: {},
problem_optimization: false,
@ -221,13 +202,15 @@ watch(dialogVisible, (bool) => {
stt_model_enable: false,
tts_model_enable: false,
tts_type: 'BROWSER',
type: 'SIMPLE'
type: 'SIMPLE',
}
applicationFormRef.value?.clearValidate()
}
})
const open = () => {
const open = (folder: string, type?: sting) => {
currentFolder.value = folder
applicationForm.value.type = type || 'SIMPLE'
dialogVisible.value = true
}
@ -235,12 +218,13 @@ const submitHandle = async (formEl: FormInstance | undefined) => {
if (!formEl) return
await formEl.validate((valid) => {
if (valid) {
applicationForm.value['folder_id'] = currentFolder.value
if (isWorkFlow(applicationForm.value.type) && appTemplate.value === 'blank') {
workflowDefault.value.nodes[0].properties.node_data.desc = applicationForm.value.desc
workflowDefault.value.nodes[0].properties.node_data.name = applicationForm.value.name
applicationForm.value['work_flow'] = workflowDefault.value
}
applicationApi.postApplication(applicationForm.value, loading).then((res) => {
applicationApi.postApplication('default', applicationForm.value, loading).then((res) => {
MsgSuccess(t('common.createSuccess'))
emit('refresh')
if (isWorkFlow(applicationForm.value.type)) {

View File

@ -47,7 +47,7 @@
</el-button>
<template #dropdown>
<el-dropdown-menu class="create-dropdown">
<el-dropdown-item>
<el-dropdown-item @click="openCreateDialog">
<div class="flex">
<el-avatar shape="square" class="avatar-blue mt-4" :size="36">
<img
@ -64,7 +64,7 @@
</div>
</div>
</el-dropdown-item>
<el-dropdown-item>
<el-dropdown-item @click="openCreateDialog('WORK_FLOW')">
<div class="flex">
<el-avatar shape="square" class="avatar-purple mt-4" :size="36">
<img
@ -235,11 +235,13 @@
<el-empty :description="$t('common.noData')" v-else />
</div>
</ContentContainer>
<CreateApplicationDialog ref="CreateApplicationDialogRef" />
</LayoutContainer>
</template>
<script lang="ts" setup>
import { onMounted, ref, reactive, computed } from 'vue'
import CreateApplicationDialog from '@/views/application/component/CreateApplicationDialog.vue'
import ApplicaitonApi from '@/api/application/application'
import { MsgSuccess, MsgConfirm } from '@/utils/message'
import useStore from '@/stores'
@ -271,18 +273,26 @@ const paginationConfig = reactive({
const folderList = ref<any[]>([])
const applicationList = ref<any[]>([])
const datasetFolderList = ref<any[]>([])
const currentFolder = ref<any>({})
function reEmbeddingDataset(row: any) {
KnowledgeApi.putReEmbeddingDataset('default', row.id).then(() => {
MsgSuccess(t('common.submitSuccess'))
})
}
const CreateApplicationDialogRef = ref()
const SyncWebDialogRef = ref()
function syncDataset(row: any) {
SyncWebDialogRef.value.open(row.id)
function openCreateDialog(type?: string) {
CreateApplicationDialogRef.value.open(currentFolder.value?.id || 'root', type)
// common
// .asyncGetValid(ValidType.Application, ValidCount.Application, loading)
// .then(async (res: any) => {
// if (res?.data) {
// CreateApplicationDialogRef.value.open()
// } else if (res?.code === 400) {
// MsgConfirm(t('common.tip'), t('views.application.tip.professionalMessage'), {
// cancelButtonText: t('common.confirm'),
// confirmButtonText: t('common.professional'),
// }).then(() => {
// window.open('https://maxkb.cn/pricing.html', '_blank')
// })
// }
// })
}
const search_type_change = () => {
@ -310,7 +320,7 @@ function getFolder() {
function folderClickHandel(row: any) {
currentFolder.value = row
datasetFolderList.value = []
applicationList.value = []
getList()
}