AI 生成: 一个开箱即用的 AI 工具聚合站,内置多种热门 AI 小工具(文本改写、代码解释、文案生成、翻译、总结等),支持一键部署,界面现代美观,谁用谁火。
This commit is contained in:
@@ -0,0 +1,83 @@
|
|||||||
|
# AI Tool Station 🚀
|
||||||
|
|
||||||
|
一个开箱即用的 **AI 工具聚合站**,内置多种热门 AI 小工具,界面现代美观,一键部署,谁用谁火。
|
||||||
|
|
||||||
|
## ✨ 功能特性
|
||||||
|
|
||||||
|
- 🎨 现代化 UI,响应式设计,移动端友好
|
||||||
|
- 🧰 内置多个 AI 小工具:
|
||||||
|
- 文本改写
|
||||||
|
- 代码解释
|
||||||
|
- 文案生成
|
||||||
|
- 中英翻译
|
||||||
|
- 内容总结
|
||||||
|
- ⚡ 纯前端实现,零依赖,一键部署到任意静态托管
|
||||||
|
- 🔌 支持接入任意 OpenAI 兼容 API(可选)
|
||||||
|
- 🧪 内置本地模拟模式,无需 API Key 也能体验
|
||||||
|
|
||||||
|
## 📁 目录结构
|
||||||
|
|
||||||
|
```
|
||||||
|
ai-tool-station/
|
||||||
|
├── index.html # 主页面
|
||||||
|
├── css/
|
||||||
|
│ └── style.css # 样式文件
|
||||||
|
├── js/
|
||||||
|
│ ├── app.js # 主逻辑
|
||||||
|
│ └── tools.js # AI 工具定义与模拟实现
|
||||||
|
├── README.md
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🚀 一键部署
|
||||||
|
|
||||||
|
### 方式一:直接打开
|
||||||
|
|
||||||
|
下载后双击 `index.html` 即可在浏览器中使用。
|
||||||
|
|
||||||
|
### 方式二:Vercel / Netlify
|
||||||
|
|
||||||
|
1. 将本项目推送到 GitHub
|
||||||
|
2. 在 Vercel / Netlify 中导入仓库
|
||||||
|
3. 无需构建命令,直接部署
|
||||||
|
|
||||||
|
### 方式三:GitHub Pages
|
||||||
|
|
||||||
|
1. 推送到 GitHub 仓库
|
||||||
|
2. Settings → Pages → 选择 `main` 分支根目录
|
||||||
|
3. 访问 `https://<你的用户名>.github.io/<仓库名>/`
|
||||||
|
|
||||||
|
### 方式四:本地服务
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python3 -m http.server 8080
|
||||||
|
# 打开 http://localhost:8080
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🔑 接入真实 AI API(可选)
|
||||||
|
|
||||||
|
在页面右上角点击 ⚙️ 设置,填入:
|
||||||
|
|
||||||
|
- API Base URL(例如 `https://api.openai.com/v1`)
|
||||||
|
- API Key
|
||||||
|
- 模型名称(例如 `gpt-4o-mini`)
|
||||||
|
|
||||||
|
保存后所有工具将调用真实 API。留空则使用内置模拟模式。
|
||||||
|
|
||||||
|
## 🧩 添加新工具
|
||||||
|
|
||||||
|
编辑 `js/tools.js`,在 `TOOLS` 数组中添加一个新的工具对象即可:
|
||||||
|
|
||||||
|
```js
|
||||||
|
{
|
||||||
|
id: 'my-tool',
|
||||||
|
name: '我的工具',
|
||||||
|
icon: '✨',
|
||||||
|
desc: '工具描述',
|
||||||
|
placeholder: '请输入内容...',
|
||||||
|
prompt: (input) => `请处理以下内容:\n${input}`
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 📄 License
|
||||||
|
|
||||||
|
MIT
|
||||||
+237
@@ -0,0 +1,237 @@
|
|||||||
|
:root {
|
||||||
|
--bg: #0b0d17;
|
||||||
|
--bg-2: #12142400;
|
||||||
|
--card: #151827;
|
||||||
|
--card-hover: #1c2033;
|
||||||
|
--border: #262a3d;
|
||||||
|
--text: #e8eaf2;
|
||||||
|
--muted: #8a90a8;
|
||||||
|
--primary: #6d5efc;
|
||||||
|
--primary-2: #22d3ee;
|
||||||
|
--accent: #ff5c8a;
|
||||||
|
--radius: 14px;
|
||||||
|
--shadow: 0 10px 30px rgba(0, 0, 0, 0.35);
|
||||||
|
}
|
||||||
|
|
||||||
|
* { box-sizing: border-box; }
|
||||||
|
|
||||||
|
html, body {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
background: radial-gradient(1200px 600px at 20% -10%, #1a1f3a 0%, transparent 60%),
|
||||||
|
radial-gradient(900px 500px at 100% 0%, #1e2a4a 0%, transparent 55%),
|
||||||
|
var(--bg);
|
||||||
|
color: var(--text);
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "PingFang SC",
|
||||||
|
"Hiragino Sans GB", "Microsoft YaHei", Roboto, Helvetica, Arial, sans-serif;
|
||||||
|
min-height: 100vh;
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
}
|
||||||
|
|
||||||
|
.topbar {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 16px 24px;
|
||||||
|
border-bottom: 1px solid var(--border);
|
||||||
|
backdrop-filter: blur(10px);
|
||||||
|
position: sticky;
|
||||||
|
top: 0;
|
||||||
|
z-index: 10;
|
||||||
|
background: rgba(11, 13, 23, 0.7);
|
||||||
|
}
|
||||||
|
|
||||||
|
.brand { display: flex; align-items: center; gap: 10px; font-weight: 700; }
|
||||||
|
.logo { font-size: 22px; }
|
||||||
|
.brand-name { letter-spacing: 0.3px; }
|
||||||
|
|
||||||
|
.container {
|
||||||
|
max-width: 1080px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 40px 20px 80px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero { text-align: center; margin-bottom: 40px; }
|
||||||
|
.hero h1 {
|
||||||
|
font-size: clamp(28px, 5vw, 44px);
|
||||||
|
margin: 0 0 12px;
|
||||||
|
font-weight: 800;
|
||||||
|
letter-spacing: -0.5px;
|
||||||
|
}
|
||||||
|
.hero .grad {
|
||||||
|
background: linear-gradient(90deg, var(--primary), var(--primary-2));
|
||||||
|
-webkit-background-clip: text;
|
||||||
|
background-clip: text;
|
||||||
|
color: transparent;
|
||||||
|
}
|
||||||
|
.hero p { color: var(--muted); margin: 0; font-size: 16px; }
|
||||||
|
|
||||||
|
.tools-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
|
||||||
|
gap: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tool-card {
|
||||||
|
background: var(--card);
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: var(--radius);
|
||||||
|
padding: 20px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: transform 0.15s ease, background 0.15s ease, border-color 0.15s ease;
|
||||||
|
text-align: left;
|
||||||
|
color: var(--text);
|
||||||
|
font: inherit;
|
||||||
|
}
|
||||||
|
.tool-card:hover {
|
||||||
|
background: var(--card-hover);
|
||||||
|
border-color: #3a3f5c;
|
||||||
|
transform: translateY(-3px);
|
||||||
|
}
|
||||||
|
.tool-icon { font-size: 26px; display: block; margin-bottom: 10px; }
|
||||||
|
.tool-name { font-size: 16px; font-weight: 700; margin-bottom: 6px; }
|
||||||
|
.tool-desc { font-size: 13px; color: var(--muted); line-height: 1.5; }
|
||||||
|
|
||||||
|
.panel {
|
||||||
|
margin-top: 32px;
|
||||||
|
background: var(--card);
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: var(--radius);
|
||||||
|
padding: 24px;
|
||||||
|
box-shadow: var(--shadow);
|
||||||
|
}
|
||||||
|
.panel-head {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
margin-bottom: 6px;
|
||||||
|
}
|
||||||
|
.panel-head h2 { margin: 0; font-size: 20px; }
|
||||||
|
.panel-desc { color: var(--muted); margin: 0 0 16px; font-size: 14px; }
|
||||||
|
|
||||||
|
.input-box {
|
||||||
|
width: 100%;
|
||||||
|
min-height: 140px;
|
||||||
|
resize: vertical;
|
||||||
|
background: #0f1220;
|
||||||
|
color: var(--text);
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: 10px;
|
||||||
|
padding: 14px;
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 1.6;
|
||||||
|
font-family: inherit;
|
||||||
|
outline: none;
|
||||||
|
transition: border-color 0.15s ease;
|
||||||
|
}
|
||||||
|
.input-box:focus { border-color: var(--primary); }
|
||||||
|
|
||||||
|
.actions {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 10px;
|
||||||
|
margin: 14px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.primary-btn, .ghost-btn {
|
||||||
|
border-radius: 10px;
|
||||||
|
padding: 10px 16px;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 600;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.15s ease;
|
||||||
|
border: 1px solid transparent;
|
||||||
|
font-family: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
.primary-btn {
|
||||||
|
background: linear-gradient(90deg, var(--primary), #8b7dff);
|
||||||
|
color: white;
|
||||||
|
box-shadow: 0 6px 18px rgba(109, 94, 252, 0.35);
|
||||||
|
}
|
||||||
|
.primary-btn:hover { filter: brightness(1.08); }
|
||||||
|
.primary-btn:disabled { opacity: 0.6; cursor: not-allowed; }
|
||||||
|
|
||||||
|
.ghost-btn {
|
||||||
|
background: transparent;
|
||||||
|
color: var(--text);
|
||||||
|
border-color: var(--border);
|
||||||
|
}
|
||||||
|
.ghost-btn:hover { background: #1b1f33; border-color: #3a3f5c; }
|
||||||
|
|
||||||
|
.output {
|
||||||
|
background: #0f1220;
|
||||||
|
border: 1px dashed var(--border);
|
||||||
|
border-radius: 10px;
|
||||||
|
padding: 16px;
|
||||||
|
min-height: 80px;
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 1.7;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
word-break: break-word;
|
||||||
|
color: #d6d9e6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer {
|
||||||
|
text-align: center;
|
||||||
|
color: var(--muted);
|
||||||
|
font-size: 13px;
|
||||||
|
padding: 24px;
|
||||||
|
border-top: 1px solid var(--border);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Modal */
|
||||||
|
.modal {
|
||||||
|
position: fixed;
|
||||||
|
inset: 0;
|
||||||
|
background: rgba(0, 0, 0, 0.55);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 20px;
|
||||||
|
z-index: 100;
|
||||||
|
}
|
||||||
|
.modal-card {
|
||||||
|
background: var(--card);
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: var(--radius);
|
||||||
|
padding: 24px;
|
||||||
|
width: 100%;
|
||||||
|
max-width: 420px;
|
||||||
|
box-shadow: var(--shadow);
|
||||||
|
}
|
||||||
|
.modal-card h3 { margin: 0 0 6px; }
|
||||||
|
.hint { color: var(--muted); font-size: 13px; margin: 0 0 16px; }
|
||||||
|
.modal-card label {
|
||||||
|
display: block;
|
||||||
|
font-size: 13px;
|
||||||
|
color: var(--muted);
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
.modal-card input {
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
margin-top: 6px;
|
||||||
|
background: #0f1220;
|
||||||
|
color: var(--text);
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 10px 12px;
|
||||||
|
font-size: 14px;
|
||||||
|
outline: none;
|
||||||
|
font-family: inherit;
|
||||||
|
}
|
||||||
|
.modal-card input:focus { border-color: var(--primary); }
|
||||||
|
.modal-actions {
|
||||||
|
display: flex;
|
||||||
|
gap: 10px;
|
||||||
|
justify-content: flex-end;
|
||||||
|
margin-top: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 520px) {
|
||||||
|
.container { padding: 24px 14px 60px; }
|
||||||
|
.panel { padding: 18px; }
|
||||||
|
.actions { flex-direction: column; }
|
||||||
|
.primary-btn, .ghost-btn { width: 100%; text-align: center; }
|
||||||
|
}
|
||||||
+73
@@ -0,0 +1,73 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="zh-CN">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>AI Tool Station · 一键部署的 AI 工具站</title>
|
||||||
|
<meta name="description" content="开箱即用的 AI 工具聚合站,内置多种热门 AI 小工具,一键部署,谁用谁火。" />
|
||||||
|
<link rel="stylesheet" href="css/style.css" />
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<header class="topbar">
|
||||||
|
<div class="brand">
|
||||||
|
<span class="logo">🚀</span>
|
||||||
|
<span class="brand-name">AI Tool Station</span>
|
||||||
|
</div>
|
||||||
|
<button id="settingsBtn" class="ghost-btn" title="设置 API">⚙️ 设置</button>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<main class="container">
|
||||||
|
<section class="hero">
|
||||||
|
<h1>一键部署的 <span class="grad">AI 工具站</span></h1>
|
||||||
|
<p>内置多个热门 AI 小工具,开箱即用,谁用谁火 🔥</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="tools-grid" id="toolsGrid">
|
||||||
|
<!-- 工具卡片由 JS 渲染 -->
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="panel" id="panel" hidden>
|
||||||
|
<div class="panel-head">
|
||||||
|
<h2 id="panelTitle">工具</h2>
|
||||||
|
<button id="closePanel" class="ghost-btn">✕ 关闭</button>
|
||||||
|
</div>
|
||||||
|
<p class="panel-desc" id="panelDesc"></p>
|
||||||
|
<textarea id="inputBox" class="input-box" placeholder="请输入内容..."></textarea>
|
||||||
|
<div class="actions">
|
||||||
|
<button id="runBtn" class="primary-btn">✨ 运行</button>
|
||||||
|
<button id="copyBtn" class="ghost-btn">📋 复制结果</button>
|
||||||
|
<button id="clearBtn" class="ghost-btn">🧹 清空</button>
|
||||||
|
</div>
|
||||||
|
<div class="output" id="outputBox" aria-live="polite"></div>
|
||||||
|
</section>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<footer class="footer">
|
||||||
|
<span>© <span id="year"></span> AI Tool Station · MIT License</span>
|
||||||
|
</footer>
|
||||||
|
|
||||||
|
<!-- 设置弹窗 -->
|
||||||
|
<div class="modal" id="settingsModal" hidden>
|
||||||
|
<div class="modal-card">
|
||||||
|
<h3>⚙️ API 设置</h3>
|
||||||
|
<p class="hint">留空则使用内置模拟模式。支持 OpenAI 兼容接口。</p>
|
||||||
|
<label>API Base URL
|
||||||
|
<input id="apiBase" type="text" placeholder="https://api.openai.com/v1" />
|
||||||
|
</label>
|
||||||
|
<label>API Key
|
||||||
|
<input id="apiKey" type="password" placeholder="sk-..." />
|
||||||
|
</label>
|
||||||
|
<label>模型
|
||||||
|
<input id="apiModel" type="text" placeholder="gpt-4o-mini" />
|
||||||
|
</label>
|
||||||
|
<div class="modal-actions">
|
||||||
|
<button id="saveSettings" class="primary-btn">保存</button>
|
||||||
|
<button id="cancelSettings" class="ghost-btn">取消</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script src="js/tools.js"></script>
|
||||||
|
<script src="js/app.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,241 @@
|
|||||||
|
/*
|
||||||
|
* 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, '&')
|
||||||
|
.replace(/</g, '<')
|
||||||
|
.replace(/>/g, '>')
|
||||||
|
.replace(/"/g, '"')
|
||||||
|
.replace(/'/g, ''');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 启动
|
||||||
|
init();
|
||||||
|
})();
|
||||||
+83
@@ -0,0 +1,83 @@
|
|||||||
|
/*
|
||||||
|
* tools.js
|
||||||
|
* AI 工具定义与本地模拟实现。
|
||||||
|
* 添加新工具:往 TOOLS 数组里追加一个对象即可。
|
||||||
|
*/
|
||||||
|
|
||||||
|
// 工具列表:id / name / icon / desc / placeholder / prompt
|
||||||
|
const TOOLS = [
|
||||||
|
{
|
||||||
|
id: 'rewrite',
|
||||||
|
name: '文本改写',
|
||||||
|
icon: '✍️',
|
||||||
|
desc: '把一段文字改写得更通顺、更专业。',
|
||||||
|
placeholder: '粘贴需要改写的文字...',
|
||||||
|
prompt: (input) => `请将以下内容改写得更通顺、更专业,保持原意:\n\n${input}`
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'explain-code',
|
||||||
|
name: '代码解释',
|
||||||
|
icon: '🧑💻',
|
||||||
|
desc: '解释一段代码的作用和逻辑。',
|
||||||
|
placeholder: '粘贴一段代码...',
|
||||||
|
prompt: (input) => `请用简洁的中文解释以下代码的作用、关键逻辑和可能的坑:\n\n${input}`
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'copywriting',
|
||||||
|
name: '文案生成',
|
||||||
|
icon: '📣',
|
||||||
|
desc: '根据主题生成吸睛的营销文案。',
|
||||||
|
placeholder: '输入主题,例如:一款便携咖啡机',
|
||||||
|
prompt: (input) => `请为主题「${input}」写 3 条不同风格的营销文案,每条不超过 50 字。`
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'translate',
|
||||||
|
name: '中英翻译',
|
||||||
|
icon: '🌐',
|
||||||
|
desc: '自动识别中英文并互译。',
|
||||||
|
placeholder: '输入中文或英文...',
|
||||||
|
prompt: (input) => `请将以下内容翻译成另一种语言(中文↔英文),只输出译文:\n\n${input}`
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'summary',
|
||||||
|
name: '内容总结',
|
||||||
|
icon: '📝',
|
||||||
|
desc: '把长文提炼成要点。',
|
||||||
|
placeholder: '粘贴一段长文...',
|
||||||
|
prompt: (input) => `请将以下内容总结为 3-5 条要点,用短句输出:\n\n${input}`
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'name-idea',
|
||||||
|
name: '起名灵感',
|
||||||
|
icon: '💡',
|
||||||
|
desc: '为产品 / 项目 / 频道起名。',
|
||||||
|
placeholder: '输入你的产品描述或关键词...',
|
||||||
|
prompt: (input) => `请根据以下描述,给出 10 个有创意的名字,并附一句话理由:\n\n${input}`
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 本地模拟实现:在没有配置 API 时使用。
|
||||||
|
* 根据工具 id 返回一段合理的假数据,让页面可以演示。
|
||||||
|
*/
|
||||||
|
function mockRun(toolId, input) {
|
||||||
|
const text = (input || '').trim();
|
||||||
|
if (!text) return '⚠️ 请先输入内容。';
|
||||||
|
|
||||||
|
switch (toolId) {
|
||||||
|
case 'rewrite':
|
||||||
|
return `【改写结果】\n${text.replace(/。/g, '。\n')}\n\n(以上为示例改写,接入真实 API 后效果更佳)`;
|
||||||
|
case 'explain-code':
|
||||||
|
return `【代码解释】\n这段代码主要做了以下几件事:\n1. 定义并初始化相关变量;\n2. 对输入进行处理与判断;\n3. 输出最终结果。\n\n原始代码长度:${text.length} 字符。`;
|
||||||
|
case 'copywriting':
|
||||||
|
return `【文案示例】\n1. 主题「${text}」,让生活更简单。\n2. 遇见「${text}」,遇见更好的自己。\n3. 「${text}」,你值得拥有。`;
|
||||||
|
case 'translate':
|
||||||
|
return `【翻译示例】\n${text}\n\n(此处为模拟翻译,接入 API 后自动中英互译)`;
|
||||||
|
case 'summary':
|
||||||
|
return `【总结要点】\n• 内容长度:${text.length} 字符\n• 核心主题:与「${text.slice(0, 12)}...」相关\n• 建议:接入真实 API 获取高质量总结`;
|
||||||
|
case 'name-idea':
|
||||||
|
return `【起名灵感】\n1. Nova${text.slice(0, 4)}\n2. Lumo\n3. 星火\n4. Echo\n5. 极光\n6. Pulse\n7. 山海\n8. Vertex\n9. 微光\n10. Orbit`;
|
||||||
|
default:
|
||||||
|
return '(模拟结果)' + text;
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user