feat: 新增4个工具(imgcompress/LamaCleaner/webp2jpg/Overleaf/LaTeX公式编辑器) + 应用广场布局优化

新增工具部署:
- imgcompress (端口18087) - 图片压缩、格式转换、AI抠图
- Lama Cleaner (端口18088) - AI图像擦除/去水印
- webp2jpg-online (端口18089) - 图片格式批量互转
- Overleaf (端口18090) - 在线LaTeX论文编辑器(docker-compose + MongoDB 8.0)
- LaTeX公式编辑器 (端口18091) - 纯前端KaTeX公式编辑

应用广场优化:
- 去掉tab切换,所有分类平铺展示
- CSS Grid自适应布局,一行可排3-4个卡片
- 重新分为4个分类:文档处理、图片处理、创作绘图、科研写作

其他:
- 更新 CLAUDE.md 项目配置文档
- PPTist AI后端优化prompt和流式输出格式

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-08 20:23:06 +08:00
parent 108022cebd
commit 6337af9481
5 changed files with 482 additions and 78 deletions

View File

@@ -0,0 +1,128 @@
"""
Overleaf 自助注册服务
提供简单的注册页面,用户输入邮箱和密码即可注册
内部通过管理员账号调用 Overleaf API 创建用户
"""
from fastapi import FastAPI, Request, Form
from fastapi.responses import HTMLResponse
import requests
import os
app = FastAPI()
OL_URL = os.getenv("OL_INSTANCE", "http://overleaf")
OL_ADMIN_EMAIL = os.getenv("OL_ADMIN_EMAIL", "")
OL_ADMIN_PASSWORD = os.getenv("OL_ADMIN_PASSWORD", "")
REGISTER_HTML = """
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>注册 - LaTeX 论文编辑器</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body { font-family: -apple-system, sans-serif; background: #f5f5f5; display: flex; justify-content: center; align-items: center; min-height: 100vh; }
.card { background: #fff; border-radius: 12px; padding: 40px; width: 400px; box-shadow: 0 4px 20px rgba(0,0,0,0.1); }
h2 { text-align: center; margin-bottom: 8px; color: #333; }
.sub { text-align: center; color: #999; font-size: 14px; margin-bottom: 30px; }
label { display: block; margin-bottom: 6px; color: #555; font-size: 14px; }
input { width: 100%; padding: 10px 14px; border: 1px solid #ddd; border-radius: 8px; font-size: 14px; margin-bottom: 16px; }
input:focus { outline: none; border-color: #004EA0; }
button { width: 100%; padding: 12px; background: #004EA0; color: #fff; border: none; border-radius: 8px; font-size: 16px; cursor: pointer; }
button:hover { background: #003a7a; }
.msg { text-align: center; margin-top: 16px; font-size: 14px; }
.msg.ok { color: #16a34a; }
.msg.err { color: #dc2626; }
a { color: #004EA0; text-decoration: none; }
</style>
</head>
<body>
<div class="card">
<h2>注册账号</h2>
<p class="sub">LaTeX 论文编辑器</p>
<form method="POST" action="/register">
<label>邮箱</label>
<input type="email" name="email" required placeholder="请输入邮箱">
<button type="submit">注册</button>
</form>
<p class="sub" style="margin-top:20px">已有账号?<a href="OLURL">去登录</a></p>
MSG_PLACEHOLDER
</div>
</body>
</html>
""".replace("OLURL", OL_URL)
class OverleafAdmin:
def __init__(self):
self.session = requests.Session()
self.logged_in = False
def _get_csrf(self, path="/login"):
resp = self.session.get(f"{OL_URL}{path}")
# 从页面中提取 CSRF token
import re
match = re.search(r'name="_csrf"[^>]*value="([^"]+)"', resp.text)
if not match:
match = re.search(r'ol-csrfToken["\s]*content="([^"]+)"', resp.text)
if not match:
match = re.search(r'csrfToken["\s]*:\s*"([^"]+)"', resp.text)
return match.group(1) if match else ""
def login(self):
csrf = self._get_csrf("/login")
resp = self.session.post(f"{OL_URL}/login", data={
"_csrf": csrf,
"email": OL_ADMIN_EMAIL,
"password": OL_ADMIN_PASSWORD,
}, allow_redirects=False)
self.logged_in = resp.status_code in (200, 302)
return self.logged_in
def register_user(self, email):
if not self.logged_in:
self.login()
csrf = self._get_csrf("/admin/register")
resp = self.session.post(f"{OL_URL}/admin/register", data={
"_csrf": csrf,
"email": email,
})
# 从响应中提取设置密码的链接
import re
set_password_url = ""
match = re.search(r'(https?://[^\s"<>]*user/password/set\?[^\s"<>]+)', resp.text)
if match:
set_password_url = match.group(1)
return resp.status_code == 200, set_password_url
admin = OverleafAdmin()
@app.get("/", response_class=HTMLResponse)
async def index():
return REGISTER_HTML.replace("MSG_PLACEHOLDER", "")
@app.post("/register", response_class=HTMLResponse)
async def register(email: str = Form(...)):
try:
ok, set_url = admin.register_user(email)
if ok and set_url:
# 把链接中的 localhost 替换为实际访问地址
import re
set_url = re.sub(r'https?://[^/]+', OL_URL, set_url)
msg = f'<p class="msg ok">注册成功!请点击下方链接设置密码:</p><p style="text-align:center;margin-top:10px"><a href="{set_url}" target="_blank" style="background:#004EA0;color:#fff;padding:10px 24px;border-radius:8px;text-decoration:none;font-size:14px">设置密码</a></p>'
elif ok:
msg = f'<p class="msg ok">注册成功!该邮箱可能已注册。请前往 <a href="{OL_URL}/login">登录页面</a> 登录。</p>'
else:
msg = f'<p class="msg err">注册失败,请稍后重试。</p>'
except Exception as e:
msg = f'<p class="msg err">注册失败:{str(e)[:100]}</p>'
return REGISTER_HTML.replace("MSG_PLACEHOLDER", msg)
if __name__ == "__main__":
import uvicorn
uvicorn.run(app, host="0.0.0.0", port=18091)