mirror of
https://gitee.com/mateos/mateclaw.git
synced 2026-09-13 03:13:41 +08:00
feat(wiki): expose per-template model picker in the transformations modal
This commit is contained in:
parent
4098346fb7
commit
ef6ad1b91a
@ -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={}",
|
||||
|
||||
@ -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)',
|
||||
|
||||
@ -1822,6 +1822,9 @@ export default {
|
||||
applyDefault: '默认运行',
|
||||
applyDefaultOn: '每次新材料完成后自动跑',
|
||||
applyDefaultOff: '仅在手动 / Agent 调用时运行',
|
||||
modelLabel: '模型',
|
||||
modelDefault: '默认(KB / 系统默认)',
|
||||
modelHelp: '默认走 KB 绑定模型或系统默认 chat 模型;选定后该模板始终使用指定模型',
|
||||
outputTargetLabel: '输出去向',
|
||||
outputTargetNone: '不保存(仅留在运行历史)',
|
||||
outputTargetPage: '保存为 Wiki 页面(可被搜索 / Agent / 关系图引用)',
|
||||
|
||||
@ -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()
|
||||
|
||||
Loading…
Reference in New Issue
Block a user