/*
* app.js
* 主逻辑:渲染工具列表、处理交互、调用 API 或本地模拟。
*/
(function () {
'use strict';
// ---------- DOM ----------
const toolsGrid = document.getElementById('toolsGrid');
const panel = document.getElementById('panel');
const panelTitle = document.getElementById('panelTitle');
const panelDesc = document.getElementById('panelDesc');
const inputBox = document.getElementById('inputBox');
const outputBox = document.getElementById('outputBox');
const runBtn = document.getElementById('runBtn');
const copyBtn = document.getElementById('copyBtn');
const clearBtn = document.getElementById('clearBtn');
const closePanel = document.getElementById('closePanel');
const settingsBtn = document.getElementById('settingsBtn');
const settingsModal = document.getElementById('settingsModal');
const apiBase = document.getElementById('apiBase');
const apiKey = document.getElementById('apiKey');
const apiModel = document.getElementById('apiModel');
const saveSettings = document.getElementById('saveSettings');
const cancelSettings = document.getElementById('cancelSettings');
const yearEl = document.getElementById('year');
// ---------- 状态 ----------
let currentTool = null;
const SETTINGS_KEY = 'ai-tool-station-settings';
// ---------- 初始化 ----------
function init() {
yearEl.textContent = new Date().getFullYear();
renderTools();
loadSettings();
bindEvents();
}
// 渲染工具卡片
function renderTools() {
toolsGrid.innerHTML = '';
TOOLS.forEach((tool) => {
const card = document.createElement('button');
card.className = 'tool-card';
card.type = 'button';
card.innerHTML = `
${tool.icon}
${escapeHtml(tool.name)}
${escapeHtml(tool.desc)}
`;
card.addEventListener('click', () => openTool(tool));
toolsGrid.appendChild(card);
});
}
// 打开某个工具
function openTool(tool) {
currentTool = tool;
panelTitle.textContent = `${tool.icon} ${tool.name}`;
panelDesc.textContent = tool.desc;
inputBox.placeholder = tool.placeholder || '请输入内容...';
inputBox.value = '';
outputBox.textContent = '';
panel.hidden = false;
panel.scrollIntoView({ behavior: 'smooth', block: 'start' });
inputBox.focus();
}
// 运行工具
async function run() {
if (!currentTool) return;
const input = inputBox.value.trim();
if (!input) {
outputBox.textContent = '⚠️ 请先输入内容。';
return;
}
runBtn.disabled = true;
runBtn.textContent = '⏳ 生成中...';
outputBox.textContent = '正在生成,请稍候...';
try {
const settings = getSettings();
let result;
if (settings.apiKey && settings.apiBase && settings.apiModel) {
result = await callRealAPI(settings, currentTool, input);
} else {
// 模拟延迟,体验更真实
await sleep(400);
result = mockRun(currentTool.id, input);
}
outputBox.textContent = result;
} catch (err) {
outputBox.textContent = '❌ 出错了:' + (err && err.message ? err.message : String(err));
} finally {
runBtn.disabled = false;
runBtn.textContent = '✨ 运行';
}
}
// 调用真实 OpenAI 兼容 API
async function callRealAPI(settings, tool, input) {
const base = settings.apiBase.replace(/\/+$/, '');
const url = base + '/chat/completions';
const body = {
model: settings.apiModel,
messages: [
{ role: 'system', content: '你是一个专业、简洁的 AI 助手,用中文回答。' },
{ role: 'user', content: tool.prompt(input) }
],
temperature: 0.7
};
const resp = await fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer ' + settings.apiKey
},
body: JSON.stringify(body)
});
if (!resp.ok) {
const t = await resp.text().catch(() => '');
throw new Error(`HTTP ${resp.status} ${t.slice(0, 200)}`);
}
const data = await resp.json();
const content = data && data.choices && data.choices[0] && data.choices[0].message
? data.choices[0].message.content
: '';
return content || '(API 返回为空)';
}
// ---------- 设置 ----------
function getSettings() {
try {
return JSON.parse(localStorage.getItem(SETTINGS_KEY) || '{}');
} catch (_) {
return {};
}
}
function loadSettings() {
const s = getSettings();
apiBase.value = s.apiBase || '';
apiKey.value = s.apiKey || '';
apiModel.value = s.apiModel || '';
}
function saveSettingsToStorage() {
const s = {
apiBase: apiBase.value.trim(),
apiKey: apiKey.value.trim(),
apiModel: apiModel.value.trim()
};
localStorage.setItem(SETTINGS_KEY, JSON.stringify(s));
}
// ---------- 事件绑定 ----------
function bindEvents() {
runBtn.addEventListener('click', run);
copyBtn.addEventListener('click', async () => {
const text = outputBox.textContent || '';
if (!text) return;
try {
await navigator.clipboard.writeText(text);
copyBtn.textContent = '✅ 已复制';
setTimeout(() => (copyBtn.textContent = '📋 复制结果'), 1200);
} catch (_) {
// 降级
const ta = document.createElement('textarea');
ta.value = text;
document.body.appendChild(ta);
ta.select();
document.execCommand('copy');
document.body.removeChild(ta);
copyBtn.textContent = '✅ 已复制';
setTimeout(() => (copyBtn.textContent = '📋 复制结果'), 1200);
}
});
clearBtn.addEventListener('click', () => {
inputBox.value = '';
outputBox.textContent = '';
inputBox.focus();
});
closePanel.addEventListener('click', () => {
panel.hidden = true;
currentTool = null;
});
settingsBtn.addEventListener('click', () => {
loadSettings();
settingsModal.hidden = false;
});
cancelSettings.addEventListener('click', () => {
settingsModal.hidden = true;
});
saveSettings.addEventListener('click', () => {
saveSettingsToStorage();
settingsModal.hidden = true;
});
// 点击遮罩关闭
settingsModal.addEventListener('click', (e) => {
if (e.target === settingsModal) settingsModal.hidden = true;
});
// 快捷键:Ctrl/Cmd + Enter 运行
inputBox.addEventListener('keydown', (e) => {
if ((e.ctrlKey || e.metaKey) && e.key === 'Enter') {
e.preventDefault();
run();
}
});
}
// ---------- 工具函数 ----------
function sleep(ms) {
return new Promise((r) => setTimeout(r, ms));
}
function escapeHtml(str) {
return String(str)
.replace(/&/g, '&')
.replace(//g, '>')
.replace(/"/g, '"')
.replace(/'/g, ''');
}
// 启动
init();
})();