48 Commits

Author SHA1 Message Date
0344ff2862 cicd
All checks were successful
自动部署 / 部署应用 (push) Successful in 5s
2026-06-02 23:40:31 +08:00
17bae862ed cicd
All checks were successful
自动部署 / 部署应用 (push) Successful in 6s
2026-06-02 23:04:38 +08:00
fb64b1a768 cicd
Some checks failed
自动部署 / 部署应用 (push) Failing after 6s
2026-06-02 23:03:08 +08:00
05c1f339e6 cicd
Some checks failed
自动部署 / 部署应用 (push) Failing after 1s
2026-06-02 23:00:25 +08:00
f822e319ba cicd 2026-06-02 23:00:03 +08:00
2962865857 cicd
Some checks failed
自动部署 / 部署应用 (push) Failing after 1s
2026-06-02 22:59:07 +08:00
5415da5b6c cicd
Some checks failed
自动部署 / 部署应用 (push) Failing after 1s
2026-06-02 22:55:39 +08:00
5149b68237 cicd
Some checks failed
自动部署 / 部署应用 (push) Failing after 1s
2026-06-02 22:52:45 +08:00
a6e7cca92b cicd 2026-06-02 22:52:36 +08:00
2c3700f4f8 cicd
Some checks failed
自动部署 / 部署应用 (push) Failing after 1m31s
2026-06-02 22:49:41 +08:00
1bcc63d5de cicd
Some checks failed
自动部署 / 部署应用 (push) Failing after 1s
2026-06-02 22:48:10 +08:00
9db3dfe675 cicd
Some checks failed
自动部署 / 部署应用 (push) Failing after 1s
2026-06-02 22:45:43 +08:00
9ee93bea60 cicd 2026-06-02 22:44:55 +08:00
04fd9f8c0e cicd
Some checks failed
自动部署 / 部署应用 (push) Failing after 1s
2026-06-02 22:44:03 +08:00
5f8e55a3a3 cicd
Some checks failed
自动部署 / 部署应用 (push) Failing after 1s
2026-06-02 22:41:52 +08:00
01ae26e869 cicd
Some checks failed
自动部署 / 部署应用 (push) Failing after 1s
2026-06-02 22:39:26 +08:00
bf61c4af4e cicd
Some checks failed
自动部署 / 部署应用 (push) Failing after 1s
2026-06-02 22:36:36 +08:00
f566a33865 cicd
Some checks failed
自动部署 / 部署应用 (push) Failing after 1s
2026-06-02 22:35:05 +08:00
d96fac324b cicd
Some checks failed
自动部署 / 部署应用 (push) Failing after 31s
2026-06-02 22:32:46 +08:00
c8129ce658 cicd
Some checks are pending
自动部署 / 部署应用 (push) Waiting to run
2026-06-02 22:31:23 +08:00
5b0783097d cicd
Some checks failed
自动部署 / 部署应用 (push) Has been cancelled
2026-06-02 22:29:18 +08:00
9cde482dfe cicd
Some checks failed
自动部署 / 部署应用 (push) Failing after 17m15s
2026-06-02 22:10:14 +08:00
8e793ea37f cicd
Some checks failed
自动部署 / deploy (push) Failing after 38s
2026-06-02 22:03:08 +08:00
dde1212a57 cicd
Some checks failed
自动部署 / deploy (push) Failing after 24s
2026-06-02 22:00:36 +08:00
6527eec133 https证书替换 2026-06-02 21:59:45 +08:00
2109903ba2 https证书替换 2026-06-02 21:58:10 +08:00
46dc2d4682 https证书替换
Some checks failed
部署 admin-ui 到 K3s / deploy (push) Failing after 22s
2026-06-02 21:54:58 +08:00
3c9d0dae22 https证书替换
Some checks failed
部署 admin-ui 到 K3s / deploy (push) Failing after 4s
2026-06-02 21:53:48 +08:00
b6a81848ea https证书替换
Some checks failed
部署 admin-ui 到 K3s / deploy (push) Failing after 4s
2026-06-02 21:52:57 +08:00
096fbb661d https证书替换
Some checks failed
部署 admin-ui 到 K3s / deploy (push) Failing after 5s
2026-06-02 21:51:45 +08:00
035c4ab000 https证书替换
Some checks failed
部署 admin-ui 到 K3s / deploy (push) Failing after 26s
2026-06-02 21:47:26 +08:00
1efff107f9 https证书替换
Some checks failed
部署 admin-ui 到 K3s / deploy (push) Failing after 23s
2026-06-02 21:44:45 +08:00
1795cd73c7 https证书替换
Some checks failed
部署 admin-ui 到 K3s / deploy (push) Failing after 43s
2026-06-02 21:36:43 +08:00
5f21126a62 https证书替换
Some checks failed
部署 admin-ui 到 K3s / deploy (push) Has been cancelled
2026-06-02 18:22:25 +08:00
2085588556 https证书替换
Some checks failed
部署 admin-ui 到 K3s / deploy (push) Failing after 0s
2026-06-02 18:18:35 +08:00
025a67ce45 https证书替换 2026-06-02 18:17:26 +08:00
cb806e19b1 https证书替换
Some checks failed
部署 admin-ui 到 K3s / deploy (push) Failing after 31s
2026-06-02 18:11:36 +08:00
d4680d258c https证书替换
Some checks failed
部署 admin-ui 到 K3s / deploy (push) Failing after 5s
2026-06-02 18:09:49 +08:00
a832883f17 https证书替换
Some checks failed
部署 admin-ui 到 K3s / deploy (push) Failing after 3s
2026-06-02 18:08:34 +08:00
c086285340 https证书替换
Some checks failed
部署 admin-ui 到 K3s / deploy (push) Failing after 22s
2026-06-02 18:05:30 +08:00
7fa05b0004 https证书替换
Some checks failed
部署 admin-ui 到 K3s / deploy (push) Failing after 36s
2026-06-02 18:03:09 +08:00
65cf893a5f https证书替换
Some checks failed
部署 admin-ui 到 K3s / deploy (push) Has been cancelled
2026-06-02 18:00:52 +08:00
e20ee8e065 https证书替换
Some checks failed
部署 admin-ui 到 K3s / deploy (push) Failing after 7s
2026-06-02 17:58:50 +08:00
7d8723715c https证书替换
Some checks failed
部署 admin-ui 到 K3s / deploy (push) Failing after 5s
2026-06-02 17:57:57 +08:00
49bb348707 https证书替换 2026-06-02 17:57:26 +08:00
ef6de1d12b https证书替换
Some checks failed
部署 admin-ui 到 K3s / deploy (push) Failing after 5s
2026-06-02 17:41:19 +08:00
c76a4e2018 ci/cd调整
Some checks failed
部署 admin-ui 到 K3s / deploy (push) Failing after 6s
2026-06-02 15:48:27 +08:00
e504f806a9 ci/cd调整 2026-06-02 15:46:05 +08:00
6 changed files with 19 additions and 177 deletions

View File

@@ -0,0 +1,11 @@
name: 自动部署
on:
push:
branches: [ master ]
jobs:
部署应用:
runs-on: ubuntu-latest
steps:
- name: 启动部署
run: |
docker exec gitea-runner /opt/deploy.sh

View File

@@ -1,18 +0,0 @@
name: 部署 admin-ui 到 K3s
on:
push:
branches:
- main
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: 拉取代码
uses: actions/checkout@v4
- name: 部署到 K3s
run: |
kubectl apply -f k8s/
kubectl rollout restart deployment admin-ui

View File

@@ -119,8 +119,6 @@ export interface ModelModuleItem {
/** 会话开关状态列表接口返回0 关 1 开;会话开关接口就绪后生效) */
chatSessionEnabled?: number;
enabled: number;
/** 是否异步 0-同步 1-异步依赖requestMapping */
isAsync?: number;
maxConcurrency: number;
queueLimit: number;
timeoutMs?: number;
@@ -130,7 +128,7 @@ export interface ModelModuleItem {
retryQueueMaxSeconds: number;
autoCleanSeconds: number;
remark?: string;
headMsg?: string | Record<string, string>;
headMsg?: string;
form?: ModelFormEntry[] | Record<string, { value: string }>;
requestMapping?: Record<string, unknown>;
responseMapping?: Record<string, unknown>;
@@ -152,12 +150,10 @@ export interface CreateModelParams {
operatorName?: string;
baseUrl: string;
httpMethod?: string;
headMsg?: Record<string, string>;
headMsg?: string;
isPrivate: number;
enabled: number;
isChatModel: number;
/** 是否异步 0-同步 1-异步依赖requestMapping默认0 */
isAsync: number;
apiKey?: string;
form: ModelFormEntry[];
requestMapping?: Record<string, unknown>;

View File

@@ -402,49 +402,16 @@ const handleCreatePrivateModel = async () => {
key: String(key),
value: String(value ?? ''),
}));
// Parse headMsg to Record<string, string> - it might be stored as string or already as object
let headMsgRecord: Record<string, string> = {};
if (builtInModel.headMsg && typeof builtInModel.headMsg === 'string') {
// Try to parse as JSON first (new format stored as string)
try {
const parsed = JSON.parse(builtInModel.headMsg);
if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
Object.entries(parsed).forEach(([k, v]) => {
headMsgRecord[k] = String(v);
});
}
} catch {
// If JSON parse fails, parse as old format "key1:value1,key2:value2"
const pairs = builtInModel.headMsg.split(',');
pairs.forEach((pair) => {
const idx = pair.indexOf(':');
if (idx === -1) return;
const key = pair.slice(0, idx).trim();
const value = pair.slice(idx + 1).trim();
if (key) {
headMsgRecord[key] = value;
}
});
}
} else if (builtInModel.headMsg && typeof builtInModel.headMsg === 'object' && !Array.isArray(builtInModel.headMsg)) {
// Already an object
Object.entries(builtInModel.headMsg).forEach(([k, v]) => {
headMsgRecord[k] = String(v);
});
}
const createParams: CreateModelParams = {
modelName: apiKeyForm.modelName,
modelType: builtInModel.modelType,
operatorName: builtInModel.operatorName || '',
baseUrl: builtInModel.baseUrl,
httpMethod: builtInModel.httpMethod || 'POST',
headMsg: headMsgRecord,
headMsg: builtInModel.headMsg || '',
isPrivate: builtInModel.isPrivate ?? 1,
enabled: builtInModel.enabled ?? 1,
isChatModel: builtInModel.isChatModel || 0,
isAsync: builtInModel.isAsync ?? 0,
apiKey: apiKeyForm.apiKey,
form: formList,
requestMapping: (builtInModel.requestMapping as Record<string, unknown>) || {},

View File

@@ -83,14 +83,6 @@
</el-radio-group>
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20">
<el-form-item label="执行模式" prop="isAsync">
<el-radio-group v-model="state.ruleForm.isAsync">
<el-radio :label="0">同步</el-radio>
<el-radio :label="1">异步</el-radio>
</el-radio-group>
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20">
<el-form-item label="请求头绑定" prop="headMsg">
<el-button @click="showHeaderDialog = true" style="width: 100%"> 配置请求头 ({{ state.headers.length }}) </el-button>
@@ -656,7 +648,6 @@ const state = reactive({
apiKey: '',
enabled: 1,
isChatModel: 0,
isAsync: 0, // 0-同步 1-异步默认0
maxConcurrency: 10,
queueLimit: 100,
timeoutSeconds: 30,
@@ -868,58 +859,7 @@ const fieldsToObject = (fields: Array<{ key: string; value: string }>) => {
return obj;
};
// 解析headers支持多种格式
// 1. 旧格式:逗号分隔的"key1:value1,key2:value2"字符串
// 2. JSON字符串格式化为JSON对象的字符串
// 3. 新格式直接是Record<string, string>对象
const parseHeaders = (raw: unknown): Array<{ key: string; value: string }> => {
if (!raw) return [];
// 如果是字符串先尝试解析为JSON如果失败则尝试解析为旧格式key:value,key2:value2
if (typeof raw === 'string') {
const trimmed = raw.trim();
if (!trimmed) return [];
// 试试JSON解析如果开头是{或者[说明是JSON
if (trimmed.startsWith('{') || trimmed.startsWith('[')) {
try {
const parsed = JSON.parse(trimmed);
return parseHeaders(parsed);
} catch {
// JSON解析失败回退到旧格式解析
return parseKeyValueString(trimmed);
}
}
// 不是JSON格式按旧格式解析
return parseKeyValueString(trimmed);
}
// 如果是数组格式 [{ key, value }]
if (Array.isArray(raw)) {
return raw
.filter((item) => item && (item.key !== undefined || item.value !== undefined))
.map((item) => ({
key: String(item.key ?? '').trim(),
value: String(item.value ?? '').trim(),
}));
}
// 如果是对象格式 { key: value }
if (typeof raw === 'object') {
const fields: Array<{ key: string; value: string }> = [];
Object.keys(raw as Record<string, unknown>).forEach((key) => {
let v = (raw as Record<string, unknown>)[key];
// 处理 { key: { value: value } } 格式(后端可能返回这种结构)
if (v && typeof v === 'object' && !Array.isArray(v) && 'value' in v) {
v = (v as { value: unknown }).value;
}
const value = String(v ?? '');
fields.push({ key: key.trim(), value });
});
return fields;
}
return [];
};
const parseHeaders = (headMsg: string) => parseKeyValueString(headMsg);
// 统一的字段解析函数支持数组、对象、JSON字符串
const parseFieldsUnified = (raw: unknown): Array<{ key: string; value: string }> => {
if (!raw) return [];
@@ -1258,16 +1198,6 @@ const fillFormFromDetailRow = (row: Record<string, unknown>) => {
? Math.floor(Number(row.timeoutMs) / 1000)
: 30;
const isPrivate = row.isPrivate !== undefined && row.isPrivate !== null ? Number(row.isPrivate) : 0;
// headMsg might already be a Record<string, string> object (new format) or a string (old format)
let ruleFormHeadMsg = '';
if (typeof row.headMsg === 'string') {
ruleFormHeadMsg = row.headMsg;
} else if (row.headMsg && typeof row.headMsg === 'object') {
// If it's already an object, stringify for compatibility
ruleFormHeadMsg = JSON.stringify(row.headMsg);
} else {
ruleFormHeadMsg = '';
}
state.ruleForm = {
id: row.id as string,
modelName: String(row.modelName ?? ''),
@@ -1277,12 +1207,11 @@ const fillFormFromDetailRow = (row: Record<string, unknown>) => {
httpMethod: String(row.httpMethod || 'POST'),
queryResponseType: String((row.queryConfig as Record<string, unknown>)?.responseType || 'sync'),
queryCallbackUrl: String((row.queryConfig as Record<string, unknown>)?.callbackUrl || ''),
headMsg: ruleFormHeadMsg,
headMsg: String(row.headMsg || ''),
isPrivate,
apiKey: isPrivate === 1 ? String(row.apiKey ?? '') : '',
enabled: Number(row.enabled ?? 1),
isChatModel: row.isChatModel !== undefined && row.isChatModel !== null ? Number(row.isChatModel) : 0,
isAsync: row.isAsync !== undefined && row.isAsync !== null ? Number(row.isAsync) : 0,
maxConcurrency: Number(row.maxConcurrency ?? 10),
queueLimit: Number(row.queueLimit ?? 100),
timeoutSeconds,
@@ -1295,7 +1224,7 @@ const fillFormFromDetailRow = (row: Record<string, unknown>) => {
responseTokenField: String(row.responseTokenField || ''),
tokenConfig: '{}',
};
state.headers = ensureKeyValueRows(parseHeaders(row.headMsg));
state.headers = ensureKeyValueRows(parseHeaders(String(row.headMsg || '')));
state.formFields = parseFormFieldsUnified(row.form);
state.requestMappingFields = ensureKeyValueRows(parseRequestMappingFields(row.requestMapping));
state.responseMappingFields = ensureKeyValueRows(parseResponseMappingFields(row.responseMapping));
@@ -1400,7 +1329,6 @@ const openDialog = async (type: string, row?: Record<string, unknown>) => {
apiKey: '',
enabled: 1,
isChatModel: 0,
isAsync: 0,
maxConcurrency: 10,
queueLimit: 100,
timeoutSeconds: 30,
@@ -1502,25 +1430,16 @@ const onSubmit = () => {
: undefined,
})) as unknown as ModelFormEntry[];
// 将headers转换为JSON对象格式
const headMsgObj: Record<string, string> = {};
state.headers
.filter((h) => h.key?.trim() && h.value?.trim())
.forEach((h) => {
headMsgObj[h.key.trim()] = h.value.trim();
});
const submitData: CreateModelParams = {
modelName: state.ruleForm.modelName,
modelType: state.ruleForm.modelType as number | string,
operatorName: state.ruleForm.operatorName,
baseUrl: state.ruleForm.baseUrl,
httpMethod: state.ruleForm.httpMethod || 'POST',
headMsg: headMsgObj,
headMsg: state.ruleForm.headMsg,
isPrivate: state.ruleForm.isPrivate,
enabled: state.ruleForm.enabled,
isChatModel: state.ruleForm.isChatModel,
isAsync: state.ruleForm.isAsync,
// 确保 API 密钥只在 isPrivate=1 时提交
apiKey: state.ruleForm.isPrivate === 1 ? String(state.ruleForm.apiKey ?? '').trim() : '',
form: processedFormFields,

View File

@@ -240,48 +240,15 @@ const handleCreatePrivateModelAndSetChat = async () => {
// 基于内置模型创建新模型(继承原模型的所有配置,只替换 apiKey
const builtInModel = builtInModelToClone.value;
// Parse headMsg to Record<string, string> - it might be stored as string or already as object
let headMsgRecord: Record<string, string> = {};
if (builtInModel.headMsg && typeof builtInModel.headMsg === 'string') {
// Try to parse as JSON first (new format stored as string)
try {
const parsed = JSON.parse(builtInModel.headMsg);
if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
Object.entries(parsed).forEach(([k, v]) => {
headMsgRecord[k] = String(v);
});
}
} catch {
// If JSON parse fails, parse as old format "key1:value1,key2:value2"
const pairs = builtInModel.headMsg.split(',');
pairs.forEach((pair: string) => {
const idx = pair.indexOf(':');
if (idx === -1) return;
const key = pair.slice(0, idx).trim();
const value = pair.slice(idx + 1).trim();
if (key) {
headMsgRecord[key] = value;
}
});
}
} else if (builtInModel.headMsg && typeof builtInModel.headMsg === 'object' && !Array.isArray(builtInModel.headMsg)) {
// Already an object
Object.entries(builtInModel.headMsg).forEach(([k, v]) => {
headMsgRecord[k] = String(v);
});
}
const createParams = {
modelName: apiKeyForm.modelName,
modelType: builtInModel.modelType,
baseUrl: builtInModel.baseUrl,
httpMethod: builtInModel.httpMethod || 'POST',
headMsg: headMsgRecord,
headMsg: builtInModel.headMsg || '',
isPrivate: builtInModel.isPrivate ?? 1,
enabled: builtInModel.enabled ?? 1,
isChatModel: 1, // 设置为会话模型
isAsync: builtInModel.isAsync ?? 0,
apiKey: apiKeyForm.apiKey,
form: builtInModel.form || {},
requestMapping: builtInModel.requestMapping || {},