Build lightweight AI agent admin
This commit is contained in:
@@ -0,0 +1,278 @@
|
||||
<script lang="ts" setup>
|
||||
import type {
|
||||
AgentImportCheckResult,
|
||||
AgentImportInput,
|
||||
} from '#/api/ai-platform/ai-platform';
|
||||
|
||||
import { computed, ref, watch } from 'vue';
|
||||
|
||||
import { Upload } from '@vben/icons';
|
||||
import { $t } from '@vben/locales';
|
||||
|
||||
import {
|
||||
ElAlert,
|
||||
ElButton,
|
||||
ElDescriptions,
|
||||
ElDescriptionsItem,
|
||||
ElInput,
|
||||
ElMessage,
|
||||
ElUpload,
|
||||
} from 'element-plus';
|
||||
|
||||
import {
|
||||
checkImportAgentApi,
|
||||
importAgentConfigApi,
|
||||
} from '#/api/ai-platform/ai-platform';
|
||||
import { ZqDialog } from '#/components/zq-dialog';
|
||||
import { useAppContextStore } from '#/store/app-context';
|
||||
|
||||
interface Props {
|
||||
modelValue: boolean;
|
||||
}
|
||||
|
||||
const props = defineProps<Props>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
imported: [];
|
||||
'update:modelValue': [value: boolean];
|
||||
}>();
|
||||
|
||||
const appContextStore = useAppContextStore();
|
||||
|
||||
const dialogVisible = computed({
|
||||
get: () => props.modelValue,
|
||||
set: (val) => emit('update:modelValue', val),
|
||||
});
|
||||
|
||||
type Step = 'check' | 'result' | 'upload';
|
||||
|
||||
const step = ref<Step>('upload');
|
||||
const loading = ref(false);
|
||||
const importData = ref<AgentImportInput | null>(null);
|
||||
const checkResult = ref<AgentImportCheckResult | null>(null);
|
||||
const newCode = ref('');
|
||||
|
||||
function resetState() {
|
||||
step.value = 'upload';
|
||||
loading.value = false;
|
||||
importData.value = null;
|
||||
checkResult.value = null;
|
||||
newCode.value = '';
|
||||
}
|
||||
|
||||
watch(dialogVisible, (val) => {
|
||||
if (!val) resetState();
|
||||
});
|
||||
|
||||
const canConfirmImport = computed(() => {
|
||||
if (!checkResult.value || !importData.value) return false;
|
||||
if (checkResult.value.code_exists && !newCode.value.trim()) return false;
|
||||
return true;
|
||||
});
|
||||
|
||||
const showFooter = computed(() => step.value === 'result');
|
||||
|
||||
function readFileAsJson(file: File): Promise<AgentImportInput> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const reader = new FileReader();
|
||||
reader.onload = () => {
|
||||
try {
|
||||
resolve(JSON.parse(reader.result as string));
|
||||
} catch {
|
||||
reject(new Error('parse'));
|
||||
}
|
||||
};
|
||||
reader.onerror = () => reject(new Error('read'));
|
||||
reader.readAsText(file);
|
||||
});
|
||||
}
|
||||
|
||||
async function handleFileChange(file: any) {
|
||||
const rawFile = (file.raw || file) as File;
|
||||
if (!rawFile) return;
|
||||
|
||||
loading.value = true;
|
||||
try {
|
||||
const data = await readFileAsJson(rawFile);
|
||||
if (!data.name || !data.code) {
|
||||
ElMessage.error($t('ai-platform.agent.importExport.fileParseError'));
|
||||
return;
|
||||
}
|
||||
importData.value = data;
|
||||
newCode.value = data.code;
|
||||
step.value = 'check';
|
||||
await runCheck();
|
||||
} catch {
|
||||
ElMessage.error($t('ai-platform.agent.importExport.fileParseError'));
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function runCheck() {
|
||||
if (!importData.value) return;
|
||||
loading.value = true;
|
||||
try {
|
||||
checkResult.value = await checkImportAgentApi({
|
||||
code: importData.value.code,
|
||||
});
|
||||
step.value = 'result';
|
||||
} catch (error: any) {
|
||||
ElMessage.error(
|
||||
error?.message || $t('ai-platform.agent.importExport.importFailed'),
|
||||
);
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function handleConfirmImport() {
|
||||
if (!importData.value || !canConfirmImport.value) return;
|
||||
loading.value = true;
|
||||
try {
|
||||
const payload: AgentImportInput = {
|
||||
...importData.value,
|
||||
application_id: appContextStore.currentApp?.id,
|
||||
code: checkResult.value?.code_exists
|
||||
? newCode.value.trim()
|
||||
: importData.value.code,
|
||||
};
|
||||
await importAgentConfigApi(payload);
|
||||
ElMessage.success($t('ai-platform.agent.importExport.importSuccess'));
|
||||
dialogVisible.value = false;
|
||||
emit('imported');
|
||||
} catch (error: any) {
|
||||
ElMessage.error(
|
||||
error?.message || $t('ai-platform.agent.importExport.importFailed'),
|
||||
);
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ZqDialog
|
||||
v-model="dialogVisible"
|
||||
:title="$t('ai-platform.agent.importExport.importTitle')"
|
||||
:confirm-loading="loading"
|
||||
width="520px"
|
||||
:show-footer="showFooter"
|
||||
@confirm="handleConfirmImport"
|
||||
>
|
||||
<template #footer>
|
||||
<ElButton @click="resetState">
|
||||
{{ $t('ai-platform.agent.importExport.reselect') }}
|
||||
</ElButton>
|
||||
<ElButton
|
||||
type="primary"
|
||||
:loading="loading"
|
||||
:disabled="!canConfirmImport"
|
||||
@click="handleConfirmImport"
|
||||
>
|
||||
{{ $t('ai-platform.agent.importExport.confirmImport') }}
|
||||
</ElButton>
|
||||
</template>
|
||||
|
||||
<div v-if="step === 'upload' || step === 'check'" class="py-4">
|
||||
<ElUpload
|
||||
drag
|
||||
:auto-upload="false"
|
||||
:show-file-list="false"
|
||||
accept=".json"
|
||||
:disabled="loading"
|
||||
@change="handleFileChange"
|
||||
>
|
||||
<div class="flex flex-col items-center py-6">
|
||||
<Upload class="text-muted-foreground mb-3 h-10 w-10" />
|
||||
<div class="text-sm">
|
||||
{{ $t('ai-platform.agent.importExport.dragOrClick') }}
|
||||
</div>
|
||||
<div class="text-muted-foreground mt-1 text-xs">
|
||||
{{ $t('ai-platform.agent.importExport.onlyJson') }}
|
||||
</div>
|
||||
</div>
|
||||
</ElUpload>
|
||||
<div
|
||||
v-if="loading && step === 'check'"
|
||||
class="text-muted-foreground mt-4 text-center text-sm"
|
||||
>
|
||||
{{ $t('ai-platform.agent.importExport.checking') }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="step === 'result' && importData && checkResult" class="space-y-4">
|
||||
<ElAlert
|
||||
:title="$t('ai-platform.agent.importExport.appTip')"
|
||||
type="info"
|
||||
show-icon
|
||||
:closable="false"
|
||||
/>
|
||||
<ElAlert
|
||||
:title="$t('ai-platform.agent.importExport.refTip')"
|
||||
type="warning"
|
||||
show-icon
|
||||
:closable="false"
|
||||
/>
|
||||
|
||||
<div>
|
||||
<div class="mb-2 text-sm font-medium">
|
||||
{{ $t('ai-platform.agent.importExport.agentInfo') }}
|
||||
</div>
|
||||
<ElDescriptions :column="1" border size="small">
|
||||
<ElDescriptionsItem :label="$t('ai-platform.agent.form.name')">
|
||||
{{ importData.name }}
|
||||
</ElDescriptionsItem>
|
||||
<ElDescriptionsItem :label="$t('ai-platform.agent.form.code')">
|
||||
{{ importData.code }}
|
||||
</ElDescriptionsItem>
|
||||
<ElDescriptionsItem
|
||||
v-if="importData.mode"
|
||||
:label="$t('ai-platform.agent.form.mode')"
|
||||
>
|
||||
{{ importData.mode }}
|
||||
</ElDescriptionsItem>
|
||||
<ElDescriptionsItem
|
||||
v-if="importData.description"
|
||||
:label="$t('ai-platform.agent.form.description')"
|
||||
>
|
||||
{{ importData.description }}
|
||||
</ElDescriptionsItem>
|
||||
<ElDescriptionsItem
|
||||
v-if="importData.model_name"
|
||||
:label="$t('ai-platform.agent.importExport.modelName')"
|
||||
>
|
||||
{{ importData.model_name }}
|
||||
</ElDescriptionsItem>
|
||||
<ElDescriptionsItem
|
||||
v-if="importData.workflow_code"
|
||||
:label="$t('ai-platform.agent.importExport.workflowCode')"
|
||||
>
|
||||
{{ importData.workflow_code }}
|
||||
</ElDescriptionsItem>
|
||||
</ElDescriptions>
|
||||
</div>
|
||||
|
||||
<div v-if="checkResult.code_exists">
|
||||
<ElAlert
|
||||
:title="$t('ai-platform.agent.importExport.codeConflictTip')"
|
||||
type="warning"
|
||||
show-icon
|
||||
:closable="false"
|
||||
class="mb-3"
|
||||
/>
|
||||
<ElInput
|
||||
v-model="newCode"
|
||||
:placeholder="$t('ai-platform.agent.importExport.newCodePlaceholder')"
|
||||
/>
|
||||
</div>
|
||||
<ElAlert
|
||||
v-else
|
||||
:title="$t('ai-platform.agent.importExport.codeAvailable')"
|
||||
type="success"
|
||||
show-icon
|
||||
:closable="false"
|
||||
/>
|
||||
</div>
|
||||
</ZqDialog>
|
||||
</template>
|
||||
Reference in New Issue
Block a user