mirror of
https://gitee.com/mateos/mateclaw.git
synced 2026-09-15 20:08:18 +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.setPromptTemplate(input.getPromptTemplate());
|
||||||
entity.setApplyDefault(Boolean.TRUE.equals(input.getApplyDefault()));
|
entity.setApplyDefault(Boolean.TRUE.equals(input.getApplyDefault()));
|
||||||
entity.setEnabled(input.getEnabled() == null ? Boolean.TRUE : input.getEnabled());
|
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()));
|
entity.setOutputTarget(normalizeOutputTarget(input.getOutputTarget()));
|
||||||
transformationMapper.insert(entity);
|
transformationMapper.insert(entity);
|
||||||
log.info("[WikiTransformation] created id={} name={} kbId={}",
|
log.info("[WikiTransformation] created id={} name={} kbId={}",
|
||||||
|
|||||||
@ -1810,6 +1810,9 @@ export default {
|
|||||||
applyDefault: 'Apply by default',
|
applyDefault: 'Apply by default',
|
||||||
applyDefaultOn: 'Runs automatically after each ingest completes',
|
applyDefaultOn: 'Runs automatically after each ingest completes',
|
||||||
applyDefaultOff: 'Manual or agent-tool runs only',
|
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',
|
outputTargetLabel: 'Output target',
|
||||||
outputTargetNone: 'None — output stays in run history',
|
outputTargetNone: 'None — output stays in run history',
|
||||||
outputTargetPage: 'Save as wiki page (searchable / agent-accessible / linkable)',
|
outputTargetPage: 'Save as wiki page (searchable / agent-accessible / linkable)',
|
||||||
|
|||||||
@ -1822,6 +1822,9 @@ export default {
|
|||||||
applyDefault: '默认运行',
|
applyDefault: '默认运行',
|
||||||
applyDefaultOn: '每次新材料完成后自动跑',
|
applyDefaultOn: '每次新材料完成后自动跑',
|
||||||
applyDefaultOff: '仅在手动 / Agent 调用时运行',
|
applyDefaultOff: '仅在手动 / Agent 调用时运行',
|
||||||
|
modelLabel: '模型',
|
||||||
|
modelDefault: '默认(KB / 系统默认)',
|
||||||
|
modelHelp: '默认走 KB 绑定模型或系统默认 chat 模型;选定后该模板始终使用指定模型',
|
||||||
outputTargetLabel: '输出去向',
|
outputTargetLabel: '输出去向',
|
||||||
outputTargetNone: '不保存(仅留在运行历史)',
|
outputTargetNone: '不保存(仅留在运行历史)',
|
||||||
outputTargetPage: '保存为 Wiki 页面(可被搜索 / Agent / 关系图引用)',
|
outputTargetPage: '保存为 Wiki 页面(可被搜索 / Agent / 关系图引用)',
|
||||||
|
|||||||
@ -41,6 +41,9 @@
|
|||||||
<span v-if="tpl.outputTarget === 'page'" class="flag flag--on">
|
<span v-if="tpl.outputTarget === 'page'" class="flag flag--on">
|
||||||
{{ t('wiki.transformations.outputTargetPageBadge') }}
|
{{ t('wiki.transformations.outputTargetPageBadge') }}
|
||||||
</span>
|
</span>
|
||||||
|
<span v-if="tpl.modelId" class="flag flag--scope">
|
||||||
|
{{ modelLabelFor(tpl.modelId) }}
|
||||||
|
</span>
|
||||||
<span class="flag" :class="{ 'flag--muted': tpl.enabled === false }">
|
<span class="flag" :class="{ 'flag--muted': tpl.enabled === false }">
|
||||||
{{ tpl.enabled === false ? t('wiki.transformations.disabled') : t('wiki.transformations.enabled') }}
|
{{ tpl.enabled === false ? t('wiki.transformations.disabled') : t('wiki.transformations.enabled') }}
|
||||||
</span>
|
</span>
|
||||||
@ -154,6 +157,17 @@
|
|||||||
/>
|
/>
|
||||||
</label>
|
</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">
|
<label class="field">
|
||||||
<span class="field-label">{{ t('wiki.transformations.prompt') }}</span>
|
<span class="field-label">{{ t('wiki.transformations.prompt') }}</span>
|
||||||
<textarea
|
<textarea
|
||||||
@ -204,7 +218,7 @@ import { computed, onMounted, reactive, ref, watch } from 'vue'
|
|||||||
import { useI18n } from 'vue-i18n'
|
import { useI18n } from 'vue-i18n'
|
||||||
import { ElIcon, ElMessage } from 'element-plus'
|
import { ElIcon, ElMessage } from 'element-plus'
|
||||||
import { Loading, WarningFilled } from '@element-plus/icons-vue'
|
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'
|
import { useWikiStore, type WikiRawMaterial } from '@/stores/useWikiStore'
|
||||||
|
|
||||||
interface WikiTransformation {
|
interface WikiTransformation {
|
||||||
@ -254,6 +268,9 @@ const editorOpen = ref(false)
|
|||||||
const editing = ref<WikiTransformation | null>(null)
|
const editing = ref<WikiTransformation | null>(null)
|
||||||
const saving = ref(false)
|
const saving = ref(false)
|
||||||
const savingRunId = ref<number | null>(null)
|
const savingRunId = ref<number | null>(null)
|
||||||
|
interface ModelOption { id: number; name: string; provider: string; modelName: string }
|
||||||
|
const availableModels = ref<ModelOption[]>([])
|
||||||
|
|
||||||
const form = reactive<{
|
const form = reactive<{
|
||||||
name: string
|
name: string
|
||||||
title: string
|
title: string
|
||||||
@ -262,6 +279,7 @@ const form = reactive<{
|
|||||||
applyDefault: boolean
|
applyDefault: boolean
|
||||||
enabled: boolean
|
enabled: boolean
|
||||||
outputTarget: 'none' | 'page'
|
outputTarget: 'none' | 'page'
|
||||||
|
modelId: number | null
|
||||||
}>({
|
}>({
|
||||||
name: '',
|
name: '',
|
||||||
title: '',
|
title: '',
|
||||||
@ -270,6 +288,7 @@ const form = reactive<{
|
|||||||
applyDefault: false,
|
applyDefault: false,
|
||||||
enabled: true,
|
enabled: true,
|
||||||
outputTarget: 'none',
|
outputTarget: 'none',
|
||||||
|
modelId: null,
|
||||||
})
|
})
|
||||||
|
|
||||||
const completedRaws = computed<WikiRawMaterial[]>(() =>
|
const completedRaws = computed<WikiRawMaterial[]>(() =>
|
||||||
@ -284,6 +303,12 @@ function rawTitleFor(rawId: number | null): string {
|
|||||||
return r?.title || `raw#${rawId}`
|
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 {
|
function formatTimestamp(iso: string | null): string {
|
||||||
if (!iso) return '—'
|
if (!iso) return '—'
|
||||||
return new Date(iso).toLocaleString()
|
return new Date(iso).toLocaleString()
|
||||||
@ -305,7 +330,10 @@ async function loadAll() {
|
|||||||
selectedRawByTemplate.value = Object.fromEntries(
|
selectedRawByTemplate.value = Object.fromEntries(
|
||||||
templates.value.map((t) => [t.id, selectedRawByTemplate.value[t.id] ?? null])
|
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) {
|
} catch (e: any) {
|
||||||
error.value = e?.message ?? String(e)
|
error.value = e?.message ?? String(e)
|
||||||
} finally {
|
} 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() {
|
function openCreate() {
|
||||||
editing.value = null
|
editing.value = null
|
||||||
form.name = ''
|
form.name = ''
|
||||||
@ -331,7 +374,9 @@ function openCreate() {
|
|||||||
form.applyDefault = false
|
form.applyDefault = false
|
||||||
form.enabled = true
|
form.enabled = true
|
||||||
form.outputTarget = 'none'
|
form.outputTarget = 'none'
|
||||||
|
form.modelId = null
|
||||||
editorOpen.value = true
|
editorOpen.value = true
|
||||||
|
ensureModelsLoaded()
|
||||||
}
|
}
|
||||||
|
|
||||||
function openEdit(tpl: WikiTransformation) {
|
function openEdit(tpl: WikiTransformation) {
|
||||||
@ -343,7 +388,9 @@ function openEdit(tpl: WikiTransformation) {
|
|||||||
form.applyDefault = tpl.applyDefault
|
form.applyDefault = tpl.applyDefault
|
||||||
form.enabled = tpl.enabled !== false
|
form.enabled = tpl.enabled !== false
|
||||||
form.outputTarget = tpl.outputTarget === 'page' ? 'page' : 'none'
|
form.outputTarget = tpl.outputTarget === 'page' ? 'page' : 'none'
|
||||||
|
form.modelId = tpl.modelId ?? null
|
||||||
editorOpen.value = true
|
editorOpen.value = true
|
||||||
|
ensureModelsLoaded()
|
||||||
}
|
}
|
||||||
|
|
||||||
function closeEditor() {
|
function closeEditor() {
|
||||||
@ -360,6 +407,8 @@ async function onSave() {
|
|||||||
saving.value = true
|
saving.value = true
|
||||||
try {
|
try {
|
||||||
if (editing.value) {
|
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, {
|
await wikiApi.updateTransformation(editing.value.id, {
|
||||||
title: form.title,
|
title: form.title,
|
||||||
description: form.description,
|
description: form.description,
|
||||||
@ -367,6 +416,7 @@ async function onSave() {
|
|||||||
applyDefault: form.applyDefault,
|
applyDefault: form.applyDefault,
|
||||||
enabled: form.enabled,
|
enabled: form.enabled,
|
||||||
outputTarget: form.outputTarget,
|
outputTarget: form.outputTarget,
|
||||||
|
modelId: updateModelId,
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
await wikiApi.createTransformation({
|
await wikiApi.createTransformation({
|
||||||
@ -378,6 +428,7 @@ async function onSave() {
|
|||||||
applyDefault: form.applyDefault,
|
applyDefault: form.applyDefault,
|
||||||
enabled: form.enabled,
|
enabled: form.enabled,
|
||||||
outputTarget: form.outputTarget,
|
outputTarget: form.outputTarget,
|
||||||
|
modelId: form.modelId,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
closeEditor()
|
closeEditor()
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user