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.
 
 
 
 
 
 

267 lines
21 KiB

import { defineComponent, ref, reactive, computed, useSSRContext } from 'vue';
import { ssrRenderAttrs, ssrIncludeBooleanAttr, ssrLooseContain, ssrLooseEqual, ssrRenderList, ssrRenderAttr, ssrInterpolate, ssrRenderStyle, ssrRenderComponent, ssrRenderClass } from 'vue/server-renderer';
import { u as useApi } from './useApi-B-E5eLsI.mjs';
import { u as useToast } from './useToast-CS21XT61.mjs';
import { l as langName } from './useLangOptions-DMhiAuTa.mjs';
import { _ as __nuxt_component_0 } from './LangSelect-C_QPlcDf.mjs';
import { _ as _export_sfc } from './server.mjs';
import './useLoading-D4zrSa2m.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 LANG_ANY = "*";
const _sfc_main = /* @__PURE__ */ defineComponent({
__name: "calltranslate",
__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: "印度" }
];
function regionLabel(v) {
return REGIONS.find((x) => x.value === v)?.label ?? "区域" + v;
}
const TRANS_TYPES = {
translate: { label: "🧩 三段式翻译", color: "#059669", bg: "#dcfce7" },
"ast-translate": { label: "🚀 端到端翻译", color: "#c2410c", bg: "#fff7ed" }
};
function typeLabel(t) {
return t && TRANS_TYPES[t] ? TRANS_TYPES[t].label : t || "未知";
}
function typeStyle(t) {
const s = t ? TRANS_TYPES[t] : void 0;
return s ? { background: s.bg, color: s.color } : { background: "#f1f5f9", color: "#64748b" };
}
useApi();
useToast();
const scopeApp = ref("");
const scopeApps = ref([]);
const curRegion = ref(REGIONS[0].value);
const rules = ref([]);
const agents = ref([]);
const editorOpen = ref(false);
const editing = ref(null);
ref(null);
ref(0);
const ed = reactive({
name: "",
srcLangs: "",
dstLangs: "",
srcAny: false,
dstAny: false,
bidir: true,
region: 0,
priority: 100,
agentId: "",
remark: "",
enable: true
});
const transAgents = computed(() => agents.value.filter((a) => a.type in TRANS_TYPES));
const agentGroups = computed(
() => Object.keys(TRANS_TYPES).map((t) => ({
type: t,
label: TRANS_TYPES[t].label,
items: transAgents.value.filter((a) => a.type === t)
})).filter((g) => g.items.length)
);
const curAgent = computed(() => agents.value.find((a) => a.id === ed.agentId) || null);
const langWarn = computed(() => {
const a = curAgent.value;
if (!a || !a.support_langs) return "";
const sup = a.support_langs.split(",").map((x) => x.trim()).filter(Boolean);
if (!sup.length) return "";
const picked = [
...ed.srcAny ? [] : ed.srcLangs.split(","),
...ed.dstAny ? [] : ed.dstLangs.split(",")
].map((x) => x.trim()).filter(Boolean);
const miss = picked.filter((c) => !sup.includes(c));
if (!miss.length) return "";
return `Agent「${a.name || a.id}」未声明支持:${miss.join("、")}`;
});
function isAny(s) {
return (s || "").trim() === LANG_ANY;
}
function langsLabel(s) {
if (isAny(s)) return "任意语言";
const arr = (s || "").split(",").map((x) => x.trim()).filter(Boolean);
if (!arr.length) return "—";
if (arr.length <= 3) return arr.map((c) => langName(c)).join("、");
return arr.slice(0, 3).map((c) => langName(c)).join("、") + ` 等 ${arr.length} 种`;
}
function agentName(id) {
return agents.value.find((a) => a.id === id)?.name || id || "未配置";
}
function agentType(id) {
return agents.value.find((a) => a.id === id)?.type || "";
}
const syncOpen = ref(false);
const syncDesc = ref("");
const syncTargets = ref([]);
const syncRegions = computed(() => REGIONS.filter((r) => r.value !== ed.region));
const testOpen = ref(false);
const testSrc = ref("zh-CN");
const testDst = ref("en-US");
const testResult = ref(null);
return (_ctx, _push, _parent, _attrs) => {
_push(`<div${ssrRenderAttrs(_attrs)} data-v-c0bab860><div class="flex items-center justify-between mb-5 flex-wrap gap-3" data-v-c0bab860><div data-v-c0bab860><h2 class="text-lg font-semibold text-slate-800" data-v-c0bab860>通话翻译规则</h2><p class="text-xs text-slate-400 mt-1" data-v-c0bab860> 一条规则覆盖「多源语言 × 多目标语言」,命中后走它指定的 Agent。多条规则按优先级从高到低匹配,第一条命中的生效。 </p></div><div class="flex items-center gap-3 flex-wrap" data-v-c0bab860><span class="text-sm text-slate-500" data-v-c0bab860>作用域</span><select class="select select-bordered select-sm" data-v-c0bab860><option value="" data-v-c0bab860${ssrIncludeBooleanAttr(Array.isArray(scopeApp.value) ? ssrLooseContain(scopeApp.value, "") : ssrLooseEqual(scopeApp.value, "")) ? " selected" : ""}>全局默认</option><!--[-->`);
ssrRenderList(scopeApps.value, (a) => {
_push(`<option${ssrRenderAttr("value", a)} data-v-c0bab860${ssrIncludeBooleanAttr(Array.isArray(scopeApp.value) ? ssrLooseContain(scopeApp.value, a) : ssrLooseEqual(scopeApp.value, a)) ? " selected" : ""}>${ssrInterpolate(a)}</option>`);
});
_push(`<!--]--></select><span class="text-sm text-slate-500" data-v-c0bab860>区域</span><select class="select select-bordered select-sm" data-v-c0bab860><!--[-->`);
ssrRenderList(REGIONS, (r) => {
_push(`<option${ssrRenderAttr("value", r.value)} data-v-c0bab860${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-c0bab860>🔍 选路试算</button><button class="btn btn-primary btn-sm text-white" data-v-c0bab860>+ 新增规则</button></div></div><div class="editor-panel" style="${ssrRenderStyle(editorOpen.value ? null : { display: "none" })}" data-v-c0bab860><div class="flex items-center justify-between mb-5" data-v-c0bab860><div class="flex items-center gap-3" data-v-c0bab860><h3 class="font-semibold text-slate-800 text-sm" data-v-c0bab860>${ssrInterpolate(editing.value ? "编辑规则" : "新增规则")}</h3><span class="region-chip" data-v-c0bab860>${ssrInterpolate(scopeApp.value || "全局默认")}</span></div><button class="btn btn-ghost btn-xs" data-v-c0bab860>✕ 关闭</button></div><section class="ed-section" data-v-c0bab860><div class="ed-section-title" data-v-c0bab860>匹配条件</div><div class="lang-row" data-v-c0bab860><div class="lang-field" data-v-c0bab860><div class="flex items-center justify-between" data-v-c0bab860><span class="ed-label" data-v-c0bab860>源语言</span><label class="any-toggle" data-v-c0bab860><input type="checkbox" class="checkbox checkbox-xs checkbox-primary"${ssrIncludeBooleanAttr(ed.srcAny) ? " checked" : ""} data-v-c0bab860> 任意语言 </label></div>`);
if (ed.srcAny) {
_push(`<div class="any-box" data-v-c0bab860>任意源语言</div>`);
} else {
_push(ssrRenderComponent(__nuxt_component_0, {
modelValue: ed.srcLangs,
"onUpdate:modelValue": ($event) => ed.srcLangs = $event,
multiple: ""
}, null, _parent));
}
_push(`</div><div class="dir-col" data-v-c0bab860><button type="button" class="${ssrRenderClass([{ on: ed.bidir }, "dir-btn"])}"${ssrRenderAttr("title", ed.bidir ? "点击改为单向" : "点击改为双向(反向也走这条规则)")} data-v-c0bab860>${ssrInterpolate(ed.bidir ? "⇄" : "→")}</button><span class="dir-tip" data-v-c0bab860>${ssrInterpolate(ed.bidir ? "双向互译" : "单向翻译")}</span></div><div class="lang-field" data-v-c0bab860><div class="flex items-center justify-between" data-v-c0bab860><span class="ed-label" data-v-c0bab860>目标语言</span><label class="any-toggle" data-v-c0bab860><input type="checkbox" class="checkbox checkbox-xs checkbox-primary"${ssrIncludeBooleanAttr(ed.dstAny) ? " checked" : ""} data-v-c0bab860> 任意语言 </label></div>`);
if (ed.dstAny) {
_push(`<div class="any-box" data-v-c0bab860>任意目标语言</div>`);
} else {
_push(ssrRenderComponent(__nuxt_component_0, {
modelValue: ed.dstLangs,
"onUpdate:modelValue": ($event) => ed.dstLangs = $event,
multiple: ""
}, null, _parent));
}
_push(`</div></div><div class="grid grid-cols-3 gap-3 mt-3" data-v-c0bab860><div data-v-c0bab860><span class="ed-label" data-v-c0bab860>生效区域</span><select class="select select-bordered select-sm w-full" data-v-c0bab860><option${ssrRenderAttr("value", 0)} data-v-c0bab860${ssrIncludeBooleanAttr(Array.isArray(ed.region) ? ssrLooseContain(ed.region, 0) : ssrLooseEqual(ed.region, 0)) ? " selected" : ""}>全部区域(兜底)</option><!--[-->`);
ssrRenderList(REGIONS, (r) => {
_push(`<option${ssrRenderAttr("value", r.value)} data-v-c0bab860${ssrIncludeBooleanAttr(Array.isArray(ed.region) ? ssrLooseContain(ed.region, r.value) : ssrLooseEqual(ed.region, r.value)) ? " selected" : ""}>${ssrInterpolate(r.label)}</option>`);
});
_push(`<!--]--></select></div><div data-v-c0bab860><span class="ed-label" data-v-c0bab860>优先级(越大越先匹配)</span><input${ssrRenderAttr("value", ed.priority)} type="number" class="input input-bordered input-sm w-full" data-v-c0bab860></div><div data-v-c0bab860><span class="ed-label" data-v-c0bab860>规则名(便于识别)</span><input${ssrRenderAttr("value", ed.name)} class="input input-bordered input-sm w-full" placeholder="如 中日韩端到端" data-v-c0bab860></div></div></section><section class="ed-section" data-v-c0bab860><div class="ed-section-title" data-v-c0bab860>命中后使用的 Agent</div><div class="grid grid-cols-2 gap-3" data-v-c0bab860><div data-v-c0bab860><span class="ed-label" data-v-c0bab860>翻译 Agent <i class="req" data-v-c0bab860>*</i></span><select class="select select-bordered select-sm w-full" data-v-c0bab860><option value="" disabled data-v-c0bab860${ssrIncludeBooleanAttr(Array.isArray(ed.agentId) ? ssrLooseContain(ed.agentId, "") : ssrLooseEqual(ed.agentId, "")) ? " selected" : ""}>${ssrInterpolate(transAgents.value.length ? "请选择 Agent" : "(无可用 Agent,请先去 Agent 配置里创建)")}</option><!--[-->`);
ssrRenderList(agentGroups.value, (g) => {
_push(`<optgroup${ssrRenderAttr("label", g.label)} data-v-c0bab860><!--[-->`);
ssrRenderList(g.items, (a) => {
_push(`<option${ssrRenderAttr("value", a.id)} data-v-c0bab860${ssrIncludeBooleanAttr(Array.isArray(ed.agentId) ? ssrLooseContain(ed.agentId, a.id) : ssrLooseEqual(ed.agentId, a.id)) ? " selected" : ""}>${ssrInterpolate(a.name || a.id)}</option>`);
});
_push(`<!--]--></optgroup>`);
});
_push(`<!--]--></select></div><div data-v-c0bab860><span class="ed-label" data-v-c0bab860>备注</span><input${ssrRenderAttr("value", ed.remark)} class="input input-bordered input-sm w-full" placeholder="可留空" data-v-c0bab860></div></div>`);
if (curAgent.value) {
_push(`<div class="agent-preview" data-v-c0bab860><span class="type-badge" style="${ssrRenderStyle(typeStyle(curAgent.value.type))}" data-v-c0bab860>${ssrInterpolate(typeLabel(curAgent.value.type))}</span><span class="text-xs text-slate-500" data-v-c0bab860>${ssrInterpolate(curAgent.value.description || "(无描述)")}</span>`);
if (curAgent.value.support_langs) {
_push(`<span class="text-[11px] text-slate-400" data-v-c0bab860>支持:${ssrInterpolate(curAgent.value.support_langs)}</span>`);
} else {
_push(`<!---->`);
}
if (!curAgent.value.enable) {
_push(`<span class="text-[11px] text-amber-600" data-v-c0bab860>⚠ 该 Agent 当前为停用状态</span>`);
} else {
_push(`<!---->`);
}
_push(`</div>`);
} else {
_push(`<!---->`);
}
if (langWarn.value) {
_push(`<p class="ed-warn" data-v-c0bab860>⚠ ${ssrInterpolate(langWarn.value)}</p>`);
} else {
_push(`<!---->`);
}
_push(`</section><div class="ed-footer" data-v-c0bab860><label class="flex items-center gap-2 text-sm cursor-pointer" data-v-c0bab860><input${ssrIncludeBooleanAttr(Array.isArray(ed.enable) ? ssrLooseContain(ed.enable, null) : ed.enable) ? " checked" : ""} type="checkbox" class="checkbox checkbox-sm checkbox-primary" data-v-c0bab860><span data-v-c0bab860>启用</span><span class="text-xs text-slate-400" data-v-c0bab860>停用的规则不参与选路</span></label><div class="flex gap-2" data-v-c0bab860><button class="btn btn-ghost btn-sm" data-v-c0bab860>取消</button><button class="btn btn-outline btn-sm" data-v-c0bab860>保存并同步…</button><button class="btn btn-primary btn-sm text-white" data-v-c0bab860>保存</button></div></div></div>`);
if (!rules.value.length) {
_push(`<div class="text-center py-20 text-slate-300" data-v-c0bab860><div class="text-5xl mb-3" data-v-c0bab860>🌐</div><p class="text-sm" data-v-c0bab860>当前区域还没有翻译规则</p><p class="text-xs mt-1" data-v-c0bab860>建议先加一条「任意 → 任意」的兜底规则,再往上叠精确规则</p></div>`);
} else {
_push(`<div class="rule-list" data-v-c0bab860><div class="rule-head" data-v-c0bab860><span data-v-c0bab860>优先级</span><span data-v-c0bab860>匹配条件</span><span data-v-c0bab860>Agent</span><span data-v-c0bab860>区域</span><span class="text-right" data-v-c0bab860>操作</span></div><!--[-->`);
ssrRenderList(rules.value, (r, i) => {
_push(`<div class="${ssrRenderClass([{ off: !r.enable }, "rule-row"])}" data-v-c0bab860><div class="prio-cell" data-v-c0bab860><span class="prio" data-v-c0bab860>${ssrInterpolate(r.priority)}</span><span class="order" data-v-c0bab860>#${ssrInterpolate(i + 1)}</span></div><div class="cond-cell" data-v-c0bab860><div class="flex items-center gap-2 flex-wrap" data-v-c0bab860><span class="${ssrRenderClass([{ any: isAny(r.src_langs) }, "lang-badge"])}" data-v-c0bab860>${ssrInterpolate(langsLabel(r.src_langs))}</span><span class="text-slate-400 font-bold" data-v-c0bab860>${ssrInterpolate(r.bidir ? "⇄" : "→")}</span><span class="${ssrRenderClass([{ any: isAny(r.dst_langs) }, "lang-badge"])}" data-v-c0bab860>${ssrInterpolate(langsLabel(r.dst_langs))}</span></div>`);
if (r.name || r.remark) {
_push(`<div class="text-[11px] text-slate-400 mt-1" data-v-c0bab860>${ssrInterpolate(r.name)}`);
if (r.remark) {
_push(`<span data-v-c0bab860> · ${ssrInterpolate(r.remark)}</span>`);
} else {
_push(`<!---->`);
}
_push(`</div>`);
} else {
_push(`<!---->`);
}
_push(`</div><div class="agent-cell" data-v-c0bab860><span class="type-badge" style="${ssrRenderStyle(typeStyle(agentType(r.agent_id)))}" data-v-c0bab860>${ssrInterpolate(typeLabel(agentType(r.agent_id)))}</span><span class="text-xs text-slate-700" data-v-c0bab860>${ssrInterpolate(agentName(r.agent_id))}</span></div><div class="text-xs text-slate-500" data-v-c0bab860>${ssrInterpolate(r.region === 0 ? "全部区域" : regionLabel(r.region))}</div><div class="text-right" data-v-c0bab860>`);
if (r.enable) {
_push(`<span class="badge badge-success badge-xs mr-2" data-v-c0bab860>启用</span>`);
} else {
_push(`<span class="badge badge-ghost badge-xs mr-2" data-v-c0bab860>停用</span>`);
}
_push(`<button class="btn btn-ghost btn-xs text-error" data-v-c0bab860>删除</button></div></div>`);
});
_push(`<!--]--></div>`);
}
_push(`<div class="ct-overlay" style="${ssrRenderStyle(testOpen.value ? null : { display: "none" })}" data-v-c0bab860><div class="ct-box" data-v-c0bab860><h3 class="text-base font-semibold text-slate-800 mb-1" data-v-c0bab860>选路试算</h3><p class="text-xs text-slate-400 mb-4" data-v-c0bab860>用后端运行时同一套匹配逻辑,算一遍这个方向实际会走哪条规则。</p><div class="grid grid-cols-2 gap-3" data-v-c0bab860><div data-v-c0bab860><span class="ed-label" data-v-c0bab860>源语言</span>`);
_push(ssrRenderComponent(__nuxt_component_0, {
modelValue: testSrc.value,
"onUpdate:modelValue": ($event) => testSrc.value = $event
}, null, _parent));
_push(`</div><div data-v-c0bab860><span class="ed-label" data-v-c0bab860>目标语言</span>`);
_push(ssrRenderComponent(__nuxt_component_0, {
modelValue: testDst.value,
"onUpdate:modelValue": ($event) => testDst.value = $event
}, null, _parent));
_push(`</div></div>`);
if (testResult.value) {
_push(`<div class="${ssrRenderClass([{ miss: !testResult.value.matched }, "test-result"])}" data-v-c0bab860>`);
if (testResult.value.matched) {
_push(`<!--[--><div class="text-sm font-semibold text-slate-800 mb-1" data-v-c0bab860> 命中规则:${ssrInterpolate(testResult.value.rule?.name || "#" + testResult.value.rule?.id)}(优先级 ${ssrInterpolate(testResult.value.rule?.priority)}) </div><div class="text-xs text-slate-600" data-v-c0bab860>${ssrInterpolate(langsLabel(testResult.value.rule?.src_langs))} ${ssrInterpolate(testResult.value.rule?.bidir ? "⇄" : "→")} ${ssrInterpolate(langsLabel(testResult.value.rule?.dst_langs))} · Agent:${ssrInterpolate(testResult.value.agent?.name || testResult.value.rule?.agent_id)} `);
if (testResult.value.agent) {
_push(`<span data-v-c0bab860>(${ssrInterpolate(typeLabel(testResult.value.agent.type))})</span>`);
} else {
_push(`<!---->`);
}
_push(`</div><!--]-->`);
} else {
_push(`<div class="text-sm text-amber-700" data-v-c0bab860>没有任何规则命中 —— 这个方向的通话翻译会不可用,建议补一条兜底规则。</div>`);
}
_push(`</div>`);
} else {
_push(`<!---->`);
}
_push(`<div class="flex gap-3 justify-end mt-5" data-v-c0bab860><button class="btn btn-ghost" data-v-c0bab860>关闭</button><button class="btn btn-primary text-white" data-v-c0bab860>试算</button></div></div></div><div class="ct-overlay" style="${ssrRenderStyle(syncOpen.value ? null : { display: "none" })}" data-v-c0bab860><div class="ct-box" data-v-c0bab860><h3 class="text-base font-semibold text-slate-800 mb-1" data-v-c0bab860>同步到其他区域</h3><p class="text-xs text-slate-400 mb-3" data-v-c0bab860>${ssrInterpolate(syncDesc.value)}</p><div class="text-xs px-3 py-2 rounded-lg mb-3" style="${ssrRenderStyle({ "background": "#f0fdf4", "border": "1px solid #bbf7d0", "color": "#15803d" })}" data-v-c0bab860> ✦ 同步内容:语言条件、Agent、优先级、启停状态一并复制到目标区域。 </div><div class="flex items-center justify-between mb-1" data-v-c0bab860><span class="text-xs font-semibold text-slate-500" data-v-c0bab860>选择目标区域</span><div class="flex gap-2" data-v-c0bab860><button class="btn btn-ghost btn-xs" data-v-c0bab860>全选</button><button class="btn btn-ghost btn-xs" data-v-c0bab860>清空</button></div></div><div class="sync-region-grid" data-v-c0bab860><!--[-->`);
ssrRenderList(syncRegions.value, (r) => {
_push(`<label class="${ssrRenderClass([{ checked: syncTargets.value.includes(r.value) }, "sync-region-item"])}" data-v-c0bab860><input type="checkbox" class="checkbox checkbox-xs checkbox-primary"${ssrIncludeBooleanAttr(syncTargets.value.includes(r.value)) ? " checked" : ""} data-v-c0bab860><span data-v-c0bab860>${ssrInterpolate(r.label)}</span></label>`);
});
_push(`<!--]--></div><p class="text-[11px] text-slate-400 mb-5" data-v-c0bab860>⚠ 目标区域已有「同 Agent + 同语言条件」的规则则覆盖,否则新建。</p><div class="flex gap-3 justify-end" data-v-c0bab860><button class="btn btn-ghost" data-v-c0bab860>取消</button><button class="btn btn-primary text-white"${ssrIncludeBooleanAttr(!syncTargets.value.length) ? " disabled" : ""} data-v-c0bab860> 保存并同步${ssrInterpolate(syncTargets.value.length ? "(" + syncTargets.value.length + " 个区域)" : "")}</button></div></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/calltranslate.vue");
return _sfc_setup ? _sfc_setup(props, ctx) : void 0;
};
const calltranslate = /* @__PURE__ */ _export_sfc(_sfc_main, [["__scopeId", "data-v-c0bab860"]]);
export { calltranslate as default };
//# sourceMappingURL=calltranslate-D-FgQw8P.mjs.map