Compare commits
8
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7c286f639e | ||
|
|
56131a4662 | ||
|
|
7209cd3ba2 | ||
|
|
a5262941ea | ||
|
|
490abd60e9 | ||
|
|
aa7c8856fb | ||
|
|
3f9b5e59a8 | ||
|
|
fbe2bcfee6 |
+2
-2
@@ -1,5 +1,5 @@
|
||||
# ==================== 第一阶段:构建前端 ====================
|
||||
FROM docker.m.daocloud.io/node:18-alpine AS builder
|
||||
FROM node:18-alpine AS builder
|
||||
WORKDIR /app
|
||||
# 配置Alpine国内镜像源
|
||||
RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g' /etc/apk/repositories
|
||||
@@ -13,7 +13,7 @@ COPY . .
|
||||
RUN npm run build
|
||||
|
||||
# ==================== 第二阶段:部署到Nginx ====================
|
||||
FROM docker.m.daocloud.io/nginx:alpine
|
||||
FROM nginx:alpine
|
||||
|
||||
# 复制构建产物
|
||||
COPY --from=builder /app/dist/ /usr/share/nginx/html/
|
||||
|
||||
@@ -24,6 +24,8 @@ export interface VOSessionInfoResult {
|
||||
totalTokens: number;
|
||||
totalFee: number;
|
||||
errorMsg: string;
|
||||
// 失败详细原因(工作流节点失败详情,如「执行工作流失败: ...节点:生成视频, 失败原因:...」),比 errorMsg 更具体
|
||||
error?: string;
|
||||
createdAt: string;
|
||||
}
|
||||
|
||||
|
||||
@@ -17,6 +17,15 @@ export function getPricingConfig(params: { subjectType: string; subjectId: strin
|
||||
});
|
||||
}
|
||||
|
||||
/** 计价对象可选计费方式(workflow/business 使用;model 计价方式由配置规则决定,不调用) */
|
||||
export function getChargeModes(params: { subjectType: string; subjectId: string }) {
|
||||
return request({
|
||||
url: '/shop-user-trade/pricing/controller/subjects/charge-modes',
|
||||
method: 'get',
|
||||
params,
|
||||
});
|
||||
}
|
||||
|
||||
/** 新增/更新计价配置(id > 0 更新,0 新增;rules 传费率对象;id 兼容雪花字符串避免精度丢失) */
|
||||
export function savePricingConfig(data: {
|
||||
id: string | number;
|
||||
|
||||
@@ -203,11 +203,12 @@ export default defineComponent({
|
||||
}
|
||||
},
|
||||
})
|
||||
.then(async () => {
|
||||
.then(() => {
|
||||
// 手动退出登录也只清理登录态缓存,保留主题、语言等本地配置。
|
||||
Session.clearAuth();
|
||||
// 显式回到登录页,避免保留之前受保护页面的重定向参数
|
||||
await router.replace('/login');
|
||||
// 整页跳转登录页(刷新重建路由实例):SPA 内 router.replace 不会重置已注册的动态路由,
|
||||
// 重新登录时 setAddRoute 的 hasRoute 判断会跳过新菜单注册,出现"菜单显示但点击 404"(如价格管理页)
|
||||
window.location.href = import.meta.env.BASE_URL + '#/login';
|
||||
})
|
||||
.catch(() => {});
|
||||
} else if (path === 'wareHouse') {
|
||||
|
||||
@@ -33,6 +33,12 @@
|
||||
|
||||
<div v-show="!collapsed" class="workflow-form-scroll">
|
||||
<el-form label-position="top" class="workflow-form">
|
||||
<!-- 计费方式:必选、不设置默认值;可选项来自计价对象 charge-modes 接口(workflow) -->
|
||||
<el-form-item label="计费方式" required>
|
||||
<el-select v-model="chargeMode" :disabled="isDisabled" placeholder="请选择计费方式" class="w100">
|
||||
<el-option v-for="opt in chargeModes" :key="opt.mode" :label="opt.name" :value="opt.mode" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<template v-if="detail?.nodeInputParams">
|
||||
<template v-for="node in detail.nodeInputParams" :key="node.id || node.nodeCode">
|
||||
<template v-if="hasFormConfig(node)">
|
||||
@@ -165,19 +171,26 @@
|
||||
|
||||
<!-- 底部操作区:editing 提示 / running 执行中 / done·failed 状态标签(执行统一由 InputBar 发送按钮触发) -->
|
||||
<div class="workflow-form-footer">
|
||||
<div v-if="isFailed" class="form-error-text">{{ formError }}</div>
|
||||
<!-- 执行中:节点文本进度推进(后端 node_start/node_complete 事件),无进度时兜底「执行中...」;
|
||||
停止统一由 InputBar 发送按钮(变停止)触发,卡片内不再提供取消入口 -->
|
||||
<div v-if="submitting" class="executing-progress">
|
||||
<span class="exec-spinner" />
|
||||
<span class="exec-text">{{ progressText }}</span>
|
||||
</div>
|
||||
<!-- 已执行卡片(done/failed,含可编辑的最后一张):显示状态标签,参数是否可编辑由 editable 决定 -->
|
||||
<el-tag v-else-if="formStatus === 'done' || formStatus === 'failed'" :type="isFailed ? 'danger' : 'success'" size="small" effect="light">
|
||||
{{ isFailed ? '执行失败' : '执行完成' }}
|
||||
</el-tag>
|
||||
<!-- 已执行卡片(done/failed,含可编辑的最后一张):状态标签 + 失败原因按钮(默认收起,点击手动展开) -->
|
||||
<div v-else-if="formStatus === 'done' || formStatus === 'failed'" class="form-status-row">
|
||||
<el-tag :type="isFailed ? 'danger' : 'success'" size="small" effect="light">
|
||||
{{ isFailed ? '执行失败' : '执行完成' }}
|
||||
</el-tag>
|
||||
<el-button v-if="isFailed && formError" text size="small" class="form-error-toggle" @click="showError = !showError">
|
||||
{{ showError ? '收起失败原因' : '查看失败原因' }}
|
||||
<el-icon class="error-arrow" :class="{ 'is-expanded': showError }"><ArrowDown /></el-icon>
|
||||
</el-button>
|
||||
</div>
|
||||
<!-- 编辑态:无执行按钮,提示通过下方发送按钮执行 -->
|
||||
<span v-else class="form-edit-tip">填写参数后,点击下方发送按钮执行工作流</span>
|
||||
<!-- 失败原因详情:默认收起,点「查看失败原因」展开(error 字段可能含多行详细原因) -->
|
||||
<div v-if="isFailed && showError && formError" class="form-error-text">{{ formError }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -188,6 +201,7 @@ import { ElMessage } from 'element-plus';
|
||||
import { ArrowDown, CircleCheckFilled, CircleCloseFilled } from '@element-plus/icons-vue';
|
||||
import PatchTemplateEditor from '/@/components/patchTemplate/PatchTemplateEditor.vue';
|
||||
import { uploadFile } from '/@/api/common/upload';
|
||||
import { getChargeModes } from '/@/api/trade/pricing';
|
||||
import { collectHomeFormFields } from '../utils/flowDsl';
|
||||
import type { WorkflowNodeStep } from '../utils/wsMessage';
|
||||
|
||||
@@ -209,7 +223,7 @@ interface Props {
|
||||
}
|
||||
|
||||
interface Emits {
|
||||
(e: 'submit', payload: { formValues: Record<string, any>; formFileNames: Record<string, string | string[]>; templates: any[] }): void;
|
||||
(e: 'submit', payload: { formValues: Record<string, any>; formFileNames: Record<string, string | string[]>; templates: any[]; chargeMode: string }): void;
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
@@ -229,11 +243,47 @@ const formFileNames = reactive<Record<string, string | string[]>>({});
|
||||
const uploadingFields = reactive<Record<string, boolean>>({});
|
||||
const templates = ref<any[]>([]);
|
||||
|
||||
// ===== 计费方式:必选、不设置默认值(新建为空强制选择;回显重跑恢复上次选择) =====
|
||||
interface ChargeModeOption {
|
||||
mode: string;
|
||||
name: string;
|
||||
}
|
||||
|
||||
const chargeMode = ref('');
|
||||
const chargeModes = ref<ChargeModeOption[]>([]);
|
||||
/** 计费方式可选项缓存:workflow 计价对象固定(subjectType/subjectId 均 workflow),模块级共享避免每张卡片重复请求 */
|
||||
let workflowChargeModesCache: ChargeModeOption[] | null = null;
|
||||
|
||||
const loadChargeModes = async () => {
|
||||
if (workflowChargeModesCache) {
|
||||
chargeModes.value = workflowChargeModesCache;
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const res: any = await getChargeModes({ subjectType: 'workflow', subjectId: 'workflow' });
|
||||
const list: ChargeModeOption[] = res?.data?.chargeModes || [];
|
||||
chargeModes.value = list;
|
||||
workflowChargeModesCache = list;
|
||||
} catch {
|
||||
// 计费方式拉取失败:全局拦截器已提示,下拉留空(必填校验会拦截提交)
|
||||
chargeModes.value = [];
|
||||
}
|
||||
};
|
||||
|
||||
// 状态派生:失败 = 携带 formError;禁用编辑 = 定格(done/failed)卡片默认只读,
|
||||
// 但最后一张已执行卡片(editable)允许改参;执行中(submitting)一律禁用
|
||||
const isFailed = computed(() => !!props.formError);
|
||||
const isDisabled = computed(() => (props.readonly && !props.editable) || props.submitting);
|
||||
|
||||
// 失败原因默认收起:新失败时重置为不展示,用户点「查看失败原因」才展开
|
||||
const showError = ref(false);
|
||||
watch(
|
||||
() => props.formError,
|
||||
() => {
|
||||
showError.value = false;
|
||||
}
|
||||
);
|
||||
|
||||
// 表单收起/展开:editing(待提交/回显)默认展开;提交(running)自动收起;
|
||||
// done/failed 保持提交时状态(默认收起,用户可手动展开查看已填值)
|
||||
const collapsed = ref(false);
|
||||
@@ -435,6 +485,11 @@ const hasFormFields = computed(() => {
|
||||
});
|
||||
|
||||
const validateFormFields = (): boolean => {
|
||||
// 计费方式必选、无默认值:未选择时阻止提交(含无表单参数的工作流)
|
||||
if (!chargeMode.value) {
|
||||
ElMessage.warning('请选择计费方式');
|
||||
return false;
|
||||
}
|
||||
if (!props.detail?.nodeInputParams) return true;
|
||||
for (const node of props.detail.nodeInputParams as any[]) {
|
||||
if (String(node?.nodeCode || '').toLowerCase() !== '__start__') continue;
|
||||
@@ -462,6 +517,10 @@ watch(
|
||||
Object.keys(fieldFiles).forEach((key) => delete fieldFiles[key]);
|
||||
Object.keys(formFileNames).forEach((key) => delete formFileNames[key]);
|
||||
Object.keys(uploadingFields).forEach((key) => delete uploadingFields[key]);
|
||||
// 计费方式:新建(handleWorkflowSelect 已清空后端自带 chargeMode)为空强制选择;
|
||||
// 回显重跑卡片从 flowContent.chargeMode 恢复上次选择
|
||||
chargeMode.value = detail?.flowContent?.chargeMode || '';
|
||||
loadChargeModes();
|
||||
// 尝试从执行详情恢复贴片模板
|
||||
// 提交路径:templates 写入开启贴片布局的节点内部(patchLayout === true 的 node.templates),顶层已删除;
|
||||
// 回显时 detail.nodeInputParams 即提交时的 nodes,从该处优先恢复;兼容旧数据(extension / detail / flowContent 顶层)
|
||||
@@ -543,6 +602,7 @@ const handleSubmit = () => {
|
||||
formValues: JSON.parse(JSON.stringify(formValues)),
|
||||
formFileNames: JSON.parse(JSON.stringify(formFileNames)),
|
||||
templates: JSON.parse(JSON.stringify(templates.value || [])),
|
||||
chargeMode: chargeMode.value,
|
||||
});
|
||||
};
|
||||
|
||||
@@ -754,19 +814,41 @@ watch(
|
||||
/* 底部操作区 */
|
||||
.workflow-form-footer {
|
||||
display: flex;
|
||||
flex-wrap: wrap; /* 失败原因详情换行到状态行下方 */
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
padding: 12px 20px;
|
||||
border-top: 1px solid #f1f5f9;
|
||||
background: #fafbfc;
|
||||
flex-shrink: 0;
|
||||
.form-error-text {
|
||||
.form-status-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
.form-error-toggle {
|
||||
padding: 0;
|
||||
height: auto;
|
||||
font-size: 12px;
|
||||
color: #ef4444;
|
||||
.error-arrow {
|
||||
transition: transform 0.2s;
|
||||
&.is-expanded { transform: rotate(180deg); }
|
||||
}
|
||||
}
|
||||
.form-error-text {
|
||||
flex-basis: 100%; /* 占整行,显示在状态行下方 */
|
||||
font-size: 12px;
|
||||
color: #ef4444;
|
||||
line-height: 1.5;
|
||||
white-space: pre-wrap; /* 失败详情可能含换行(error 字段多行原因),允许换行展示 */
|
||||
word-break: break-word;
|
||||
background: #fef2f2;
|
||||
border: 1px solid #fee2e2;
|
||||
border-radius: 6px;
|
||||
padding: 8px 12px;
|
||||
}
|
||||
.form-edit-tip {
|
||||
flex: 1;
|
||||
|
||||
@@ -105,7 +105,7 @@ import { applyHomeFormValues, buildOutputsFromUrls } from './utils/flowDsl';
|
||||
import type { WorkflowOutput } from './utils/flowDsl';
|
||||
import { getChatModel, listModelManage } from '/@/api/settings/modelConfigV2';
|
||||
import { connectSessionSocket, sendAgentStart, sendWorkflowStart, sendCancel } from './utils/wsExecute';
|
||||
import { parseWsMessage, getDelta, getAnswer, getRecordId, getErrorText, getNodeEvent, getToolCallName, getToolResultText, type WorkflowNodeProgress } from './utils/wsMessage';
|
||||
import { parseWsMessage, getDelta, getAnswer, getRecordId, getErrorText, getErrorDetail, getNodeEvent, getToolCallName, getToolResultText, type WorkflowNodeProgress } from './utils/wsMessage';
|
||||
import { getApiErrorMessage } from '/@/utils/request';
|
||||
import {
|
||||
getWorkflowDetail,
|
||||
@@ -690,6 +690,8 @@ const handleWorkflowSelect = async (workflowId: string | null, isTemplate?: bool
|
||||
removeDraftFormCard();
|
||||
const res = await getWorkflowDetail(workflowId);
|
||||
selectedWorkflowDetail.value = res.data || null;
|
||||
// 新建表单不沿用后端工作流详情自带的 chargeMode(如缺省 per_item):计费方式必选且无默认值,强制用户选择
|
||||
if (res.data?.flowContent) delete res.data.flowContent.chargeMode;
|
||||
// 对话卡片化:选中工作流即推送一张可填表单卡片进入消息流
|
||||
if (res.data) pushFormCard(res.data);
|
||||
} catch {
|
||||
@@ -1042,7 +1044,7 @@ const runWorkflow = async (
|
||||
sid: string,
|
||||
sessionId: string,
|
||||
detail: any,
|
||||
opts: { formValues: Record<string, any>; formFileNames: Record<string, string | string[]>; templates?: any[] },
|
||||
opts: { formValues: Record<string, any>; formFileNames: Record<string, string | string[]>; templates?: any[]; chargeMode?: string },
|
||||
formMsg: ChatMessage
|
||||
) => {
|
||||
const curSession = historyList.value.find((h) => h.id === sid);
|
||||
@@ -1119,6 +1121,8 @@ const runWorkflow = async (
|
||||
// 构建 flowContent:templates 不再放最外层(删除 detail.flowContent 可能遗留的顶层 templates,统一走节点)
|
||||
const updatedFlowContent: any = { ...detail.flowContent, nodes: nodeInputParams };
|
||||
delete updatedFlowContent.templates;
|
||||
// 计费方式写入 flowContent(与 version 平级),后端按此计费;表单必填已拦截未选,此处仅兜底
|
||||
if (opts.chargeMode) updatedFlowContent.chargeMode = opts.chargeMode;
|
||||
|
||||
// 3. 工作流模型由后端按 flow 配置决定,无需前端取模型 id(普通对话取模型见 runChat)
|
||||
|
||||
@@ -1136,13 +1140,14 @@ const runWorkflow = async (
|
||||
formMsg.recordType = 'workflow';
|
||||
}
|
||||
} else if (msg.type === 'error') {
|
||||
// 失败:执行中的节点标记为失败,执行过程区展示中断位置
|
||||
// 失败:执行中的节点标记为失败,执行过程区展示中断位置;
|
||||
// 失败原因绑定 error 字段(节点失败详情),供卡片「查看失败原因」手动展开
|
||||
if (formMsg.formProgress) {
|
||||
formMsg.formProgress.nodes.forEach((n) => {
|
||||
if (n.status === 'running') n.status = 'failed';
|
||||
});
|
||||
}
|
||||
finishExec(false, getErrorText(msg));
|
||||
finishExec(false, getErrorDetail(msg));
|
||||
} else if (msg.type === 'flow_complete') {
|
||||
// flow_complete 为后端新增的工作流完成信号,事件自带产出文件 URL 列表 → 直接渲染产出卡片;
|
||||
// 以 round_start 首帧填充的 roundRecordId 作 backendId,保证实时产出可删除(与回显路径一致)
|
||||
@@ -1247,7 +1252,7 @@ const snapshotTemplatesToMsg = (msg: ChatMessage, templates: any[]) => {
|
||||
|
||||
const startWorkflowFromCard = async (
|
||||
msg: ChatMessage,
|
||||
payload: { formValues: Record<string, any>; formFileNames: Record<string, string | string[]>; templates?: any[] }
|
||||
payload: { formValues: Record<string, any>; formFileNames: Record<string, string | string[]>; templates?: any[]; chargeMode?: string }
|
||||
) => {
|
||||
// 定位卡片所在会话
|
||||
let sid = '';
|
||||
@@ -1269,6 +1274,12 @@ const startWorkflowFromCard = async (
|
||||
if (Array.isArray(payload.templates)) {
|
||||
snapshotTemplatesToMsg(msg, payload.templates);
|
||||
}
|
||||
// 计费方式写回消息对象 flowContent(快照):会话认领(virtual→UUID 触发 MainContent 重建)/历史回显重挂载时,
|
||||
// WorkflowFormCard 从 flowContent.chargeMode 恢复本次选择,避免重挂载后计费方式被清空
|
||||
if (payload.chargeMode) {
|
||||
if (!msg.form.flowContent || typeof msg.form.flowContent !== 'object') msg.form.flowContent = {};
|
||||
msg.form.flowContent.chargeMode = payload.chargeMode;
|
||||
}
|
||||
|
||||
// 标记本轮发送中:工作流执行期间 InputBar 显示停止按钮(isGenerating 依赖 sendingSessions)
|
||||
sendingSessions[sid] = true;
|
||||
@@ -1647,8 +1658,8 @@ const loadWorkflowSessionMessages = async (sid: string, results: VOSessionInfoRe
|
||||
if (q) msgs.push({ id: 'rq-' + r.id, content: String(q), time: r.createdAt || '', isUser: true, recordId: String(r.id), recordType: r.type });
|
||||
if (r.resultContent) msgs.push({ id: 'ra-' + r.id, content: String(r.resultContent), time: r.createdAt || '', isUser: false, recordId: String(r.id), recordType: r.type });
|
||||
} else if (r.type === 'workflow') {
|
||||
// 失败判断:以 errorMsg 为准(部分失败记录 status 仍是 1,仅凭 status 会误判为成功)
|
||||
const failed = !!r.errorMsg;
|
||||
// 失败判断:error / errorMsg 任一有值即失败(部分失败记录 status 仍是 1,仅凭 status 会误判为成功)
|
||||
const failed = !!(r.errorMsg || r.error);
|
||||
// 每次执行都渲染带值表单卡片(requestParams.nodes 即该次提交的表单值快照);
|
||||
// form 补 id=flowId,供失败卡片「重新编辑并执行」时作为启动 flowId
|
||||
if (Array.isArray(r.requestParams?.nodes)) {
|
||||
@@ -1665,7 +1676,8 @@ const loadWorkflowSessionMessages = async (sid: string, results: VOSessionInfoRe
|
||||
flowContent: r.requestParams,
|
||||
},
|
||||
formStatus: failed ? 'failed' : 'done',
|
||||
formError: failed ? r.errorMsg || '执行失败' : '',
|
||||
// 失败原因绑定 error 字段(节点失败详情);errorMsg(如「用户已终止执行」)仅作兜底
|
||||
formError: failed ? r.error || r.errorMsg || '执行失败' : '',
|
||||
});
|
||||
}
|
||||
// 结果消息:成功记录直接带产出(session/get 记录自带的 resultFileUrl),不再调 execution/get
|
||||
|
||||
@@ -169,3 +169,16 @@ export function getErrorText(msg: WsStreamMessage): string {
|
||||
}
|
||||
return '执行失败,请重试';
|
||||
}
|
||||
|
||||
/** error 详细原因(error 字段优先展示失败详情;message 仅在 error 缺失时兜底)——供工作流失败卡片「查看失败原因」使用 */
|
||||
export function getErrorDetail(msg: WsStreamMessage): string {
|
||||
if (typeof msg?.error === 'string' && msg.error) return msg.error;
|
||||
if (typeof msg?.message === 'string' && msg.message) return msg.message;
|
||||
const d = msg?.data;
|
||||
if (typeof d === 'string') return d;
|
||||
if (d && typeof d === 'object') {
|
||||
const nested = d.message ?? d.error ?? d.msg;
|
||||
if (typeof nested === 'string' && nested) return nested;
|
||||
}
|
||||
return '执行失败,请重试';
|
||||
}
|
||||
|
||||
@@ -1,5 +1,32 @@
|
||||
<template>
|
||||
<div class="editor-body">
|
||||
<div class="editor-col">
|
||||
<!-- 通用配置 -->
|
||||
<div class="card">
|
||||
<div class="card-head">
|
||||
<span class="card-title">通用配置</span>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="cfg-row">
|
||||
<div class="cfg-item">
|
||||
<div class="cfg-label">门禁金额(元)</div>
|
||||
<el-input-number v-model="minBalance" :min="0" :precision="5" :step="0.01" :controls="false" class="cfg-min-balance" />
|
||||
</div>
|
||||
<div class="cfg-item">
|
||||
<div class="cfg-label">币种</div>
|
||||
<el-select v-model="currency" class="cfg-currency">
|
||||
<el-option label="人民币" value="CNY" />
|
||||
<el-option label="美元" value="USD" />
|
||||
</el-select>
|
||||
</div>
|
||||
<div class="cfg-item">
|
||||
<div class="cfg-label">启用</div>
|
||||
<el-switch v-model="enabled" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="editor-col">
|
||||
<!-- 周期订阅 -->
|
||||
<div class="card">
|
||||
@@ -9,58 +36,27 @@
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<el-form label-position="top" size="default">
|
||||
<el-form-item label="周期 period">
|
||||
<el-form-item label="周期">
|
||||
<el-select v-model="period" class="ctrl-select">
|
||||
<el-option label="year(按年)" value="year" />
|
||||
<el-option label="按年" value="year" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="每周期价格(元)">
|
||||
<el-input-number
|
||||
v-model="price"
|
||||
:min="0"
|
||||
:precision="2"
|
||||
:precision="5"
|
||||
:step="0.01"
|
||||
:controls="false"
|
||||
class="ctrl-input"
|
||||
/>
|
||||
</el-form-item>
|
||||
<div class="form-hint">本期支持按年订阅(period=year);订阅购买 / 续费流程接入另期。</div>
|
||||
<div class="form-hint">本期支持按年订阅;订阅购买 / 续费流程接入另期。</div>
|
||||
</el-form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="editor-col">
|
||||
<!-- 通用配置 -->
|
||||
<div class="card">
|
||||
<div class="card-head">
|
||||
<span class="card-title">通用配置</span>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<el-form label-position="top" size="default">
|
||||
<el-form-item label="门禁 min_balance(元)">
|
||||
<el-input-number
|
||||
v-model="minBalance"
|
||||
:min="0"
|
||||
:precision="2"
|
||||
:step="0.01"
|
||||
:controls="false"
|
||||
class="ctrl-input"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="币种">
|
||||
<el-select v-model="currency" class="ctrl-select">
|
||||
<el-option label="人民币 (CNY)" value="CNY" />
|
||||
<el-option label="美元 (USD)" value="USD" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="启用">
|
||||
<el-switch v-model="enabled" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -134,14 +130,12 @@ defineExpose({ save });
|
||||
|
||||
<style scoped lang="scss">
|
||||
.editor-body {
|
||||
display: flex;
|
||||
gap: 20px;
|
||||
flex-wrap: wrap;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.editor-col {
|
||||
flex: 1;
|
||||
min-width: 380px;
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.card {
|
||||
@@ -182,6 +176,33 @@ defineExpose({ save });
|
||||
line-height: 1.7;
|
||||
}
|
||||
|
||||
|
||||
.cfg-row {
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
gap: 40px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.cfg-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.cfg-label {
|
||||
font-size: 13px;
|
||||
color: #606266;
|
||||
}
|
||||
|
||||
.cfg-min-balance {
|
||||
width: 200px;
|
||||
}
|
||||
|
||||
.cfg-currency {
|
||||
width: 200px;
|
||||
}
|
||||
|
||||
.ctrl-input {
|
||||
width: 220px;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,32 @@
|
||||
<template>
|
||||
<div class="editor-body">
|
||||
<div class="editor-col">
|
||||
<!-- 通用配置 -->
|
||||
<div class="card">
|
||||
<div class="card-head">
|
||||
<span class="card-title">通用配置</span>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="cfg-row">
|
||||
<div class="cfg-item">
|
||||
<div class="cfg-label">门禁金额(元)</div>
|
||||
<el-input-number v-model="minBalance" :min="0" :precision="5" :step="0.01" :controls="false" class="cfg-min-balance" />
|
||||
</div>
|
||||
<div class="cfg-item">
|
||||
<div class="cfg-label">币种</div>
|
||||
<el-select v-model="currency" class="cfg-currency">
|
||||
<el-option label="人民币" value="CNY" />
|
||||
<el-option label="美元" value="USD" />
|
||||
</el-select>
|
||||
</div>
|
||||
<div class="cfg-item">
|
||||
<div class="cfg-label">启用</div>
|
||||
<el-switch v-model="enabled" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="editor-col">
|
||||
<!-- 基础计费 -->
|
||||
<div class="card">
|
||||
@@ -8,12 +35,12 @@
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<el-form label-position="top" size="default">
|
||||
<el-form-item label="计费单位 unit">
|
||||
<el-form-item label="计费单位">
|
||||
<el-select v-model="unit" class="ctrl-select" @change="onUnitChange">
|
||||
<el-option v-for="o in UNIT_OPTIONS" :key="o.value" :label="o.label" :value="o.value" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="阶梯分档 tiered">
|
||||
<el-form-item label="阶梯分档">
|
||||
<el-switch v-model="tiered" :disabled="!isTokenUnit" />
|
||||
</el-form-item>
|
||||
<div class="form-hint">阶梯=按输入 token 分档,<b>命中哪档整单按哪档价(不累加)</b>。档位区间相邻不重叠(下档 max+1 = 上档 min)。仅 token 单位(per_1K / per_1M)可用。</div>
|
||||
@@ -40,66 +67,90 @@
|
||||
<el-button size="small" type="danger" text @click="removeRule(i)">删除</el-button>
|
||||
</div>
|
||||
|
||||
<div class="rc-sec match">命中条件 match</div>
|
||||
<div class="rc-sec match">命中条件</div>
|
||||
<el-form label-position="top" size="small">
|
||||
<div class="rc-grid">
|
||||
<el-form-item label="媒体类型">
|
||||
<el-select v-model="r.mediaType" class="rc-control">
|
||||
<el-option v-for="o in MEDIA_TYPE_OPTIONS" :key="o.value" :label="o.label" :value="o.value" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<template v-if="subject.modelType === 'reason'">
|
||||
<el-form-item label="thinking">
|
||||
<el-form-item label="思考模式">
|
||||
<el-select v-model="r.thinking" class="rc-control">
|
||||
<el-option v-for="o in BOOL_TRIPLE_OPTIONS" :key="o.value" :label="o.label" :value="o.value" />
|
||||
<el-option v-for="o in THINKING_OPTIONS" :key="o.value" :label="o.label" :value="o.value" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="输入 token 下界">
|
||||
<el-form-item label="输入长度下界">
|
||||
<el-input-number v-model="r.inLenMin" :min="0" :precision="0" :controls="false" class="rc-control" />
|
||||
</el-form-item>
|
||||
<el-form-item label="输入 token 上界">
|
||||
<el-form-item label="输入长度上界">
|
||||
<el-input-number v-model="r.inLenMax" :min="0" :precision="0" :controls="false" class="rc-control" />
|
||||
</el-form-item>
|
||||
</template>
|
||||
<template v-if="subject.modelType === 'video'">
|
||||
<el-form-item label="outputAudio">
|
||||
<el-form-item label="输出音频">
|
||||
<el-select v-model="r.outputAudio" class="rc-control">
|
||||
<el-option v-for="o in BOOL_TRIPLE_OPTIONS" :key="o.value" :label="o.label" :value="o.value" />
|
||||
<el-option v-for="o in OUTPUT_AUDIO_OPTIONS" :key="o.value" :label="o.label" :value="o.value" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="outputResolution">
|
||||
<el-input v-model="r.outputResolution" placeholder="480p/720p/1080p/2k/4k" class="rc-control" />
|
||||
<el-form-item label="输出分辨率">
|
||||
<el-select v-model="r.outputResolution" class="rc-control">
|
||||
<el-option v-for="o in RESOLUTION_OPTIONS.video" :key="o.value" :label="o.label" :value="o.value" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</template>
|
||||
<template v-if="subject.modelType === 'image'">
|
||||
<el-form-item label="outputResolution">
|
||||
<el-input v-model="r.outputResolution" placeholder="512x512/1024x1024/2048x2048" class="rc-control" />
|
||||
<el-form-item label="输出分辨率">
|
||||
<el-select v-model="r.outputResolution" class="rc-control">
|
||||
<el-option v-for="o in RESOLUTION_OPTIONS.image" :key="o.value" :label="o.label" :value="o.value" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</template>
|
||||
</div>
|
||||
</el-form>
|
||||
|
||||
<div class="rc-sec price">计价项 price</div>
|
||||
<div class="rc-sec price">计价项</div>
|
||||
<el-form label-position="top" size="small">
|
||||
<div class="rc-grid">
|
||||
<template v-if="isTokenUnit">
|
||||
<el-form-item label="输入单价 input">
|
||||
<el-input-number v-model="r.input" :min="0" :precision="6" :step="0.0001" :controls="false" class="rc-control" />
|
||||
<el-form-item label="输入单价">
|
||||
<el-input-number v-model="r.input" :min="0" :precision="5" :step="0.0001" :controls="false" class="rc-control" />
|
||||
</el-form-item>
|
||||
<el-form-item label="输出单价 output">
|
||||
<el-input-number v-model="r.output" :min="0" :precision="6" :step="0.0001" :controls="false" class="rc-control" />
|
||||
<el-form-item label="输出单价">
|
||||
<el-input-number v-model="r.output" :min="0" :precision="5" :step="0.0001" :controls="false" class="rc-control" />
|
||||
</el-form-item>
|
||||
<el-form-item label="缓存命中 cacheHit">
|
||||
<el-input-number v-model="r.cacheHit" :min="0" :precision="6" :step="0.0001" :controls="false" class="rc-control" />
|
||||
<el-form-item label="缓存命中">
|
||||
<el-input-number v-model="r.cacheHit" :min="0" :precision="5" :step="0.0001" :controls="false" class="rc-control" />
|
||||
</el-form-item>
|
||||
</template>
|
||||
<template v-else>
|
||||
<el-form-item label="每单位单价 unitPrice">
|
||||
<el-input-number v-model="r.unitPrice" :min="0" :precision="6" :step="0.0001" :controls="false" class="rc-control" />
|
||||
<el-form-item label="每单位单价">
|
||||
<el-input-number v-model="r.unitPrice" :min="0" :precision="5" :step="0.0001" :controls="false" class="rc-control" />
|
||||
</el-form-item>
|
||||
</template>
|
||||
</div>
|
||||
</el-form>
|
||||
|
||||
<!-- 媒体价格模板(仅 token 单位):媒体命中条件已移除,改为对特定媒体类型设独立覆盖价 mediaPrices -->
|
||||
<div v-if="isTokenUnit" class="media-price-section">
|
||||
<div class="rc-sec price">媒体价格模板(可选)</div>
|
||||
<div v-for="(mp, mIdx) in r.mediaPrices" :key="mIdx" class="media-price-row">
|
||||
<el-select v-model="mp.mediaType" class="mp-media" placeholder="媒体类型">
|
||||
<el-option v-for="o in getAvailableMediaOptions(r, mIdx)" :key="o.value" :label="o.label" :value="o.value" />
|
||||
</el-select>
|
||||
<div class="mp-field">
|
||||
<span class="mp-label">输入价</span>
|
||||
<el-input-number v-model="mp.input" :min="0" :precision="5" :step="0.0001" :controls="false" class="mp-num" />
|
||||
</div>
|
||||
<div class="mp-field">
|
||||
<span class="mp-label">输出价</span>
|
||||
<el-input-number v-model="mp.output" :min="0" :precision="5" :step="0.0001" :controls="false" class="mp-num" />
|
||||
</div>
|
||||
<div class="mp-field">
|
||||
<span class="mp-label">缓存命中价</span>
|
||||
<el-input-number v-model="mp.cacheHit" :min="0" :precision="5" :step="0.0001" :controls="false" class="mp-num" />
|
||||
</div>
|
||||
<el-button type="danger" text size="small" @click="removeMediaPrice(r, mIdx)">删除</el-button>
|
||||
</div>
|
||||
<el-button size="small" class="media-add-btn" @click="addMediaPrice(r)">+ 添加媒体价格</el-button>
|
||||
<div class="mp-tip">基础价(input/output/cacheHit)为默认价;媒体模板仅覆盖指定媒体类型的价,未选媒体类型的行保存时丢弃。</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<el-button size="small" class="add-btn" @click="addRule">+ 添加规则</el-button>
|
||||
@@ -110,51 +161,23 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="editor-col">
|
||||
<!-- 通用配置 -->
|
||||
<div class="card">
|
||||
<div class="card-head">
|
||||
<span class="card-title">通用配置</span>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<el-form label-position="top" size="default">
|
||||
<el-form-item label="门禁 min_balance(元)">
|
||||
<el-input-number
|
||||
v-model="minBalance"
|
||||
:min="0"
|
||||
:precision="2"
|
||||
:step="0.01"
|
||||
:controls="false"
|
||||
class="ctrl-input"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="币种">
|
||||
<el-select v-model="currency" class="ctrl-select">
|
||||
<el-option label="人民币 (CNY)" value="CNY" />
|
||||
<el-option label="美元 (USD)" value="USD" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="启用">
|
||||
<el-switch v-model="enabled" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, ref, watch } from 'vue';
|
||||
import {
|
||||
BOOL_TRIPLE_OPTIONS,
|
||||
EXAMPLES,
|
||||
MEDIA_TYPE_OPTIONS,
|
||||
MEDIA_PRICE_OPTIONS,
|
||||
OUTPUT_AUDIO_OPTIONS,
|
||||
RESOLUTION_OPTIONS,
|
||||
THINKING_OPTIONS,
|
||||
UNIT_OPTIONS,
|
||||
type BuiltModelRule,
|
||||
type BuiltModelRuleMatch,
|
||||
type Currency,
|
||||
type EditorInitConfig,
|
||||
type MediaPriceRow,
|
||||
type ModelRule,
|
||||
type ModelRules,
|
||||
type Subject,
|
||||
@@ -193,7 +216,6 @@ const priceNote = computed(() =>
|
||||
function emptyRule(): ModelRule {
|
||||
return {
|
||||
name: '',
|
||||
mediaType: '',
|
||||
thinking: '',
|
||||
outputAudio: '',
|
||||
outputResolution: '',
|
||||
@@ -203,6 +225,7 @@ function emptyRule(): ModelRule {
|
||||
output: 0,
|
||||
cacheHit: 0,
|
||||
unitPrice: 0,
|
||||
mediaPrices: [],
|
||||
};
|
||||
}
|
||||
|
||||
@@ -216,7 +239,6 @@ function loadExample(index: number) {
|
||||
if (ex.minBalance != null) minBalance.value = ex.minBalance;
|
||||
modelRules.value = ex.rules.map((r) => ({
|
||||
name: r.name,
|
||||
mediaType: (r.mediaType as ModelRule['mediaType']) || '',
|
||||
thinking: (r.thinking == null ? '' : String(r.thinking)) as ModelRule['thinking'],
|
||||
outputAudio: (r.outputAudio == null ? '' : String(r.outputAudio)) as ModelRule['outputAudio'],
|
||||
outputResolution: r.outputResolution || '',
|
||||
@@ -226,6 +248,7 @@ function loadExample(index: number) {
|
||||
output: r.output ?? 0,
|
||||
cacheHit: r.cacheHit ?? 0,
|
||||
unitPrice: r.unitPrice ?? 0,
|
||||
mediaPrices: mediaPricesToRows(r.mediaPrices),
|
||||
}));
|
||||
onUnitChange();
|
||||
}
|
||||
@@ -248,7 +271,6 @@ function fromBuiltRule(r: BuiltModelRule): ModelRule {
|
||||
const m = r.match;
|
||||
return {
|
||||
name: r.name,
|
||||
mediaType: (m?.mediaType as ModelRule['mediaType']) || '',
|
||||
thinking: (m?.thinking == null ? '' : String(m.thinking)) as ModelRule['thinking'],
|
||||
outputAudio: (m?.outputAudio == null ? '' : String(m.outputAudio)) as ModelRule['outputAudio'],
|
||||
outputResolution: m?.outputResolution || '',
|
||||
@@ -258,6 +280,7 @@ function fromBuiltRule(r: BuiltModelRule): ModelRule {
|
||||
output: r.price.output ?? 0,
|
||||
cacheHit: r.price.cacheHit ?? 0,
|
||||
unitPrice: r.price.unitPrice ?? 0,
|
||||
mediaPrices: mediaPricesToRows(r.mediaPrices),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -277,15 +300,16 @@ function applyInit(cfg: EditorInitConfig) {
|
||||
/** 过滤空白规则:规则名或任一命中/价格字段非空才保留 */
|
||||
function keepRule(r: ModelRule): boolean {
|
||||
if (r.name.trim()) return true;
|
||||
if (r.mediaType || r.thinking || r.outputAudio || r.outputResolution) return true;
|
||||
if (r.thinking || r.outputAudio || r.outputResolution) return true;
|
||||
if (r.inLenMin != null || r.inLenMax != null) return true;
|
||||
return r.input > 0 || r.output > 0 || r.cacheHit > 0 || r.unitPrice > 0;
|
||||
if (r.input > 0 || r.output > 0 || r.cacheHit > 0 || r.unitPrice > 0) return true;
|
||||
// 仅填写了媒体价格模板也算有效规则
|
||||
return !!buildMediaPrices(r.mediaPrices);
|
||||
}
|
||||
|
||||
/** 编辑器行态 → 构建态规则(match 只收录非空字段) */
|
||||
function buildBuiltRule(r: ModelRule): BuiltModelRule {
|
||||
const match: BuiltModelRuleMatch = {};
|
||||
if (r.mediaType) match.mediaType = r.mediaType;
|
||||
if (r.thinking === 'true' || r.thinking === 'false') match.thinking = r.thinking === 'true';
|
||||
if (r.outputAudio === 'true' || r.outputAudio === 'false') match.outputAudio = r.outputAudio === 'true';
|
||||
if (r.outputResolution) match.outputResolution = r.outputResolution;
|
||||
@@ -298,9 +322,54 @@ function buildBuiltRule(r: ModelRule): BuiltModelRule {
|
||||
name: r.name,
|
||||
match: Object.keys(match).length ? match : undefined,
|
||||
price,
|
||||
mediaPrices: buildMediaPrices(r.mediaPrices),
|
||||
};
|
||||
}
|
||||
|
||||
/** 媒体价格行 → mediaPrices 对象(仅保留填写了价格的媒体;无覆盖返回 undefined 不写入) */
|
||||
function buildMediaPrices(rows: MediaPriceRow[]): Record<string, Record<string, number>> | undefined {
|
||||
if (!rows?.length) return undefined;
|
||||
const map: Record<string, Record<string, number>> = {};
|
||||
rows.forEach((row) => {
|
||||
if (!row.mediaType) return;
|
||||
const prices: Record<string, number> = {};
|
||||
if (row.input > 0) prices.input = row.input;
|
||||
if (row.output > 0) prices.output = row.output;
|
||||
if (row.cacheHit > 0) prices.cacheHit = row.cacheHit;
|
||||
if (Object.keys(prices).length) map[row.mediaType] = prices;
|
||||
});
|
||||
return Object.keys(map).length ? map : undefined;
|
||||
}
|
||||
|
||||
/** mediaPrices 对象 → 编辑态媒体价格行(兼容后端两种结构:按媒体分组的内层对象,或空/缺失) */
|
||||
function mediaPricesToRows(mediaPrices?: unknown): MediaPriceRow[] {
|
||||
if (!mediaPrices || typeof mediaPrices !== 'object') return [];
|
||||
return Object.entries(mediaPrices as Record<string, Record<string, number | undefined>>)
|
||||
.filter(([, prices]) => prices && typeof prices === 'object')
|
||||
.map(([mediaType, prices]) => ({
|
||||
mediaType,
|
||||
input: prices.input ?? 0,
|
||||
output: prices.output ?? 0,
|
||||
cacheHit: prices.cacheHit ?? 0,
|
||||
}));
|
||||
}
|
||||
|
||||
/** 添加一条媒体价格行 */
|
||||
function addMediaPrice(r: ModelRule) {
|
||||
r.mediaPrices.push({ mediaType: '', input: 0, output: 0, cacheHit: 0 });
|
||||
}
|
||||
|
||||
/** 删除一条媒体价格行 */
|
||||
function removeMediaPrice(r: ModelRule, index: number) {
|
||||
r.mediaPrices.splice(index, 1);
|
||||
}
|
||||
|
||||
/** 媒体价格行的可选媒体:排除本规则其他行已选中的媒体,避免同一条规则重复选择同一媒体 */
|
||||
function getAvailableMediaOptions(r: ModelRule, curIndex: number) {
|
||||
const selected = new Set(r.mediaPrices.filter((_, i) => i !== curIndex).map((row) => row.mediaType));
|
||||
return MEDIA_PRICE_OPTIONS.filter((opt) => !selected.has(opt.value) || r.mediaPrices[curIndex]?.mediaType === opt.value);
|
||||
}
|
||||
|
||||
/** 构建模型费率 JSON(对齐后端结构,由父组件 stringify 提交) */
|
||||
function buildRules(): ModelRules {
|
||||
return {
|
||||
@@ -341,14 +410,12 @@ defineExpose({ save });
|
||||
|
||||
<style scoped lang="scss">
|
||||
.editor-body {
|
||||
display: flex;
|
||||
gap: 20px;
|
||||
flex-wrap: wrap;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.editor-col {
|
||||
flex: 1;
|
||||
min-width: 380px;
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.card {
|
||||
@@ -397,13 +464,6 @@ defineExpose({ save });
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.ctrl-input {
|
||||
width: 220px;
|
||||
}
|
||||
|
||||
.ctrl-select {
|
||||
width: 220px;
|
||||
}
|
||||
|
||||
.rule-card {
|
||||
border: 1px solid #e4e7ed;
|
||||
@@ -467,6 +527,53 @@ defineExpose({ save });
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/* 媒体价格模板区(token 单位):基础价下方的媒体覆盖价动态行 */
|
||||
.media-price-section {
|
||||
margin-top: 12px;
|
||||
border-top: 1px dashed #e4e7ed;
|
||||
padding-top: 10px;
|
||||
}
|
||||
|
||||
.media-price-row {
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
gap: 8px;
|
||||
margin-bottom: 8px;
|
||||
|
||||
.mp-media {
|
||||
width: 120px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.mp-field {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
|
||||
.mp-label {
|
||||
font-size: 12px;
|
||||
color: #606266;
|
||||
}
|
||||
|
||||
.mp-num {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.media-add-btn {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.mp-tip {
|
||||
font-size: 12px;
|
||||
color: #909399;
|
||||
line-height: 1.7;
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.note {
|
||||
border: 1px solid #e6a23c;
|
||||
background: #fdf6ec;
|
||||
@@ -476,4 +583,34 @@ defineExpose({ save });
|
||||
font-size: 12.5px;
|
||||
line-height: 1.7;
|
||||
}
|
||||
|
||||
.cfg-row {
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
gap: 40px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.cfg-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.cfg-label {
|
||||
font-size: 13px;
|
||||
color: #606266;
|
||||
}
|
||||
|
||||
.cfg-min-balance {
|
||||
width: 200px;
|
||||
}
|
||||
|
||||
.cfg-currency {
|
||||
width: 200px;
|
||||
}
|
||||
|
||||
.ctrl-select {
|
||||
width: 220px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -2,19 +2,6 @@
|
||||
<div class="subject-list">
|
||||
<div v-if="loading" class="subject-loading">加载计价对象…</div>
|
||||
<template v-else>
|
||||
<!-- 模型类型筛选 -->
|
||||
<div v-if="hasModel" class="filter-bar">
|
||||
<button
|
||||
v-for="f in FILTER_OPTIONS"
|
||||
:key="f.value"
|
||||
class="filter-chip"
|
||||
:class="{ active: modelFilter === f.value }"
|
||||
@click="modelFilter = f.value"
|
||||
>
|
||||
{{ f.label }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- 工作流 -->
|
||||
<div class="grp-head">
|
||||
<span class="grp-name">工作流</span>
|
||||
@@ -29,7 +16,6 @@
|
||||
>
|
||||
<span class="type-badge t-workflow">工作流</span>
|
||||
<span class="subj-name" :title="s.name">{{ s.name }}</span>
|
||||
<span v-for="m in s.modes" :key="m" class="mode-chip">{{ m }}</span>
|
||||
<span
|
||||
class="status-dot"
|
||||
:class="s.hasConfig ? 'on' : 'off'"
|
||||
@@ -37,13 +23,13 @@
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- 模型(支持类型筛选) -->
|
||||
<!-- 模型 -->
|
||||
<div class="grp-head">
|
||||
<span class="grp-name">模型</span>
|
||||
<span class="grp-count">{{ filteredModels.length }}</span>
|
||||
<span class="grp-count">{{ modelSubjects.length }}</span>
|
||||
</div>
|
||||
<div
|
||||
v-for="s in filteredModels"
|
||||
v-for="s in modelSubjects"
|
||||
:key="`${s.type}:${s.id}`"
|
||||
class="subj-item"
|
||||
:class="{ active: `${s.type}:${s.id}` === currentKey }"
|
||||
@@ -57,7 +43,7 @@
|
||||
:title="s.hasConfig ? '已配置' : '未配置'"
|
||||
/>
|
||||
</div>
|
||||
<div v-if="!filteredModels.length" class="empty-tip">该类型暂无模型</div>
|
||||
<div v-if="!modelSubjects.length" class="empty-tip">暂无模型</div>
|
||||
|
||||
<!-- 业务模块 -->
|
||||
<div class="grp-head">
|
||||
@@ -73,7 +59,6 @@
|
||||
>
|
||||
<span class="type-badge t-business">业务</span>
|
||||
<span class="subj-name" :title="s.name">{{ s.name }}</span>
|
||||
<span v-for="m in s.modes" :key="m" class="mode-chip">{{ m }}</span>
|
||||
<span
|
||||
class="status-dot"
|
||||
:class="s.hasConfig ? 'on' : 'off'"
|
||||
@@ -85,12 +70,12 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, ref } from 'vue';
|
||||
import type { ModelType, Subject } from '../mock';
|
||||
import { computed } from 'vue';
|
||||
import type { Subject } from '../mock';
|
||||
|
||||
/**
|
||||
* 左侧计价对象枚举列表。
|
||||
* 分三组渲染(工作流 / 模型 / 业务模块),模型组支持类型筛选;
|
||||
* 分三组渲染(工作流 / 模型 / 业务模块);
|
||||
* 配置状态以实心(已配置)/ 空心(未配置)圆点呈现。
|
||||
*/
|
||||
const props = defineProps<{
|
||||
@@ -105,57 +90,10 @@ const workflowSubjects = computed(() => props.subjects.filter((s) => s.type ===
|
||||
const modelSubjects = computed(() => props.subjects.filter((s) => s.type === 'model'));
|
||||
const businessSubjects = computed(() => props.subjects.filter((s) => s.type === 'business'));
|
||||
|
||||
const hasModel = computed(() => modelSubjects.value.length > 0);
|
||||
|
||||
/** 模型类型筛选 */
|
||||
const FILTER_OPTIONS: { value: ModelType | 'all'; label: string }[] = [
|
||||
{ value: 'all', label: '全部' },
|
||||
{ value: 'reason', label: '推理' },
|
||||
{ value: 'video', label: '视频' },
|
||||
{ value: 'audio', label: '音频' },
|
||||
{ value: 'image', label: '图片' },
|
||||
];
|
||||
const modelFilter = ref<ModelType | 'all'>('all');
|
||||
const filteredModels = computed(() =>
|
||||
modelFilter.value === 'all'
|
||||
? modelSubjects.value
|
||||
: modelSubjects.value.filter((s) => s.modelType === modelFilter.value),
|
||||
);
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.subject-list {
|
||||
.filter-bar {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 6px;
|
||||
padding: 10px 16px 2px;
|
||||
|
||||
.filter-chip {
|
||||
border: 1px solid #dcdfe6;
|
||||
background: #fff;
|
||||
color: #606266;
|
||||
border-radius: 14px;
|
||||
padding: 3px 12px;
|
||||
font-size: 12px;
|
||||
cursor: pointer;
|
||||
transition: all 0.15s;
|
||||
line-height: 1.4;
|
||||
|
||||
&:hover {
|
||||
color: #409eff;
|
||||
border-color: #409eff;
|
||||
}
|
||||
|
||||
&.active {
|
||||
background: #ecf5ff;
|
||||
border-color: #409eff;
|
||||
color: #409eff;
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.grp-head {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@@ -251,14 +189,6 @@ const filteredModels = computed(() =>
|
||||
}
|
||||
}
|
||||
|
||||
.mode-chip {
|
||||
font-size: 11px;
|
||||
background: #f4f4f5;
|
||||
color: #606266;
|
||||
border-radius: 3px;
|
||||
padding: 1px 5px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.status-dot {
|
||||
width: 8px;
|
||||
|
||||
@@ -1,5 +1,32 @@
|
||||
<template>
|
||||
<div class="editor-body">
|
||||
<div class="editor-col">
|
||||
<!-- 通用配置 -->
|
||||
<div class="card">
|
||||
<div class="card-head">
|
||||
<span class="card-title">通用配置</span>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="cfg-row">
|
||||
<div class="cfg-item">
|
||||
<div class="cfg-label">门禁金额(元)</div>
|
||||
<el-input-number v-model="minBalance" :min="0" :precision="5" :step="0.01" :controls="false" class="cfg-min-balance" />
|
||||
</div>
|
||||
<div class="cfg-item">
|
||||
<div class="cfg-label">币种</div>
|
||||
<el-select v-model="currency" class="cfg-currency">
|
||||
<el-option label="人民币" value="CNY" />
|
||||
<el-option label="美元" value="USD" />
|
||||
</el-select>
|
||||
</div>
|
||||
<div class="cfg-item">
|
||||
<div class="cfg-label">启用</div>
|
||||
<el-switch v-model="enabled" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="editor-col">
|
||||
<!-- 计费模式 -->
|
||||
<div class="card">
|
||||
@@ -11,12 +38,12 @@
|
||||
<!-- 按条计费 per_item -->
|
||||
<div class="mode-item">
|
||||
<div class="mode-item-head">
|
||||
<span class="mode-name">按条计费 <span class="mode-code">per_item</span></span>
|
||||
<span class="mode-name">按条计费</span>
|
||||
<el-switch v-model="modes.per_item" />
|
||||
</div>
|
||||
<div v-if="modes.per_item" class="mode-item-body">
|
||||
<el-form label-position="top" size="default">
|
||||
<el-form-item label="档位列表(上不封顶,命中首个 maxSec ≥ 时长 的档)">
|
||||
<el-form-item label="档位列表(上不封顶,命中首个档位上限 ≥ 时长的档)">
|
||||
<div class="tier-list">
|
||||
<div v-for="(tier, i) in tiers" :key="i" class="tier-row">
|
||||
<el-input-number
|
||||
@@ -24,13 +51,13 @@
|
||||
:min="0"
|
||||
:precision="0"
|
||||
:controls="false"
|
||||
placeholder="maxSec"
|
||||
placeholder="档位上限(秒)"
|
||||
class="tier-input"
|
||||
/>
|
||||
<el-input-number
|
||||
v-model="tier.price"
|
||||
:min="0"
|
||||
:precision="2"
|
||||
:precision="5"
|
||||
:controls="false"
|
||||
placeholder="价格(元)"
|
||||
class="tier-input"
|
||||
@@ -44,7 +71,7 @@
|
||||
<el-input-number
|
||||
v-model="overflowUnitPrice"
|
||||
:min="0"
|
||||
:precision="2"
|
||||
:precision="5"
|
||||
:step="0.01"
|
||||
:controls="false"
|
||||
class="ctrl-input"
|
||||
@@ -57,7 +84,7 @@
|
||||
<!-- 按秒计费 per_second -->
|
||||
<div class="mode-item">
|
||||
<div class="mode-item-head">
|
||||
<span class="mode-name">按秒计费 <span class="mode-code">per_second</span></span>
|
||||
<span class="mode-name">按秒计费</span>
|
||||
<el-switch v-model="modes.per_second" />
|
||||
</div>
|
||||
<div v-if="modes.per_second" class="mode-item-body">
|
||||
@@ -66,7 +93,7 @@
|
||||
<el-input-number
|
||||
v-model="perSecondUnitPrice"
|
||||
:min="0"
|
||||
:precision="2"
|
||||
:precision="5"
|
||||
:step="0.01"
|
||||
:controls="false"
|
||||
class="ctrl-input"
|
||||
@@ -79,7 +106,7 @@
|
||||
<!-- 按 token 计费 per_token -->
|
||||
<div class="mode-item">
|
||||
<div class="mode-item-head">
|
||||
<span class="mode-name">按 Token 计费 <span class="mode-code">per_token</span></span>
|
||||
<span class="mode-name">按 Token 计费</span>
|
||||
<el-switch v-model="modes.per_token" />
|
||||
</div>
|
||||
<div v-if="modes.per_token" class="mode-item-body">
|
||||
@@ -93,37 +120,6 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="editor-col">
|
||||
<!-- 通用配置 -->
|
||||
<div class="card">
|
||||
<div class="card-head">
|
||||
<span class="card-title">通用配置</span>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<el-form label-position="top" size="default">
|
||||
<el-form-item label="门禁 min_balance(元)">
|
||||
<el-input-number
|
||||
v-model="minBalance"
|
||||
:min="0"
|
||||
:precision="2"
|
||||
:step="0.01"
|
||||
:controls="false"
|
||||
class="ctrl-input"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="币种">
|
||||
<el-select v-model="currency" class="ctrl-select">
|
||||
<el-option label="人民币 (CNY)" value="CNY" />
|
||||
<el-option label="美元 (USD)" value="USD" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="启用">
|
||||
<el-switch v-model="enabled" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -224,14 +220,12 @@ defineExpose({ save });
|
||||
|
||||
<style scoped lang="scss">
|
||||
.editor-body {
|
||||
display: flex;
|
||||
gap: 20px;
|
||||
flex-wrap: wrap;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.editor-col {
|
||||
flex: 1;
|
||||
min-width: 380px;
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.card {
|
||||
@@ -286,19 +280,12 @@ defineExpose({ save });
|
||||
.mode-name {
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
|
||||
.mode-code {
|
||||
font-size: 12px;
|
||||
font-weight: 400;
|
||||
color: #909399;
|
||||
margin-left: 6px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.mode-item-body {
|
||||
padding: 12px;
|
||||
}
|
||||
.mode-item-body {
|
||||
padding: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
.tier-list {
|
||||
@@ -328,7 +315,29 @@ defineExpose({ save });
|
||||
width: 220px;
|
||||
}
|
||||
|
||||
.ctrl-select {
|
||||
width: 220px;
|
||||
.cfg-row {
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
gap: 40px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.cfg-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.cfg-label {
|
||||
font-size: 13px;
|
||||
color: #606266;
|
||||
}
|
||||
|
||||
.cfg-min-balance {
|
||||
width: 200px;
|
||||
}
|
||||
|
||||
.cfg-currency {
|
||||
width: 200px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -141,9 +141,9 @@ const current = computed<Subject | undefined>(() =>
|
||||
const headerSub = computed(() => {
|
||||
const s = current.value;
|
||||
if (!s) return '加载计价对象…';
|
||||
if (s.type === 'workflow') return `subjectType=workflow · subjectId=${s.id}`;
|
||||
if (s.type === 'model') return `subjectType=model · subjectId=${s.id}(${s.typeLabel},modelType=${s.modelType})`;
|
||||
return `subjectType=business · subjectId=${s.id}`;
|
||||
if (s.type === 'workflow') return `工作流对象 · 标识:${s.id}`;
|
||||
if (s.type === 'model') return `模型对象(${s.typeLabel})· 标识:${s.id}`;
|
||||
return `业务模块对象 · 标识:${s.id}`;
|
||||
});
|
||||
|
||||
/** 详情请求序号,防止快速切换时旧响应覆盖当前对象 */
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
* 说明:
|
||||
* - 计价对象枚举来自后端 GET .../subjects(见 src/api/trade/pricing/index.ts),
|
||||
* 本文件提供后端项 → 页面 Subject 的映射(中文 modelType/modelTypeCode → 英文枚举等)
|
||||
* - 计价配置读取后端的 rules JSON 字符串(JSON.parse),保存时 JSON.stringify 提交
|
||||
* - 计价配置读取/提交后端的 rules 均为对象(config/get 返回对象,save 也传对象)
|
||||
* - 模型类型编码:reason=100推理 / image=200图片 / audio=300音频 / video=600视频
|
||||
* - EXAMPLES 仅为编辑器本地"示例填充" UX,不参与后端数据
|
||||
*/
|
||||
@@ -37,6 +37,8 @@ export interface ApiSubjectItem {
|
||||
modelType?: string;
|
||||
/** 仅 model:模型类型编码(100推理/200图片/300音频/600视频) */
|
||||
modelTypeCode?: number;
|
||||
/** 该对象支持的计费模式(workflow/business 由后端 subjects.chargeModes 下发;model 无此字段) */
|
||||
chargeModes?: string[];
|
||||
}
|
||||
|
||||
/** modelTypeCode → 英文模型类型 */
|
||||
@@ -47,12 +49,6 @@ export const MODEL_TYPE_CODE_TO_EN: Record<number, ModelType> = {
|
||||
600: 'video',
|
||||
};
|
||||
|
||||
/** 各 subjectType 固定的计费模式(后端不下发 modes,按类型硬编码展示) */
|
||||
export const SUBJECT_MODE_MAP: Partial<Record<SubjectType, string[]>> = {
|
||||
workflow: ['per_item', 'per_second', 'per_token'],
|
||||
business: ['per_period'],
|
||||
};
|
||||
|
||||
/** 后端枚举项 → 页面 Subject */
|
||||
export function toSubject(item: ApiSubjectItem): Subject {
|
||||
if (item.subjectType === 'model') {
|
||||
@@ -71,7 +67,7 @@ export function toSubject(item: ApiSubjectItem): Subject {
|
||||
type: item.subjectType,
|
||||
id: item.subjectId,
|
||||
name: item.name,
|
||||
modes: SUBJECT_MODE_MAP[item.subjectType] ?? [],
|
||||
modes: item.chargeModes ?? [],
|
||||
hasConfig: false,
|
||||
};
|
||||
}
|
||||
@@ -90,7 +86,7 @@ export interface Subject {
|
||||
modelTypeCode?: number;
|
||||
/** 仅 model:模型中文类型(推理/视频/音频/图片) */
|
||||
typeLabel?: string;
|
||||
/** workflow/business:支持的计费模式(本地硬编码) */
|
||||
/** workflow/business:支持的计费模式(后端 subjects.chargeModes 下发) */
|
||||
modes?: string[];
|
||||
/** 是否已配置计价(选中后由 config/get 判断) */
|
||||
hasConfig: boolean;
|
||||
@@ -122,10 +118,21 @@ export interface WorkflowRules {
|
||||
|
||||
/* ================= 模型费率 JSON ================= */
|
||||
|
||||
/** 编辑器内部规则行态(thinking/outputAudio 用字符串承载"未设置") */
|
||||
/** 媒体价格模板行(token 单位):对特定媒体类型设置独立覆盖价(mediaPrices) */
|
||||
export interface MediaPriceRow {
|
||||
/** 媒体类型:text / audio / video / image */
|
||||
mediaType: string;
|
||||
/** 输入单价 */
|
||||
input: number;
|
||||
/** 输出单价 */
|
||||
output: number;
|
||||
/** 缓存命中输入单价 */
|
||||
cacheHit: number;
|
||||
}
|
||||
|
||||
/** 编辑器内部规则行态(thinking/outputAudio 用字符串承载"未设置";媒体命中条件已移除,媒体改走价格模板) */
|
||||
export interface ModelRule {
|
||||
name: string;
|
||||
mediaType: '' | 'text' | 'audio' | 'video' | 'image';
|
||||
thinking: '' | 'true' | 'false';
|
||||
outputAudio: '' | 'true' | 'false';
|
||||
outputResolution: string;
|
||||
@@ -139,12 +146,13 @@ export interface ModelRule {
|
||||
cacheHit: number;
|
||||
/** 非 token 单位:每单位单价 */
|
||||
unitPrice: number;
|
||||
/** 媒体价格模板行(token 单位;空数组=无媒体覆盖价) */
|
||||
mediaPrices: MediaPriceRow[];
|
||||
}
|
||||
|
||||
/** 示例数据源规则(thinking/outputAudio 为布尔,由 loadExample 转换) */
|
||||
export interface ExampleRule {
|
||||
name: string;
|
||||
mediaType?: string;
|
||||
thinking?: boolean;
|
||||
outputAudio?: boolean;
|
||||
outputResolution?: string;
|
||||
@@ -154,11 +162,12 @@ export interface ExampleRule {
|
||||
output?: number;
|
||||
cacheHit?: number;
|
||||
unitPrice?: number;
|
||||
/** 媒体价格模板示例(可选):媒体类型 → 覆盖价 */
|
||||
mediaPrices?: Record<string, { input?: number; output?: number; cacheHit?: number }>;
|
||||
}
|
||||
|
||||
/** 构建态:规则命中条件 */
|
||||
/** 构建态:规则命中条件(媒体类型已改为价格模板,不再作为命中条件) */
|
||||
export interface BuiltModelRuleMatch {
|
||||
mediaType?: string;
|
||||
thinking?: boolean;
|
||||
outputAudio?: boolean;
|
||||
outputResolution?: string;
|
||||
@@ -171,6 +180,8 @@ export interface BuiltModelRule {
|
||||
name: string;
|
||||
match?: BuiltModelRuleMatch;
|
||||
price: Record<string, number>;
|
||||
/** 媒体价格模板(token 单位):媒体类型 → 该媒体独立覆盖价;无覆盖时省略 */
|
||||
mediaPrices?: Record<string, Record<string, number>>;
|
||||
}
|
||||
|
||||
/** 模型费率 JSON 结构(对齐后端,含内嵌币种) */
|
||||
@@ -229,39 +240,65 @@ export const MODEL_TYPE_CODES: Record<ModelType, number> = {
|
||||
};
|
||||
|
||||
export const UNIT_OPTIONS: { value: Unit; label: string }[] = [
|
||||
{ value: 'per_1M', label: 'per_1M(每百万token)' },
|
||||
{ value: 'per_1K', label: 'per_1K(每千token)' },
|
||||
{ value: 'per_1', label: 'per_1(每个/每张图)' },
|
||||
{ value: 'per_second', label: 'per_second(每秒)' },
|
||||
{ value: 'per_minute', label: 'per_minute(每分钟)' },
|
||||
{ value: 'per_hour', label: 'per_hour(每小时)' },
|
||||
{ value: 'per_char', label: 'per_char(每字)' },
|
||||
{ value: 'per_1M', label: '每百万 Token' },
|
||||
{ value: 'per_1K', label: '每千 Token' },
|
||||
{ value: 'per_1', label: '每个 / 每张图' },
|
||||
{ value: 'per_second', label: '每秒' },
|
||||
{ value: 'per_minute', label: '每分钟' },
|
||||
{ value: 'per_hour', label: '每小时' },
|
||||
{ value: 'per_char', label: '每字' },
|
||||
];
|
||||
|
||||
export const MEDIA_TYPE_OPTIONS: { value: ModelRule['mediaType']; label: string }[] = [
|
||||
/** 媒体价格模板可选媒体类型(媒体命中条件已移除,媒体改按模板定价) */
|
||||
export const MEDIA_PRICE_OPTIONS: { value: 'text' | 'audio' | 'video' | 'image'; label: string }[] = [
|
||||
{ value: 'text', label: '文本' },
|
||||
{ value: 'audio', label: '音频' },
|
||||
{ value: 'video', label: '视频' },
|
||||
{ value: 'image', label: '图片' },
|
||||
];
|
||||
|
||||
/** outputAudio 命中条件(video):给用户展示中文(有声/无声) */
|
||||
export const OUTPUT_AUDIO_OPTIONS: { value: '' | 'true' | 'false'; label: string }[] = [
|
||||
{ value: '', label: '不限制' },
|
||||
{ value: 'text', label: '文本 text' },
|
||||
{ value: 'audio', label: '音频 audio' },
|
||||
{ value: 'video', label: '视频 video' },
|
||||
{ value: 'image', label: '图片 image' },
|
||||
{ value: 'true', label: '有声' },
|
||||
{ value: 'false', label: '无声' },
|
||||
];
|
||||
|
||||
export const BOOL_TRIPLE_OPTIONS: { value: '' | 'true' | 'false'; label: string }[] = [
|
||||
/** thinking 命中条件(reason):给用户展示中文(是/否) */
|
||||
export const THINKING_OPTIONS: { value: '' | 'true' | 'false'; label: string }[] = [
|
||||
{ value: '', label: '不限制' },
|
||||
{ value: 'true', label: '是' },
|
||||
{ value: 'false', label: '否' },
|
||||
];
|
||||
|
||||
/** 输出分辨率命中条件下拉选项(按模型类型区分:video 横版 / image 方形) */
|
||||
export const RESOLUTION_OPTIONS: Record<'video' | 'image', { value: string; label: string }[]> = {
|
||||
video: [
|
||||
{ value: '', label: '不限制' },
|
||||
{ value: '480p', label: '480p' },
|
||||
{ value: '720p', label: '720p' },
|
||||
{ value: '1080p', label: '1080p' },
|
||||
{ value: '2k', label: '2k' },
|
||||
{ value: '4k', label: '4k' },
|
||||
],
|
||||
image: [
|
||||
{ value: '', label: '不限制' },
|
||||
{ value: '512x512', label: '512x512' },
|
||||
{ value: '1024x1024', label: '1024x1024' },
|
||||
{ value: '2048x2048', label: '2048x2048' },
|
||||
],
|
||||
};
|
||||
|
||||
/** 模型示例(本地填充 UX,逐字对应原型定价配置) */
|
||||
export const EXAMPLES: ModelExampleMap = {
|
||||
video: [
|
||||
{
|
||||
label: '视频·按秒(分辨率×有声/无声×输入媒体)',
|
||||
label: '视频·按秒(分辨率×有声/无声)',
|
||||
unit: 'per_second',
|
||||
tiered: false,
|
||||
rules: [
|
||||
{ name: '无声-720p-输入不含视频', mediaType: 'text', outputAudio: false, outputResolution: '720p', unitPrice: 0.8 },
|
||||
{ name: '有声-1080p-输入含视频', mediaType: 'video', outputAudio: true, outputResolution: '1080p', unitPrice: 1.5 },
|
||||
{ name: '无声-720p', outputAudio: false, outputResolution: '720p', unitPrice: 0.8 },
|
||||
{ name: '有声-1080p', outputAudio: true, outputResolution: '1080p', unitPrice: 1.5 },
|
||||
],
|
||||
},
|
||||
{
|
||||
@@ -269,20 +306,28 @@ export const EXAMPLES: ModelExampleMap = {
|
||||
unit: 'per_1M',
|
||||
tiered: false,
|
||||
rules: [
|
||||
{ name: '文本输入-无声-1080p', mediaType: 'text', outputAudio: false, outputResolution: '1080p', input: 0.6, output: 3.6, cacheHit: 0.12 },
|
||||
{ name: '视频输入-有声-1080p', mediaType: 'video', outputAudio: true, outputResolution: '1080p', input: 9, output: 27, cacheHit: 1.8 },
|
||||
{ name: '无声-1080p', outputAudio: false, outputResolution: '1080p', input: 0.6, output: 3.6, cacheHit: 0.12 },
|
||||
{ name: '有声-1080p', outputAudio: true, outputResolution: '1080p', input: 9, output: 27, cacheHit: 1.8 },
|
||||
],
|
||||
},
|
||||
],
|
||||
reason: [
|
||||
{
|
||||
label: '推理·阶梯(思考×输入token档×输入媒体)',
|
||||
label: '推理·阶梯(思考×输入长度档×媒体模板)',
|
||||
unit: 'per_1M',
|
||||
tiered: true,
|
||||
rules: [
|
||||
{ name: '思考-输入≤32000-文本', mediaType: 'text', thinking: true, inLenMax: 32000, input: 0.6, output: 3.6, cacheHit: 0.12 },
|
||||
{ name: '思考-输入32001~128000-文本', mediaType: 'text', thinking: true, inLenMin: 32001, inLenMax: 128000, input: 0.9, output: 5.4, cacheHit: 0.18 },
|
||||
{ name: '思考-输入含音频', mediaType: 'audio', thinking: true, input: 9, output: 3.6, cacheHit: 1.8 },
|
||||
{ name: '思考-输入长度[0,32k]', thinking: true, inLenMax: 32000, input: 0.6, output: 3.6, cacheHit: 0.12 },
|
||||
{
|
||||
name: '思考-输入长度(32k,128k]',
|
||||
thinking: true,
|
||||
inLenMin: 32001,
|
||||
inLenMax: 128000,
|
||||
input: 0.9,
|
||||
output: 5.4,
|
||||
cacheHit: 0.18,
|
||||
mediaPrices: { audio: { input: 9, output: 3.6, cacheHit: 1.8 } },
|
||||
},
|
||||
{ name: '非思考-统一价', thinking: false, input: 0.3, output: 1.2, cacheHit: 0.06 },
|
||||
],
|
||||
},
|
||||
|
||||
@@ -1,27 +1,27 @@
|
||||
-----BEGIN RSA PRIVATE KEY-----
|
||||
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
|
||||
-----END RSA PRIVATE KEY-----
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIGJTCCBQ2gAwIBAgIQCXpUlFT9Cue3+TsPpxvnWjANBgkqhkiG9w0BAQsFADBu
|
||||
MIIGJTCCBQ2gAwIBAgIQA0m9UBf/1I217Y9jcPp0vTANBgkqhkiG9w0BAQsFADBu
|
||||
MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
|
||||
d3cuZGlnaWNlcnQuY29tMS0wKwYDVQQDEyRFbmNyeXB0aW9uIEV2ZXJ5d2hlcmUg
|
||||
RFYgVExTIENBIC0gRzIwHhcNMjYwNjEwMDAwMDAwWhcNMjYwOTA3MjM1OTU5WjAj
|
||||
RFYgVExTIENBIC0gRzIwHhcNMjYwOTA3MDAwMDAwWhcNMjYxMjA1MjM1OTU5WjAj
|
||||
MSEwHwYDVQQDExhnaXRlYS5yZWRwb3dlcmZ1dHVyZS5jb20wggEiMA0GCSqGSIb3
|
||||
DQEBAQUAA4IBDwAwggEKAoIBAQCqVsfwTHPJbcPzuCUOCNLPoigpaZXWJmHfcMhi
|
||||
YMXNtoap0FmBbJbhNZsGf2G5UOuCVKGN4gSMB18rcFwcq/j/GH5eMaPOn8bK8qSC
|
||||
AsYHYi1zdarDGXICGWbu2BTRuZAY+7/4Kci+ayzuUpbH1vkv15hT5OhTev8Y4/iO
|
||||
4apxRgcvKFZkOPLZ+M7km7j9bRTU5xouGjVJqmf0QVcUiMnlFuKI4XxBWjwTPcRt
|
||||
X8SxCjBoi+k6iotPUYYFF3qzP8vVMyWLDpQ9wZ35NNoANOHg/FKSVixequjS+y1+
|
||||
3SSK4ASVqHcfDddkw2JF8ColW+Fzz+ld0DqQfgW5kp/EAXwLAgMBAAGjggMIMIID
|
||||
BDAfBgNVHSMEGDAWgBR435GQX+7erPbFdevVTFVT7yRKtjAdBgNVHQ4EFgQUOiq0
|
||||
OCRofHA6BrxoM9do+AWnNgowQQYDVR0RBDowOIIYZ2l0ZWEucmVkcG93ZXJmdXR1
|
||||
DQEBAQUAA4IBDwAwggEKAoIBAQCUvl+uz6tWWTAea9IYnFT35vJzbfPF2wutwVBL
|
||||
l4QVTNBUPK7Ptpmpvc1AzJNGgZwuxRhEfJBZkx2x7O7XES6a80v0THUGqHf9SUoM
|
||||
QFUX/PJWtaGF9RQvl53GWJY2vDCYexfUUbL4xpVXIHzNXvXnrIZy8KrtB8A01Pqq
|
||||
2W2KiCo7qswpaBP/2F0Bn4U9BonaEUeyDPbRNaAqRLZWRnW2asDHAgjpoUEvKqSS
|
||||
Y6cMWwjCtbzOQaKctWmq8jqjIceA71UqD5MxgtWyykrCage5IQJFBVvbRvWOk+J+
|
||||
yybGg9jGRjz1objhWMYO5YNTXW1tTvc9oSdltFnT5zBJAJcVAgMBAAGjggMIMIID
|
||||
BDAfBgNVHSMEGDAWgBR435GQX+7erPbFdevVTFVT7yRKtjAdBgNVHQ4EFgQUYPN9
|
||||
3He9hDMC/uFm78xCWWEF4/owQQYDVR0RBDowOIIYZ2l0ZWEucmVkcG93ZXJmdXR1
|
||||
cmUuY29tghx3d3cuZ2l0ZWEucmVkcG93ZXJmdXR1cmUuY29tMD4GA1UdIAQ3MDUw
|
||||
MwYGZ4EMAQIBMCkwJwYIKwYBBQUHAgEWG2h0dHA6Ly93d3cuZGlnaWNlcnQuY29t
|
||||
L0NQUzAOBgNVHQ8BAf8EBAMCBaAwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUF
|
||||
@@ -19,19 +19,19 @@ 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
|
||||
-----END CERTIFICATE-----
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIEqjCCA5KgAwIBAgIQDeD/te5iy2EQn2CMnO1e0zANBgkqhkiG9w0BAQsFADBh
|
||||
|
||||
@@ -1,27 +1,27 @@
|
||||
-----BEGIN RSA PRIVATE KEY-----
|
||||
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
|
||||
-----END RSA PRIVATE KEY-----
|
||||
|
||||
@@ -1,37 +1,37 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIGJTCCBQ2gAwIBAgIQAWng8l3t5rBYcCmLpCDdJTANBgkqhkiG9w0BAQsFADBu
|
||||
MIIGJDCCBQygAwIBAgIQCG68erA4eBvEiFO3I/+fkDANBgkqhkiG9w0BAQsFADBu
|
||||
MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
|
||||
d3cuZGlnaWNlcnQuY29tMS0wKwYDVQQDEyRFbmNyeXB0aW9uIEV2ZXJ5d2hlcmUg
|
||||
RFYgVExTIENBIC0gRzIwHhcNMjYwNjEyMDAwMDAwWhcNMjYwOTA5MjM1OTU5WjAj
|
||||
RFYgVExTIENBIC0gRzIwHhcNMjYwOTA3MDAwMDAwWhcNMjYxMjA1MjM1OTU5WjAj
|
||||
MSEwHwYDVQQDExhtaW5pby5yZWRwb3dlcmZ1dHVyZS5jb20wggEiMA0GCSqGSIb3
|
||||
DQEBAQUAA4IBDwAwggEKAoIBAQC+gxSvGFHa9gL+3IZmWmWQA2m2YV+pyCWd34RO
|
||||
TWmkT+0zKsg/EEu+M3UEhxsEWQe2G6/9S391KC1LSFsjnZnVswAJsnlJ08tMFnM2
|
||||
leAdZdQd/9I6xAHyzuCwJ6RxWXLdN/V6axfFzJ5dDu7z9ms0QAoZnBPfGQC+217T
|
||||
U2ol5ogx11jWj+euVWGBXoK8O0IAUtFW/DannNz9V6Nz8wsRdXiX2DdUg4N9whsu
|
||||
xzNSaxP3zM4s7DCBWywJBx2cDXZPVfdTixfhTQ+KfQx7FPkvz8rHekdtqOlAr4gP
|
||||
RMOmY/hAp/lCQAcnEzunLi7UQc0GjeeSvFY099lNOeFu+jCPAgMBAAGjggMIMIID
|
||||
BDAfBgNVHSMEGDAWgBR435GQX+7erPbFdevVTFVT7yRKtjAdBgNVHQ4EFgQUm+W1
|
||||
VEi2C0CdxtuncyBFtX9cS5UwQQYDVR0RBDowOIIYbWluaW8ucmVkcG93ZXJmdXR1
|
||||
DQEBAQUAA4IBDwAwggEKAoIBAQCkmgtAQ/KZv8wrvYOiih8xozGCaoTWiW5ye/Hf
|
||||
tnX07Sxt1IaAfFUMHtaoEzsVBLwuTALYINWsCTrolvrgy6i/WvpsTGbZn7mkLjmK
|
||||
T8db1ToBueW/eePdHXg2Q1Pgi/Cy5/cwkp29WXvxRsbiINP4jqsma+4vuqPuXV6W
|
||||
o6HfXL3xddPl/ap0ScjCe+aZKB/hWwR3O4QNSmQFLY6jb7tgH3hqwRJzjOnqkQHA
|
||||
H2Tcdx/gUYYUErPqyyqjhFJW5uDvh1Ha5jcbU2XphOiBgI3wElCkmfAqt4WZGU0z
|
||||
kIluYkwZDxe3nN7iSZl9SbmgwTmlO1g04rC3T5WRQlk08dVdAgMBAAGjggMHMIID
|
||||
AzAfBgNVHSMEGDAWgBR435GQX+7erPbFdevVTFVT7yRKtjAdBgNVHQ4EFgQUbMUu
|
||||
74Ronh+u0yO+sQpoSQ4TbFMwQQYDVR0RBDowOIIYbWluaW8ucmVkcG93ZXJmdXR1
|
||||
cmUuY29tghx3d3cubWluaW8ucmVkcG93ZXJmdXR1cmUuY29tMD4GA1UdIAQ3MDUw
|
||||
MwYGZ4EMAQIBMCkwJwYIKwYBBQUHAgEWG2h0dHA6Ly93d3cuZGlnaWNlcnQuY29t
|
||||
L0NQUzAOBgNVHQ8BAf8EBAMCBaAwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUF
|
||||
BwMCMIGABggrBgEFBQcBAQR0MHIwJAYIKwYBBQUHMAGGGGh0dHA6Ly9vY3NwLmRp
|
||||
Z2ljZXJ0LmNvbTBKBggrBgEFBQcwAoY+aHR0cDovL2NhY2VydHMuZGlnaWNlcnQu
|
||||
Y29tL0VuY3J5cHRpb25FdmVyeXdoZXJlRFZUTFNDQS1HMi5jcnQwDAYDVR0TAQH/
|
||||
BAIwADCCAX0GCisGAQQB1nkCBAIEggFtBIIBaQFnAHUAwjF+V0UZo0XufzjespBB
|
||||
68fCIVoiv3/Vta12mtkOUs0AAAGeucefAgAABAMARjBEAiBdD793ndAhFw/xk4gS
|
||||
Pycc/U4IGSbSFNg1wXMMBkma9gIgSK8LNW9lqHn4wEYFRQrBfmYMUkJhUR51JmOb
|
||||
rUESBu8AdgDXbX0Q0af1d8LH6V/XAL/5gskzWmXh0LMBcxfAyMVpdwAAAZ65x57r
|
||||
AAAEAwBHMEUCIALrNS+sHrfHVl72YGHkqSb4quYzH6xZ0JNCTLakfjK4AiEA0jbR
|
||||
ORUL7BLzYbw5geZfMb8NJmD+7CbgXdHNPWmowdYAdgCUTkOH+uzB74HzGSQmqBhl
|
||||
AcfTXzgCAT9yZ31VNy4Z2AAAAZ65x58NAAAEAwBHMEUCIQCn753LSIK5ODnAw8za
|
||||
2BUf7PZQhtwhe1I0fEfrFjwcAAIgPZtnV83cNV8UK9zGQhWso4QX/oK+ziXQMuP5
|
||||
+lApPpcwDQYJKoZIhvcNAQELBQADggEBAKceFp1JWPgaR5TGrMXQNY0qgRPhY0ZC
|
||||
MSVLqtbgWPeODl3p+BTpLJ/taGR1v1mlL/2cM+EU/RQVDobYUu91Td0Tibp1T8vv
|
||||
KKgnGTj8fVTzimTR5YsFHKc0XFqHpbneArZFb9tbMnRLheDjAqJ3Xs5H5t/XNDSi
|
||||
vasqxdvAYZPP+7JZPRrFqq/fPZR79NRz7kl45YiQX199d/8oPRVFRrq1TGtp41Nk
|
||||
7rP31tq0YZDpBHXXmpYXmOZalWBHoASnZJDq0+H5bSBzNVHR25eOZe0dt5lWMzrf
|
||||
bzFMatWnPKI/aLhc3GGZAxyy7PCRA5pJGB+qnaJAakODvB02VvJey/Y=
|
||||
BAIwADCCAXwGCisGAQQB1nkCBAIEggFsBIIBaAFmAHUAwjF+V0UZo0XufzjespBB
|
||||
68fCIVoiv3/Vta12mtkOUs0AAAGgebrkkgAABAMARjBEAiA3QXzJe/8VFDnVdr/6
|
||||
BRerjq1qVsc61eFPJfMdmgbeCAIgTTMUJI0RnOIX7O0umzAWJa6T4lljD2+2ybyw
|
||||
FNTGoaQAdgDYCVU7lE96/8gWGW+UT4WrsPj8XodVJg8V0S5yu0VLFAAAAaB5uuSb
|
||||
AAAEAwBHMEUCIDb3e4Fb75I9JRnvfM5q8/iPLgs4u/NPdH4brm//kO9KAiEAujgF
|
||||
VeUmRxadsYgF1pVRytH2vURymXUvcTtZfWLbk/YAdQCUTkOH+uzB74HzGSQmqBhl
|
||||
AcfTXzgCAT9yZ31VNy4Z2AAAAaB5uuSbAAAEAwBGMEQCIGyiorlBMD7UARZvFGRz
|
||||
VsPQH+oqh3cEtHq9t/EdDFH7AiAb7dllHHDE1zJrf41OuXOHQZZrA5VBNPpBu1Lj
|
||||
avU6kzANBgkqhkiG9w0BAQsFAAOCAQEADDNB+zpouXcPD43XUYYE8dOpqFCHXGpe
|
||||
MOn44OGPpfU/DvsQgsMZl//bdIcSlmuyHIr/Z5CojN/sCiyCmqnM+vSRMjcjoqxU
|
||||
y7Ly0df9moxXprB20/XiWhCMSy0+bK051Kk4Lh3O0jWKGe2edEUEzwb1vvyhDylV
|
||||
skd347/BUVGEf+PGT4NqI2ImWoUBc73C8oZ6eHe/bHAiESOI1RnUefOUw3G8El2o
|
||||
t5DUflbkT1+Gzi/NgxvnSwbuvsf77mA9DOg8F6wwsPk4je+ekGycrzoVj/hnzy+h
|
||||
4BT6OiBxzBkRa7TLMblQAPiDTXkbP41ZzLNYnCN8XwpXRvrt29Hf6g==
|
||||
-----END CERTIFICATE-----
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIEqjCCA5KgAwIBAgIQDeD/te5iy2EQn2CMnO1e0zANBgkqhkiG9w0BAQsFADBh
|
||||
|
||||
Reference in New Issue
Block a user