mirror of
https://github.com/Ed1s0nZ/CyberStrikeAI.git
synced 2026-08-15 15:40:38 +02:00
Add files via upload
This commit is contained in:
@@ -1939,6 +1939,13 @@
|
|||||||
"openaiBaseUrlPlaceholder": "https://api.openai.com/v1",
|
"openaiBaseUrlPlaceholder": "https://api.openai.com/v1",
|
||||||
"openaiApiKeyPlaceholder": "Enter OpenAI API Key",
|
"openaiApiKeyPlaceholder": "Enter OpenAI API Key",
|
||||||
"modelPlaceholder": "gpt-4",
|
"modelPlaceholder": "gpt-4",
|
||||||
|
"fetchModels": "Fetch list",
|
||||||
|
"modelsListFetching": "Fetching model list...",
|
||||||
|
"modelsListSelectPlaceholder": "Select a model",
|
||||||
|
"modelsListSuccess": "Loaded {count} models — use the dropdown on the right, or type in the input",
|
||||||
|
"modelsListFailed": "Failed to fetch model list",
|
||||||
|
"modelsListNeedApiKey": "Please enter API Key first",
|
||||||
|
"modelsListClaudeHint": "Claude does not support auto model list; enter the model name manually",
|
||||||
"maxTotalTokens": "Max Context Tokens",
|
"maxTotalTokens": "Max Context Tokens",
|
||||||
"maxTotalTokensPlaceholder": "120000",
|
"maxTotalTokensPlaceholder": "120000",
|
||||||
"maxTotalTokensHint": "Shared by memory compression and attack chain building. Default: 120000",
|
"maxTotalTokensHint": "Shared by memory compression and attack chain building. Default: 120000",
|
||||||
|
|||||||
@@ -1927,6 +1927,13 @@
|
|||||||
"openaiBaseUrlPlaceholder": "https://api.openai.com/v1",
|
"openaiBaseUrlPlaceholder": "https://api.openai.com/v1",
|
||||||
"openaiApiKeyPlaceholder": "输入OpenAI API Key",
|
"openaiApiKeyPlaceholder": "输入OpenAI API Key",
|
||||||
"modelPlaceholder": "gpt-4",
|
"modelPlaceholder": "gpt-4",
|
||||||
|
"fetchModels": "获取列表",
|
||||||
|
"modelsListFetching": "正在获取模型列表...",
|
||||||
|
"modelsListSelectPlaceholder": "请选择模型",
|
||||||
|
"modelsListSuccess": "已加载 {count} 个模型,请用右侧下拉框选择,或继续在左侧输入",
|
||||||
|
"modelsListFailed": "获取模型列表失败",
|
||||||
|
"modelsListNeedApiKey": "请先填写 API Key",
|
||||||
|
"modelsListClaudeHint": "Claude 不支持自动获取模型列表,请手动填写",
|
||||||
"maxTotalTokens": "最大上下文 Token 数",
|
"maxTotalTokens": "最大上下文 Token 数",
|
||||||
"maxTotalTokensPlaceholder": "120000",
|
"maxTotalTokensPlaceholder": "120000",
|
||||||
"maxTotalTokensHint": "内存压缩和攻击链构建共用此配置,默认 120000",
|
"maxTotalTokensHint": "内存压缩和攻击链构建共用此配置,默认 120000",
|
||||||
|
|||||||
+207
-1
@@ -299,6 +299,7 @@ async function loadConfig(loadTools = true) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fillVisionConfigFromCurrent(currentConfig.vision || {});
|
fillVisionConfigFromCurrent(currentConfig.vision || {});
|
||||||
|
initModelListControls();
|
||||||
|
|
||||||
// 填充FOFA配置
|
// 填充FOFA配置
|
||||||
const fofa = currentConfig.fofa || {};
|
const fofa = currentConfig.fofa || {};
|
||||||
@@ -1569,9 +1570,214 @@ function syncVisionFormEnabled() {
|
|||||||
if (panel) {
|
if (panel) {
|
||||||
panel.style.opacity = enabled ? '1' : '0.55';
|
panel.style.opacity = enabled ? '1' : '0.55';
|
||||||
panel.querySelectorAll('input, select, textarea, a').forEach(el => {
|
panel.querySelectorAll('input, select, textarea, a').forEach(el => {
|
||||||
if (el.id === 'test-vision-btn') return;
|
if (el.id === 'test-vision-btn' || el.id === 'fetch-vision-models-btn' || el.id === 'vision-model-select') return;
|
||||||
el.disabled = !enabled;
|
el.disabled = !enabled;
|
||||||
});
|
});
|
||||||
|
syncModelListFetchButtons();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function initModelListControls() {
|
||||||
|
const providerEl = document.getElementById('openai-provider');
|
||||||
|
if (providerEl && !providerEl.dataset.modelListBound) {
|
||||||
|
providerEl.dataset.modelListBound = '1';
|
||||||
|
providerEl.addEventListener('change', syncModelListFetchButtons);
|
||||||
|
}
|
||||||
|
const visionProv = document.getElementById('vision-provider');
|
||||||
|
if (visionProv && !visionProv.dataset.modelListBound) {
|
||||||
|
visionProv.dataset.modelListBound = '1';
|
||||||
|
visionProv.addEventListener('change', syncModelListFetchButtons);
|
||||||
|
}
|
||||||
|
bindModelSelect('openai');
|
||||||
|
bindModelSelect('vision');
|
||||||
|
syncModelListFetchButtons();
|
||||||
|
}
|
||||||
|
|
||||||
|
function modelSelectIds(scope) {
|
||||||
|
if (scope === 'vision') {
|
||||||
|
return { selectId: 'vision-model-select', inputId: 'vision-model' };
|
||||||
|
}
|
||||||
|
return { selectId: 'openai-model-select', inputId: 'openai-model' };
|
||||||
|
}
|
||||||
|
|
||||||
|
function bindModelSelect(scope) {
|
||||||
|
const { selectId, inputId } = modelSelectIds(scope);
|
||||||
|
const select = document.getElementById(selectId);
|
||||||
|
if (!select || select.dataset.bound) return;
|
||||||
|
select.dataset.bound = '1';
|
||||||
|
select.addEventListener('change', function () {
|
||||||
|
if (!select.value) return;
|
||||||
|
const input = document.getElementById(inputId);
|
||||||
|
if (input) input.value = select.value;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function resolveModelListCredentials(scope) {
|
||||||
|
if (scope === 'vision') {
|
||||||
|
const vp = (document.getElementById('vision-provider')?.value || '').trim();
|
||||||
|
const provider = vp || document.getElementById('openai-provider')?.value || 'openai';
|
||||||
|
const baseUrl = (document.getElementById('vision-base-url')?.value || '').trim()
|
||||||
|
|| (document.getElementById('openai-base-url')?.value || '').trim();
|
||||||
|
const apiKey = (document.getElementById('vision-api-key')?.value || '').trim()
|
||||||
|
|| (document.getElementById('openai-api-key')?.value || '').trim();
|
||||||
|
return { provider, base_url: baseUrl, api_key: apiKey };
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
provider: document.getElementById('openai-provider')?.value || 'openai',
|
||||||
|
base_url: (document.getElementById('openai-base-url')?.value || '').trim(),
|
||||||
|
api_key: (document.getElementById('openai-api-key')?.value || '').trim()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function syncModelListFetchButtons() {
|
||||||
|
const tFn = typeof window.t === 'function' ? window.t : (k) => k;
|
||||||
|
const openaiProv = document.getElementById('openai-provider')?.value || 'openai';
|
||||||
|
const openaiBtn = document.getElementById('fetch-openai-models-btn');
|
||||||
|
const openaiHint = document.getElementById('fetch-openai-models-hint');
|
||||||
|
const openaiSelect = document.getElementById('openai-model-select');
|
||||||
|
const isClaudeOpenai = openaiProv === 'claude';
|
||||||
|
if (openaiBtn) {
|
||||||
|
openaiBtn.style.display = isClaudeOpenai ? 'none' : '';
|
||||||
|
}
|
||||||
|
if (openaiSelect && isClaudeOpenai) {
|
||||||
|
openaiSelect.style.display = 'none';
|
||||||
|
}
|
||||||
|
if (openaiHint) {
|
||||||
|
if (isClaudeOpenai) {
|
||||||
|
openaiHint.textContent = tFn('settingsBasic.modelsListClaudeHint');
|
||||||
|
openaiHint.style.display = '';
|
||||||
|
} else {
|
||||||
|
openaiHint.textContent = '';
|
||||||
|
openaiHint.style.display = 'none';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const vp = (document.getElementById('vision-provider')?.value || '').trim();
|
||||||
|
const visionEffectiveProv = vp || openaiProv;
|
||||||
|
const visionBtn = document.getElementById('fetch-vision-models-btn');
|
||||||
|
const visionHint = document.getElementById('fetch-vision-models-hint');
|
||||||
|
const visionSelect = document.getElementById('vision-model-select');
|
||||||
|
const isClaudeVision = visionEffectiveProv === 'claude';
|
||||||
|
if (visionBtn) {
|
||||||
|
visionBtn.style.display = isClaudeVision ? 'none' : '';
|
||||||
|
}
|
||||||
|
if (visionSelect && isClaudeVision) {
|
||||||
|
visionSelect.style.display = 'none';
|
||||||
|
}
|
||||||
|
if (visionHint) {
|
||||||
|
if (isClaudeVision) {
|
||||||
|
visionHint.textContent = tFn('settingsBasic.modelsListClaudeHint');
|
||||||
|
visionHint.style.display = '';
|
||||||
|
} else {
|
||||||
|
visionHint.textContent = '';
|
||||||
|
visionHint.style.display = 'none';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function populateModelSelect(scope, models, currentValue) {
|
||||||
|
const { selectId, inputId } = modelSelectIds(scope);
|
||||||
|
const select = document.getElementById(selectId);
|
||||||
|
const input = document.getElementById(inputId);
|
||||||
|
if (!select) return;
|
||||||
|
const tFn = typeof window.t === 'function' ? window.t : (k) => k;
|
||||||
|
select.innerHTML = '';
|
||||||
|
const placeholder = document.createElement('option');
|
||||||
|
placeholder.value = '';
|
||||||
|
placeholder.disabled = true;
|
||||||
|
placeholder.textContent = tFn('settingsBasic.modelsListSelectPlaceholder');
|
||||||
|
select.appendChild(placeholder);
|
||||||
|
|
||||||
|
const seen = new Set();
|
||||||
|
const addOption = (id) => {
|
||||||
|
const val = (id || '').trim();
|
||||||
|
if (!val || seen.has(val)) return;
|
||||||
|
seen.add(val);
|
||||||
|
const opt = document.createElement('option');
|
||||||
|
opt.value = val;
|
||||||
|
opt.textContent = val;
|
||||||
|
select.appendChild(opt);
|
||||||
|
};
|
||||||
|
(models || []).forEach(addOption);
|
||||||
|
const cur = (currentValue || (input && input.value) || '').trim();
|
||||||
|
if (cur && seen.has(cur)) {
|
||||||
|
select.value = cur;
|
||||||
|
} else {
|
||||||
|
select.value = '';
|
||||||
|
}
|
||||||
|
select.style.display = select.options.length > 1 ? '' : 'none';
|
||||||
|
}
|
||||||
|
|
||||||
|
async function fetchModelList(scope) {
|
||||||
|
const tFn = typeof window.t === 'function' ? window.t : (k) => k;
|
||||||
|
const creds = resolveModelListCredentials(scope);
|
||||||
|
const btnId = scope === 'vision' ? 'fetch-vision-models-btn' : 'fetch-openai-models-btn';
|
||||||
|
const resultId = scope === 'vision' ? 'fetch-vision-models-result' : 'fetch-openai-models-result';
|
||||||
|
const inputId = scope === 'vision' ? 'vision-model' : 'openai-model';
|
||||||
|
const btn = document.getElementById(btnId);
|
||||||
|
const resultEl = document.getElementById(resultId);
|
||||||
|
const inputEl = document.getElementById(inputId);
|
||||||
|
|
||||||
|
if (creds.provider === 'claude') {
|
||||||
|
if (resultEl) {
|
||||||
|
resultEl.textContent = tFn('settingsBasic.modelsListClaudeHint');
|
||||||
|
resultEl.style.color = 'var(--text-muted, #718096)';
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!creds.api_key) {
|
||||||
|
if (resultEl) {
|
||||||
|
resultEl.textContent = tFn('settingsBasic.modelsListNeedApiKey');
|
||||||
|
resultEl.style.color = 'var(--error-color, #e53e3e)';
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (btn) {
|
||||||
|
btn.style.pointerEvents = 'none';
|
||||||
|
btn.style.opacity = '0.5';
|
||||||
|
}
|
||||||
|
if (resultEl) {
|
||||||
|
resultEl.textContent = tFn('settingsBasic.modelsListFetching');
|
||||||
|
resultEl.style.color = 'var(--text-muted, #718096)';
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await apiFetch('/api/config/list-models', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify(creds)
|
||||||
|
});
|
||||||
|
const result = await response.json();
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(result.error || '请求失败');
|
||||||
|
}
|
||||||
|
if (!result.success) {
|
||||||
|
if (resultEl) {
|
||||||
|
resultEl.textContent = (result.supported === false
|
||||||
|
? tFn('settingsBasic.modelsListClaudeHint')
|
||||||
|
: tFn('settingsBasic.modelsListFailed')) + ': ' + (result.error || '');
|
||||||
|
resultEl.style.color = 'var(--error-color, #e53e3e)';
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const currentValue = inputEl ? inputEl.value.trim() : '';
|
||||||
|
populateModelSelect(scope, result.models || [], currentValue);
|
||||||
|
if (resultEl) {
|
||||||
|
const count = result.count != null ? result.count : (result.models || []).length;
|
||||||
|
resultEl.textContent = tFn('settingsBasic.modelsListSuccess').replace('{count}', String(count));
|
||||||
|
resultEl.style.color = 'var(--success-color, #38a169)';
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
if (resultEl) {
|
||||||
|
resultEl.textContent = tFn('settingsBasic.modelsListFailed') + ': ' + error.message;
|
||||||
|
resultEl.style.color = 'var(--error-color, #e53e3e)';
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
if (btn) {
|
||||||
|
btn.style.pointerEvents = '';
|
||||||
|
btn.style.opacity = '';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2408,7 +2408,15 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="openai-model"><span data-i18n="settingsBasic.model">模型</span> <span style="color: red;">*</span></label>
|
<label for="openai-model"><span data-i18n="settingsBasic.model">模型</span> <span style="color: red;">*</span></label>
|
||||||
<input type="text" id="openai-model" data-i18n="settingsBasic.modelPlaceholder" data-i18n-attr="placeholder" placeholder="gpt-4" required />
|
<div style="display: flex; gap: 8px; align-items: center; flex-wrap: wrap;">
|
||||||
|
<input type="text" id="openai-model" data-i18n="settingsBasic.modelPlaceholder" data-i18n-attr="placeholder" placeholder="gpt-4" required style="flex: 1; min-width: 140px;" />
|
||||||
|
<select id="openai-model-select" class="model-pick-select" style="display: none; min-width: 160px; max-width: 240px;" title="">
|
||||||
|
<option value="" disabled data-i18n="settingsBasic.modelsListSelectPlaceholder">请选择模型</option>
|
||||||
|
</select>
|
||||||
|
<a href="javascript:void(0)" id="fetch-openai-models-btn" onclick="fetchModelList('openai')" style="font-size: 0.8125rem; color: var(--accent-color, #3182ce); text-decoration: none; cursor: pointer; user-select: none; white-space: nowrap;" data-i18n="settingsBasic.fetchModels">获取列表</a>
|
||||||
|
</div>
|
||||||
|
<small id="fetch-openai-models-hint" class="form-hint" style="display: none; font-size: 0.75rem; margin-top: 4px;"></small>
|
||||||
|
<span id="fetch-openai-models-result" style="font-size: 0.75rem; margin-top: 2px; display: block;"></span>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="openai-max-total-tokens"><span data-i18n="settingsBasic.maxTotalTokens">最大上下文 Token 数</span></label>
|
<label for="openai-max-total-tokens"><span data-i18n="settingsBasic.maxTotalTokens">最大上下文 Token 数</span></label>
|
||||||
@@ -2486,7 +2494,15 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="vision-model"><span data-i18n="settingsBasic.visionModel">视觉模型</span> <span style="color: red;">*</span></label>
|
<label for="vision-model"><span data-i18n="settingsBasic.visionModel">视觉模型</span> <span style="color: red;">*</span></label>
|
||||||
<input type="text" id="vision-model" data-i18n="settingsBasic.visionModelPlaceholder" data-i18n-attr="placeholder" placeholder="qwen-vl-max" />
|
<div style="display: flex; gap: 8px; align-items: center; flex-wrap: wrap;">
|
||||||
|
<input type="text" id="vision-model" data-i18n="settingsBasic.visionModelPlaceholder" data-i18n-attr="placeholder" placeholder="qwen-vl-max" style="flex: 1; min-width: 140px;" />
|
||||||
|
<select id="vision-model-select" class="model-pick-select" style="display: none; min-width: 160px; max-width: 240px;">
|
||||||
|
<option value="" disabled data-i18n="settingsBasic.modelsListSelectPlaceholder">请选择模型</option>
|
||||||
|
</select>
|
||||||
|
<a href="javascript:void(0)" id="fetch-vision-models-btn" onclick="fetchModelList('vision')" style="font-size: 0.8125rem; color: var(--accent-color, #3182ce); text-decoration: none; cursor: pointer; user-select: none; white-space: nowrap;" data-i18n="settingsBasic.fetchModels">获取列表</a>
|
||||||
|
</div>
|
||||||
|
<small id="fetch-vision-models-hint" class="form-hint" style="display: none; font-size: 0.75rem; margin-top: 4px;"></small>
|
||||||
|
<span id="fetch-vision-models-result" style="font-size: 0.75rem; margin-top: 2px; display: block;"></span>
|
||||||
</div>
|
</div>
|
||||||
<details style="margin-top: 8px;">
|
<details style="margin-top: 8px;">
|
||||||
<summary style="cursor: pointer; font-size: 0.875rem; color: var(--accent-color, #3182ce);" data-i18n="settingsBasic.visionAdvanced">高级:预处理与限制</summary>
|
<summary style="cursor: pointer; font-size: 0.875rem; color: var(--accent-color, #3182ce);" data-i18n="settingsBasic.visionAdvanced">高级:预处理与限制</summary>
|
||||||
|
|||||||
Reference in New Issue
Block a user