feat(wiki): expose per-template model picker in the transformations modal

This commit is contained in:
matevip 2026-05-12 11:25:38 +08:00
parent 4098346fb7
commit ef6ad1b91a
4 changed files with 62 additions and 3 deletions

View File

@ -106,7 +106,9 @@ public class WikiTransformationService {
entity.setPromptTemplate(input.getPromptTemplate());
entity.setApplyDefault(Boolean.TRUE.equals(input.getApplyDefault()));
entity.setEnabled(input.getEnabled() == null ? Boolean.TRUE : input.getEnabled());
entity.setModelId(input.getModelId());
// Treat negative values as the "clear / use default" sentinel so the
// create and update paths accept the same payload from the UI.
entity.setModelId(input.getModelId() != null && input.getModelId() < 0 ? null : input.getModelId());
entity.setOutputTarget(normalizeOutputTarget(input.getOutputTarget()));
transformationMapper.insert(entity);
log.info("[WikiTransformation] created id={} name={} kbId={}",

View File

@ -1810,6 +1810,9 @@ export default {
applyDefault: 'Apply by default',
applyDefaultOn: 'Runs automatically after each ingest completes',
applyDefaultOff: 'Manual or agent-tool runs only',
modelLabel: 'Model',
modelDefault: 'Default (KB / system)',
modelHelp: 'Falls back to the KB-bound chat model or the system default; once chosen this template always uses the specified model',
outputTargetLabel: 'Output target',
outputTargetNone: 'None — output stays in run history',
outputTargetPage: 'Save as wiki page (searchable / agent-accessible / linkable)',

View File

@ -1822,6 +1822,9 @@ export default {
applyDefault: '默认运行',
applyDefaultOn: '每次新材料完成后自动跑',
applyDefaultOff: '仅在手动 / Agent 调用时运行',
modelLabel: '模型',
modelDefault: '默认KB / 系统默认)',
modelHelp: '默认走 KB 绑定模型或系统默认 chat 模型;选定后该模板始终使用指定模型',
outputTargetLabel: '输出去向',
outputTargetNone: '不保存(仅留在运行历史)',
outputTargetPage: '保存为 Wiki 页面(可被搜索 / Agent / 关系图引用)',

View File

@ -41,6 +41,9 @@
<span v-if="tpl.outputTarget === 'page'" class="flag flag--on">
{{ t('wiki.transformations.outputTargetPageBadge') }}
</span>
<span v-if="tpl.modelId" class="flag flag--scope">
{{ modelLabelFor(tpl.modelId) }}
</span>
<span class="flag" :class="{ 'flag--muted': tpl.enabled === false }">
{{ tpl.enabled === false ? t('wiki.transformations.disabled') : t('wiki.transformations.enabled') }}
</span>
@ -154,6 +157,17 @@
/>
</label>
<label class="field">
<span class="field-label">{{ t('wiki.transformations.modelLabel') }}</span>
<select v-model="form.modelId" class="field-input">
<option :value="null">{{ t('wiki.transformations.modelDefault') }}</option>
<option v-for="m in availableModels" :key="m.id" :value="m.id">
{{ m.name }} <span v-if="m.provider"> · {{ m.provider }}</span>
</option>
</select>
<span class="field-hint">{{ t('wiki.transformations.modelHelp') }}</span>
</label>
<label class="field">
<span class="field-label">{{ t('wiki.transformations.prompt') }}</span>
<textarea
@ -204,7 +218,7 @@ import { computed, onMounted, reactive, ref, watch } from 'vue'
import { useI18n } from 'vue-i18n'
import { ElIcon, ElMessage } from 'element-plus'
import { Loading, WarningFilled } from '@element-plus/icons-vue'
import { wikiApi } from '@/api/index'
import { wikiApi, modelApi } from '@/api/index'
import { useWikiStore, type WikiRawMaterial } from '@/stores/useWikiStore'
interface WikiTransformation {
@ -254,6 +268,9 @@ const editorOpen = ref(false)
const editing = ref<WikiTransformation | null>(null)
const saving = ref(false)
const savingRunId = ref<number | null>(null)
interface ModelOption { id: number; name: string; provider: string; modelName: string }
const availableModels = ref<ModelOption[]>([])
const form = reactive<{
name: string
title: string
@ -262,6 +279,7 @@ const form = reactive<{
applyDefault: boolean
enabled: boolean
outputTarget: 'none' | 'page'
modelId: number | null
}>({
name: '',
title: '',
@ -270,6 +288,7 @@ const form = reactive<{
applyDefault: false,
enabled: true,
outputTarget: 'none',
modelId: null,
})
const completedRaws = computed<WikiRawMaterial[]>(() =>
@ -284,6 +303,12 @@ function rawTitleFor(rawId: number | null): string {
return r?.title || `raw#${rawId}`
}
function modelLabelFor(modelId: number | null | undefined): string {
if (modelId == null) return ''
const m = availableModels.value.find((x) => x.id === modelId)
return m ? m.name : `model#${modelId}`
}
function formatTimestamp(iso: string | null): string {
if (!iso) return '—'
return new Date(iso).toLocaleString()
@ -305,7 +330,10 @@ async function loadAll() {
selectedRawByTemplate.value = Object.fromEntries(
templates.value.map((t) => [t.id, selectedRawByTemplate.value[t.id] ?? null])
)
await Promise.all(templates.value.map((tpl) => loadRunsFor(tpl.id)))
await Promise.all([
ensureModelsLoaded(),
...templates.value.map((tpl) => loadRunsFor(tpl.id)),
])
} catch (e: any) {
error.value = e?.message ?? String(e)
} finally {
@ -322,6 +350,21 @@ async function loadRunsFor(templateId: number) {
}
}
async function ensureModelsLoaded() {
if (availableModels.value.length > 0) return
try {
const res: any = await modelApi.listEnabled()
availableModels.value = (res?.data || []).map((m: any) => ({
id: m.id,
name: m.name,
provider: m.provider,
modelName: m.modelName,
}))
} catch {
// Empty list = picker only offers "default".
}
}
function openCreate() {
editing.value = null
form.name = ''
@ -331,7 +374,9 @@ function openCreate() {
form.applyDefault = false
form.enabled = true
form.outputTarget = 'none'
form.modelId = null
editorOpen.value = true
ensureModelsLoaded()
}
function openEdit(tpl: WikiTransformation) {
@ -343,7 +388,9 @@ function openEdit(tpl: WikiTransformation) {
form.applyDefault = tpl.applyDefault
form.enabled = tpl.enabled !== false
form.outputTarget = tpl.outputTarget === 'page' ? 'page' : 'none'
form.modelId = tpl.modelId ?? null
editorOpen.value = true
ensureModelsLoaded()
}
function closeEditor() {
@ -360,6 +407,8 @@ async function onSave() {
saving.value = true
try {
if (editing.value) {
// Update path: backend treats `-1` as "clear modelId"; null is skipped.
const updateModelId = form.modelId == null ? -1 : form.modelId
await wikiApi.updateTransformation(editing.value.id, {
title: form.title,
description: form.description,
@ -367,6 +416,7 @@ async function onSave() {
applyDefault: form.applyDefault,
enabled: form.enabled,
outputTarget: form.outputTarget,
modelId: updateModelId,
})
} else {
await wikiApi.createTransformation({
@ -378,6 +428,7 @@ async function onSave() {
applyDefault: form.applyDefault,
enabled: form.enabled,
outputTarget: form.outputTarget,
modelId: form.modelId,
})
}
closeEditor()