15 changed files with 412 additions and 359 deletions
+2 -2
View File
@@ -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/
+2
View File
@@ -24,6 +24,8 @@ export interface VOSessionInfoResult {
totalTokens: number;
totalFee: number;
errorMsg: string;
// 失败详细原因(工作流节点失败详情,如「执行工作流失败: ...节点:生成视频, 失败原因:...」),比 errorMsg 更具体
error?: string;
createdAt: string;
}
+44 -6
View File
@@ -171,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>
@@ -268,6 +275,15 @@ const loadChargeModes = async () => {
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);
@@ -798,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;
+8 -6
View File
@@ -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,
@@ -1140,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,保证实时产出可删除(与回显路径一致)
@@ -1657,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)) {
@@ -1675,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
+13
View File
@@ -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,7 +67,7 @@
<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">
<template v-if="subject.modelType === 'reason'">
@@ -78,23 +105,23 @@
</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>
@@ -102,14 +129,23 @@
<!-- 媒体价格模板 token 单位媒体命中条件已移除改为对特定媒体类型设独立覆盖价 mediaPrices -->
<div v-if="isTokenUnit" class="media-price-section">
<div class="rc-sec price">媒体价格模板 mediaPrices可选</div>
<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>
<el-input-number v-model="mp.input" :min="0" :precision="6" :step="0.0001" :controls="false" class="mp-num" placeholder="input" />
<el-input-number v-model="mp.output" :min="0" :precision="6" :step="0.0001" :controls="false" class="mp-num" placeholder="output" />
<el-input-number v-model="mp.cacheHit" :min="0" :precision="6" :step="0.0001" :controls="false" class="mp-num" placeholder="cacheHit" />
<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>
@@ -125,37 +161,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>
@@ -405,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 {
@@ -461,13 +464,6 @@ defineExpose({ save });
margin-top: 10px;
}
.ctrl-input {
width: 220px;
}
.ctrl-select {
width: 220px;
}
.rule-card {
border: 1px solid #e4e7ed;
@@ -540,7 +536,7 @@ defineExpose({ save });
.media-price-row {
display: flex;
align-items: center;
align-items: flex-end;
gap: 8px;
margin-bottom: 8px;
@@ -549,9 +545,21 @@ defineExpose({ save });
flex-shrink: 0;
}
.mp-num {
.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%;
}
}
}
@@ -575,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>
+3 -3
View File
@@ -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}`;
});
/** 详情请求序号,防止快速切换时旧响应覆盖当前对象 */
+11 -11
View File
@@ -240,21 +240,21 @@ 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_PRICE_OPTIONS: { value: 'text' | 'audio' | 'video' | 'image'; label: string }[] = [
{ value: 'text', label: '文本 text' },
{ value: 'audio', label: '音频 audio' },
{ value: 'video', label: '视频 video' },
{ value: 'image', label: '图片 image' },
{ value: 'text', label: '文本' },
{ value: 'audio', label: '音频' },
{ value: 'video', label: '视频' },
{ value: 'image', label: '图片' },
];
/** outputAudio 命中条件(video):给用户展示中文(有声/无声) */
+25 -25
View File
@@ -1,27 +1,27 @@
-----BEGIN RSA PRIVATE KEY-----
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
-----END RSA PRIVATE KEY-----
+23 -23
View File
@@ -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 @@ BwMCMIGABggrBgEFBQcBAQR0MHIwJAYIKwYBBQUHMAGGGGh0dHA6Ly9vY3NwLmRp
Z2ljZXJ0LmNvbTBKBggrBgEFBQcwAoY+aHR0cDovL2NhY2VydHMuZGlnaWNlcnQu
Y29tL0VuY3J5cHRpb25FdmVyeXdoZXJlRFZUTFNDQS1HMi5jcnQwDAYDVR0TAQH/
BAIwADCCAX0GCisGAQQB1nkCBAIEggFtBIIBaQFnAHUAwjF+V0UZo0XufzjespBB
68fCIVoiv3/Vta12mtkOUs0AAAGesEYy8QAABAMARjBEAiBD7255l5PRI6mZXWxU
iC7txvWxkLlBgVFzAkRrMW7RXQIgZyupslAslfGDqDMk9H948AFZGe0JwB6YiuXK
lx1sox0AdgDYCVU7lE96/8gWGW+UT4WrsPj8XodVJg8V0S5yu0VLFAAAAZ6wRjNI
AAAEAwBHMEUCIBqfIfl4sL0ME60NI0KG6JIHImcJjR5qkLGDjCp3sEhrAiEA4cvy
yFaz3BSjjQa+UspsqgfKxeTjCLJq5pLbDcgBgGQAdgCUTkOH+uzB74HzGSQmqBhl
AcfTXzgCAT9yZ31VNy4Z2AAAAZ6wRjL3AAAEAwBHMEUCIQDIIa8mLpO6BmhgCecw
pI+QNznn4lhIxmtJQyHmZo7qRAIgFPq9xlLLqqe5/+WgcNwoawggpi8skhg8t/yT
JJ5TmV0wDQYJKoZIhvcNAQELBQADggEBAOeiZrRwpNh/bNIuKe+1vWBrLHu2Zj70
cKAVUhT08ZAl8scEYusbuqtSAq+oz9sm595YyPgmrB0wSZEMTd/+pUy5GrrJAXal
kOx1a3TBxCvpK9F4T7KmBaBWA5HuljHfw9833jXi7v9jLYShDjyNRzSWeKl41kdt
Hq4CbDLXp9XVzVPBlOongGZV/6cDrvy4hoJcBq8UANU/QWw0PaJQWxPwHcULekBw
AefTX7JwKbHcZ3gGV4EzJU/eoycBOu9VsYgJu9U+p1nNjYHwS8RQjij+cgs8r5Wx
r4+wYkdN2767iK2+NRRdQD/ZqBMTgSZ6AXCM7gS8oP/ylLwnzyq7ikc=
68fCIVoiv3/Vta12mtkOUs0AAAGgebrk6AAABAMARjBEAiAgFMcnwhg3c0h/LxiP
nZ+YWMLYgXC9ePXRZ+F0CFotEAIgR5H6BlW0mUMwOr5n82ukGvdlme/gkKINeQA2
r33cbtQAdgDYCVU7lE96/8gWGW+UT4WrsPj8XodVJg8V0S5yu0VLFAAAAaB5uuUP
AAAEAwBHMEUCIHMpgTZWPL/owVG92G0tYuZEiCPkElXskLLFygZOqbKkAiEA7RGC
gJ+Gd+XWH5n1kvVyofTnRccp9PVwc7gYUzfNQU0AdgCUTkOH+uzB74HzGSQmqBhl
AcfTXzgCAT9yZ31VNy4Z2AAAAaB5uuT1AAAEAwBHMEUCIAzth4PgMSzYLoUFxNAu
ErCqHKZNRACffuKdr1sKodLJAiEAh67qPx8Pfr87kSR2bnMYnpBcXjJ38S4cz2RL
v6dFDaowDQYJKoZIhvcNAQELBQADggEBABPRvwySa7ou11Mlf5265pizxyR6qhG8
ywlCy0IImyFBZ/mu4YzXrLbBVB9c/KNGiLxCAUBg+rEZsbVmPW//cfo31uUtzenb
DhDOXqVlULG1nwDkhcCw90jMGUOl5X6zKZ2KSXd3Uq++EVTjuqXC3fbheocXLNZr
22DWhQ7wfFvogw6bKCkS15y4WGjWa2ut4Avso9pzCA+HDzJCTMLgJStOZNArRkZQ
u3OhuLjnbG5kWQiwVXU/ixxOaL4JCwZWKueMhumux20Ww6bvdj+wx+l4M5Dg+qXp
D3bLSWOxFnv2SqxFf1AA/7hf5rqomO8XhsFGSBV/ESGoKvAQyo5gnRw=
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIEqjCCA5KgAwIBAgIQDeD/te5iy2EQn2CMnO1e0zANBgkqhkiG9w0BAQsFADBh
+25 -25
View File
@@ -1,27 +1,27 @@
-----BEGIN RSA PRIVATE KEY-----
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
-----END RSA PRIVATE KEY-----
+24 -24
View File
@@ -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