feat: 完成 issue #166 [A3] iAOP API 客户端封装(infer.ts LLM 网关 + templates.ts 模板 JSON + plans 迁入 public)
This commit is contained in:
@@ -0,0 +1,37 @@
|
|||||||
|
{
|
||||||
|
"subscribe": "alarm_stream",
|
||||||
|
"severityStyles": {
|
||||||
|
"P0": {
|
||||||
|
"fg": "#ff4d4f",
|
||||||
|
"bg": "rgba(255,77,79,0.10)",
|
||||||
|
"icon": "alert-octagon",
|
||||||
|
"border": "#ff4d4f"
|
||||||
|
},
|
||||||
|
"P1": {
|
||||||
|
"fg": "#faad14",
|
||||||
|
"bg": "rgba(250,173,20,0.10)",
|
||||||
|
"icon": "alert-triangle",
|
||||||
|
"border": "#faad14"
|
||||||
|
},
|
||||||
|
"P2": {
|
||||||
|
"fg": "#1677ff",
|
||||||
|
"bg": "rgba(22,119,255,0.08)",
|
||||||
|
"icon": "info"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"rulesSource": {
|
||||||
|
"kind": "asset",
|
||||||
|
"ref": "ti-cl4/impurity-forecast/config/alert_rules.template.yaml"
|
||||||
|
},
|
||||||
|
"showSop": true,
|
||||||
|
"requireAck": true,
|
||||||
|
"muteLower": "P2",
|
||||||
|
"groupBy": "severity",
|
||||||
|
"sortBy": "severity",
|
||||||
|
"maxItems": 50,
|
||||||
|
"thresholdsSource": {
|
||||||
|
"kind": "asset",
|
||||||
|
"ref": "ti-cl4/impurity-forecast/config/alert_rules.template.yaml"
|
||||||
|
},
|
||||||
|
"ackTimeoutS": 300
|
||||||
|
}
|
||||||
@@ -0,0 +1,236 @@
|
|||||||
|
{
|
||||||
|
"$schema": "iAOP-cockpit-layout-v1",
|
||||||
|
"title": "吸附树脂车间驾驶舱",
|
||||||
|
"theme": "light",
|
||||||
|
"themeTokens": {
|
||||||
|
"--cockpit-bg": "#f0f2f5",
|
||||||
|
"--cockpit-surface": "#ffffff",
|
||||||
|
"--cockpit-fg": "rgba(0,0,0,0.88)",
|
||||||
|
"--cockpit-fg-muted": "rgba(0,0,0,0.45)",
|
||||||
|
"--cockpit-accent": "#1677ff",
|
||||||
|
"--cockpit-warn": "#faad14",
|
||||||
|
"--cockpit-grid-line": "rgba(5,5,5,0.06)"
|
||||||
|
},
|
||||||
|
"grid": {
|
||||||
|
"columns": 12
|
||||||
|
},
|
||||||
|
"widgets": [
|
||||||
|
{
|
||||||
|
"id": "process_view-0",
|
||||||
|
"component": "ProcessView",
|
||||||
|
"type": "process_view",
|
||||||
|
"grid": {
|
||||||
|
"columnStart": 1,
|
||||||
|
"columnSpan": 12,
|
||||||
|
"rowStart": 1,
|
||||||
|
"rowSpan": 4,
|
||||||
|
"widthPct": 100.0,
|
||||||
|
"style": "grid-column: 1 / span 12; grid-row: 1 / span 4;"
|
||||||
|
},
|
||||||
|
"description": "四状态工艺流程(合成 → 交联 → 洗涤 → 干燥)",
|
||||||
|
"props": {
|
||||||
|
"src": "resin_four_state.svg",
|
||||||
|
"states": [
|
||||||
|
"running",
|
||||||
|
"warning",
|
||||||
|
"alarm",
|
||||||
|
"offline"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"perf": {
|
||||||
|
"virtualScroll": false,
|
||||||
|
"downsample": false,
|
||||||
|
"worker": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "trend-1",
|
||||||
|
"component": "TrendChart",
|
||||||
|
"type": "trend",
|
||||||
|
"grid": {
|
||||||
|
"columnStart": 1,
|
||||||
|
"columnSpan": 6,
|
||||||
|
"rowStart": 5,
|
||||||
|
"rowSpan": 2,
|
||||||
|
"widthPct": 50.0,
|
||||||
|
"style": "grid-column: 1 / span 6; grid-row: 5 / span 2;"
|
||||||
|
},
|
||||||
|
"description": "反应釜温度实时趋势",
|
||||||
|
"props": {
|
||||||
|
"series": "R-801.TEMP",
|
||||||
|
"window": "PT30M"
|
||||||
|
},
|
||||||
|
"perf": {
|
||||||
|
"virtualScroll": false,
|
||||||
|
"downsample": false,
|
||||||
|
"worker": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "trend-2",
|
||||||
|
"component": "TrendChart",
|
||||||
|
"type": "trend",
|
||||||
|
"grid": {
|
||||||
|
"columnStart": 7,
|
||||||
|
"columnSpan": 6,
|
||||||
|
"rowStart": 5,
|
||||||
|
"rowSpan": 2,
|
||||||
|
"widthPct": 50.0,
|
||||||
|
"style": "grid-column: 7 / span 6; grid-row: 5 / span 2;"
|
||||||
|
},
|
||||||
|
"description": "搅拌转速实时趋势",
|
||||||
|
"props": {
|
||||||
|
"series": "R-801.AGIT",
|
||||||
|
"window": "PT30M"
|
||||||
|
},
|
||||||
|
"perf": {
|
||||||
|
"virtualScroll": false,
|
||||||
|
"downsample": false,
|
||||||
|
"worker": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "kpi_card-3",
|
||||||
|
"component": "KpiCard",
|
||||||
|
"type": "kpi_card",
|
||||||
|
"grid": {
|
||||||
|
"columnStart": 1,
|
||||||
|
"columnSpan": 3,
|
||||||
|
"rowStart": 7,
|
||||||
|
"rowSpan": 2,
|
||||||
|
"widthPct": 25.0,
|
||||||
|
"style": "grid-column: 1 / span 3; grid-row: 7 / span 2;"
|
||||||
|
},
|
||||||
|
"description": "当批平均交换容量(mmol/g)",
|
||||||
|
"props": {
|
||||||
|
"metric": "resin_exchange_capacity",
|
||||||
|
"label": "交换容量"
|
||||||
|
},
|
||||||
|
"perf": {
|
||||||
|
"virtualScroll": false,
|
||||||
|
"downsample": false,
|
||||||
|
"worker": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "kpi_card-4",
|
||||||
|
"component": "KpiCard",
|
||||||
|
"type": "kpi_card",
|
||||||
|
"grid": {
|
||||||
|
"columnStart": 4,
|
||||||
|
"columnSpan": 3,
|
||||||
|
"rowStart": 7,
|
||||||
|
"rowSpan": 2,
|
||||||
|
"widthPct": 25.0,
|
||||||
|
"style": "grid-column: 4 / span 3; grid-row: 7 / span 2;"
|
||||||
|
},
|
||||||
|
"description": "当批平均交联度(%)",
|
||||||
|
"props": {
|
||||||
|
"metric": "resin_crosslink_degree",
|
||||||
|
"label": "交联度"
|
||||||
|
},
|
||||||
|
"perf": {
|
||||||
|
"virtualScroll": false,
|
||||||
|
"downsample": false,
|
||||||
|
"worker": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "kpi_card-5",
|
||||||
|
"component": "KpiCard",
|
||||||
|
"type": "kpi_card",
|
||||||
|
"grid": {
|
||||||
|
"columnStart": 7,
|
||||||
|
"columnSpan": 3,
|
||||||
|
"rowStart": 7,
|
||||||
|
"rowSpan": 2,
|
||||||
|
"widthPct": 25.0,
|
||||||
|
"style": "grid-column: 7 / span 3; grid-row: 7 / span 2;"
|
||||||
|
},
|
||||||
|
"description": "当批产率(%)",
|
||||||
|
"props": {
|
||||||
|
"metric": "batch_yield",
|
||||||
|
"label": "批产率"
|
||||||
|
},
|
||||||
|
"perf": {
|
||||||
|
"virtualScroll": false,
|
||||||
|
"downsample": false,
|
||||||
|
"worker": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "kpi_card-6",
|
||||||
|
"component": "KpiCard",
|
||||||
|
"type": "kpi_card",
|
||||||
|
"grid": {
|
||||||
|
"columnStart": 10,
|
||||||
|
"columnSpan": 3,
|
||||||
|
"rowStart": 7,
|
||||||
|
"rowSpan": 2,
|
||||||
|
"widthPct": 25.0,
|
||||||
|
"style": "grid-column: 10 / span 3; grid-row: 7 / span 2;"
|
||||||
|
},
|
||||||
|
"description": "单吨树脂综合能耗(kWh/t)",
|
||||||
|
"props": {
|
||||||
|
"metric": "energy_per_ton",
|
||||||
|
"label": "单吨能耗"
|
||||||
|
},
|
||||||
|
"perf": {
|
||||||
|
"virtualScroll": false,
|
||||||
|
"downsample": false,
|
||||||
|
"worker": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "alarm_panel-7",
|
||||||
|
"component": "AlarmPanel",
|
||||||
|
"type": "alarm_panel",
|
||||||
|
"grid": {
|
||||||
|
"columnStart": 1,
|
||||||
|
"columnSpan": 9,
|
||||||
|
"rowStart": 9,
|
||||||
|
"rowSpan": 3,
|
||||||
|
"widthPct": 75.0,
|
||||||
|
"style": "grid-column: 1 / span 9; grid-row: 9 / span 3;"
|
||||||
|
},
|
||||||
|
"description": "告警面板(温度/交联度/质量预测异常)",
|
||||||
|
"props": {
|
||||||
|
"subscribe": "alarm_stream"
|
||||||
|
},
|
||||||
|
"perf": {
|
||||||
|
"virtualScroll": false,
|
||||||
|
"downsample": false,
|
||||||
|
"worker": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "nl_query-8",
|
||||||
|
"component": "NlQuery",
|
||||||
|
"type": "nl_query",
|
||||||
|
"grid": {
|
||||||
|
"columnStart": 10,
|
||||||
|
"columnSpan": 3,
|
||||||
|
"rowStart": 9,
|
||||||
|
"rowSpan": 3,
|
||||||
|
"widthPct": 25.0,
|
||||||
|
"style": "grid-column: 10 / span 3; grid-row: 9 / span 3;"
|
||||||
|
},
|
||||||
|
"description": "自然语言查询入口(配方/质量/能耗问答)",
|
||||||
|
"props": {
|
||||||
|
"placeholder": "输入自然语言查询(配方 / 质量 / 能耗)"
|
||||||
|
},
|
||||||
|
"perf": {
|
||||||
|
"virtualScroll": false,
|
||||||
|
"downsample": false,
|
||||||
|
"worker": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"widgetCount": 9,
|
||||||
|
"perfFlags": {
|
||||||
|
"virtualScroll": false,
|
||||||
|
"downsample": false,
|
||||||
|
"worker": false
|
||||||
|
},
|
||||||
|
"perfHint": null
|
||||||
|
}
|
||||||
@@ -0,0 +1,260 @@
|
|||||||
|
{
|
||||||
|
"$schema": "iAOP-cockpit-layout-v1",
|
||||||
|
"title": "海绵钛车间驾驶舱",
|
||||||
|
"theme": "light",
|
||||||
|
"themeTokens": {
|
||||||
|
"--cockpit-bg": "#f0f2f5",
|
||||||
|
"--cockpit-surface": "#ffffff",
|
||||||
|
"--cockpit-fg": "rgba(0,0,0,0.88)",
|
||||||
|
"--cockpit-fg-muted": "rgba(0,0,0,0.45)",
|
||||||
|
"--cockpit-accent": "#1677ff",
|
||||||
|
"--cockpit-warn": "#faad14",
|
||||||
|
"--cockpit-grid-line": "rgba(5,5,5,0.06)"
|
||||||
|
},
|
||||||
|
"grid": {
|
||||||
|
"columns": 12
|
||||||
|
},
|
||||||
|
"widgets": [
|
||||||
|
{
|
||||||
|
"id": "process_view-0",
|
||||||
|
"component": "ProcessView",
|
||||||
|
"type": "process_view",
|
||||||
|
"grid": {
|
||||||
|
"columnStart": 1,
|
||||||
|
"columnSpan": 12,
|
||||||
|
"rowStart": 1,
|
||||||
|
"rowSpan": 4,
|
||||||
|
"widthPct": 100.0,
|
||||||
|
"style": "grid-column: 1 / span 12; grid-row: 1 / span 4;"
|
||||||
|
},
|
||||||
|
"description": "四状态工艺流程(氯化 → 精制 → 还原 → 蒸馏)",
|
||||||
|
"props": {
|
||||||
|
"src": "ti_four_state.svg",
|
||||||
|
"states": [
|
||||||
|
"running",
|
||||||
|
"warning",
|
||||||
|
"alarm",
|
||||||
|
"offline"
|
||||||
|
],
|
||||||
|
"stages": [
|
||||||
|
{
|
||||||
|
"id": "chlorination",
|
||||||
|
"name": "氯化",
|
||||||
|
"device": "CLF-01",
|
||||||
|
"order": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "purification",
|
||||||
|
"name": "精制",
|
||||||
|
"order": 2
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "reduction",
|
||||||
|
"name": "还原",
|
||||||
|
"device": "RF-01",
|
||||||
|
"order": 3
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "distillation",
|
||||||
|
"name": "蒸馏",
|
||||||
|
"order": 4
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"perf": {
|
||||||
|
"virtualScroll": false,
|
||||||
|
"downsample": false,
|
||||||
|
"worker": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "trend-1",
|
||||||
|
"component": "TrendChart",
|
||||||
|
"type": "trend",
|
||||||
|
"grid": {
|
||||||
|
"columnStart": 1,
|
||||||
|
"columnSpan": 6,
|
||||||
|
"rowStart": 5,
|
||||||
|
"rowSpan": 2,
|
||||||
|
"widthPct": 50.0,
|
||||||
|
"style": "grid-column: 1 / span 6; grid-row: 5 / span 2;"
|
||||||
|
},
|
||||||
|
"description": "氯化炉温度实时趋势(沸腾氯化炉温 850±50℃)",
|
||||||
|
"props": {
|
||||||
|
"series": "CLF-01.TEMP",
|
||||||
|
"window": "PT30M"
|
||||||
|
},
|
||||||
|
"perf": {
|
||||||
|
"virtualScroll": false,
|
||||||
|
"downsample": false,
|
||||||
|
"worker": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "trend-2",
|
||||||
|
"component": "TrendChart",
|
||||||
|
"type": "trend",
|
||||||
|
"grid": {
|
||||||
|
"columnStart": 7,
|
||||||
|
"columnSpan": 6,
|
||||||
|
"rowStart": 5,
|
||||||
|
"rowSpan": 2,
|
||||||
|
"widthPct": 50.0,
|
||||||
|
"style": "grid-column: 7 / span 6; grid-row: 5 / span 2;"
|
||||||
|
},
|
||||||
|
"description": "氯气流量实时趋势(流态化监控)",
|
||||||
|
"props": {
|
||||||
|
"series": "CLF-01.CL2",
|
||||||
|
"window": "PT30M"
|
||||||
|
},
|
||||||
|
"perf": {
|
||||||
|
"virtualScroll": false,
|
||||||
|
"downsample": false,
|
||||||
|
"worker": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "kpi_card-3",
|
||||||
|
"component": "KpiCard",
|
||||||
|
"type": "kpi_card",
|
||||||
|
"grid": {
|
||||||
|
"columnStart": 1,
|
||||||
|
"columnSpan": 3,
|
||||||
|
"rowStart": 7,
|
||||||
|
"rowSpan": 2,
|
||||||
|
"widthPct": 25.0,
|
||||||
|
"style": "grid-column: 1 / span 3; grid-row: 7 / span 2;"
|
||||||
|
},
|
||||||
|
"description": "还原 TiCl₄ 纯度(%,工艺 ≥ 99.9%)",
|
||||||
|
"props": {
|
||||||
|
"metric": "ticl4_purity",
|
||||||
|
"label": "TiCl₄纯度"
|
||||||
|
},
|
||||||
|
"perf": {
|
||||||
|
"virtualScroll": false,
|
||||||
|
"downsample": false,
|
||||||
|
"worker": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "kpi_card-4",
|
||||||
|
"component": "KpiCard",
|
||||||
|
"type": "kpi_card",
|
||||||
|
"grid": {
|
||||||
|
"columnStart": 4,
|
||||||
|
"columnSpan": 3,
|
||||||
|
"rowStart": 7,
|
||||||
|
"rowSpan": 2,
|
||||||
|
"widthPct": 25.0,
|
||||||
|
"style": "grid-column: 4 / span 3; grid-row: 7 / span 2;"
|
||||||
|
},
|
||||||
|
"description": "还原杂质含量(%,越低越好)",
|
||||||
|
"props": {
|
||||||
|
"metric": "ticl4_impurity",
|
||||||
|
"label": "杂质含量"
|
||||||
|
},
|
||||||
|
"perf": {
|
||||||
|
"virtualScroll": false,
|
||||||
|
"downsample": false,
|
||||||
|
"worker": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "kpi_card-5",
|
||||||
|
"component": "KpiCard",
|
||||||
|
"type": "kpi_card",
|
||||||
|
"grid": {
|
||||||
|
"columnStart": 7,
|
||||||
|
"columnSpan": 3,
|
||||||
|
"rowStart": 7,
|
||||||
|
"rowSpan": 2,
|
||||||
|
"widthPct": 25.0,
|
||||||
|
"style": "grid-column: 7 / span 3; grid-row: 7 / span 2;"
|
||||||
|
},
|
||||||
|
"description": "车间累计电耗(kWh,单吨海绵钛综合能耗输入)",
|
||||||
|
"props": {
|
||||||
|
"metric": "energy_per_ton",
|
||||||
|
"label": "累计电耗"
|
||||||
|
},
|
||||||
|
"perf": {
|
||||||
|
"virtualScroll": false,
|
||||||
|
"downsample": false,
|
||||||
|
"worker": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "kpi_card-6",
|
||||||
|
"component": "KpiCard",
|
||||||
|
"type": "kpi_card",
|
||||||
|
"grid": {
|
||||||
|
"columnStart": 10,
|
||||||
|
"columnSpan": 3,
|
||||||
|
"rowStart": 7,
|
||||||
|
"rowSpan": 2,
|
||||||
|
"widthPct": 25.0,
|
||||||
|
"style": "grid-column: 10 / span 3; grid-row: 7 / span 2;"
|
||||||
|
},
|
||||||
|
"description": "蒸汽流量(t/h,公用工程监控)",
|
||||||
|
"props": {
|
||||||
|
"metric": "steam_flow",
|
||||||
|
"label": "蒸汽流量"
|
||||||
|
},
|
||||||
|
"perf": {
|
||||||
|
"virtualScroll": false,
|
||||||
|
"downsample": false,
|
||||||
|
"worker": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "alarm_panel-7",
|
||||||
|
"component": "AlarmPanel",
|
||||||
|
"type": "alarm_panel",
|
||||||
|
"grid": {
|
||||||
|
"columnStart": 1,
|
||||||
|
"columnSpan": 9,
|
||||||
|
"rowStart": 9,
|
||||||
|
"rowSpan": 3,
|
||||||
|
"widthPct": 75.0,
|
||||||
|
"style": "grid-column: 1 / span 9; grid-row: 9 / span 3;"
|
||||||
|
},
|
||||||
|
"description": "告警面板(氯化炉温/还原真空度/纯度/杂质异常)",
|
||||||
|
"props": {
|
||||||
|
"subscribe": "alarm_stream"
|
||||||
|
},
|
||||||
|
"perf": {
|
||||||
|
"virtualScroll": false,
|
||||||
|
"downsample": false,
|
||||||
|
"worker": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "nl_query-8",
|
||||||
|
"component": "NlQuery",
|
||||||
|
"type": "nl_query",
|
||||||
|
"grid": {
|
||||||
|
"columnStart": 10,
|
||||||
|
"columnSpan": 3,
|
||||||
|
"rowStart": 9,
|
||||||
|
"rowSpan": 3,
|
||||||
|
"widthPct": 25.0,
|
||||||
|
"style": "grid-column: 10 / span 3; grid-row: 9 / span 3;"
|
||||||
|
},
|
||||||
|
"description": "自然语言查询入口(工艺/质量/能耗问答)",
|
||||||
|
"props": {
|
||||||
|
"placeholder": "输入自然语言查询(配方 / 质量 / 能耗)"
|
||||||
|
},
|
||||||
|
"perf": {
|
||||||
|
"virtualScroll": false,
|
||||||
|
"downsample": false,
|
||||||
|
"worker": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"widgetCount": 9,
|
||||||
|
"perfFlags": {
|
||||||
|
"virtualScroll": false,
|
||||||
|
"downsample": false,
|
||||||
|
"worker": false
|
||||||
|
},
|
||||||
|
"perfHint": null
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
/**
|
||||||
|
* iAOP API 客户端出口(Epic #163 / issue #166 [A3])
|
||||||
|
*
|
||||||
|
* - infer.ts —— LLM 网关(/v1/*:health / models / chat/completions)
|
||||||
|
* - templates.ts —— 驾驶舱布局与告警面板模板 JSON(public/iaop/plans)
|
||||||
|
*/
|
||||||
|
export * from './infer';
|
||||||
|
export * from './templates';
|
||||||
@@ -0,0 +1,105 @@
|
|||||||
|
/**
|
||||||
|
* iAOP LLM 网关客户端(Epic #163 / issue #166 [A3])
|
||||||
|
*
|
||||||
|
* 对齐旧 `web/` 各模块的 INFER_BASE(nginx 反代 /v1/ → 推理服务 :30800):
|
||||||
|
* GET /v1/health —— 健康巡检
|
||||||
|
* GET /v1/models —— 模型列表
|
||||||
|
* POST /v1/chat/completions —— OpenAI 兼容对话(dry-run 占位 / 真实推理)
|
||||||
|
*
|
||||||
|
* 推理服务为 iAOP 独立通道(不要求 FBA JWT),故用原生 fetch 封装,
|
||||||
|
* 统一超时 / 错误信息 / 结果结构;FBA requestClient 仅用于 /fba/* 接口。
|
||||||
|
*/
|
||||||
|
import { useMessage } from '@vben/hooks';
|
||||||
|
|
||||||
|
const BASE_URL = '/v1';
|
||||||
|
const TIMEOUT_MS = 15000;
|
||||||
|
|
||||||
|
export interface InferResult {
|
||||||
|
text: string;
|
||||||
|
meta: Record<string, unknown>;
|
||||||
|
backend: string;
|
||||||
|
latencyMs: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ChatCompletionChoice {
|
||||||
|
index: number;
|
||||||
|
message: { role: string; content: string };
|
||||||
|
finish_reason: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ChatCompletionResponse {
|
||||||
|
id: string;
|
||||||
|
object: string;
|
||||||
|
choices: ChatCompletionChoice[];
|
||||||
|
model: string;
|
||||||
|
meta?: Record<string, unknown>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 带超时的 fetch */
|
||||||
|
async function fetchJson<T>(path: string, init?: RequestInit): Promise<T> {
|
||||||
|
const controller = new AbortController();
|
||||||
|
const timer = setTimeout(() => controller.abort(), TIMEOUT_MS);
|
||||||
|
try {
|
||||||
|
const resp = await fetch(`${BASE_URL}${path}`, {
|
||||||
|
...init,
|
||||||
|
signal: controller.signal,
|
||||||
|
headers: { 'Content-Type': 'application/json', ...(init?.headers || {}) },
|
||||||
|
});
|
||||||
|
if (!resp.ok) {
|
||||||
|
throw new Error(`推理服务 HTTP ${resp.status}`);
|
||||||
|
}
|
||||||
|
const text = await resp.text();
|
||||||
|
return (text ? JSON.parse(text) : {}) as T;
|
||||||
|
} catch (err) {
|
||||||
|
if (err instanceof DOMException && err.name === 'AbortError') {
|
||||||
|
throw new Error(`推理服务请求超时(>${TIMEOUT_MS / 1000}s)`);
|
||||||
|
}
|
||||||
|
throw err;
|
||||||
|
} finally {
|
||||||
|
clearTimeout(timer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 健康巡检(前端徽标展示;失败不抛出,由调用方降级) */
|
||||||
|
export async function fetchHealth(): Promise<Record<string, unknown> | null> {
|
||||||
|
try {
|
||||||
|
return await fetchJson<Record<string, unknown>>('/health');
|
||||||
|
} catch {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 模型列表 */
|
||||||
|
export async function fetchModels(): Promise<string[]> {
|
||||||
|
const data = await fetchJson<{ models?: string[] }>('/v1/models');
|
||||||
|
return data.models || [];
|
||||||
|
}
|
||||||
|
|
||||||
|
/** OpenAI 兼容对话(含 dry-run 占位响应) */
|
||||||
|
export async function chatCompletion(
|
||||||
|
prompt: string,
|
||||||
|
opts?: { model?: string; maxTokens?: number; temperature?: number },
|
||||||
|
): Promise<ChatCompletionResponse> {
|
||||||
|
const body = {
|
||||||
|
model: opts?.model || '',
|
||||||
|
prompt,
|
||||||
|
max_tokens: opts?.maxTokens || 1024,
|
||||||
|
temperature: opts?.temperature ?? 0.1,
|
||||||
|
};
|
||||||
|
return fetchJson<ChatCompletionResponse>('/v1/chat/completions', {
|
||||||
|
method: 'POST',
|
||||||
|
body: JSON.stringify(body),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 便捷:返回纯文本(B 系列页面统一使用) */
|
||||||
|
export async function ask(prompt: string, opts?: Parameters<typeof chatCompletion>[1]): Promise<InferResult> {
|
||||||
|
const resp = await chatCompletion(prompt, opts);
|
||||||
|
const msg = resp.choices?.[0]?.message?.content || '';
|
||||||
|
return {
|
||||||
|
text: msg,
|
||||||
|
meta: (resp.meta as Record<string, unknown>) || {},
|
||||||
|
backend: String((resp.meta as Record<string, unknown>)?.backend || 'gpu'),
|
||||||
|
latencyMs: 0,
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
/**
|
||||||
|
* iAOP 模板 JSON 加载器(Epic #163 / issue #166 [A3])
|
||||||
|
*
|
||||||
|
* 从 `public/iaop/plans/` 加载驾驶舱布局与告警面板模板资产(自 web/cockpit/plans 迁入):
|
||||||
|
* ti-cl4.json —— 海绵钛驾驶舱布局(RenderPlan,iAOP-cockpit-layout-v1)
|
||||||
|
* resin.json —— 树脂驾驶舱布局
|
||||||
|
* alarm_panel.ti.json —— 告警面板配置(severity 配色 / SOP / 确认)
|
||||||
|
*
|
||||||
|
* 统一:带缓存(会话内重复加载不重发请求)+ 结构校验 + 错误提示。
|
||||||
|
*/
|
||||||
|
import { useMessage } from '@vben/hooks';
|
||||||
|
|
||||||
|
export interface CockpitPlan {
|
||||||
|
$schema?: string;
|
||||||
|
title?: string;
|
||||||
|
theme?: string;
|
||||||
|
themeTokens?: Record<string, string>;
|
||||||
|
grid?: { rows?: number; cols?: number; gap?: number };
|
||||||
|
widgets?: Array<Record<string, unknown>>;
|
||||||
|
[k: string]: unknown;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AlarmPanelConfig {
|
||||||
|
subscribe?: string;
|
||||||
|
severityStyles?: Record<string, Record<string, string>>;
|
||||||
|
rulesSource?: string;
|
||||||
|
showSop?: boolean;
|
||||||
|
requireAck?: boolean;
|
||||||
|
muteLower?: number;
|
||||||
|
[k: string]: unknown;
|
||||||
|
}
|
||||||
|
|
||||||
|
const CACHE = new Map<string, unknown>();
|
||||||
|
|
||||||
|
async function loadJson<T>(name: string, base: string): Promise<T> {
|
||||||
|
const key = `${base}/${name}`;
|
||||||
|
if (CACHE.has(key)) {
|
||||||
|
return CACHE.get(key) as T;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const resp = await fetch(`${base}/${name}`);
|
||||||
|
if (!resp.ok) {
|
||||||
|
throw new Error(`模板 ${name} 加载失败(HTTP ${resp.status})`);
|
||||||
|
}
|
||||||
|
const data = (await resp.json()) as T;
|
||||||
|
CACHE.set(key, data);
|
||||||
|
return data;
|
||||||
|
} catch (err) {
|
||||||
|
const message = useMessage();
|
||||||
|
message.error(`模板 ${name} 加载失败`);
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 驾驶舱布局(按行业模板切换:ti-cl4 / resin) */
|
||||||
|
export function loadCockpitPlan(template: 'ti-cl4' | 'resin'): Promise<CockpitPlan> {
|
||||||
|
return loadJson<CockpitPlan>(`${template}.json`, '/iaop/plans');
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 告警面板配置 */
|
||||||
|
export function loadAlarmPanel(): Promise<AlarmPanelConfig> {
|
||||||
|
return loadJson<AlarmPanelConfig>('alarm_panel.ti.json', '/iaop/plans');
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 清理模板缓存(发布新版本后调用) */
|
||||||
|
export function clearTemplateCache(): void {
|
||||||
|
CACHE.clear();
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user