Files
ai-tool-station-20260928221103/js/app.js
T

242 lines
7.0 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*
* 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 = `
<span class="tool-icon">${tool.icon}</span>
<div class="tool-name">${escapeHtml(tool.name)}</div>
<div class="tool-desc">${escapeHtml(tool.desc)}</div>
`;
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, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#39;');
}
// 启动
init();
})();