You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
577 lines
55 KiB
577 lines
55 KiB
import { _ as __nuxt_component_0 } from './LangSelect-Bvw-e1Cn.mjs';
|
|
import { defineComponent, ref, computed, reactive, watch, useSSRContext } from 'vue';
|
|
import { ssrRenderAttrs, ssrRenderStyle, ssrIncludeBooleanAttr, ssrLooseContain, ssrLooseEqual, ssrRenderList, ssrRenderAttr, ssrInterpolate, ssrRenderClass, ssrRenderComponent, ssrRenderDynamicModel } from 'vue/server-renderer';
|
|
import { u as useApi } from './useApi-BNSkSrat.mjs';
|
|
import { u as useToast } from './useToast-MBvLaNjH.mjs';
|
|
import { _ as _export_sfc } from './server.mjs';
|
|
import './useLangOptions-CXXbv5NM.mjs';
|
|
import '../nitro/nitro.mjs';
|
|
import 'node:http';
|
|
import 'node:https';
|
|
import 'node:events';
|
|
import 'node:buffer';
|
|
import 'node:fs';
|
|
import 'node:path';
|
|
import 'node:crypto';
|
|
import 'node:url';
|
|
import '../routes/renderer.mjs';
|
|
import 'vue-bundle-renderer/runtime';
|
|
import 'unhead/server';
|
|
import 'devalue';
|
|
import 'unhead/utils';
|
|
import 'pinia';
|
|
import 'perfect-debounce';
|
|
|
|
const SCAT_MCP = 10;
|
|
const _sfc_main = /* @__PURE__ */ defineComponent({
|
|
__name: "serviceconfig",
|
|
__ssrInlineRender: true,
|
|
setup(__props) {
|
|
const REGIONS = [
|
|
{ value: 1, label: "中国" },
|
|
{ value: 2, label: "美国" },
|
|
{ value: 3, label: "东亚" },
|
|
{ value: 4, label: "东南亚" },
|
|
{ value: 5, label: "南亚" },
|
|
{ value: 6, label: "中东" },
|
|
{ value: 7, label: "欧洲" },
|
|
{ value: 8, label: "俄罗斯" },
|
|
{ value: 9, label: "非洲" },
|
|
{ value: 10, label: "南美" },
|
|
{ value: 11, label: "北美" },
|
|
{ value: 12, label: "大洋洲" },
|
|
{ value: 13, label: "巴西" },
|
|
{ value: 14, label: "印度" }
|
|
];
|
|
const SCAT = [
|
|
{ v: 1, label: "STT 语音识别", short: "STT", color: "#0369a1", bg: "#dbeafe" },
|
|
{ v: 8, label: "录音文件识别", short: "录音识别", color: "#0e7490", bg: "#cffafe" },
|
|
{ v: 2, label: "TTS 语音合成", short: "TTS", color: "#7c3aed", bg: "#ede9fe" },
|
|
{ v: 3, label: "MT 机器翻译", short: "MT", color: "#059669", bg: "#dcfce7" },
|
|
{ v: 4, label: "AST 端到端翻译", short: "AST", color: "#c2410c", bg: "#fff7ed" },
|
|
{ v: 5, label: "LLM 文本大模型", short: "文本LLM", color: "#b45309", bg: "#fef9c3" },
|
|
{ v: 9, label: "多媒体大模型", short: "多媒体LLM", color: "#9333ea", bg: "#f3e8ff" },
|
|
{ v: 7, label: "STS 端到端对话", short: "STS", color: "#be185d", bg: "#fce7f3" },
|
|
{ v: 6, label: "存储 OSS/COS", short: "存储", color: "#475569", bg: "#f1f5f9" },
|
|
{ v: 10, label: "MCP 服务", short: "MCP", color: "#0d9488", bg: "#ccfbf1" }
|
|
];
|
|
const { webApi } = useApi();
|
|
useToast();
|
|
const scopeApp = ref("");
|
|
const scopeApps = ref([]);
|
|
function scatInfo(v) {
|
|
return SCAT.find((s) => s.v === v) || { short: "?", color: "#999", bg: "#eee", v: 0, label: "" };
|
|
}
|
|
function isMcpType() {
|
|
if (editType.value === SCAT_MCP) return true;
|
|
return !!curSvc.value && parseCats(curSvc.value.categories).includes(SCAT_MCP);
|
|
}
|
|
function parseCats(s) {
|
|
return (s || "").split(",").map((x) => parseInt(x) || 0).filter((x) => x > 0);
|
|
}
|
|
function encCount(s) {
|
|
return (s.fields || []).filter((f) => f.encrypted).length;
|
|
}
|
|
function regionChip(s) {
|
|
const rs = svcRegions.value[s.id] || [];
|
|
const names = rs.map((v) => REGIONS.find((x) => x.value === v)?.label || "区域" + v);
|
|
if (curRegion.value === 0) {
|
|
if (!rs.length) return { text: "全区域通用", cls: "plain", title: "未做任何区域分叉,各区域都用默认配置" };
|
|
return { text: rs.length + " 区域分叉", cls: "has", title: "已分叉:" + names.join("、") };
|
|
}
|
|
if (rs.includes(curRegion.value)) {
|
|
return { text: "本区域已分叉", cls: "has", title: curRegionLabel.value + "有独立的字段配置" };
|
|
}
|
|
return { text: "继承默认", cls: "plain", title: curRegionLabel.value + "未分叉,使用默认配置" };
|
|
}
|
|
const VOICE_CATS = [2, 4, 7];
|
|
const voiceRows = ref([]);
|
|
const voicesLoading = ref(false);
|
|
const isVoiceCat = computed(() => VOICE_CATS.includes(editType.value));
|
|
const voiceLangGroups = computed(() => {
|
|
const map = /* @__PURE__ */ new Map();
|
|
for (const v of voiceRows.value) {
|
|
if (!map.has(v.language)) map.set(v.language, []);
|
|
map.get(v.language).push(v);
|
|
}
|
|
return Array.from(map, ([lang, items]) => ({ lang, items }));
|
|
});
|
|
async function loadVoicesForProvider(provider) {
|
|
const p = (provider || "").trim();
|
|
voiceRows.value = [];
|
|
if (!p) return;
|
|
voicesLoading.value = true;
|
|
try {
|
|
const d = await webApi("getttsvoices", { provider: p });
|
|
voiceRows.value = d?.voices ?? [];
|
|
} catch {
|
|
voiceRows.value = [];
|
|
} finally {
|
|
voicesLoading.value = false;
|
|
}
|
|
}
|
|
const backendReady = ref(true);
|
|
const allSvcs = ref([]);
|
|
const svcRegions = ref({});
|
|
const curSvc = ref(null);
|
|
const curRegion = ref(0);
|
|
const svcModalVisible = ref(false);
|
|
const detailMode = ref("default");
|
|
const editForm = reactive({ id: "", name: "", provider: "", description: "", enable: false });
|
|
const editType = ref(0);
|
|
const editFields = ref([]);
|
|
const selectedTemplateId = ref("");
|
|
const wizardStep = ref(1);
|
|
const overrideFields = ref([]);
|
|
const extraRegionFields = ref([]);
|
|
const regionLoading = ref(false);
|
|
const templates = ref([]);
|
|
const curRegionLabel = computed(() => {
|
|
const r = REGIONS.find((x) => x.value === curRegion.value);
|
|
return r ? r.label : "区域" + curRegion.value;
|
|
});
|
|
const groupedSvcs = computed(() => {
|
|
const groups = SCAT.map((sc) => ({
|
|
...sc,
|
|
items: allSvcs.value.filter((s) => parseCats(s.categories).includes(sc.v))
|
|
})).filter((g) => g.items.length);
|
|
const unclassified = allSvcs.value.filter((s) => !parseCats(s.categories).length);
|
|
if (unclassified.length) {
|
|
groups.push({ v: 0, label: "未分类", short: "未分类", color: "#64748b", bg: "#f1f5f9", items: unclassified });
|
|
}
|
|
return groups;
|
|
});
|
|
const folded = reactive({});
|
|
const allFolded = computed(() => groupedSvcs.value.length > 0 && groupedSvcs.value.every((g) => folded[g.v]));
|
|
const templatesByCat = computed(() => {
|
|
return SCAT.map((sc) => ({
|
|
cat: sc.v,
|
|
label: sc.label,
|
|
items: templates.value.filter((t) => t.category === sc.v)
|
|
})).filter((g) => g.items.length);
|
|
});
|
|
const templatesForType = computed(() => templates.value.filter((t) => t.category === editType.value));
|
|
const syncVisible = ref(false);
|
|
ref(false);
|
|
const syncDesc = ref("");
|
|
const syncScopeNote = ref("");
|
|
const syncTargets = ref([]);
|
|
const syncRegionList = computed(() => REGIONS.filter((r) => r.value !== curRegion.value));
|
|
const tplModalVisible = ref(false);
|
|
const tplView = ref("list");
|
|
const tplEditing = ref(false);
|
|
const tplForm = reactive({ id: "", category: 2, provider: "", name: "", description: "" });
|
|
const tplFields = ref([]);
|
|
watch([() => editForm.provider, isVoiceCat, svcModalVisible], ([provider, isVoice, visible]) => {
|
|
if (!visible || !isVoice) {
|
|
voiceRows.value = [];
|
|
return;
|
|
}
|
|
loadVoicesForProvider(provider);
|
|
}, { immediate: false });
|
|
return (_ctx, _push, _parent, _attrs) => {
|
|
const _component_LangSelect = __nuxt_component_0;
|
|
_push(`<div${ssrRenderAttrs(_attrs)} data-v-e10e0bc9>`);
|
|
if (!backendReady.value) {
|
|
_push(`<div class="mb-4 rounded-lg px-4 py-3 text-sm" style="${ssrRenderStyle({ "background": "#fffbeb", "border": "1px solid #fde68a", "color": "#92400e" })}" data-v-e10e0bc9> ⚠ 「第三方服务配置」后端接口暂不可用(连接失败),请检查 console 服务是否运行。 </div>`);
|
|
} else {
|
|
_push(`<!---->`);
|
|
}
|
|
_push(`<div class="flex items-center justify-between mb-5" data-v-e10e0bc9><div data-v-e10e0bc9><h2 class="text-lg font-semibold text-slate-800" data-v-e10e0bc9>第三方服务配置</h2><p class="text-xs text-slate-400 mt-1" data-v-e10e0bc9>按服务类型分组管理已接入的第三方服务(火山引擎、阿里云、Azure 等)。默认展示<b data-v-e10e0bc9>全局默认</b>配置,可切换到具体应用;区域默认<b data-v-e10e0bc9>中国</b>,切换区域后点击卡片可为该区域覆盖字段值。</p></div><div class="flex items-center gap-3" data-v-e10e0bc9><select class="select select-bordered select-sm" data-v-e10e0bc9><option value="" data-v-e10e0bc9${ssrIncludeBooleanAttr(Array.isArray(scopeApp.value) ? ssrLooseContain(scopeApp.value, "") : ssrLooseEqual(scopeApp.value, "")) ? " selected" : ""}>全局默认</option><!--[-->`);
|
|
ssrRenderList(scopeApps.value, (a) => {
|
|
_push(`<option${ssrRenderAttr("value", a)} data-v-e10e0bc9${ssrIncludeBooleanAttr(Array.isArray(scopeApp.value) ? ssrLooseContain(scopeApp.value, a) : ssrLooseEqual(scopeApp.value, a)) ? " selected" : ""}>应用:${ssrInterpolate(a)}</option>`);
|
|
});
|
|
_push(`<!--]--></select><select class="select select-bordered select-sm" data-v-e10e0bc9><option${ssrRenderAttr("value", 0)} data-v-e10e0bc9${ssrIncludeBooleanAttr(Array.isArray(curRegion.value) ? ssrLooseContain(curRegion.value, 0) : ssrLooseEqual(curRegion.value, 0)) ? " selected" : ""}>默认(全区域)</option><!--[-->`);
|
|
ssrRenderList(REGIONS, (r) => {
|
|
_push(`<option${ssrRenderAttr("value", r.value)} data-v-e10e0bc9${ssrIncludeBooleanAttr(Array.isArray(curRegion.value) ? ssrLooseContain(curRegion.value, r.value) : ssrLooseEqual(curRegion.value, r.value)) ? " selected" : ""}>${ssrInterpolate(r.label)}</option>`);
|
|
});
|
|
_push(`<!--]--></select><button class="btn btn-ghost btn-sm" data-v-e10e0bc9>${ssrInterpolate(allFolded.value ? "展开全部" : "折叠全部")}</button><button class="btn btn-ghost btn-sm" data-v-e10e0bc9>服务商模板</button><button class="btn btn-primary btn-sm text-white" data-v-e10e0bc9>+ 添加服务</button></div></div>`);
|
|
if (curRegion.value !== 0) {
|
|
_push(`<div class="rounded-lg px-3 py-2 mb-4 text-xs" style="${ssrRenderStyle({ "background": "#fefce8", "border": "1px solid #fde047", "color": "#854d0e" })}" data-v-e10e0bc9> ✦ 当前处于「${ssrInterpolate(curRegionLabel.value)}」区域视图:点击服务卡片编辑该区域的字段分叉(覆盖值 / 停用字段 / 区域专属字段)。要改服务本身的默认配置,请切回「默认(全区域)」。 </div>`);
|
|
} else {
|
|
_push(`<!---->`);
|
|
}
|
|
if (groupedSvcs.value.length) {
|
|
_push(`<div class="flex flex-col gap-6" data-v-e10e0bc9><!--[-->`);
|
|
ssrRenderList(groupedSvcs.value, (g) => {
|
|
_push(`<section data-v-e10e0bc9><div class="group-head" data-v-e10e0bc9><span class="${ssrRenderClass([{ folded: folded[g.v] }, "fold-arrow"])}" data-v-e10e0bc9>▾</span><span class="cat-chip" style="${ssrRenderStyle({ color: g.color, background: g.bg })}" data-v-e10e0bc9>${ssrInterpolate(g.short)}</span><h3 class="text-sm font-semibold text-slate-700" data-v-e10e0bc9>${ssrInterpolate(g.label)}</h3><span class="text-xs text-slate-400" data-v-e10e0bc9>${ssrInterpolate(g.items.length)} 个服务</span><div class="flex-1 h-px bg-slate-100 ml-2" data-v-e10e0bc9></div></div><div class="svc-grid" style="${ssrRenderStyle(!folded[g.v] ? null : { display: "none" })}" data-v-e10e0bc9><!--[-->`);
|
|
ssrRenderList(g.items, (s) => {
|
|
_push(`<div class="${ssrRenderClass([{ off: !s.enable }, "svc-card"])}" data-v-e10e0bc9><div class="card-head" data-v-e10e0bc9><div class="svc-logo" style="${ssrRenderStyle({ background: s.color || "#6366f1" })}" data-v-e10e0bc9>${ssrInterpolate(s.icon || (s.name || "").charAt(0))}</div><div class="head-text" data-v-e10e0bc9><div class="name-line" data-v-e10e0bc9><span class="svc-name"${ssrRenderAttr("title", s.name)} data-v-e10e0bc9>${ssrInterpolate(s.name)}</span><span class="${ssrRenderClass([s.enable ? "on" : "off", "state-chip"])}" data-v-e10e0bc9>${ssrInterpolate(s.enable ? "启用" : "停用")}</span></div><p class="svc-id"${ssrRenderAttr("title", s.id)} data-v-e10e0bc9>${ssrInterpolate(s.id)}</p></div></div><p class="svc-desc"${ssrRenderAttr("title", s.description || "")} data-v-e10e0bc9>${ssrInterpolate(s.description || "—")}</p><div class="meta-row" data-v-e10e0bc9>`);
|
|
if (s.provider) {
|
|
_push(`<span class="provider-chip"${ssrRenderAttr("title", s.provider)} data-v-e10e0bc9>${ssrInterpolate(s.provider)}</span>`);
|
|
} else {
|
|
_push(`<!---->`);
|
|
}
|
|
_push(`<span class="field-count" data-v-e10e0bc9>${ssrInterpolate((s.fields || []).length)} 字段</span>`);
|
|
if (encCount(s)) {
|
|
_push(`<span class="lock-badge" data-v-e10e0bc9>🔒${ssrInterpolate(encCount(s))}</span>`);
|
|
} else {
|
|
_push(`<!---->`);
|
|
}
|
|
_push(`</div><div class="region-row" data-v-e10e0bc9><span class="${ssrRenderClass([regionChip(s).cls, "region-chip"])}"${ssrRenderAttr("title", regionChip(s).title)} data-v-e10e0bc9>${ssrInterpolate(regionChip(s).text)}</span></div></div>`);
|
|
});
|
|
_push(`<!--]--><div class="svc-card add-card" data-v-e10e0bc9><span class="text-2xl text-slate-300" data-v-e10e0bc9>+</span><span class="text-xs text-slate-400 mt-1" data-v-e10e0bc9>添加${ssrInterpolate(g.short)}服务</span></div></div></section>`);
|
|
});
|
|
_push(`<!--]--></div>`);
|
|
} else {
|
|
_push(`<div class="bg-white rounded-2xl border border-slate-100 shadow-sm text-center text-slate-300 py-20" data-v-e10e0bc9><div class="text-5xl mb-3" data-v-e10e0bc9>⚙️</div><p class="text-sm mb-4" data-v-e10e0bc9>尚未配置任何第三方服务</p><button class="btn btn-primary btn-sm text-white" data-v-e10e0bc9>+ 添加服务</button></div>`);
|
|
}
|
|
if (svcModalVisible.value) {
|
|
_push(`<div class="modal-overlay" data-v-e10e0bc9><div class="svc-box" data-v-e10e0bc9>`);
|
|
if (detailMode.value === "default" && curSvc.value) {
|
|
_push(`<!--[--><div class="flex items-center justify-between mb-5" data-v-e10e0bc9><div class="flex items-center gap-3" data-v-e10e0bc9><div class="svc-logo" style="${ssrRenderStyle({ background: curSvc.value.color || "#6366f1", width: "44px", height: "44px", fontSize: "20px" })}" data-v-e10e0bc9>${ssrInterpolate(curSvc.value.icon || (curSvc.value.name || "").charAt(0))}</div><div data-v-e10e0bc9><h3 class="text-base font-semibold text-slate-800" data-v-e10e0bc9>${ssrInterpolate(curSvc.value.name)}</h3><p class="text-xs text-slate-400 mt-0.5" data-v-e10e0bc9>${ssrInterpolate(curSvc.value.id)}`);
|
|
if (editForm.provider) {
|
|
_push(`<span data-v-e10e0bc9> · 服务商 ${ssrInterpolate(editForm.provider)}</span>`);
|
|
} else {
|
|
_push(`<!---->`);
|
|
}
|
|
_push(`</p></div></div><div class="flex gap-2" data-v-e10e0bc9><button class="btn btn-error btn-sm btn-outline" data-v-e10e0bc9>删除服务</button><button class="btn btn-primary btn-sm text-white" data-v-e10e0bc9>保存</button><button class="btn btn-ghost btn-sm" data-v-e10e0bc9>关闭</button></div></div><div class="divider text-xs text-slate-400 my-3" data-v-e10e0bc9>基本信息</div><div class="grid grid-cols-2 gap-4 mb-4" data-v-e10e0bc9><div class="form-control" data-v-e10e0bc9><label class="label" data-v-e10e0bc9><span class="label-text" data-v-e10e0bc9>服务名称 *</span></label><input${ssrRenderAttr("value", editForm.name)} class="input input-bordered" data-v-e10e0bc9></div><div class="form-control" data-v-e10e0bc9><label class="label" data-v-e10e0bc9><span class="label-text" data-v-e10e0bc9>启用</span></label><input${ssrIncludeBooleanAttr(Array.isArray(editForm.enable) ? ssrLooseContain(editForm.enable, null) : editForm.enable) ? " checked" : ""} type="checkbox" class="toggle toggle-primary mt-3" data-v-e10e0bc9></div></div><div class="form-control mb-4" data-v-e10e0bc9><label class="label" data-v-e10e0bc9><span class="label-text" data-v-e10e0bc9>服务描述</span></label><input${ssrRenderAttr("value", editForm.description)} class="input input-bordered" data-v-e10e0bc9></div><div class="mb-4" data-v-e10e0bc9><p class="text-sm font-medium text-slate-700 mb-2" data-v-e10e0bc9>服务类型</p>`);
|
|
if (editType.value) {
|
|
_push(`<span class="cat-chip" style="${ssrRenderStyle({ color: scatInfo(editType.value).color, background: scatInfo(editType.value).bg })}" data-v-e10e0bc9>${ssrInterpolate(scatInfo(editType.value).short)}</span>`);
|
|
} else {
|
|
_push(`<!---->`);
|
|
}
|
|
_push(`<span class="text-xs text-slate-500 ml-2" data-v-e10e0bc9>${ssrInterpolate(editType.value ? scatInfo(editType.value).label : "未设置")}</span><span class="text-[11px] text-slate-400 ml-2" data-v-e10e0bc9>(类型在创建时确定,不可更改)</span></div>`);
|
|
if (isVoiceCat.value) {
|
|
_push(`<div class="voice-card" data-v-e10e0bc9><div class="flex items-center justify-between" data-v-e10e0bc9><span class="text-sm font-medium text-slate-700" data-v-e10e0bc9>内置音色表</span><span class="flex items-center gap-2" data-v-e10e0bc9><span class="text-[11px] text-slate-400" data-v-e10e0bc9>`);
|
|
if (voicesLoading.value) {
|
|
_push(`<!--[-->加载中…<!--]-->`);
|
|
} else if (voiceRows.value.length) {
|
|
_push(`<!--[-->${ssrInterpolate(editForm.provider)} · ${ssrInterpolate(voiceRows.value.length)} 个音色 / ${ssrInterpolate(voiceLangGroups.value.length)} 种语言<!--]-->`);
|
|
} else {
|
|
_push(`<!--[-->未覆盖 ${ssrInterpolate(editForm.provider || "该服务商")}<!--]-->`);
|
|
}
|
|
_push(`</span>`);
|
|
if (voiceRows.value.length) {
|
|
_push(`<button class="btn btn-ghost btn-xs" data-v-e10e0bc9>填入 languages</button>`);
|
|
} else {
|
|
_push(`<!---->`);
|
|
}
|
|
_push(`</span></div>`);
|
|
if (!voicesLoading.value && !voiceRows.value.length) {
|
|
_push(`<p class="text-[11px] text-slate-400 mt-1" data-v-e10e0bc9> 内置音色表里没有这家服务商,不影响接入——Agent 配音色时手填服务商的音色 id 即可。 </p>`);
|
|
} else if (voiceRows.value.length) {
|
|
_push(`<div class="voice-lang-list" data-v-e10e0bc9><!--[-->`);
|
|
ssrRenderList(voiceLangGroups.value, (g) => {
|
|
_push(`<div class="voice-lang-row" data-v-e10e0bc9><span class="voice-lang" data-v-e10e0bc9>${ssrInterpolate(g.lang)}</span><span class="voice-ids" data-v-e10e0bc9><!--[-->`);
|
|
ssrRenderList(g.items, (v, i) => {
|
|
_push(`<span class="voice-pill"${ssrRenderAttr("title", v.name)} data-v-e10e0bc9>${ssrInterpolate(v.gender === 1 ? "♂" : v.gender === 2 ? "♀" : "·")} ${ssrInterpolate(v.voice_id)}`);
|
|
if (i === 0) {
|
|
_push(`<em data-v-e10e0bc9>默认</em>`);
|
|
} else {
|
|
_push(`<!---->`);
|
|
}
|
|
_push(`</span>`);
|
|
});
|
|
_push(`<!--]--></span></div>`);
|
|
});
|
|
_push(`<!--]--></div>`);
|
|
} else {
|
|
_push(`<!---->`);
|
|
}
|
|
if (voiceRows.value.length) {
|
|
_push(`<p class="text-[11px] text-slate-400 mt-1" data-v-e10e0bc9> 这 ${ssrInterpolate(voiceLangGroups.value.length)} 种语言就是该服务能说的语言,已作为下方 languages 的预填值——通话翻译、AI 助手排查「这条链路有没有服务支持」看的就是它。 音色本身是代码里的常量表(后端 comm/ttsvoice.go),实际用哪个在 Agent 里按语言+性别选,同组第一个是默认。 </p>`);
|
|
} else {
|
|
_push(`<!---->`);
|
|
}
|
|
_push(`</div>`);
|
|
} else {
|
|
_push(`<!---->`);
|
|
}
|
|
_push(`<div class="divider text-xs text-slate-400 my-3" data-v-e10e0bc9>配置字段(全区域共用)</div>`);
|
|
if (isMcpType()) {
|
|
_push(`<div class="rounded-lg px-3 py-2 mb-3 text-xs" style="${ssrRenderStyle({ "background": "#f0fdfa", "border": "1px solid #99f6e4", "color": "#0f766e" })}" data-v-e10e0bc9> ✦ MCP 服务与其他第三方服务同表存储(svc_config):这里填的 url/type/tools 为<b data-v-e10e0bc9>全区域默认</b>; 只在某些区域生效的地址,切到对应区域做分叉即可,卡片上会显示已分叉的区域数。 </div>`);
|
|
} else {
|
|
_push(`<!---->`);
|
|
}
|
|
_push(`<div data-v-e10e0bc9>`);
|
|
if (!editFields.value.length) {
|
|
_push(`<p class="text-xs text-slate-400 py-4 text-center" data-v-e10e0bc9>尚无字段,点击下方添加</p>`);
|
|
} else {
|
|
_push(`<div class="overflow-x-auto" data-v-e10e0bc9><table class="table text-xs" data-v-e10e0bc9><thead data-v-e10e0bc9><tr data-v-e10e0bc9><th class="w-44" data-v-e10e0bc9>字段名</th><th data-v-e10e0bc9>字段描述</th><th class="w-16 text-center" data-v-e10e0bc9>加密</th><th data-v-e10e0bc9>默认值</th><th class="w-12" data-v-e10e0bc9></th></tr></thead><tbody data-v-e10e0bc9><!--[-->`);
|
|
ssrRenderList(editFields.value, (f, i) => {
|
|
_push(`<tr data-v-e10e0bc9><td data-v-e10e0bc9><input class="field-input"${ssrRenderAttr("value", f.key)} placeholder="key"${ssrRenderAttr("title", f.key)} data-v-e10e0bc9></td><td data-v-e10e0bc9><input class="field-input"${ssrRenderAttr("value", f.description)} placeholder="用途描述" data-v-e10e0bc9></td><td class="text-center" data-v-e10e0bc9><input type="checkbox" class="checkbox checkbox-xs checkbox-primary"${ssrIncludeBooleanAttr(Array.isArray(f.encrypted) ? ssrLooseContain(f.encrypted, null) : f.encrypted) ? " checked" : ""} data-v-e10e0bc9></td><td data-v-e10e0bc9>`);
|
|
if (f.type === "langs") {
|
|
_push(ssrRenderComponent(_component_LangSelect, {
|
|
modelValue: f.def_value,
|
|
"onUpdate:modelValue": ($event) => f.def_value = $event,
|
|
multiple: true
|
|
}, null, _parent));
|
|
} else if (f.type === "lang") {
|
|
_push(ssrRenderComponent(_component_LangSelect, {
|
|
modelValue: f.def_value,
|
|
"onUpdate:modelValue": ($event) => f.def_value = $event
|
|
}, null, _parent));
|
|
} else if (f.encrypted) {
|
|
_push(`<!--[--><div class="relative" data-v-e10e0bc9><input${ssrRenderAttr("type", f._show ? "text" : "password")} class="field-input pr-14"${ssrRenderDynamicModel(f._show ? "text" : "password", f.def_value, null)} placeholder="留空保留原值" data-v-e10e0bc9><button type="button" class="absolute right-2 top-1/2 -translate-y-1/2 text-xs text-slate-400 hover:text-slate-700" data-v-e10e0bc9>${ssrInterpolate(f._show ? "隐藏" : "显示")}</button></div><span class="lock-badge ml-1" data-v-e10e0bc9>🔒 加密</span><!--]-->`);
|
|
} else if (isMcpType() && f.key === "type") {
|
|
_push(`<select class="field-input" data-v-e10e0bc9><option value="0" data-v-e10e0bc9${ssrIncludeBooleanAttr(Array.isArray(f.def_value) ? ssrLooseContain(f.def_value, "0") : ssrLooseEqual(f.def_value, "0")) ? " selected" : ""}>HTTP</option><option value="1" data-v-e10e0bc9${ssrIncludeBooleanAttr(Array.isArray(f.def_value) ? ssrLooseContain(f.def_value, "1") : ssrLooseEqual(f.def_value, "1")) ? " selected" : ""}>SSE</option></select>`);
|
|
} else {
|
|
_push(`<input class="field-input"${ssrRenderAttr("value", f.def_value)} data-v-e10e0bc9>`);
|
|
}
|
|
_push(`</td><td data-v-e10e0bc9><button class="btn btn-ghost btn-xs text-error" data-v-e10e0bc9>×</button></td></tr>`);
|
|
});
|
|
_push(`<!--]--></tbody></table></div>`);
|
|
}
|
|
_push(`<button class="btn btn-ghost btn-sm mt-2 text-primary" data-v-e10e0bc9>+ 添加字段</button></div><!--]-->`);
|
|
} else if (detailMode.value === "new") {
|
|
_push(`<!--[--><div class="flex items-center justify-between mb-4" data-v-e10e0bc9><h3 class="text-base font-semibold text-slate-800" data-v-e10e0bc9>新增第三方服务</h3><button class="btn btn-ghost btn-sm" data-v-e10e0bc9>取消</button></div><div class="wizard-steps mb-5" data-v-e10e0bc9><div class="${ssrRenderClass([{ on: wizardStep.value >= 1, cur: wizardStep.value === 1 }, "wstep"])}" data-v-e10e0bc9>① 服务类型`);
|
|
if (editType.value) {
|
|
_push(`<span data-v-e10e0bc9> · ${ssrInterpolate(scatInfo(editType.value).short)}</span>`);
|
|
} else {
|
|
_push(`<!---->`);
|
|
}
|
|
_push(`</div><div class="wline" data-v-e10e0bc9></div><div class="${ssrRenderClass([{ on: wizardStep.value >= 2, cur: wizardStep.value === 2, disabled: !editType.value }, "wstep"])}" data-v-e10e0bc9>② 服务商`);
|
|
if (editForm.provider) {
|
|
_push(`<span data-v-e10e0bc9> · ${ssrInterpolate(editForm.provider)}</span>`);
|
|
} else {
|
|
_push(`<!---->`);
|
|
}
|
|
_push(`</div><div class="wline" data-v-e10e0bc9></div><div class="${ssrRenderClass([{ on: wizardStep.value >= 3, cur: wizardStep.value === 3, disabled: wizardStep.value < 3 }, "wstep"])}" data-v-e10e0bc9>③ 配置</div></div>`);
|
|
if (wizardStep.value === 1) {
|
|
_push(`<!--[--><p class="text-sm text-slate-500 mb-3" data-v-e10e0bc9>请选择要接入的服务类型(每个服务归属单一类型,独立配置):</p><div class="type-grid" data-v-e10e0bc9><!--[-->`);
|
|
ssrRenderList(SCAT, (sc) => {
|
|
_push(`<div class="${ssrRenderClass([{ on: editType.value === sc.v }, "type-card"])}" data-v-e10e0bc9><span class="cat-chip" style="${ssrRenderStyle({ color: sc.color, background: sc.bg })}" data-v-e10e0bc9>${ssrInterpolate(sc.short)}</span><span class="text-sm text-slate-700 mt-2" data-v-e10e0bc9>${ssrInterpolate(sc.label)}</span></div>`);
|
|
});
|
|
_push(`<!--]--></div><!--]-->`);
|
|
} else if (wizardStep.value === 2) {
|
|
_push(`<!--[--><div class="flex items-center justify-between mb-3" data-v-e10e0bc9><p class="text-sm text-slate-500" data-v-e10e0bc9>为「${ssrInterpolate(scatInfo(editType.value).label)}」选择服务商:</p><button class="btn btn-ghost btn-xs" data-v-e10e0bc9>← 上一步</button></div><div class="type-grid" data-v-e10e0bc9><!--[-->`);
|
|
ssrRenderList(templatesForType.value, (t) => {
|
|
_push(`<div class="${ssrRenderClass([{ on: selectedTemplateId.value === t.id }, "type-card"])}" data-v-e10e0bc9><span class="provider-chip" data-v-e10e0bc9>${ssrInterpolate(t.provider)}</span><span class="text-sm text-slate-700 mt-2" data-v-e10e0bc9>${ssrInterpolate(t.name)}</span><span class="text-[11px] text-slate-400 mt-0.5" data-v-e10e0bc9>${ssrInterpolate((t.fields || []).length)} 字段</span></div>`);
|
|
});
|
|
_push(`<!--]--><div class="type-card" data-v-e10e0bc9><span class="provider-chip" style="${ssrRenderStyle({ "background": "#f1f5f9", "color": "#475569" })}" data-v-e10e0bc9>自定义</span><span class="text-sm text-slate-700 mt-2" data-v-e10e0bc9>空白自定义</span><span class="text-[11px] text-slate-400 mt-0.5" data-v-e10e0bc9>手动添加字段</span></div></div>`);
|
|
if (!templatesForType.value.length) {
|
|
_push(`<p class="text-xs text-slate-400 mt-2" data-v-e10e0bc9>该类型暂无内置模板,可选「空白自定义」,或到「服务商模板」新增。</p>`);
|
|
} else {
|
|
_push(`<!---->`);
|
|
}
|
|
_push(`<!--]-->`);
|
|
} else {
|
|
_push(`<!--[--><div class="flex items-center justify-between mb-3" data-v-e10e0bc9><div class="flex items-center gap-2" data-v-e10e0bc9><span class="cat-chip" style="${ssrRenderStyle({ color: scatInfo(editType.value).color, background: scatInfo(editType.value).bg })}" data-v-e10e0bc9>${ssrInterpolate(scatInfo(editType.value).short)}</span>`);
|
|
if (editForm.provider) {
|
|
_push(`<span class="provider-chip" data-v-e10e0bc9>${ssrInterpolate(editForm.provider)}</span>`);
|
|
} else {
|
|
_push(`<!---->`);
|
|
}
|
|
_push(`</div><div class="flex gap-2" data-v-e10e0bc9><button class="btn btn-ghost btn-xs" data-v-e10e0bc9>← 上一步</button><button class="btn btn-primary btn-sm text-white" data-v-e10e0bc9>创建</button></div></div><div class="grid grid-cols-2 gap-4 mb-4" data-v-e10e0bc9><div class="form-control" data-v-e10e0bc9><label class="label" data-v-e10e0bc9><span class="label-text" data-v-e10e0bc9>服务ID * <span class="text-slate-400 text-xs" data-v-e10e0bc9>(唯一,如 tts_azure)</span></span></label><input${ssrRenderAttr("value", editForm.id)} class="input input-bordered font-mono" placeholder="tts_azure" data-v-e10e0bc9></div><div class="form-control" data-v-e10e0bc9><label class="label" data-v-e10e0bc9><span class="label-text" data-v-e10e0bc9>服务名称 *</span></label><input${ssrRenderAttr("value", editForm.name)} class="input input-bordered" placeholder="微软 Azure TTS" data-v-e10e0bc9></div></div><div class="grid grid-cols-2 gap-4 mb-4" data-v-e10e0bc9><div class="form-control" data-v-e10e0bc9><label class="label" data-v-e10e0bc9><span class="label-text" data-v-e10e0bc9>服务商标识 <span class="text-slate-400 text-xs" data-v-e10e0bc9>(如 azure/alibaba)</span></span></label><input${ssrRenderAttr("value", editForm.provider)} class="input input-bordered font-mono" placeholder="azure" data-v-e10e0bc9></div><div class="form-control" data-v-e10e0bc9><label class="label" data-v-e10e0bc9><span class="label-text" data-v-e10e0bc9>启用</span></label><input${ssrIncludeBooleanAttr(Array.isArray(editForm.enable) ? ssrLooseContain(editForm.enable, null) : editForm.enable) ? " checked" : ""} type="checkbox" class="toggle toggle-primary mt-3" data-v-e10e0bc9></div></div><div class="form-control mb-4" data-v-e10e0bc9><label class="label" data-v-e10e0bc9><span class="label-text" data-v-e10e0bc9>服务描述</span></label><input${ssrRenderAttr("value", editForm.description)} class="input input-bordered" placeholder="简述该服务的用途" data-v-e10e0bc9></div>`);
|
|
if (isVoiceCat.value) {
|
|
_push(`<div class="voice-card" data-v-e10e0bc9><div class="flex items-center justify-between" data-v-e10e0bc9><span class="text-sm font-medium text-slate-700" data-v-e10e0bc9>内置音色表</span><span class="text-[11px] text-slate-400" data-v-e10e0bc9>`);
|
|
if (voicesLoading.value) {
|
|
_push(`<!--[-->加载中…<!--]-->`);
|
|
} else if (voiceRows.value.length) {
|
|
_push(`<!--[-->${ssrInterpolate(editForm.provider)} · ${ssrInterpolate(voiceRows.value.length)} 个音色 / ${ssrInterpolate(voiceLangGroups.value.length)} 种语言<!--]-->`);
|
|
} else {
|
|
_push(`<!--[-->未覆盖 ${ssrInterpolate(editForm.provider || "该服务商")}<!--]-->`);
|
|
}
|
|
_push(`</span></div>`);
|
|
if (!voicesLoading.value && !voiceRows.value.length) {
|
|
_push(`<p class="text-[11px] text-slate-400 mt-1" data-v-e10e0bc9> 内置音色表里没有这家服务商,不影响接入——Agent 配音色时手填服务商的音色 id 即可。 </p>`);
|
|
} else if (voiceRows.value.length) {
|
|
_push(`<div class="voice-lang-list" data-v-e10e0bc9><!--[-->`);
|
|
ssrRenderList(voiceLangGroups.value, (g) => {
|
|
_push(`<div class="voice-lang-row" data-v-e10e0bc9><span class="voice-lang" data-v-e10e0bc9>${ssrInterpolate(g.lang)}</span><span class="voice-ids" data-v-e10e0bc9><!--[-->`);
|
|
ssrRenderList(g.items, (v, i) => {
|
|
_push(`<span class="voice-pill"${ssrRenderAttr("title", v.name)} data-v-e10e0bc9>${ssrInterpolate(v.gender === 1 ? "♂" : v.gender === 2 ? "♀" : "·")} ${ssrInterpolate(v.voice_id)}`);
|
|
if (i === 0) {
|
|
_push(`<em data-v-e10e0bc9>默认</em>`);
|
|
} else {
|
|
_push(`<!---->`);
|
|
}
|
|
_push(`</span>`);
|
|
});
|
|
_push(`<!--]--></span></div>`);
|
|
});
|
|
_push(`<!--]--></div>`);
|
|
} else {
|
|
_push(`<!---->`);
|
|
}
|
|
if (voiceRows.value.length) {
|
|
_push(`<p class="text-[11px] text-slate-400 mt-1" data-v-e10e0bc9> 这 ${ssrInterpolate(voiceLangGroups.value.length)} 种语言就是该服务能说的语言,已作为下方 languages 的预填值——通话翻译、AI 助手排查「这条链路有没有服务支持」看的就是它。 音色本身是代码里的常量表(后端 comm/ttsvoice.go),实际用哪个在 Agent 里按语言+性别选,同组第一个是默认。 </p>`);
|
|
} else {
|
|
_push(`<!---->`);
|
|
}
|
|
_push(`</div>`);
|
|
} else {
|
|
_push(`<!---->`);
|
|
}
|
|
_push(`<div class="divider text-xs text-slate-400 my-3" data-v-e10e0bc9>配置字段</div><div data-v-e10e0bc9>`);
|
|
if (!editFields.value.length) {
|
|
_push(`<p class="text-xs text-slate-400 py-4 text-center" data-v-e10e0bc9>尚无字段,选模板自动带出或点击下方添加</p>`);
|
|
} else {
|
|
_push(`<div class="overflow-x-auto" data-v-e10e0bc9><table class="table text-xs" data-v-e10e0bc9><thead data-v-e10e0bc9><tr data-v-e10e0bc9><th class="w-44" data-v-e10e0bc9>字段名</th><th data-v-e10e0bc9>字段描述</th><th class="w-16 text-center" data-v-e10e0bc9>加密</th><th data-v-e10e0bc9>默认值</th><th class="w-12" data-v-e10e0bc9></th></tr></thead><tbody data-v-e10e0bc9><!--[-->`);
|
|
ssrRenderList(editFields.value, (f, i) => {
|
|
_push(`<tr data-v-e10e0bc9><td data-v-e10e0bc9><input class="field-input"${ssrRenderAttr("value", f.key)} placeholder="key"${ssrRenderAttr("title", f.key)} data-v-e10e0bc9></td><td data-v-e10e0bc9><input class="field-input"${ssrRenderAttr("value", f.description)} placeholder="用途描述" data-v-e10e0bc9></td><td class="text-center" data-v-e10e0bc9><input type="checkbox" class="checkbox checkbox-xs checkbox-primary"${ssrIncludeBooleanAttr(Array.isArray(f.encrypted) ? ssrLooseContain(f.encrypted, null) : f.encrypted) ? " checked" : ""} data-v-e10e0bc9></td><td data-v-e10e0bc9>`);
|
|
if (f.type === "langs") {
|
|
_push(ssrRenderComponent(_component_LangSelect, {
|
|
modelValue: f.def_value,
|
|
"onUpdate:modelValue": ($event) => f.def_value = $event,
|
|
multiple: true
|
|
}, null, _parent));
|
|
} else if (f.type === "lang") {
|
|
_push(ssrRenderComponent(_component_LangSelect, {
|
|
modelValue: f.def_value,
|
|
"onUpdate:modelValue": ($event) => f.def_value = $event
|
|
}, null, _parent));
|
|
} else if (f.encrypted) {
|
|
_push(`<!--[--><div class="relative" data-v-e10e0bc9><input${ssrRenderAttr("type", f._show ? "text" : "password")} class="field-input pr-14"${ssrRenderDynamicModel(f._show ? "text" : "password", f.def_value, null)} placeholder="留空保留原值" data-v-e10e0bc9><button type="button" class="absolute right-2 top-1/2 -translate-y-1/2 text-xs text-slate-400 hover:text-slate-700" data-v-e10e0bc9>${ssrInterpolate(f._show ? "隐藏" : "显示")}</button></div><span class="lock-badge ml-1" data-v-e10e0bc9>🔒 加密</span><!--]-->`);
|
|
} else if (isMcpType() && f.key === "type") {
|
|
_push(`<select class="field-input" data-v-e10e0bc9><option value="0" data-v-e10e0bc9${ssrIncludeBooleanAttr(Array.isArray(f.def_value) ? ssrLooseContain(f.def_value, "0") : ssrLooseEqual(f.def_value, "0")) ? " selected" : ""}>HTTP</option><option value="1" data-v-e10e0bc9${ssrIncludeBooleanAttr(Array.isArray(f.def_value) ? ssrLooseContain(f.def_value, "1") : ssrLooseEqual(f.def_value, "1")) ? " selected" : ""}>SSE</option></select>`);
|
|
} else {
|
|
_push(`<input class="field-input"${ssrRenderAttr("value", f.def_value)} data-v-e10e0bc9>`);
|
|
}
|
|
_push(`</td><td data-v-e10e0bc9><button class="btn btn-ghost btn-xs text-error" data-v-e10e0bc9>×</button></td></tr>`);
|
|
});
|
|
_push(`<!--]--></tbody></table></div>`);
|
|
}
|
|
_push(`<button class="btn btn-ghost btn-sm mt-2 text-primary" data-v-e10e0bc9>+ 添加字段</button></div><!--]-->`);
|
|
}
|
|
_push(`<!--]-->`);
|
|
} else if (detailMode.value === "region" && curSvc.value) {
|
|
_push(`<!--[-->`);
|
|
if (regionLoading.value) {
|
|
_push(`<div class="text-center py-10 text-slate-400" data-v-e10e0bc9>加载中...</div>`);
|
|
} else {
|
|
_push(`<!--[--><div class="flex items-center justify-between mb-5" data-v-e10e0bc9><div class="flex items-center gap-3" data-v-e10e0bc9><div class="svc-logo" style="${ssrRenderStyle({ background: curSvc.value.color || "#6366f1", width: "44px", height: "44px", fontSize: "20px" })}" data-v-e10e0bc9>${ssrInterpolate(curSvc.value.icon || (curSvc.value.name || "").charAt(0))}</div><div data-v-e10e0bc9><h3 class="text-base font-semibold text-slate-800" data-v-e10e0bc9>${ssrInterpolate(curSvc.value.name)}</h3><span class="badge badge-warning badge-sm mt-0.5" data-v-e10e0bc9>${ssrInterpolate(curRegionLabel.value)} 分叉模式</span></div></div><div class="flex gap-2" data-v-e10e0bc9><button class="btn btn-primary btn-sm text-white" data-v-e10e0bc9>保存分叉</button><button class="btn btn-ghost btn-sm" data-v-e10e0bc9>关闭</button></div></div><div class="rounded-lg px-3 py-2 mb-4 text-xs" style="${ssrRenderStyle({ "background": "#fefce8", "border": "1px solid #fde047", "color": "#854d0e" })}" data-v-e10e0bc9> ✦ 当前编辑「${ssrInterpolate(curRegionLabel.value)}」的字段分叉:默认字段可覆盖值或勾选「不适用」停用;也可新增本区域专属字段。留空的覆盖字段继承默认值,加密字段留空保留已存储密文。 </div><p class="text-sm font-medium text-slate-700 mb-2" data-v-e10e0bc9>默认字段</p><div class="overflow-x-auto" data-v-e10e0bc9><table class="table text-xs" data-v-e10e0bc9><thead data-v-e10e0bc9><tr data-v-e10e0bc9><th class="w-44" data-v-e10e0bc9>字段名</th><th data-v-e10e0bc9>字段描述</th><th class="w-24 text-center" data-v-e10e0bc9>状态</th><th class="w-20 text-center" data-v-e10e0bc9>本区域不适用</th><th data-v-e10e0bc9>有效值</th></tr></thead><tbody data-v-e10e0bc9><!--[-->`);
|
|
ssrRenderList(overrideFields.value, (f, i) => {
|
|
_push(`<tr class="${ssrRenderClass({ "opacity-50": f.disabled })}" data-v-e10e0bc9><td class="font-mono text-slate-700 break-all"${ssrRenderAttr("title", f.key)} data-v-e10e0bc9>${ssrInterpolate(f.key)}</td><td class="text-slate-500" data-v-e10e0bc9>${ssrInterpolate(f.description)}</td><td class="text-center" data-v-e10e0bc9>`);
|
|
if (f.disabled) {
|
|
_push(`<span class="disabled-badge" data-v-e10e0bc9>停用</span>`);
|
|
} else if (f.is_overridden) {
|
|
_push(`<span class="override-badge" data-v-e10e0bc9>覆盖</span>`);
|
|
} else {
|
|
_push(`<span class="inherit-badge" data-v-e10e0bc9>继承</span>`);
|
|
}
|
|
_push(`</td><td class="text-center" data-v-e10e0bc9><input type="checkbox" class="checkbox checkbox-xs"${ssrIncludeBooleanAttr(Array.isArray(f.disabled) ? ssrLooseContain(f.disabled, null) : f.disabled) ? " checked" : ""} data-v-e10e0bc9></td><td data-v-e10e0bc9>`);
|
|
if (f.type === "langs") {
|
|
_push(ssrRenderComponent(_component_LangSelect, {
|
|
modelValue: f._input,
|
|
"onUpdate:modelValue": ($event) => f._input = $event,
|
|
multiple: true,
|
|
disabled: f.disabled
|
|
}, null, _parent));
|
|
} else if (f.type === "lang") {
|
|
_push(ssrRenderComponent(_component_LangSelect, {
|
|
modelValue: f._input,
|
|
"onUpdate:modelValue": ($event) => f._input = $event,
|
|
disabled: f.disabled
|
|
}, null, _parent));
|
|
} else if (f.encrypted) {
|
|
_push(`<!--[--><div class="relative" data-v-e10e0bc9><input${ssrRenderAttr("type", f._show ? "text" : "password")} class="field-input pr-14"${ssrRenderDynamicModel(f._show ? "text" : "password", f._input, null)}${ssrIncludeBooleanAttr(f.disabled) ? " disabled" : ""} placeholder="留空继承默认" data-v-e10e0bc9><button type="button" class="absolute right-2 top-1/2 -translate-y-1/2 text-xs text-slate-400 hover:text-slate-700" data-v-e10e0bc9>${ssrInterpolate(f._show ? "隐藏" : "显示")}</button></div><span class="lock-badge ml-1" data-v-e10e0bc9>🔒</span><!--]-->`);
|
|
} else if (isMcpType() && f.key === "type") {
|
|
_push(`<select class="field-input"${ssrIncludeBooleanAttr(f.disabled) ? " disabled" : ""} data-v-e10e0bc9><option value="" data-v-e10e0bc9${ssrIncludeBooleanAttr(Array.isArray(f._input) ? ssrLooseContain(f._input, "") : ssrLooseEqual(f._input, "")) ? " selected" : ""}>继承默认</option><option value="0" data-v-e10e0bc9${ssrIncludeBooleanAttr(Array.isArray(f._input) ? ssrLooseContain(f._input, "0") : ssrLooseEqual(f._input, "0")) ? " selected" : ""}>HTTP</option><option value="1" data-v-e10e0bc9${ssrIncludeBooleanAttr(Array.isArray(f._input) ? ssrLooseContain(f._input, "1") : ssrLooseEqual(f._input, "1")) ? " selected" : ""}>SSE</option></select>`);
|
|
} else {
|
|
_push(`<input class="field-input"${ssrRenderAttr("value", f._input)}${ssrIncludeBooleanAttr(f.disabled) ? " disabled" : ""}${ssrRenderAttr("placeholder", "留空继承:" + (f.value || ""))} data-v-e10e0bc9>`);
|
|
}
|
|
_push(`</td></tr>`);
|
|
});
|
|
_push(`<!--]-->`);
|
|
if (!overrideFields.value.length) {
|
|
_push(`<tr data-v-e10e0bc9><td colspan="5" class="text-center text-slate-400 py-3" data-v-e10e0bc9>该服务尚无默认字段</td></tr>`);
|
|
} else {
|
|
_push(`<!---->`);
|
|
}
|
|
_push(`</tbody></table></div><div class="divider text-xs text-slate-400 my-3" data-v-e10e0bc9>${ssrInterpolate(curRegionLabel.value)} 专属字段</div><div data-v-e10e0bc9>`);
|
|
if (!extraRegionFields.value.length) {
|
|
_push(`<p class="text-xs text-slate-400 py-2 text-center" data-v-e10e0bc9>尚无区域专属字段,点击下方添加</p>`);
|
|
} else {
|
|
_push(`<div class="overflow-x-auto" data-v-e10e0bc9><table class="table text-xs" data-v-e10e0bc9><thead data-v-e10e0bc9><tr data-v-e10e0bc9><th class="w-44" data-v-e10e0bc9>字段名</th><th data-v-e10e0bc9>字段描述</th><th class="w-16 text-center" data-v-e10e0bc9>加密</th><th data-v-e10e0bc9>值</th><th class="w-12" data-v-e10e0bc9></th></tr></thead><tbody data-v-e10e0bc9><!--[-->`);
|
|
ssrRenderList(extraRegionFields.value, (f, i) => {
|
|
_push(`<tr data-v-e10e0bc9><td data-v-e10e0bc9><input class="field-input"${ssrRenderAttr("value", f.key)} placeholder="key"${ssrRenderAttr("title", f.key)} data-v-e10e0bc9></td><td data-v-e10e0bc9><input class="field-input"${ssrRenderAttr("value", f.description)} placeholder="用途描述" data-v-e10e0bc9></td><td class="text-center" data-v-e10e0bc9><input type="checkbox" class="checkbox checkbox-xs checkbox-primary"${ssrIncludeBooleanAttr(Array.isArray(f.encrypted) ? ssrLooseContain(f.encrypted, null) : f.encrypted) ? " checked" : ""} data-v-e10e0bc9></td><td data-v-e10e0bc9>`);
|
|
if (f.encrypted) {
|
|
_push(`<!--[--><div class="relative" data-v-e10e0bc9><input${ssrRenderAttr("type", f._show ? "text" : "password")} class="field-input pr-14"${ssrRenderDynamicModel(f._show ? "text" : "password", f._input, null)} placeholder="留空保留原值" data-v-e10e0bc9><button type="button" class="absolute right-2 top-1/2 -translate-y-1/2 text-xs text-slate-400 hover:text-slate-700" data-v-e10e0bc9>${ssrInterpolate(f._show ? "隐藏" : "显示")}</button></div><span class="lock-badge ml-1" data-v-e10e0bc9>🔒</span><!--]-->`);
|
|
} else {
|
|
_push(`<input class="field-input"${ssrRenderAttr("value", f._input)} data-v-e10e0bc9>`);
|
|
}
|
|
_push(`</td><td data-v-e10e0bc9><button class="btn btn-ghost btn-xs text-error" data-v-e10e0bc9>×</button></td></tr>`);
|
|
});
|
|
_push(`<!--]--></tbody></table></div>`);
|
|
}
|
|
_push(`<button class="btn btn-ghost btn-sm mt-2 text-primary" data-v-e10e0bc9>+ 添加区域专属字段</button></div><!--]-->`);
|
|
}
|
|
_push(`<!--]-->`);
|
|
} else {
|
|
_push(`<!---->`);
|
|
}
|
|
_push(`</div></div>`);
|
|
} else {
|
|
_push(`<!---->`);
|
|
}
|
|
_push(`<div class="modal-overlay" style="${ssrRenderStyle([
|
|
{ "z-index": "120" },
|
|
syncVisible.value ? null : { display: "none" }
|
|
])}" data-v-e10e0bc9><div class="sync-box" data-v-e10e0bc9><div class="flex items-start gap-3 mb-4" data-v-e10e0bc9><div style="${ssrRenderStyle({ "width": "42px", "height": "42px", "background": "linear-gradient(135deg,#4f46e5,#7c3aed)", "border-radius": "13px", "display": "flex", "align-items": "center", "justify-content": "center", "flex-shrink": "0" })}" data-v-e10e0bc9><svg style="${ssrRenderStyle({ "width": "20px", "height": "20px", "color": "#fff" })}" fill="none" viewBox="0 0 24 24" stroke="currentColor" data-v-e10e0bc9><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z" data-v-e10e0bc9></path></svg></div><div data-v-e10e0bc9><h3 class="text-base font-semibold text-slate-800" data-v-e10e0bc9>同步到其他区域</h3><p class="text-xs text-slate-400 mt-1" data-v-e10e0bc9>${ssrInterpolate(syncDesc.value)}</p></div></div><div class="text-xs px-3 py-2 rounded-lg mb-3" style="${ssrRenderStyle({ "background": "#f0fdf4", "border": "1px solid #bbf7d0", "color": "#15803d" })}" data-v-e10e0bc9>${syncScopeNote.value ?? ""}</div><div class="flex items-center justify-between mb-1" data-v-e10e0bc9><span class="text-xs font-semibold text-slate-500" data-v-e10e0bc9>选择目标区域</span><div class="flex gap-2" data-v-e10e0bc9><button class="btn btn-ghost btn-xs" data-v-e10e0bc9>全选</button><button class="btn btn-ghost btn-xs" data-v-e10e0bc9>清空</button></div></div><div class="sync-region-grid" data-v-e10e0bc9><!--[-->`);
|
|
ssrRenderList(syncRegionList.value, (r) => {
|
|
_push(`<label class="${ssrRenderClass([{ checked: syncTargets.value.includes(r.value) }, "sync-region-item"])}" data-v-e10e0bc9><input type="checkbox" class="checkbox checkbox-xs checkbox-primary"${ssrIncludeBooleanAttr(syncTargets.value.includes(r.value)) ? " checked" : ""} data-v-e10e0bc9><span data-v-e10e0bc9>${ssrInterpolate(r.label)}</span></label>`);
|
|
});
|
|
_push(`<!--]--></div><p class="text-[11px] text-slate-400 mb-5" data-v-e10e0bc9>⚠ 已勾选区域将被当前配置(值覆盖 / 区域专属字段 / 停用字段)整体替换;加密字段密文一并同步。</p><div class="flex gap-3 justify-end" data-v-e10e0bc9><button class="btn btn-ghost" data-v-e10e0bc9>仅保存当前区域</button><button class="btn btn-primary text-white" data-v-e10e0bc9>保存并同步</button></div></div></div><div class="modal-overlay" style="${ssrRenderStyle([
|
|
{ "z-index": "130" },
|
|
tplModalVisible.value ? null : { display: "none" }
|
|
])}" data-v-e10e0bc9><div class="tpl-box" data-v-e10e0bc9>`);
|
|
if (tplView.value === "list") {
|
|
_push(`<!--[--><div class="flex items-center justify-between mb-4" data-v-e10e0bc9><div data-v-e10e0bc9><h3 class="text-base font-semibold text-slate-800" data-v-e10e0bc9>服务商模板</h3><p class="text-xs text-slate-400 mt-1" data-v-e10e0bc9>模板是字段预设,新增服务时可一键带出。内置模板可编辑;删除后重启会被重新补回。</p></div><div class="flex gap-2" data-v-e10e0bc9><button class="btn btn-primary btn-sm text-white" data-v-e10e0bc9>+ 新增模板</button><button class="btn btn-ghost btn-sm" data-v-e10e0bc9>关闭</button></div></div><div class="tpl-list" data-v-e10e0bc9><!--[-->`);
|
|
ssrRenderList(templatesByCat.value, (g) => {
|
|
_push(`<div class="mb-3" data-v-e10e0bc9><p class="text-xs font-semibold text-slate-500 mb-1" data-v-e10e0bc9>${ssrInterpolate(g.label)}</p><!--[-->`);
|
|
ssrRenderList(g.items, (t) => {
|
|
_push(`<div class="tpl-row" data-v-e10e0bc9><div style="${ssrRenderStyle({ "min-width": "0", "flex": "1" })}" data-v-e10e0bc9><div class="flex items-center gap-2" data-v-e10e0bc9><span class="font-semibold text-sm text-slate-800 truncate" data-v-e10e0bc9>${ssrInterpolate(t.name)}</span><span class="provider-chip" data-v-e10e0bc9>${ssrInterpolate(t.provider)}</span>`);
|
|
if (t.builtin) {
|
|
_push(`<span class="badge badge-xs badge-ghost" data-v-e10e0bc9>内置</span>`);
|
|
} else {
|
|
_push(`<!---->`);
|
|
}
|
|
_push(`</div><p class="text-xs text-slate-400 mt-0.5" data-v-e10e0bc9>${ssrInterpolate(t.id)} · ${ssrInterpolate((t.fields || []).length)} 字段</p></div><div class="flex gap-1" data-v-e10e0bc9><button class="btn btn-ghost btn-xs" data-v-e10e0bc9>编辑</button><button class="btn btn-ghost btn-xs text-error" data-v-e10e0bc9>删除</button></div></div>`);
|
|
});
|
|
_push(`<!--]--></div>`);
|
|
});
|
|
_push(`<!--]-->`);
|
|
if (!templates.value.length) {
|
|
_push(`<p class="text-xs text-slate-400 text-center py-6" data-v-e10e0bc9>暂无模板</p>`);
|
|
} else {
|
|
_push(`<!---->`);
|
|
}
|
|
_push(`</div><!--]-->`);
|
|
} else {
|
|
_push(`<!--[--><div class="flex items-center justify-between mb-4" data-v-e10e0bc9><h3 class="text-base font-semibold text-slate-800" data-v-e10e0bc9>${ssrInterpolate(tplEditing.value ? "编辑模板" : "新增模板")}</h3><div class="flex gap-2" data-v-e10e0bc9><button class="btn btn-ghost btn-sm" data-v-e10e0bc9>返回</button><button class="btn btn-primary btn-sm text-white" data-v-e10e0bc9>保存</button></div></div><div class="grid grid-cols-2 gap-3 mb-3" data-v-e10e0bc9><div class="form-control" data-v-e10e0bc9><label class="label" data-v-e10e0bc9><span class="label-text text-xs" data-v-e10e0bc9>模板ID *</span></label><input${ssrRenderAttr("value", tplForm.id)}${ssrIncludeBooleanAttr(tplEditing.value) ? " disabled" : ""} class="input input-bordered input-sm font-mono" placeholder="tts_azure" data-v-e10e0bc9></div><div class="form-control" data-v-e10e0bc9><label class="label" data-v-e10e0bc9><span class="label-text text-xs" data-v-e10e0bc9>类别 *</span></label><select class="select select-bordered select-sm" data-v-e10e0bc9><!--[-->`);
|
|
ssrRenderList(SCAT, (sc) => {
|
|
_push(`<option${ssrRenderAttr("value", sc.v)} data-v-e10e0bc9${ssrIncludeBooleanAttr(Array.isArray(tplForm.category) ? ssrLooseContain(tplForm.category, sc.v) : ssrLooseEqual(tplForm.category, sc.v)) ? " selected" : ""}>${ssrInterpolate(sc.label)}</option>`);
|
|
});
|
|
_push(`<!--]--></select></div><div class="form-control" data-v-e10e0bc9><label class="label" data-v-e10e0bc9><span class="label-text text-xs" data-v-e10e0bc9>服务商标识 *</span></label><input${ssrRenderAttr("value", tplForm.provider)} class="input input-bordered input-sm font-mono" placeholder="azure" data-v-e10e0bc9></div><div class="form-control" data-v-e10e0bc9><label class="label" data-v-e10e0bc9><span class="label-text text-xs" data-v-e10e0bc9>展示名 *</span></label><input${ssrRenderAttr("value", tplForm.name)} class="input input-bordered input-sm" placeholder="微软 Azure TTS" data-v-e10e0bc9></div></div><div class="form-control mb-3" data-v-e10e0bc9><label class="label" data-v-e10e0bc9><span class="label-text text-xs" data-v-e10e0bc9>描述</span></label><input${ssrRenderAttr("value", tplForm.description)} class="input input-bordered input-sm" data-v-e10e0bc9></div><p class="text-sm font-medium text-slate-700 mb-1" data-v-e10e0bc9>字段</p><div class="overflow-x-auto" style="${ssrRenderStyle({ "max-height": "34vh" })}" data-v-e10e0bc9><table class="table text-xs" data-v-e10e0bc9><thead data-v-e10e0bc9><tr data-v-e10e0bc9><th class="w-44" data-v-e10e0bc9>字段名</th><th data-v-e10e0bc9>字段描述</th><th class="w-16 text-center" data-v-e10e0bc9>加密</th><th data-v-e10e0bc9>默认/提示值</th><th class="w-12" data-v-e10e0bc9></th></tr></thead><tbody data-v-e10e0bc9><!--[-->`);
|
|
ssrRenderList(tplFields.value, (f, i) => {
|
|
_push(`<tr data-v-e10e0bc9><td data-v-e10e0bc9><input class="field-input"${ssrRenderAttr("value", f.key)} placeholder="key"${ssrRenderAttr("title", f.key)} data-v-e10e0bc9></td><td data-v-e10e0bc9><input class="field-input"${ssrRenderAttr("value", f.description)} placeholder="用途描述" data-v-e10e0bc9></td><td class="text-center" data-v-e10e0bc9><input type="checkbox" class="checkbox checkbox-xs checkbox-primary"${ssrIncludeBooleanAttr(Array.isArray(f.encrypted) ? ssrLooseContain(f.encrypted, null) : f.encrypted) ? " checked" : ""} data-v-e10e0bc9></td><td data-v-e10e0bc9>`);
|
|
if (f.type === "langs") {
|
|
_push(ssrRenderComponent(_component_LangSelect, {
|
|
modelValue: f.def_value,
|
|
"onUpdate:modelValue": ($event) => f.def_value = $event,
|
|
multiple: true
|
|
}, null, _parent));
|
|
} else if (f.type === "lang") {
|
|
_push(ssrRenderComponent(_component_LangSelect, {
|
|
modelValue: f.def_value,
|
|
"onUpdate:modelValue": ($event) => f.def_value = $event
|
|
}, null, _parent));
|
|
} else {
|
|
_push(`<input class="field-input"${ssrRenderAttr("value", f.def_value)} placeholder="可留空" data-v-e10e0bc9>`);
|
|
}
|
|
_push(`</td><td data-v-e10e0bc9><button class="btn btn-ghost btn-xs text-error" data-v-e10e0bc9>×</button></td></tr>`);
|
|
});
|
|
_push(`<!--]-->`);
|
|
if (!tplFields.value.length) {
|
|
_push(`<tr data-v-e10e0bc9><td colspan="5" class="text-center text-slate-400 py-2" data-v-e10e0bc9>尚无字段</td></tr>`);
|
|
} else {
|
|
_push(`<!---->`);
|
|
}
|
|
_push(`</tbody></table></div><button class="btn btn-ghost btn-sm mt-2 text-primary" data-v-e10e0bc9>+ 添加字段</button><!--]-->`);
|
|
}
|
|
_push(`</div></div></div>`);
|
|
};
|
|
}
|
|
});
|
|
const _sfc_setup = _sfc_main.setup;
|
|
_sfc_main.setup = (props, ctx) => {
|
|
const ssrContext = useSSRContext();
|
|
(ssrContext.modules || (ssrContext.modules = /* @__PURE__ */ new Set())).add("pages/serviceconfig.vue");
|
|
return _sfc_setup ? _sfc_setup(props, ctx) : void 0;
|
|
};
|
|
const serviceconfig = /* @__PURE__ */ _export_sfc(_sfc_main, [["__scopeId", "data-v-e10e0bc9"]]);
|
|
|
|
export { serviceconfig as default };
|
|
//# sourceMappingURL=serviceconfig-Bq9We0Qx.mjs.map
|
|
|