release: v2.0 - 完整功能管理系统

主要功能:
- 完整的Flask-Admin后台管理系统
- 网站/标签/新闻管理功能
- 用户登录认证系统
- 科技感/未来风UI设计
- 标签分类系统(取代传统分类)
- 详情页面展示
- 数据库迁移脚本
- 书签导入解析工具

技术栈:
- Flask + SQLAlchemy
- Flask-Admin管理界面
- Bootstrap 4响应式设计
- 用户认证与权限管理

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
Jowe
2025-12-28 19:21:17 +08:00
parent 2fbca6ebc7
commit 9e47ebe749
49 changed files with 6274 additions and 1353 deletions

View File

@@ -16,7 +16,11 @@
"Bash(findstr:*)",
"Bash(dir:*)",
"Bash(git init:*)",
"Bash(git add:*)"
"Bash(git add:*)",
"Bash(git commit:*)",
"Bash(curl:*)",
"WebFetch(domain:zjpb.net)",
"Bash(del import_bookmarks.py test_bookmark_parse.py test_simple_parse.py result.txt)"
]
}
}

View File

@@ -10,3 +10,7 @@ SECRET_KEY=your-secret-key-here
# 运行环境 (development/production)
FLASK_ENV=development
# DeepSeek API配置
DEEPSEEK_API_KEY=your_deepseek_api_key_here
DEEPSEEK_BASE_URL=https://api.deepseek.com

8
.mcp.json Normal file
View File

@@ -0,0 +1,8 @@
{
"mcpServers": {
"tdesign-mcp-server": {
"command": "npx",
"args": ["-y", "tdesign-mcp-server"]
}
}
}

49
=1.0.0 Normal file
View File

@@ -0,0 +1,49 @@
Defaulting to user installation because normal site-packages is not writeable
Collecting openai
Downloading openai-2.14.0-py3-none-any.whl.metadata (29 kB)
Collecting anyio<5,>=3.5.0 (from openai)
Downloading anyio-4.12.0-py3-none-any.whl.metadata (4.3 kB)
Collecting distro<2,>=1.7.0 (from openai)
Downloading distro-1.9.0-py3-none-any.whl.metadata (6.8 kB)
Collecting httpx<1,>=0.23.0 (from openai)
Downloading httpx-0.28.1-py3-none-any.whl.metadata (7.1 kB)
Collecting jiter<1,>=0.10.0 (from openai)
Downloading jiter-0.12.0-cp313-cp313-win_amd64.whl.metadata (5.3 kB)
Collecting pydantic<3,>=1.9.0 (from openai)
Downloading pydantic-2.12.5-py3-none-any.whl.metadata (90 kB)
Collecting sniffio (from openai)
Downloading sniffio-1.3.1-py3-none-any.whl.metadata (3.9 kB)
Collecting tqdm>4 (from openai)
Downloading tqdm-4.67.1-py3-none-any.whl.metadata (57 kB)
Requirement already satisfied: typing-extensions<5,>=4.11 in c:\users\linha\appdata\local\packages\pythonsoftwarefoundation.python.3.13_qbz5n2kfra8p0\localcache\local-packages\python313\site-packages (from openai) (4.15.0)
Requirement already satisfied: idna>=2.8 in c:\users\linha\appdata\local\packages\pythonsoftwarefoundation.python.3.13_qbz5n2kfra8p0\localcache\local-packages\python313\site-packages (from anyio<5,>=3.5.0->openai) (3.11)
Requirement already satisfied: certifi in c:\users\linha\appdata\local\packages\pythonsoftwarefoundation.python.3.13_qbz5n2kfra8p0\localcache\local-packages\python313\site-packages (from httpx<1,>=0.23.0->openai) (2025.11.12)
Collecting httpcore==1.* (from httpx<1,>=0.23.0->openai)
Downloading httpcore-1.0.9-py3-none-any.whl.metadata (21 kB)
Collecting h11>=0.16 (from httpcore==1.*->httpx<1,>=0.23.0->openai)
Downloading h11-0.16.0-py3-none-any.whl.metadata (8.3 kB)
Collecting annotated-types>=0.6.0 (from pydantic<3,>=1.9.0->openai)
Downloading annotated_types-0.7.0-py3-none-any.whl.metadata (15 kB)
Collecting pydantic-core==2.41.5 (from pydantic<3,>=1.9.0->openai)
Downloading pydantic_core-2.41.5-cp313-cp313-win_amd64.whl.metadata (7.4 kB)
Collecting typing-inspection>=0.4.2 (from pydantic<3,>=1.9.0->openai)
Downloading typing_inspection-0.4.2-py3-none-any.whl.metadata (2.6 kB)
Requirement already satisfied: colorama in c:\users\linha\appdata\local\packages\pythonsoftwarefoundation.python.3.13_qbz5n2kfra8p0\localcache\local-packages\python313\site-packages (from tqdm>4->openai) (0.4.6)
Downloading openai-2.14.0-py3-none-any.whl (1.1 MB)
---------------------------------------- 1.1/1.1 MB 2.6 MB/s 0:00:00
Downloading anyio-4.12.0-py3-none-any.whl (113 kB)
Downloading distro-1.9.0-py3-none-any.whl (20 kB)
Downloading httpx-0.28.1-py3-none-any.whl (73 kB)
Downloading httpcore-1.0.9-py3-none-any.whl (78 kB)
Downloading jiter-0.12.0-cp313-cp313-win_amd64.whl (204 kB)
Downloading pydantic-2.12.5-py3-none-any.whl (463 kB)
Downloading pydantic_core-2.41.5-cp313-cp313-win_amd64.whl (2.0 MB)
---------------------------------------- 2.0/2.0 MB 6.3 MB/s 0:00:00
Downloading annotated_types-0.7.0-py3-none-any.whl (13 kB)
Downloading h11-0.16.0-py3-none-any.whl (37 kB)
Downloading tqdm-4.67.1-py3-none-any.whl (78 kB)
Downloading typing_inspection-0.4.2-py3-none-any.whl (14 kB)
Downloading sniffio-1.3.1-py3-none-any.whl (10 kB)
Installing collected packages: typing-inspection, tqdm, sniffio, pydantic-core, jiter, h11, distro, anyio, annotated-types, pydantic, httpcore, httpx, openai
Successfully installed annotated-types-0.7.0 anyio-4.12.0 distro-1.9.0 h11-0.16.0 httpcore-1.0.9 httpx-0.28.1 jiter-0.12.0 openai-2.14.0 pydantic-2.12.5 pydantic-core-2.41.5 sniffio-1.3.1 tqdm-4.67.1 typing-inspection-0.4.2

506
app.py
View File

@@ -1,12 +1,13 @@
import os
from flask import Flask, render_template, redirect, url_for, request, flash, jsonify
from flask_login import LoginManager, login_user, logout_user, login_required, current_user
from flask_admin import Admin, AdminIndexView
from flask_admin import Admin, AdminIndexView, expose
from flask_admin.contrib.sqla import ModelView
from datetime import datetime
from config import config
from models import db, Site, Tag, Admin as AdminModel
from models import db, Site, Tag, Admin as AdminModel, News
from utils.website_fetcher import WebsiteFetcher
from utils.tag_generator import TagGenerator
def create_app(config_name='default'):
"""应用工厂函数"""
@@ -35,37 +36,77 @@ def create_app(config_name='default'):
# 获取所有启用的标签
tags = Tag.query.order_by(Tag.sort_order.desc(), Tag.id).all()
# 获取筛选的标签
# 获取筛选参数
tag_slug = request.args.get('tag')
search_query = request.args.get('q', '').strip()
page = request.args.get('page', 1, type=int)
per_page = 100 # 每页显示100个站点
selected_tag = None
# 构建基础查询
query = Site.query.filter_by(is_active=True)
# 标签筛选
if tag_slug:
selected_tag = Tag.query.filter_by(slug=tag_slug).first()
if selected_tag:
sites = Site.query.filter(
Site.is_active == True,
Site.tags.contains(selected_tag)
).order_by(Site.sort_order.desc(), Site.id.desc()).all()
query = query.filter(Site.tags.contains(selected_tag))
else:
sites = []
else:
# 获取所有启用的网站
sites = Site.query.filter_by(is_active=True).order_by(
Site.sort_order.desc(), Site.id.desc()
).all()
pagination = None
return render_template('index_new.html', sites=sites, tags=tags,
selected_tag=selected_tag, search_query=search_query,
pagination=pagination)
return render_template('index.html', sites=sites, tags=tags, selected_tag=selected_tag)
# 搜索功能
if search_query:
# 使用OR条件搜索网站名称、URL、描述
search_pattern = f'%{search_query}%'
query = query.filter(
db.or_(
Site.name.like(search_pattern),
Site.url.like(search_pattern),
Site.description.like(search_pattern),
Site.short_desc.like(search_pattern)
)
)
@app.route('/site/<slug>')
def site_detail(slug):
# 排序并分页
query = query.order_by(Site.sort_order.desc(), Site.id.desc())
pagination = query.paginate(page=page, per_page=per_page, error_out=False)
sites = pagination.items
return render_template('index_new.html', sites=sites, tags=tags,
selected_tag=selected_tag, search_query=search_query,
pagination=pagination)
@app.route('/site/<code>')
def site_detail(code):
"""网站详情页"""
site = Site.query.filter_by(slug=slug, is_active=True).first_or_404()
site = Site.query.filter_by(code=code, is_active=True).first_or_404()
# 增加浏览次数
site.view_count += 1
db.session.commit()
return render_template('detail.html', site=site)
# 获取该网站的相关新闻最多显示5条
news_list = News.query.filter_by(
site_id=site.id,
is_active=True
).order_by(News.published_at.desc()).limit(5).all()
# 获取同类工具推荐通过标签匹配最多显示4个
recommended_sites = []
if site.tags:
# 获取有相同标签的其他网站
recommended_sites = Site.query.filter(
Site.id != site.id,
Site.is_active == True,
Site.tags.any(Tag.id.in_([tag.id for tag in site.tags]))
).order_by(Site.view_count.desc()).limit(4).all()
return render_template('detail_new.html', site=site, news_list=news_list, recommended_sites=recommended_sites)
# ========== 后台登录路由 ==========
@app.route('/admin/login', methods=['GET', 'POST'])
@@ -144,9 +185,289 @@ def create_app(config_name='default'):
'message': f'抓取失败: {str(e)}'
}), 500
@app.route('/api/generate-tags', methods=['POST'])
@login_required
def generate_tags():
"""使用DeepSeek自动生成标签"""
try:
data = request.get_json()
name = data.get('name', '').strip()
description = data.get('description', '').strip()
if not name or not description:
return jsonify({
'success': False,
'message': '请提供网站名称和描述'
}), 400
# 获取现有标签作为参考
existing_tags = [tag.name for tag in Tag.query.all()]
# 生成标签
generator = TagGenerator()
suggested_tags = generator.generate_tags(name, description, existing_tags)
if not suggested_tags:
return jsonify({
'success': False,
'message': 'DeepSeek标签生成失败请检查API配置'
}), 500
return jsonify({
'success': True,
'tags': suggested_tags
})
except ValueError as e:
return jsonify({
'success': False,
'message': str(e)
}), 400
except Exception as e:
return jsonify({
'success': False,
'message': f'生成失败: {str(e)}'
}), 500
# ========== 批量导入路由 ==========
@app.route('/admin/batch-import', methods=['GET', 'POST'])
@login_required
def batch_import():
"""批量导入网站"""
from utils.bookmark_parser import BookmarkParser
from utils.website_fetcher import WebsiteFetcher
results = None
if request.method == 'POST':
import_type = request.form.get('import_type')
auto_activate = request.form.get('auto_activate') == 'on'
parser = BookmarkParser()
fetcher = WebsiteFetcher(timeout=15)
urls_to_import = []
try:
# 解析输入
if import_type == 'url_list':
url_list_text = request.form.get('url_list', '')
urls_to_import = parser.parse_url_list(url_list_text)
elif import_type == 'bookmark_file':
bookmark_file = request.files.get('bookmark_file')
if not bookmark_file:
flash('请选择书签文件', 'error')
return render_template('admin/batch_import.html')
html_content = bookmark_file.read().decode('utf-8', errors='ignore')
all_bookmarks = parser.parse_html_file(html_content)
# 筛选文件夹(如果指定)
folder_filter = request.form.get('folder_filter', '').strip()
if folder_filter:
urls_to_import = [
b for b in all_bookmarks
if folder_filter.lower() in b.get('folder', '').lower()
]
else:
urls_to_import = all_bookmarks
# 批量导入
success_list = []
failed_list = []
for idx, item in enumerate(urls_to_import, 1):
url = item['url']
name = item.get('name', '')
# 为每个URL创建独立的事务
try:
# 1. 检查URL是否已存在
try:
existing = Site.query.filter_by(url=url).first()
if existing:
failed_list.append({
'url': url,
'name': name or existing.name,
'error': f'该URL已存在网站名称{existing.name}'
})
continue
except Exception as e:
failed_list.append({
'url': url,
'name': name,
'error': f'检查URL时出错: {str(e)}'
})
continue
# 2. 抓取网站信息(带超时和错误处理)
info = None
try:
info = fetcher.fetch_website_info(url)
except Exception as e:
print(f"抓取 {url} 失败: {str(e)}")
# 抓取失败不是致命错误,继续尝试使用书签名称
# 3. 处理网站信息
if not info or not info.get('name'):
# 如果有书签名称,使用书签名称
if name:
info = {
'name': name,
'description': '',
'logo_url': ''
}
else:
# 尝试从URL提取域名作为名称
from urllib.parse import urlparse
try:
parsed = urlparse(url)
domain = parsed.netloc or parsed.path
if domain:
info = {
'name': domain,
'description': '',
'logo_url': ''
}
else:
failed_list.append({
'url': url,
'name': name,
'error': '无法获取网站信息且没有备用名称'
})
continue
except Exception:
failed_list.append({
'url': url,
'name': name,
'error': '无法获取网站信息且URL解析失败'
})
continue
# 4. 下载Logo失败不影响导入
logo_path = None
if info.get('logo_url'):
try:
logo_path = fetcher.download_logo(info['logo_url'])
except Exception as e:
print(f"下载Logo失败 ({url}): {str(e)}")
# Logo下载失败不影响网站导入
# 5. 生成code和slug
try:
import random
from pypinyin import lazy_pinyin
import re
# 生成唯一的code
site_code = None
max_attempts = 10
for _ in range(max_attempts):
code = str(random.randint(10000000, 99999999))
if not Site.query.filter_by(code=code).first():
site_code = code
break
if not site_code:
# 如果10次都失败使用时间戳
import time
site_code = str(int(time.time() * 1000))[-8:]
# 生成slug
site_name = info.get('name', name or 'Unknown')[:100]
slug = ''.join(lazy_pinyin(site_name))
slug = slug.lower()
slug = re.sub(r'[^\w\s-]', '', slug)
slug = re.sub(r'[-\s]+', '-', slug).strip('-')
if not slug:
slug = f"site-{site_code}"
# 确保slug唯一
base_slug = slug[:50] # 限制长度
counter = 1
final_slug = slug
while Site.query.filter_by(slug=final_slug).first():
final_slug = f"{base_slug}-{counter}"
counter += 1
if counter > 100: # 防止无限循环
final_slug = f"{base_slug}-{site_code}"
break
# 6. 创建网站记录带code和slug
site = Site(
code=site_code,
slug=final_slug,
name=site_name,
url=url[:500], # 限制URL长度
logo=logo_path or info.get('logo_url', '')[:500] if info.get('logo_url') else '',
short_desc=info.get('description', '')[:200] if info.get('description') else '',
description=info.get('description', '')[:2000] if info.get('description') else '',
is_active=auto_activate
)
# 添加到数据库并提交
db.session.add(site)
db.session.commit()
success_list.append({
'name': site.name,
'url': site.url
})
print(f"成功导入 [{idx}/{len(urls_to_import)}]: {site.name}")
except Exception as e:
db.session.rollback()
failed_list.append({
'url': url,
'name': name or info.get('name', 'Unknown'),
'error': f'数据库保存失败: {str(e)}'
})
continue
except Exception as e:
# 捕获所有未预期的错误
db.session.rollback()
failed_list.append({
'url': url,
'name': name,
'error': f'未知错误: {str(e)}'
})
print(f"导入 {url} 时发生未知错误: {str(e)}")
continue
results = {
'total_count': len(urls_to_import),
'success_count': len(success_list),
'failed_count': len(failed_list),
'success_list': success_list,
'failed_list': failed_list
}
if success_list:
flash(f'成功导入 {len(success_list)} 个网站!', 'success')
except Exception as e:
flash(f'导入失败: {str(e)}', 'error')
return render_template('admin/batch_import.html', results=results)
# ========== Flask-Admin 配置 ==========
class SecureModelView(ModelView):
"""需要登录的模型视图"""
# 中文化配置
can_set_page_size = True
page_size = 20
# 自定义文本
list_template = 'admin/model/list.html'
create_template = 'admin/model/create.html'
edit_template = 'admin/model/edit.html'
# 覆盖英文文本
named_filter_urls = True
def is_accessible(self):
return current_user.is_authenticated
@@ -161,17 +482,44 @@ def create_app(config_name='default'):
def inaccessible_callback(self, name, **kwargs):
return redirect(url_for('admin_login'))
@expose('/')
def index(self):
"""控制台首页,显示统计信息"""
# 统计数据
stats = {
'sites_count': Site.query.filter_by(is_active=True).count(),
'tags_count': Tag.query.count(),
'news_count': News.query.filter_by(is_active=True).count(),
'total_views': db.session.query(db.func.sum(Site.view_count)).scalar() or 0
}
# 最近添加的工具最多5个
recent_sites = Site.query.order_by(Site.created_at.desc()).limit(5).all()
return self.render('admin/index.html', stats=stats, recent_sites=recent_sites)
# 网站管理视图
class SiteAdmin(SecureModelView):
# 自定义模板
create_template = 'admin/site/create.html'
edit_template = 'admin/site/edit.html'
column_list = ['id', 'name', 'url', 'slug', 'is_active', 'view_count', 'created_at']
column_searchable_list = ['name', 'url', 'description']
# 启用编辑和删除
can_edit = True
can_delete = True
can_create = True
can_view_details = False # 禁用查看详情,点击名称即可查看
# 显示操作列
column_display_actions = True
action_disallowed_list = []
column_list = ['id', 'code', 'name', 'url', 'slug', 'is_active', 'view_count', 'created_at']
column_searchable_list = ['code', 'name', 'url', 'description']
column_filters = ['is_active', 'tags']
column_labels = {
'id': 'ID',
'code': '网站编码',
'name': '网站名称',
'url': 'URL',
'slug': 'URL别名',
@@ -188,8 +536,76 @@ def create_app(config_name='default'):
}
form_columns = ['name', 'url', 'slug', 'logo', 'short_desc', 'description', 'features', 'tags', 'is_active', 'sort_order']
# 自定义编辑/删除文字
column_extra_row_actions = None
def on_model_change(self, form, model, is_created):
"""保存前自动生成code和slug如果为空"""
import re
import random
from pypinyin import lazy_pinyin
# 使用no_autoflush防止在查询时触发提前flush
with db.session.no_autoflush:
# 如果code为空自动生成唯一的8位数字编码
if not model.code or model.code.strip() == '':
max_attempts = 10
for attempt in range(max_attempts):
# 生成10000000-99999999之间的随机数
code = str(random.randint(10000000, 99999999))
# 检查是否已存在
existing = Site.query.filter(Site.code == code).first()
if not existing or existing.id == model.id:
model.code = code
break
# 如果10次都失败使用时间戳
if not model.code:
import time
model.code = str(int(time.time() * 1000))[-8:]
# 如果slug为空从name自动生成
if not model.slug or model.slug.strip() == '':
# 将中文转换为拼音
slug = ''.join(lazy_pinyin(model.name))
# 转换为小写,移除特殊字符
slug = slug.lower()
slug = re.sub(r'[^\w\s-]', '', slug)
slug = re.sub(r'[-\s]+', '-', slug).strip('-')
# 如果转换后为空使用code
if not slug:
slug = f"site-{model.code}"
# 确保slug唯一限制长度和重试次数
base_slug = slug[:50]
counter = 1
final_slug = slug
max_slug_attempts = 100
while counter < max_slug_attempts:
existing = Site.query.filter(Site.slug == final_slug).first()
if not existing or existing.id == model.id:
break
final_slug = f"{base_slug}-{counter}"
counter += 1
# 如果100次都失败使用code确保唯一
if counter >= max_slug_attempts:
final_slug = f"{base_slug}-{model.code}"
model.slug = final_slug
# 标签管理视图
class TagAdmin(SecureModelView):
can_edit = True
can_delete = True
can_create = True
can_view_details = False
# 显示操作列
column_display_actions = True
column_list = ['id', 'name', 'slug', 'description', 'sort_order']
column_searchable_list = ['name', 'description']
column_labels = {
@@ -205,6 +621,14 @@ def create_app(config_name='default'):
# 管理员视图
class AdminAdmin(SecureModelView):
can_edit = True
can_delete = True
can_create = True
can_view_details = False
# 显示操作列
column_display_actions = True
column_list = ['id', 'username', 'email', 'is_active', 'last_login', 'created_at']
column_searchable_list = ['username', 'email']
column_filters = ['is_active']
@@ -223,17 +647,55 @@ def create_app(config_name='default'):
if is_created:
model.set_password('admin123') # 默认密码
# 新闻管理视图
class NewsAdmin(SecureModelView):
can_edit = True
can_delete = True
can_create = True
can_view_details = False
# 显示操作列
column_display_actions = True
column_list = ['id', 'site', 'title', 'news_type', 'published_at', 'is_active']
column_searchable_list = ['title', 'content']
column_filters = ['site', 'news_type', 'is_active', 'published_at']
column_labels = {
'id': 'ID',
'site': '关联网站',
'title': '新闻标题',
'content': '新闻内容',
'news_type': '新闻类型',
'url': '新闻链接',
'published_at': '发布时间',
'is_active': '是否启用',
'created_at': '创建时间',
'updated_at': '更新时间'
}
form_columns = ['site', 'title', 'content', 'news_type', 'url', 'published_at', 'is_active']
# 可选的新闻类型
form_choices = {
'news_type': [
('Product Update', 'Product Update'),
('Industry News', 'Industry News'),
('Company News', 'Company News'),
('Other', 'Other')
]
}
# 初始化 Flask-Admin
admin = Admin(
app,
name='ZJPB 焦提示词 - 后台管理',
name='ZJPB 焦提示词',
template_mode='bootstrap4',
index_view=SecureAdminIndexView(),
base_template='admin/custom_base.html'
index_view=SecureAdminIndexView(name='控制台', url='/admin'),
base_template='admin/master.html'
)
admin.add_view(SiteAdmin(Site, db.session, name='网站管理'))
admin.add_view(TagAdmin(Tag, db.session, name='标签管理'))
admin.add_view(NewsAdmin(News, db.session, name='新闻管理'))
admin.add_view(AdminAdmin(AdminModel, db.session, name='管理员', endpoint='admin_users'))
return app

View File

@@ -20,6 +20,20 @@ class Config:
SQLALCHEMY_TRACK_MODIFICATIONS = False
SQLALCHEMY_ECHO = False
# 数据库连接池配置
SQLALCHEMY_ENGINE_OPTIONS = {
'pool_size': 10, # 连接池大小
'pool_recycle': 3600, # 连接回收时间(秒)
'pool_pre_ping': True, # 每次取连接前先ping确保连接有效
'pool_timeout': 30, # 连接池超时时间
'max_overflow': 20, # 超过pool_size后最多创建的连接数
'connect_args': {
'connect_timeout': 10, # 连接超时(秒)
'read_timeout': 30, # 读取超时(秒)
'write_timeout': 30, # 写入超时(秒)
}
}
# 分页配置
SITES_PER_PAGE = 20

View File

@@ -0,0 +1,62 @@
"""添加code字段的迁移脚本"""
import sys
import os
import random
# 添加项目根目录到系统路径
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from app import create_app
from models import db, Site
from sqlalchemy import text
# 创建应用上下文
app = create_app()
def generate_unique_code():
"""生成唯一的8位数字编码"""
while True:
# 生成10000000-99999999之间的随机数
code = str(random.randint(10000000, 99999999))
# 检查是否已存在
if not Site.query.filter_by(code=code).first():
return code
with app.app_context():
try:
print("Step 1: Adding code column to sites table...")
# 添加code列先允许为空
with db.engine.connect() as connection:
connection.execute(text('ALTER TABLE sites ADD COLUMN code VARCHAR(8) NULL COMMENT "8位数字编码"'))
connection.commit()
print(" - Column added successfully!")
print("\nStep 2: Generating codes for existing sites...")
# 为现有网站生成code
sites = Site.query.all()
for site in sites:
site.code = generate_unique_code()
print(f" - Site #{site.id} '{site.name}': code = {site.code}")
db.session.commit()
print(f" - Generated codes for {len(sites)} sites!")
print("\nStep 3: Making code column NOT NULL and UNIQUE...")
# 现在修改列为NOT NULL和UNIQUE
with db.engine.connect() as connection:
connection.execute(text('ALTER TABLE sites MODIFY COLUMN code VARCHAR(8) NOT NULL'))
connection.execute(text('ALTER TABLE sites ADD UNIQUE INDEX idx_site_code (code)'))
connection.commit()
print(" - Code column constraints added!")
print("\n✓ Migration completed successfully!")
except Exception as e:
print(f"\n✗ Migration failed: {str(e)}")
import traceback
traceback.print_exc()
db.session.rollback()

View File

@@ -0,0 +1,17 @@
"""添加news表的迁移脚本"""
import sys
import os
# 添加项目根目录到系统路径
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from app import create_app
from models import db, News
# 创建应用上下文
app = create_app()
with app.app_context():
# 创建news表
db.create_all()
print("✓ News表创建成功")

View File

@@ -0,0 +1,73 @@
"""修复空slug的迁移脚本"""
import sys
import os
import re
# 添加项目根目录到系统路径
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from app import create_app
from models import db, Site
from datetime import datetime
from pypinyin import lazy_pinyin
# 创建应用上下文
app = create_app()
def generate_slug(name, site_id=None):
"""从名称生成slug"""
# 将中文转换为拼音
slug = ''.join(lazy_pinyin(name))
# 转换为小写,移除特殊字符
slug = slug.lower()
slug = re.sub(r'[^\w\s-]', '', slug)
slug = re.sub(r'[-\s]+', '-', slug).strip('-')
# 如果转换后为空使用ID或时间戳
if not slug:
if site_id:
slug = f"site-{site_id}"
else:
slug = f"site-{datetime.now().strftime('%Y%m%d%H%M%S')}"
# 确保slug唯一
base_slug = slug
counter = 1
while Site.query.filter(Site.slug == slug, Site.id != site_id).first():
slug = f"{base_slug}-{counter}"
counter += 1
return slug
with app.app_context():
try:
# 查找所有slug为空或包含乱码的网站
all_sites = Site.query.all()
sites_to_fix = []
for site in all_sites:
# 检查slug是否为空、None、或包含乱码
if (not site.slug or
site.slug == 'None' or
site.slug.strip() == '' or
any(ord(char) > 127 and not char.isalpha() for char in site.slug)):
sites_to_fix.append(site)
if not sites_to_fix:
print("No sites to fix. All good!")
else:
print(f"Found {len(sites_to_fix)} sites to fix...")
for site in sites_to_fix:
old_slug = site.slug
site.slug = generate_slug(site.name, site.id)
print(f" - Site #{site.id} '{site.name}': '{old_slug}' -> '{site.slug}'")
db.session.commit()
print(f"Successfully fixed {len(sites_to_fix)} sites!")
except Exception as e:
print(f"Migration failed: {str(e)}")
import traceback
traceback.print_exc()
db.session.rollback()

View File

@@ -0,0 +1,25 @@
"""修改slug字段为可空的迁移脚本"""
import sys
import os
# 添加项目根目录到系统路径
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from app import create_app
from models import db
from sqlalchemy import text
# 创建应用上下文
app = create_app()
with app.app_context():
# 使用原生SQL修改列属性
try:
# MySQL语法修改slug列为可空
with db.engine.connect() as connection:
connection.execute(text('ALTER TABLE sites MODIFY COLUMN slug VARCHAR(100) NULL'))
connection.commit()
print("slug field successfully updated to nullable!")
except Exception as e:
print(f"Migration failed: {str(e)}")
print("Note: If the field is already nullable, you can ignore this error.")

View File

@@ -16,9 +16,10 @@ class Site(db.Model):
__tablename__ = 'sites'
id = db.Column(db.Integer, primary_key=True)
code = db.Column(db.String(8), unique=True, nullable=False, comment='8位数字编码')
name = db.Column(db.String(100), nullable=False, comment='网站名称')
url = db.Column(db.String(500), nullable=False, comment='网站URL')
slug = db.Column(db.String(100), unique=True, nullable=False, comment='URL别名')
slug = db.Column(db.String(100), unique=True, nullable=True, comment='URL别名(SEO用)')
logo = db.Column(db.String(500), comment='Logo图片路径')
short_desc = db.Column(db.String(200), comment='简短描述')
description = db.Column(db.Text, comment='详细介绍')
@@ -40,6 +41,7 @@ class Site(db.Model):
"""转换为字典"""
return {
'id': self.id,
'code': self.code,
'name': self.name,
'url': self.url,
'slug': self.slug,
@@ -78,6 +80,40 @@ class Tag(db.Model):
'icon': self.icon
}
class News(db.Model):
"""新闻模型"""
__tablename__ = 'news'
id = db.Column(db.Integer, primary_key=True)
site_id = db.Column(db.Integer, db.ForeignKey('sites.id'), nullable=False, comment='关联网站ID')
title = db.Column(db.String(200), nullable=False, comment='新闻标题')
content = db.Column(db.Text, comment='新闻内容')
news_type = db.Column(db.String(50), default='Industry News', comment='新闻类型')
url = db.Column(db.String(500), comment='新闻链接')
published_at = db.Column(db.DateTime, default=datetime.now, comment='发布时间')
is_active = db.Column(db.Boolean, default=True, comment='是否启用')
created_at = db.Column(db.DateTime, default=datetime.now, comment='创建时间')
updated_at = db.Column(db.DateTime, default=datetime.now, onupdate=datetime.now, comment='更新时间')
# 关联网站
site = db.relationship('Site', backref=db.backref('news', lazy='dynamic', order_by='News.published_at.desc()'))
def __repr__(self):
return f'<News {self.title}>'
def to_dict(self):
"""转换为字典"""
return {
'id': self.id,
'site_id': self.site_id,
'title': self.title,
'content': self.content,
'news_type': self.news_type,
'url': self.url,
'published_at': self.published_at.strftime('%Y-%m-%d') if self.published_at else None,
'created_at': self.created_at.strftime('%Y-%m-%d %H:%M:%S') if self.created_at else None
}
class Admin(UserMixin, db.Model):
"""管理员模型"""
__tablename__ = 'admins'

View File

@@ -10,3 +10,4 @@ WTForms==2.3.3
requests==2.31.0
beautifulsoup4==4.12.2
Pillow>=10.2.0
openai>=1.0.0

View File

@@ -0,0 +1,97 @@
/* 管理后台操作按钮样式 - 添加中文文字 */
/* 表格操作列 */
td.list-buttons-column,
th.list-buttons-column {
white-space: nowrap;
min-width: 140px;
text-align: center;
}
/* 隐藏所有图标 */
td.list-buttons-column a.icon .glyphicon,
td.list-buttons-column a.icon .fa,
td.list-buttons-column a.icon span.glyphicon,
td.list-buttons-column a.icon i,
td.list-buttons-column form .glyphicon,
td.list-buttons-column form .fa,
td.list-buttons-column form span.glyphicon,
td.list-buttons-column form i {
display: none !important;
}
/* 通用操作按钮样式(链接) */
td.list-buttons-column a.icon {
display: inline-flex !important;
align-items: center;
justify-content: center;
min-width: 60px;
height: 32px;
padding: 0 12px;
margin: 0 4px;
border-radius: 4px;
font-size: 13px;
font-weight: 500;
text-decoration: none;
transition: all 0.2s;
font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
}
/* 删除表单样式 */
td.list-buttons-column form {
display: inline-block;
margin: 0;
padding: 0;
}
/* 删除表单的提交按钮 */
td.list-buttons-column form button,
td.list-buttons-column form input[type="submit"] {
display: inline-flex !important;
align-items: center;
justify-content: center;
min-width: 60px;
height: 32px;
padding: 0 12px;
margin: 0 4px;
border-radius: 4px;
font-size: 13px;
font-weight: 500;
text-decoration: none;
transition: all 0.2s;
font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
background: #E34D59 !important;
color: white !important;
border: none;
cursor: pointer;
}
td.list-buttons-column form button:hover,
td.list-buttons-column form input[type="submit"]:hover {
background: #C9353F !important;
}
/* 使用::before添加删除文字 */
td.list-buttons-column form button::before,
td.list-buttons-column form input[type="submit"]::before {
content: "删除";
}
/* 编辑按钮 - 通过href匹配 */
td.list-buttons-column a[href*="/edit/"] {
background: #0052D9 !important;
color: white !important;
}
td.list-buttons-column a[href*="/edit/"]:hover {
background: #0041A8 !important;
}
td.list-buttons-column a[href*="/edit/"]::after {
content: "编辑";
}
/* 查看按钮 - 完全隐藏 */
td.list-buttons-column a[href*="/details/"] {
display: none !important;
}

83
static/css/admin-i18n.css Normal file
View File

@@ -0,0 +1,83 @@
/* Flask-Admin 界面中文化样式 */
/* ========== 顶部导航栏中文化 ========== */
/* List (4) 标签页 - 只针对第一个li */
.nav-tabs li:first-child a {
font-size: 0 !important;
}
.nav-tabs li:first-child a::after {
font-size: 14px;
content: "列表";
}
/* 保留数字显示 */
.nav-tabs li a .badge {
font-size: 12px !important;
}
/* Create 按钮 - 通过href精确匹配 */
.nav.nav-tabs ~ .btn-group a.btn-primary,
a.btn.btn-primary[href$="/new/"] {
font-size: 0 !important;
}
.nav.nav-tabs ~ .btn-group a.btn-primary::before,
a.btn.btn-primary[href$="/new/"]::before {
font-size: 14px;
content: "创建";
font-family: inherit;
}
.nav.nav-tabs ~ .btn-group a.btn-primary .fa,
a.btn.btn-primary[href$="/new/"] .fa {
font-size: 14px !important;
margin-right: 4px;
}
/* Add Filter 下拉按钮 */
.btn-group .dropdown-toggle {
font-size: 0 !important;
}
.btn-group .dropdown-toggle::before {
font-size: 14px;
}
/* 根据位置区分不同的dropdown */
/* Add Filter - 第一个dropdown */
.btn-group:nth-of-type(1) .dropdown-toggle::before {
content: "添加筛选";
}
/* With selected - 第二个dropdown (通常在表格上方) */
form[id^="action_confirmation"] ~ .btn-group .dropdown-toggle::before,
.actions-nav .dropdown-toggle::before {
content: "批量操作";
}
/* 保留下拉箭头 */
.btn-group .dropdown-toggle .caret {
font-size: 0;
margin-left: 4px;
display: inline-block;
width: 0;
height: 0;
border-left: 4px solid transparent;
border-right: 4px solid transparent;
border-top: 4px solid currentColor;
}
/* Delete 批量删除选项 */
.dropdown-menu li a {
font-size: 14px !important;
}
/* ========== 修正:避免影响其他链接 ========== */
/* 重置可能被误伤的元素 */
.admin-sidebar a,
.nav-item a,
table a {
font-size: 14px !important;
}

View File

@@ -0,0 +1,636 @@
/* ========== Flask-Admin 左侧菜单布局 - ZJPB焦提示词 ========== */
/* TDesign 色彩系统 */
:root {
--td-brand-color: #0052D9;
--td-brand-color-hover: #266FE8;
--td-brand-color-active: #0034B5;
--td-brand-color-light: #ECF2FE;
--td-success-color: #00A870;
--td-warning-color: #E37318;
--td-error-color: #D54941;
--td-bg-color-page: #F5F7FA;
--td-bg-color-container: #FFFFFF;
--td-bg-color-container-hover: #F5F5F5;
--td-text-color-primary: #000000;
--td-text-color-secondary: #606266;
--td-text-color-placeholder: #C0C4CC;
--td-border-color: #DCDFE6;
--td-border-color-light: #E4E7ED;
--td-border-radius: 3px;
--td-border-radius-medium: 6px;
--td-shadow-1: 0 1px 4px rgba(0, 0, 0, .05);
--td-shadow-2: 0 2px 12px rgba(0, 0, 0, .08);
--sidebar-width: 240px;
}
/* 全局样式重置 */
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body.admin-sidebar-layout {
background: var(--td-bg-color-page);
color: var(--td-text-color-primary);
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, 'Noto Sans', sans-serif;
font-size: 14px;
line-height: 1.5;
overflow-x: hidden;
}
/* ========== 左侧边栏 ========== */
.admin-sidebar {
position: fixed;
left: 0;
top: 0;
bottom: 0;
width: var(--sidebar-width);
background: var(--td-bg-color-container);
border-right: 1px solid var(--td-border-color);
display: flex;
flex-direction: column;
z-index: 1000;
}
/* Logo */
.sidebar-logo {
display: flex;
align-items: center;
gap: 12px;
padding: 20px 24px;
border-bottom: 1px solid var(--td-border-color);
}
.sidebar-logo .logo-icon {
font-size: 32px;
color: var(--td-brand-color);
}
.sidebar-logo .logo-text {
font-size: 18px;
font-weight: 600;
color: var(--td-text-color-primary);
font-family: 'Space Grotesk', sans-serif;
}
/* 导航区域 */
.sidebar-nav {
flex: 1;
overflow-y: auto;
padding: 16px 0;
}
.nav-section {
margin-bottom: 24px;
}
.nav-section-title {
font-size: 12px;
font-weight: 600;
color: var(--td-text-color-secondary);
text-transform: uppercase;
letter-spacing: 0.5px;
padding: 8px 24px;
margin-bottom: 4px;
}
.nav-menu {
list-style: none;
padding: 0;
margin: 0;
}
.nav-item {
margin: 2px 12px;
}
.nav-link {
display: flex;
align-items: center;
gap: 12px;
padding: 10px 12px;
color: var(--td-text-color-secondary);
text-decoration: none;
border-radius: var(--td-border-radius-medium);
transition: all 0.2s cubic-bezier(0.38, 0, 0.24, 1);
}
.nav-link:hover {
background: var(--td-bg-color-container-hover);
color: var(--td-text-color-primary);
text-decoration: none;
}
.nav-item.active .nav-link {
background: var(--td-brand-color-light);
color: var(--td-brand-color);
font-weight: 500;
}
.nav-icon {
font-size: 20px;
width: 20px;
height: 20px;
display: flex;
align-items: center;
justify-content: center;
}
.nav-text {
flex: 1;
font-size: 14px;
}
/* 用户信息 */
.sidebar-user {
display: flex;
align-items: center;
gap: 12px;
padding: 16px 24px;
border-top: 1px solid var(--td-border-color);
background: var(--td-bg-color-page);
}
.user-avatar {
width: 40px;
height: 40px;
border-radius: 50%;
background: var(--td-brand-color-light);
display: flex;
align-items: center;
justify-content: center;
color: var(--td-brand-color);
}
.user-avatar .material-symbols-outlined {
font-size: 24px;
}
.user-info {
flex: 1;
min-width: 0;
}
.user-name {
font-size: 14px;
font-weight: 500;
color: var(--td-text-color-primary);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.user-email {
font-size: 12px;
color: var(--td-text-color-secondary);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
/* ========== 主内容区域 ========== */
.admin-main {
margin-left: var(--sidebar-width);
min-height: 100vh;
display: flex;
flex-direction: column;
}
/* 顶部导航 */
.admin-header {
position: sticky;
top: 0;
z-index: 100;
background: var(--td-bg-color-container);
border-bottom: 1px solid var(--td-border-color);
padding: 16px 32px;
display: flex;
align-items: center;
justify-content: space-between;
box-shadow: var(--td-shadow-1);
}
.header-breadcrumb {
display: flex;
align-items: center;
gap: 8px;
font-size: 14px;
}
.breadcrumb-link {
color: var(--td-text-color-secondary);
text-decoration: none;
transition: color 0.2s;
}
.breadcrumb-link:hover {
color: var(--td-brand-color);
text-decoration: none;
}
.breadcrumb-separator {
color: var(--td-text-color-placeholder);
}
.breadcrumb-current {
color: var(--td-text-color-primary);
font-weight: 500;
}
.header-actions {
display: flex;
align-items: center;
gap: 12px;
}
.search-box {
position: relative;
width: 300px;
}
.search-icon {
position: absolute;
left: 12px;
top: 50%;
transform: translateY(-50%);
color: var(--td-text-color-placeholder);
font-size: 20px;
}
.search-input {
width: 100%;
padding: 8px 12px 8px 40px;
border: 1px solid var(--td-border-color);
border-radius: var(--td-border-radius);
font-size: 14px;
background: var(--td-bg-color-page);
transition: all 0.2s;
}
.search-input:focus {
outline: none;
border-color: var(--td-brand-color);
box-shadow: 0 0 0 2px rgba(0, 82, 217, 0.1);
}
.header-btn {
width: 36px;
height: 36px;
display: flex;
align-items: center;
justify-content: center;
background: transparent;
border: none;
border-radius: var(--td-border-radius);
color: var(--td-text-color-secondary);
cursor: pointer;
transition: all 0.2s;
}
.header-btn:hover {
background: var(--td-bg-color-container-hover);
color: var(--td-text-color-primary);
}
.header-btn .material-symbols-outlined {
font-size: 20px;
}
/* 页面内容 */
.admin-content {
flex: 1;
padding: 32px;
}
.page-header {
display: flex;
align-items: flex-start;
justify-content: space-between;
margin-bottom: 24px;
}
.page-title {
font-size: 28px;
font-weight: 600;
color: var(--td-text-color-primary);
margin: 0 0 8px 0;
font-family: 'Space Grotesk', sans-serif;
}
.page-description {
font-size: 14px;
color: var(--td-text-color-secondary);
margin: 0;
}
/* ========== 表格样式 ========== */
.table {
background: var(--td-bg-color-container);
border-radius: var(--td-border-radius-medium);
overflow: hidden;
box-shadow: var(--td-shadow-1);
border: 1px solid var(--td-border-color);
}
.table thead th {
background: var(--td-bg-color-page);
border: none;
color: var(--td-text-color-secondary);
font-size: 12px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.5px;
padding: 16px;
border-bottom: 1px solid var(--td-border-color);
}
.table tbody tr {
border-bottom: 1px solid var(--td-border-color-light);
transition: background-color 0.2s;
}
.table tbody tr:hover {
background: var(--td-bg-color-page);
}
.table tbody tr:last-child {
border-bottom: none;
}
.table td {
padding: 16px;
vertical-align: middle;
color: var(--td-text-color-primary);
border: none;
}
/* ========== 表单样式 ========== */
.form-control {
background: var(--td-bg-color-container);
border: 1px solid var(--td-border-color);
border-radius: var(--td-border-radius);
padding: 8px 12px;
font-size: 14px;
color: var(--td-text-color-primary);
transition: all 0.2s cubic-bezier(0.38, 0, 0.24, 1);
}
.form-control:focus {
border-color: var(--td-brand-color);
box-shadow: 0 0 0 2px rgba(0, 82, 217, 0.1);
outline: none;
}
.form-control::placeholder {
color: var(--td-text-color-placeholder);
}
label, .form-label {
color: var(--td-text-color-primary);
font-weight: 500;
font-size: 14px;
margin-bottom: 8px;
}
/* ========== 按钮样式 ========== */
.btn {
padding: 8px 16px;
border-radius: var(--td-border-radius);
font-size: 14px;
font-weight: 500;
border: none;
cursor: pointer;
transition: all 0.2s cubic-bezier(0.38, 0, 0.24, 1);
display: inline-flex;
align-items: center;
gap: 6px;
}
.btn-primary {
background: var(--td-brand-color);
color: #FFFFFF;
}
.btn-primary:hover {
background: var(--td-brand-color-hover);
transform: translateY(-1px);
box-shadow: 0 4px 8px rgba(0, 82, 217, 0.2);
}
.btn-success {
background: var(--td-success-color);
color: #FFFFFF;
}
.btn-warning {
background: var(--td-warning-color);
color: #FFFFFF;
}
.btn-danger {
background: var(--td-error-color);
color: #FFFFFF;
}
.btn-secondary, .btn-default {
background: var(--td-bg-color-container);
border: 1px solid var(--td-border-color);
color: var(--td-text-color-primary);
}
.btn-secondary:hover, .btn-default:hover {
background: var(--td-bg-color-container-hover);
}
/* ========== 卡片样式 ========== */
.card, .panel {
background: var(--td-bg-color-container);
border: 1px solid var(--td-border-color);
border-radius: var(--td-border-radius-medium);
box-shadow: var(--td-shadow-1);
margin-bottom: 16px;
}
.card-header, .panel-heading {
background: transparent;
border-bottom: 1px solid var(--td-border-color);
padding: 16px 20px;
font-weight: 600;
font-size: 16px;
}
.card-body, .panel-body {
padding: 20px;
}
/* ========== 警告框 ========== */
.alert {
border-radius: var(--td-border-radius-medium);
border: none;
padding: 12px 16px;
margin-bottom: 16px;
}
.alert-success {
background: rgba(0, 168, 112, 0.1);
color: var(--td-success-color);
}
.alert-danger {
background: rgba(213, 73, 65, 0.1);
color: var(--td-error-color);
}
.alert-info {
background: rgba(0, 82, 217, 0.1);
color: var(--td-brand-color);
}
.alert-warning {
background: rgba(227, 115, 24, 0.1);
color: var(--td-warning-color);
}
/* ========== 分页 ========== */
.pagination {
display: flex;
gap: 4px;
margin: 20px 0;
}
.pagination .page-link {
background: var(--td-bg-color-container);
border: 1px solid var(--td-border-color);
color: var(--td-text-color-primary);
border-radius: var(--td-border-radius);
padding: 6px 12px;
transition: all 0.2s;
}
.pagination .page-link:hover {
background: var(--td-brand-color-light);
border-color: var(--td-brand-color);
color: var(--td-brand-color);
text-decoration: none;
}
.pagination .page-item.active .page-link {
background: var(--td-brand-color);
border-color: var(--td-brand-color);
color: #FFFFFF;
}
.pagination .page-item.disabled .page-link {
background: var(--td-bg-color-page);
border-color: var(--td-border-color);
color: var(--td-text-color-placeholder);
cursor: not-allowed;
}
/* ========== 模态框 ========== */
.modal-content {
background: var(--td-bg-color-container);
border: 1px solid var(--td-border-color);
border-radius: var(--td-border-radius-medium);
box-shadow: var(--td-shadow-2);
}
.modal-header {
border-bottom: 1px solid var(--td-border-color);
padding: 20px 24px;
}
.modal-title {
font-size: 18px;
font-weight: 600;
color: var(--td-text-color-primary);
}
.modal-body {
padding: 24px;
}
.modal-footer {
border-top: 1px solid var(--td-border-color);
padding: 16px 24px;
}
/* ========== 下拉菜单 ========== */
.dropdown-menu {
background: var(--td-bg-color-container);
border: 1px solid var(--td-border-color);
border-radius: var(--td-border-radius-medium);
box-shadow: var(--td-shadow-2);
}
.dropdown-item {
color: var(--td-text-color-primary);
padding: 8px 16px;
font-size: 14px;
}
.dropdown-item:hover {
background: var(--td-brand-color-light);
color: var(--td-brand-color);
}
/* ========== Select2 ========== */
.select2-container--bootstrap4 .select2-selection {
background: var(--td-bg-color-container);
border: 1px solid var(--td-border-color);
border-radius: var(--td-border-radius);
}
.select2-dropdown {
background: var(--td-bg-color-container);
border: 1px solid var(--td-border-color);
border-radius: var(--td-border-radius-medium);
box-shadow: var(--td-shadow-2);
}
.select2-results__option {
color: var(--td-text-color-primary);
padding: 8px 12px;
}
.select2-results__option--highlighted {
background: var(--td-brand-color-light);
color: var(--td-brand-color);
}
/* ========== 滚动条 ========== */
::-webkit-scrollbar {
width: 8px;
height: 8px;
}
::-webkit-scrollbar-track {
background: var(--td-bg-color-page);
}
::-webkit-scrollbar-thumb {
background: var(--td-border-color);
border-radius: 4px;
}
::-webkit-scrollbar-thumb:hover {
background: var(--td-text-color-placeholder);
}
/* ========== 响应式 ========== */
@media (max-width: 768px) {
.admin-sidebar {
transform: translateX(-100%);
transition: transform 0.3s;
}
.admin-main {
margin-left: 0;
}
.search-box {
display: none;
}
}

View File

@@ -1,295 +1,459 @@
/* ========== Flask-Admin 后台科技感主题 - ZJPB焦提示词 ========== */
/* ========== Flask-Admin TDesign主题 - ZJPB焦提示词 (亮色主题) ========== */
/* 深色主题覆盖 */
body.admin-theme {
background: #111618 !important;
background-image:
radial-gradient(at 20% 20%, rgba(37, 192, 244, 0.08) 0px, transparent 50%),
radial-gradient(at 80% 80%, rgba(124, 58, 237, 0.08) 0px, transparent 50%);
color: #ffffff !important;
font-family: 'Space Grotesk', 'Noto Sans', sans-serif !important;
/* TDesign 色彩系统 */
:root {
--td-brand-color: #0052D9;
--td-brand-color-hover: #266FE8;
--td-brand-color-active: #0034B5;
--td-brand-color-light: #ECF2FE;
--td-success-color: #00A870;
--td-warning-color: #E37318;
--td-error-color: #D54941;
--td-bg-color-page: #F3F3F3;
--td-bg-color-container: #FFFFFF;
--td-bg-color-container-hover: #F5F5F5;
--td-text-color-primary: #000000;
--td-text-color-secondary: #606266;
--td-text-color-placeholder: #C0C4CC;
--td-border-color: #DCDFE6;
--td-border-color-light: #E4E7ED;
--td-border-radius: 3px;
--td-border-radius-medium: 6px;
--td-shadow-1: 0 1px 10px rgba(0, 0, 0, .05);
--td-shadow-2: 0 2px 20px rgba(0, 0, 0, .08);
}
/* 亮色模式 - 全局覆盖 */
body, body.admin-theme, .admin-theme {
background: var(--td-bg-color-page) !important;
color: var(--td-text-color-primary) !important;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, 'Noto Sans', sans-serif !important;
}
/* 主容器 */
.container-fluid, .container {
background: transparent !important;
}
/* 导航栏 */
.navbar-admin {
background: rgba(27, 36, 39, 0.95) !important;
backdrop-filter: blur(20px);
border-bottom: 1px solid #283539;
box-shadow: 0 4px 30px rgba(0, 0, 0, 0.3);
.navbar, .navbar-default, .navbar-inverse, .navbar-admin {
background: #FFFFFF !important;
border-bottom: 1px solid var(--td-border-color) !important;
box-shadow: var(--td-shadow-1) !important;
border: none !important;
min-height: 64px !important;
}
.navbar-admin .navbar-brand {
background: linear-gradient(to right, #25c0f4, #c084fc);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
font-weight: 700;
font-family: 'Space Grotesk', sans-serif !important;
.navbar-brand, .navbar .navbar-brand {
color: var(--td-brand-color) !important;
font-weight: 600 !important;
font-size: 18px !important;
}
.navbar-nav .nav-link, .navbar-nav > li > a {
color: var(--td-text-color-secondary) !important;
font-size: 14px !important;
padding: 8px 16px !important;
border-radius: var(--td-border-radius) !important;
transition: all 0.2s ease !important;
}
.navbar-nav .nav-link:hover, .navbar-nav > li > a:hover,
.navbar-nav .nav-link:focus, .navbar-nav > li > a:focus {
color: var(--td-text-color-primary) !important;
background: var(--td-bg-color-container-hover) !important;
}
/* 侧边栏 */
.nav-sidebar {
background: rgba(27, 36, 39, 0.8) !important;
.nav-sidebar, .nav.nav-pills {
background: #FFFFFF !important;
border-right: 1px solid var(--td-border-color) !important;
}
.nav-sidebar .nav-link {
color: #9cb2ba !important;
transition: all 0.3s ease;
.nav-sidebar .nav-link, .nav-sidebar > li > a,
.nav-pills > li > a {
color: var(--td-text-color-secondary) !important;
padding: 12px 16px !important;
margin: 4px 8px !important;
border-radius: var(--td-border-radius-medium) !important;
font-size: 14px !important;
transition: all 0.2s cubic-bezier(0.38, 0, 0.24, 1) !important;
background: transparent !important;
}
.nav-sidebar .nav-link:hover,
.nav-sidebar .nav-link.active {
color: #ffffff !important;
background: rgba(37, 192, 244, 0.15) !important;
border-left: 3px solid #25c0f4;
.nav-sidebar .nav-link:hover, .nav-sidebar > li > a:hover,
.nav-pills > li > a:hover {
color: var(--td-text-color-primary) !important;
background: var(--td-brand-color-light) !important;
}
.nav-sidebar .nav-link.active, .nav-sidebar > li.active > a,
.nav-pills > li.active > a {
color: var(--td-brand-color) !important;
background: var(--td-brand-color-light) !important;
font-weight: 500 !important;
}
/* 卡片和面板 */
.card, .panel {
background: rgba(27, 36, 39, 0.6) !important;
backdrop-filter: blur(20px);
border: 1px solid #283539 !important;
border-radius: 12px !important;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
background: var(--td-bg-color-container) !important;
border: 1px solid var(--td-border-color) !important;
border-radius: var(--td-border-radius-medium) !important;
box-shadow: var(--td-shadow-1);
margin-bottom: 16px;
}
.card-header, .panel-heading {
background: rgba(37, 192, 244, 0.08) !important;
border-bottom: 1px solid #283539 !important;
color: #ffffff !important;
background: transparent !important;
border-bottom: 1px solid var(--td-border-color) !important;
color: var(--td-text-color-primary) !important;
font-weight: 600;
font-size: 16px;
padding: 16px !important;
}
/* 表格 */
.table {
color: #ffffff !important;
color: var(--td-text-color-primary) !important;
font-size: 14px;
border-collapse: separate;
border-spacing: 0;
}
.table thead th {
background: rgba(30, 39, 44, 0.8) !important;
border-color: #283539 !important;
color: #9cb2ba !important;
font-size: 11px;
background: var(--td-bg-color-container-hover) !important;
border: none !important;
color: var(--td-text-color-secondary) !important;
font-size: 12px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.05em;
letter-spacing: 0.5px;
padding: 12px 16px !important;
height: 48px;
}
.table thead th:first-child {
border-top-left-radius: var(--td-border-radius);
}
.table thead th:last-child {
border-top-right-radius: var(--td-border-radius);
}
.table tbody tr {
background: transparent !important;
border-color: #283539 !important;
transition: all 0.2s ease;
border-bottom: 1px solid var(--td-border-color-light) !important;
transition: background-color 0.2s ease;
}
.table tbody tr:hover {
background: rgba(30, 39, 44, 0.5) !important;
background: var(--td-brand-color-light) !important;
}
.table td, .table th {
border-color: #283539 !important;
color: #ffffff !important;
.table tbody tr:last-child {
border-bottom: none !important;
}
.table td {
border: none !important;
color: var(--td-text-color-primary) !important;
padding: 16px !important;
vertical-align: middle;
}
.table th {
border: none !important;
}
/* 表单 */
.form-control {
background: #111618 !important;
border: 1px solid #283539 !important;
color: #ffffff !important;
border-radius: 8px !important;
transition: all 0.3s ease;
background: var(--td-bg-color-container) !important;
border: 1px solid var(--td-border-color) !important;
color: var(--td-text-color-primary) !important;
border-radius: var(--td-border-radius) !important;
padding: 8px 12px !important;
font-size: 14px;
line-height: 22px;
transition: all 0.2s cubic-bezier(0.38, 0, 0.24, 1);
}
.form-control:focus {
background: rgba(27, 36, 39, 0.8) !important;
border-color: #25c0f4 !important;
box-shadow: 0 0 0 3px rgba(37, 192, 244, 0.1) !important;
background: var(--td-bg-color-container) !important;
border-color: var(--td-brand-color) !important;
box-shadow: 0 0 0 2px rgba(0, 82, 217, 0.1) !important;
outline: none;
}
.form-control::placeholder {
color: #9cb2ba !important;
color: var(--td-text-color-placeholder) !important;
}
.form-label, label {
color: #9cb2ba !important;
color: var(--td-text-color-primary) !important;
font-weight: 500;
font-size: 13px;
font-size: 14px;
margin-bottom: 8px;
}
.form-text {
color: var(--td-text-color-secondary) !important;
font-size: 12px;
}
/* 按钮 */
.btn {
border-radius: var(--td-border-radius) !important;
font-size: 14px;
font-weight: 500;
padding: 8px 16px !important;
line-height: 22px;
transition: all 0.2s cubic-bezier(0.38, 0, 0.24, 1);
border: none;
}
.btn-primary {
background: #25c0f4 !important;
border: none !important;
color: #111618 !important;
font-weight: 600;
box-shadow: 0 0 20px rgba(37, 192, 244, 0.3);
transition: all 0.3s ease;
background: var(--td-brand-color) !important;
color: #FFFFFF !important;
box-shadow: 0 2px 4px rgba(0, 82, 217, 0.2);
}
.btn-primary:hover {
background: #1fa8d8 !important;
transform: translateY(-2px);
box-shadow: 0 0 30px rgba(37, 192, 244, 0.5);
background: var(--td-brand-color-hover) !important;
box-shadow: 0 4px 8px rgba(0, 82, 217, 0.3);
transform: translateY(-1px);
}
.btn-primary:active {
background: var(--td-brand-color-active) !important;
transform: translateY(0);
}
.btn-success {
background: var(--td-success-color) !important;
color: #FFFFFF !important;
}
.btn-warning {
background: var(--td-warning-color) !important;
color: #FFFFFF !important;
}
.btn-danger {
background: var(--td-error-color) !important;
color: #FFFFFF !important;
}
.btn-info {
background: linear-gradient(135deg, #25c0f4 0%, #00f2fe 100%) !important;
border: none !important;
color: #111618 !important;
font-weight: 600;
background: #029CD4 !important;
color: #FFFFFF !important;
}
.btn-secondary, .btn-default {
background: rgba(40, 53, 57, 0.6) !important;
border: 1px solid #283539 !important;
color: #ffffff !important;
transition: all 0.3s ease;
background: #FFFFFF !important;
border: 1px solid var(--td-border-color) !important;
color: var(--td-text-color-primary) !important;
}
.btn-secondary:hover, .btn-default:hover {
background: rgba(52, 66, 71, 0.8) !important;
border-color: #4a5a60 !important;
background: var(--td-bg-color-container-hover) !important;
border-color: var(--td-border-color) !important;
}
/* 模态框 */
.modal-content {
background: rgba(27, 36, 39, 0.95) !important;
backdrop-filter: blur(20px);
border: 1px solid #283539 !important;
border-radius: 12px !important;
background: var(--td-bg-color-container) !important;
border: 1px solid var(--td-border-color) !important;
border-radius: var(--td-border-radius-medium) !important;
box-shadow: var(--td-shadow-2);
}
.modal-header {
border-bottom-color: #283539 !important;
background: rgba(37, 192, 244, 0.05);
border-bottom: 1px solid var(--td-border-color) !important;
padding: 20px 24px;
}
.modal-title {
color: var(--td-text-color-primary) !important;
font-size: 18px;
font-weight: 600;
}
.modal-body {
padding: 24px;
}
.modal-footer {
border-top-color: #283539 !important;
border-top: 1px solid var(--td-border-color) !important;
padding: 16px 24px;
}
/* 分页 */
.pagination {
gap: 4px;
}
.pagination .page-link {
background: rgba(27, 36, 39, 0.6) !important;
border-color: #283539 !important;
color: #9cb2ba !important;
background: var(--td-bg-color-container) !important;
border: 1px solid var(--td-border-color) !important;
color: var(--td-text-color-primary) !important;
border-radius: var(--td-border-radius) !important;
padding: 6px 12px;
margin: 0;
transition: all 0.2s ease;
}
.pagination .page-link:hover {
background: rgba(37, 192, 244, 0.1) !important;
border-color: #25c0f4 !important;
color: #ffffff !important;
background: var(--td-brand-color-light) !important;
border-color: var(--td-brand-color) !important;
color: var(--td-brand-color) !important;
}
.pagination .page-item.active .page-link {
background: #25c0f4 !important;
border-color: #25c0f4 !important;
color: #111618 !important;
background: var(--td-brand-color) !important;
border-color: var(--td-brand-color) !important;
color: #FFFFFF !important;
}
.pagination .page-item.disabled .page-link {
background: var(--td-bg-color-container) !important;
border-color: var(--td-border-color) !important;
color: var(--td-text-color-placeholder) !important;
cursor: not-allowed;
}
/* 警告框 */
.alert {
background: rgba(27, 36, 39, 0.8) !important;
border: 1px solid #283539 !important;
color: #ffffff !important;
border-radius: 8px !important;
border-radius: var(--td-border-radius-medium) !important;
border: none !important;
padding: 12px 16px;
font-size: 14px;
}
.alert-success {
background: rgba(34, 197, 94, 0.1) !important;
border-color: rgba(34, 197, 94, 0.3) !important;
color: #4ade80 !important;
background: rgba(0, 168, 112, 0.1) !important;
color: #00A870 !important;
}
.alert-danger {
background: rgba(239, 68, 68, 0.1) !important;
border-color: rgba(239, 68, 68, 0.3) !important;
color: #f87171 !important;
background: rgba(213, 73, 65, 0.1) !important;
color: #D54941 !important;
}
.alert-info {
background: rgba(37, 192, 244, 0.1) !important;
border-color: rgba(37, 192, 244, 0.3) !important;
color: #25c0f4 !important;
background: rgba(2, 156, 212, 0.1) !important;
color: #029CD4 !important;
}
.alert-warning {
background: rgba(251, 191, 36, 0.1) !important;
border-color: rgba(251, 191, 36, 0.3) !important;
color: #fbbf24 !important;
background: rgba(227, 115, 24, 0.1) !important;
color: #E37318 !important;
}
/* 链接 */
a {
color: #25c0f4 !important;
color: var(--td-brand-color) !important;
text-decoration: none;
transition: color 0.2s ease;
}
a:hover {
color: #1fa8d8 !important;
color: var(--td-brand-color-hover) !important;
}
/* 文本颜色 */
.text-muted {
color: #9cb2ba !important;
color: var(--td-text-color-secondary) !important;
}
/* 输入组 */
.input-group-text {
background: rgba(27, 36, 39, 0.6) !important;
border-color: #283539 !important;
color: #9cb2ba !important;
background: var(--td-bg-color-container-hover) !important;
border: 1px solid var(--td-border-color) !important;
color: var(--td-text-color-secondary) !important;
}
/* Select2 下拉框 */
.select2-container--bootstrap4 .select2-selection {
background: #111618 !important;
border-color: #283539 !important;
color: #ffffff !important;
background: var(--td-bg-color-container) !important;
border: 1px solid var(--td-border-color) !important;
color: var(--td-text-color-primary) !important;
border-radius: var(--td-border-radius) !important;
}
.select2-dropdown {
background: rgba(27, 36, 39, 0.95) !important;
border-color: #283539 !important;
backdrop-filter: blur(10px);
background: var(--td-bg-color-container) !important;
border: 1px solid var(--td-border-color) !important;
border-radius: var(--td-border-radius-medium) !important;
box-shadow: var(--td-shadow-2);
}
.select2-results__option {
color: #ffffff !important;
color: var(--td-text-color-primary) !important;
padding: 8px 12px;
}
.select2-results__option--highlighted {
background: rgba(37, 192, 244, 0.2) !important;
background: var(--td-brand-color-light) !important;
color: var(--td-brand-color) !important;
}
/* 徽章 */
.badge {
border-radius: 2px !important;
font-size: 12px;
font-weight: 500;
padding: 2px 8px;
}
.badge-primary {
background: #25c0f4 !important;
color: #111618 !important;
background: var(--td-brand-color) !important;
color: #FFFFFF !important;
}
.badge-success {
background: var(--td-success-color) !important;
color: #FFFFFF !important;
}
.badge-warning {
background: var(--td-warning-color) !important;
color: #FFFFFF !important;
}
.badge-danger {
background: var(--td-error-color) !important;
color: #FFFFFF !important;
}
.badge-secondary {
background: #283539 !important;
color: #9cb2ba !important;
background: var(--td-bg-color-container-hover) !important;
color: var(--td-text-color-secondary) !important;
}
/* 进度条 */
.progress {
background: rgba(27, 36, 39, 0.6) !important;
background: var(--td-bg-color-container-hover) !important;
border-radius: 2px !important;
height: 8px;
}
.progress-bar {
background: #25c0f4 !important;
background: var(--td-brand-color) !important;
}
/* 额外优化 */
.navbar-nav .nav-link {
color: #9cb2ba !important;
/* 复选框和单选框 */
.form-check-input {
background-color: var(--td-bg-color-container) !important;
border: 1px solid var(--td-border-color) !important;
}
.navbar-nav .nav-link:hover {
color: #ffffff !important;
.form-check-input:checked {
background-color: var(--td-brand-color) !important;
border-color: var(--td-brand-color) !important;
}
.form-check-input:focus {
box-shadow: 0 0 0 2px rgba(0, 82, 217, 0.1) !important;
}
/* 自定义滚动条 */
@@ -299,15 +463,123 @@ a:hover {
}
::-webkit-scrollbar-track {
background: #111618;
background: var(--td-bg-color-page);
}
::-webkit-scrollbar-thumb {
background: #283539;
background: var(--td-border-color);
border-radius: 4px;
}
::-webkit-scrollbar-thumb:hover {
background: #3a4b50;
background: var(--td-text-color-placeholder);
}
/* 工具提示 */
.tooltip-inner {
background: var(--td-text-color-primary) !important;
color: #FFFFFF !important;
border-radius: var(--td-border-radius) !important;
padding: 6px 12px;
font-size: 12px;
}
/* 面包屑 */
.breadcrumb {
background: transparent !important;
padding: 0;
margin-bottom: 16px;
}
.breadcrumb-item {
color: var(--td-text-color-secondary) !important;
font-size: 14px;
}
.breadcrumb-item.active {
color: var(--td-text-color-primary) !important;
}
.breadcrumb-item + .breadcrumb-item::before {
color: var(--td-text-color-placeholder) !important;
}
/* 标签页 */
.nav-tabs {
border-bottom: 1px solid var(--td-border-color) !important;
}
.nav-tabs .nav-link {
border: none !important;
color: var(--td-text-color-secondary) !important;
padding: 12px 24px;
margin-bottom: -1px;
border-bottom: 2px solid transparent;
}
.nav-tabs .nav-link:hover {
color: var(--td-text-color-primary) !important;
border-bottom-color: var(--td-border-color) !important;
}
.nav-tabs .nav-link.active {
color: var(--td-brand-color) !important;
background: transparent !important;
border-bottom-color: var(--td-brand-color) !important;
}
/* 下拉菜单 */
.dropdown-menu {
background: var(--td-bg-color-container) !important;
border: 1px solid var(--td-border-color) !important;
border-radius: var(--td-border-radius-medium) !important;
box-shadow: var(--td-shadow-2);
}
.dropdown-item {
color: var(--td-text-color-primary) !important;
padding: 8px 16px;
font-size: 14px;
}
.dropdown-item:hover {
background: var(--td-brand-color-light) !important;
color: var(--td-brand-color) !important;
}
/* 表格操作按钮 */
.table .btn-sm {
padding: 4px 12px !important;
font-size: 12px;
}
/* 空状态 */
.empty-state {
text-align: center;
padding: 48px 24px;
color: var(--td-text-color-secondary);
}
.empty-state-icon {
font-size: 48px;
color: var(--td-text-color-placeholder);
margin-bottom: 16px;
}
/* 加载状态 */
.spinner-border {
border-color: var(--td-brand-color);
border-right-color: transparent;
}
/* 响应式优化 */
@media (max-width: 768px) {
.table {
font-size: 12px;
}
.table td, .table th {
padding: 8px !important;
}
}

View File

@@ -0,0 +1,108 @@
<!DOCTYPE html>
<html lang="en"><head>
<meta charset="utf-8"/>
<meta content="width=device-width, initial-scale=1.0" name="viewport"/>
<title>Admin Login - AI Discovery</title>
<link href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@300;400;500;600;700&amp;display=swap" rel="stylesheet"/>
<link href="https://fonts.googleapis.com/css2?family=Noto+Sans:wght@300;400;500;600;700&amp;display=swap" rel="stylesheet"/>
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&amp;display=swap" rel="stylesheet"/>
<script src="https://cdn.tailwindcss.com?plugins=forms,container-queries"></script>
<script>
tailwind.config = {
theme: {
extend: {
colors: {
"primary": "#0ea5e9", // Sky 500 - Clearer on light
"primary-dark": "#0284c7", // Sky 600 - Hover state
"background": "#f8fafc", // Slate 50 - Light, clean
"surface": "#ffffff",
"input-bg": "#ffffff",
"input-border": "#e2e8f0", // Slate 200
"text-main": "#0f172a", // Slate 900
"text-secondary": "#334155", // Slate 700
"text-muted": "#64748b", // Slate 500
},
fontFamily: {
"display": ["Space Grotesk", "sans-serif"],
"body": ["Noto Sans", "sans-serif"],
},
backgroundImage: {
// Very subtle grid for light mode
'tech-grid': "radial-gradient(circle at center, rgba(14, 165, 233, 0.04) 0%, transparent 60%), linear-gradient(rgba(14, 165, 233, 0.05) 1px, transparent 1px), linear-gradient(90deg, rgba(14, 165, 233, 0.05) 1px, transparent 1px)",
}
},
},
}
</script>
<style>
.glass-panel {
background: rgba(255, 255, 255, 0.75);
backdrop-filter: blur(20px);
-webkit-backdrop-filter: blur(20px);
}
.tech-bg-size {
background-size: 100% 100%, 40px 40px, 40px 40px;
}
</style>
</head>
<body class="bg-background font-display text-text-main min-h-screen flex flex-col items-center justify-center relative overflow-hidden selection:bg-primary/20 selection:text-primary-dark">
<div class="absolute inset-0 z-0 bg-tech-grid tech-bg-size pointer-events-none"></div>
<div class="absolute top-[-20%] right-[-10%] w-[600px] h-[600px] bg-sky-200/40 rounded-full blur-[120px] pointer-events-none mix-blend-multiply"></div>
<div class="absolute bottom-[-10%] left-[-10%] w-[500px] h-[500px] bg-indigo-100/60 rounded-full blur-[100px] pointer-events-none mix-blend-multiply"></div>
<div class="w-full max-w-[480px] px-4 z-10 flex flex-col gap-6">
<a class="group flex items-center gap-2 text-text-muted hover:text-text-main transition-colors w-fit" href="#">
<div class="flex items-center justify-center w-8 h-8 rounded-full border border-input-border bg-white group-hover:border-primary/50 group-hover:bg-primary/5 transition-all shadow-sm">
<span class="material-symbols-outlined text-sm">arrow_back</span>
</div>
<span class="text-sm font-medium">Back to Home</span>
</a>
<div class="glass-panel border border-white rounded-2xl shadow-[0_4px_30px_rgba(0,0,0,0.03)] p-8 md:p-12 relative overflow-hidden ring-1 ring-black/5">
<div class="absolute top-0 left-0 w-full h-[2px] bg-gradient-to-r from-transparent via-primary/50 to-transparent"></div>
<div class="flex flex-col gap-2 mb-8">
<div class="flex items-center gap-3 mb-2">
<div class="p-2 rounded-lg bg-primary/10 text-primary">
<span class="material-symbols-outlined text-2xl">shield_person</span>
</div>
<span class="text-xs font-bold tracking-widest uppercase text-primary">System Access</span>
</div>
<h1 class="text-3xl font-black tracking-tight text-text-main leading-tight">管理员登录</h1>
<p class="text-text-muted text-base font-normal">Enter your credentials to access the AI Control Panel.</p>
</div>
<form class="flex flex-col gap-5">
<div class="space-y-2">
<label class="text-text-secondary text-sm font-semibold leading-normal" for="username">Username</label>
<div class="relative group">
<input class="form-input flex w-full h-12 pl-11 pr-4 rounded-lg text-text-main focus:outline-0 focus:ring-2 focus:ring-primary/20 border border-input-border bg-input-bg focus:border-primary placeholder:text-slate-400 text-base font-normal leading-normal transition-all shadow-sm" id="username" placeholder="Enter your username" type="text"/>
<div class="absolute left-3 top-1/2 -translate-y-1/2 text-slate-400 group-focus-within:text-primary transition-colors flex items-center justify-center pointer-events-none">
<span class="material-symbols-outlined text-[20px]">person</span>
</div>
</div>
</div>
<div class="space-y-2">
<div class="flex justify-between items-end">
<label class="text-text-secondary text-sm font-semibold leading-normal" for="password">Password</label>
<a class="text-xs font-medium text-primary hover:text-primary-dark transition-colors mb-1" href="#">Forgot password?</a>
</div>
<div class="flex w-full items-stretch rounded-lg group relative">
<input class="form-input flex w-full h-12 pl-11 pr-11 rounded-lg text-text-main focus:outline-0 focus:ring-2 focus:ring-primary/20 border border-input-border bg-input-bg focus:border-primary placeholder:text-slate-400 text-base font-normal leading-normal transition-all shadow-sm z-10" id="password" placeholder="Enter your password" type="password"/>
<div class="absolute left-3 top-1/2 -translate-y-1/2 text-slate-400 group-focus-within:text-primary transition-colors flex items-center justify-center pointer-events-none z-20">
<span class="material-symbols-outlined text-[20px]">lock</span>
</div>
<button class="absolute right-0 top-0 h-full px-3 text-slate-400 hover:text-text-secondary transition-colors flex items-center justify-center z-20 focus:outline-none" type="button">
<span class="material-symbols-outlined text-[20px]">visibility_off</span>
</button>
</div>
</div>
<button class="mt-4 flex w-full h-12 cursor-pointer items-center justify-center overflow-hidden rounded-lg bg-primary text-white text-base font-bold leading-normal tracking-wide hover:bg-primary-dark transition-all active:scale-[0.98] shadow-lg shadow-primary/25 hover:shadow-primary/40" type="button">
<span class="truncate">Login</span>
</button>
</form>
<div class="mt-8 flex justify-center gap-6 border-t border-slate-100 pt-6">
<p class="text-text-muted text-xs text-center font-medium">
Protected by reCAPTCHA. <a class="underline hover:text-primary decoration-slate-300 hover:decoration-primary" href="#">Privacy</a> &amp; <a class="underline hover:text-primary decoration-slate-300 hover:decoration-primary" href="#">Terms</a>.
</p>
</div>
</div>
</div>
</body></html>

Binary file not shown.

After

Width:  |  Height:  |  Size: 604 KiB

View File

@@ -0,0 +1,365 @@
<!DOCTYPE html>
<html lang="en"><head>
<meta charset="utf-8"/>
<meta content="width=device-width, initial-scale=1.0" name="viewport"/>
<title>Admin Management Interface</title>
<link href="https://fonts.googleapis.com" rel="preconnect"/>
<link crossorigin="" href="https://fonts.gstatic.com" rel="preconnect"/>
<link href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@300;400;500;700&amp;family=Noto+Sans:wght@400;500;700&amp;display=swap" rel="stylesheet"/>
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&amp;display=swap" rel="stylesheet"/>
<script src="https://cdn.tailwindcss.com?plugins=forms,container-queries"></script>
<script id="tailwind-config">
tailwind.config = {
theme: {
extend: {
colors: {
"primary": "#0ea5e9", // Sky 500
"primary-hover": "#0284c7", // Sky 600
"primary-light": "#e0f2fe", // Sky 100
"surface": "#ffffff",
"surface-highlight": "#f1f5f9", // Slate 100
"border-subtle": "#e2e8f0", // Slate 200
"text-main": "#0f172a", // Slate 900
"text-secondary": "#64748b", // Slate 500
"bg-main": "#f8fafc", // Slate 50
},
fontFamily: {
"display": ["Space Grotesk", "sans-serif"],
"body": ["Noto Sans", "sans-serif"],
},
borderRadius: {"DEFAULT": "0.25rem", "lg": "0.5rem", "xl": "0.75rem", "2xl": "1rem", "full": "9999px"},
},
},
}
</script>
<style>
body {
font-family: "Noto Sans", sans-serif;
}
h1, h2, h3, h4, h5, h6, .font-display {
font-family: "Space Grotesk", sans-serif;
}::-webkit-scrollbar {
width: 8px;
height: 8px;
}
::-webkit-scrollbar-track {
background: #f1f5f9;
}
::-webkit-scrollbar-thumb {
background: #cbd5e1;
border-radius: 4px;
}
::-webkit-scrollbar-thumb:hover {
background: #94a3b8;
}
</style>
</head>
<body class="bg-bg-main text-text-main overflow-hidden flex h-screen w-full">
<aside class="flex flex-col w-64 h-full border-r border-border-subtle bg-surface flex-shrink-0 transition-all duration-300">
<div class="p-6 flex items-center gap-3">
<div class="size-8 text-primary">
<svg fill="none" viewBox="0 0 48 48" xmlns="http://www.w3.org/2000/svg">
<path clip-rule="evenodd" d="M24 18.4228L42 11.475V34.3663C42 34.7796 41.7457 35.1504 41.3601 35.2992L24 42V18.4228Z" fill="currentColor" fill-rule="evenodd"></path>
<path clip-rule="evenodd" d="M24 8.18819L33.4123 11.574L24 15.2071L14.5877 11.574L24 8.18819ZM9 15.8487L21 20.4805V37.6263L9 32.9945V15.8487ZM27 37.6263V20.4805L39 15.8487V32.9945L27 37.6263ZM25.354 2.29885C24.4788 1.98402 23.5212 1.98402 22.646 2.29885L4.98454 8.65208C3.7939 9.08038 3 10.2097 3 11.475V34.3663C3 36.0196 4.01719 37.5026 5.55962 38.098L22.9197 44.7987C23.6149 45.0671 24.3851 45.0671 25.0803 44.7987L42.4404 38.098C43.9828 37.5026 45 36.0196 45 34.3663V11.475C45 10.2097 44.2061 9.08038 43.0155 8.65208L25.354 2.29885Z" fill="currentColor" fill-rule="evenodd"></path>
</svg>
</div>
<h1 class="text-text-main text-xl font-bold tracking-tight">AI Discovery</h1>
</div>
<nav class="flex-1 px-4 flex flex-col gap-2 mt-4">
<div class="px-3 py-2">
<p class="text-text-secondary text-xs font-bold uppercase tracking-wider mb-2">Main Menu</p>
<a class="flex items-center gap-3 px-3 py-2.5 rounded-lg bg-primary-light/50 text-primary border border-primary/10 group transition-colors" href="#">
<span class="material-symbols-outlined text-primary transition-colors">language</span>
<span class="text-sm font-medium">Website Management</span>
</a>
<a class="flex items-center gap-3 px-3 py-2.5 rounded-lg text-text-secondary hover:bg-surface-highlight hover:text-text-main transition-colors mt-1" href="#">
<span class="material-symbols-outlined">label</span>
<span class="text-sm font-medium">Tag Management</span>
</a>
<a class="flex items-center gap-3 px-3 py-2.5 rounded-lg text-text-secondary hover:bg-surface-highlight hover:text-text-main transition-colors mt-1" href="#">
<span class="material-symbols-outlined">group</span>
<span class="text-sm font-medium">Administrators</span>
</a>
</div>
<div class="px-3 py-2 mt-auto">
<p class="text-text-secondary text-xs font-bold uppercase tracking-wider mb-2">System</p>
<a class="flex items-center gap-3 px-3 py-2.5 rounded-lg text-text-secondary hover:bg-surface-highlight hover:text-text-main transition-colors" href="#">
<span class="material-symbols-outlined">settings</span>
<span class="text-sm font-medium">Settings</span>
</a>
</div>
</nav>
<div class="p-4 border-t border-border-subtle">
<div class="flex items-center gap-3">
<div class="bg-center bg-no-repeat bg-cover rounded-full size-10 border border-border-subtle" data-alt="User profile picture showing a minimal avatar" style='background-image: url("https://lh3.googleusercontent.com/aida-public/AB6AXuDlD4gGno1M8YZQOQ6QyLdUZtaU7klHxTjeH2Kf3aH39GkQ56ld-iSPZ6RCbkxzCeXJzMq6_29atBkPks_wP-Q7pvpyNAAZW2FJKxOFTSrcQZp2O30dVhn-DPYELqG1fdqM-hPfluduBN-I0k_Q8cJrJ3LKVUUgnxsE8bWD4CSo8CoYvXmtK03j86TehszyrfWfKpoBcu8XaHpjh10EGoDHFYeSJ6AKJQDsp_0iz5LC4bOXvyg0_Nef5mQOBtQ9zKuKSbCYhRuu7Wyo");'></div>
<div class="flex flex-col overflow-hidden">
<h2 class="text-text-main text-sm font-medium truncate">Admin User</h2>
<p class="text-text-secondary text-xs truncate">admin@ai-discovery.com</p>
</div>
</div>
</div>
</aside>
<main class="flex-1 flex flex-col h-full overflow-hidden bg-bg-main relative">
<header class="h-16 border-b border-border-subtle bg-surface/90 backdrop-blur px-8 flex items-center justify-between shrink-0 z-20">
<div class="flex items-center gap-2 text-sm">
<a class="text-text-secondary hover:text-text-main transition-colors" href="#">Dashboard</a>
<span class="text-text-secondary">/</span>
<span class="text-text-main font-medium">Website Management</span>
</div>
<div class="flex items-center gap-6">
<div class="relative w-64 hidden md:block">
<span class="absolute inset-y-0 left-0 flex items-center pl-3 text-text-secondary">
<span class="material-symbols-outlined text-[20px]">search</span>
</span>
<input class="w-full bg-surface-highlight border-none rounded-lg py-2 pl-10 pr-4 text-sm text-text-main placeholder-text-secondary focus:ring-1 focus:ring-primary focus:bg-white transition-all shadow-sm" placeholder="Global search..." type="text"/>
</div>
<button class="relative text-text-secondary hover:text-text-main transition-colors">
<span class="material-symbols-outlined">notifications</span>
<span class="absolute top-0 right-0 size-2 bg-primary rounded-full ring-2 ring-white"></span>
</button>
<button class="text-text-secondary hover:text-text-main transition-colors">
<span class="material-symbols-outlined">logout</span>
</button>
</div>
</header>
<div class="flex-1 overflow-y-auto overflow-x-hidden p-8 scroll-smooth">
<div class="max-w-[1400px] mx-auto flex flex-col gap-8 pb-10">
<div class="flex flex-col md:flex-row md:items-end justify-between gap-4">
<div>
<h1 class="text-3xl md:text-4xl font-bold text-text-main mb-2">Website Management</h1>
<p class="text-text-secondary text-base">Manage and curate AI tools for the discovery platform.</p>
</div>
<div class="flex gap-3">
<button class="flex items-center gap-2 h-10 px-4 rounded-lg bg-surface border border-border-subtle hover:bg-surface-highlight text-text-main text-sm font-medium transition-colors shadow-sm">
<span class="material-symbols-outlined text-[20px]">filter_list</span>
<span>Filter</span>
</button>
<button class="flex items-center gap-2 h-10 px-4 rounded-lg bg-surface border border-border-subtle hover:bg-surface-highlight text-text-main text-sm font-medium transition-colors shadow-sm">
<span class="material-symbols-outlined text-[20px]">download</span>
<span>Export</span>
</button>
</div>
</div>
<div class="grid grid-cols-1 xl:grid-cols-3 gap-8 items-start">
<div class="xl:col-span-2 flex flex-col gap-4">
<div class="bg-surface border border-border-subtle rounded-t-xl p-4 flex flex-col sm:flex-row justify-between items-center gap-4 shadow-sm">
<div class="relative w-full sm:w-72">
<span class="absolute inset-y-0 left-0 flex items-center pl-3 text-text-secondary">
<span class="material-symbols-outlined text-[20px]">search</span>
</span>
<input class="w-full bg-surface border border-border-subtle rounded-lg py-2 pl-10 pr-4 text-sm text-text-main placeholder-text-secondary focus:ring-1 focus:ring-primary focus:border-primary" placeholder="Search websites..." type="text"/>
</div>
<div class="text-sm text-text-secondary">
Showing <span class="text-text-main font-medium">1-10</span> of <span class="text-text-main font-medium">128</span>
</div>
</div>
<div class="bg-surface border border-border-subtle rounded-b-xl overflow-hidden shadow-sm">
<div class="overflow-x-auto">
<table class="w-full text-left border-collapse">
<thead>
<tr class="bg-surface-highlight border-b border-border-subtle">
<th class="p-4 text-xs font-semibold uppercase tracking-wider text-text-secondary w-10">
<input class="rounded bg-surface border-gray-300 text-primary focus:ring-primary focus:ring-offset-white" type="checkbox"/>
</th>
<th class="p-4 text-xs font-semibold uppercase tracking-wider text-text-secondary">Website / Tool</th>
<th class="p-4 text-xs font-semibold uppercase tracking-wider text-text-secondary">Tags</th>
<th class="p-4 text-xs font-semibold uppercase tracking-wider text-text-secondary">Status</th>
<th class="p-4 text-xs font-semibold uppercase tracking-wider text-text-secondary text-right">Actions</th>
</tr>
</thead>
<tbody class="divide-y divide-border-subtle">
<tr class="group hover:bg-surface-highlight/50 transition-colors">
<td class="p-4">
<input class="rounded bg-surface border-gray-300 text-primary focus:ring-primary focus:ring-offset-white" type="checkbox"/>
</td>
<td class="p-4">
<div class="flex items-center gap-3">
<div class="size-10 rounded-lg bg-cover bg-center shrink-0 border border-border-subtle" data-alt="Icon of Chat AI Tool" style='background-image: url("https://lh3.googleusercontent.com/aida-public/AB6AXuB_TTlnbkP1ygA1GNesXOhx_H9yup1gOKwumL9Qnh8VcPEUlwDRA89LRKhO8tyDtOtvtfrGh7CRSnP5xMOyb_GvJgQ-FQToY20cgHuTpVeI97COVlA4e2wOp04SH5ogk98S2SFF4jtSoS7RldXDdaafrysnsulA7_euKFIDf42NTPk_YwR4aglKFOeiQCQT-ITD6hCZjE8kqEfam8E6onBbJ14KZt2sE8gn0VP-pzzgAgPlr3htq5j6T26Tijk8V7SIFQGlB20GjSsb");'></div>
<div>
<h3 class="text-sm font-bold text-text-main group-hover:text-primary transition-colors">ChatGPT</h3>
<p class="text-xs text-text-secondary truncate max-w-[200px]">openai.com/chatgpt</p>
</div>
</div>
</td>
<td class="p-4">
<div class="flex flex-wrap gap-1">
<span class="px-2 py-0.5 rounded text-[10px] font-medium bg-surface-highlight text-text-secondary border border-border-subtle">LLM</span>
<span class="px-2 py-0.5 rounded text-[10px] font-medium bg-surface-highlight text-text-secondary border border-border-subtle">Chatbot</span>
</div>
</td>
<td class="p-4">
<span class="inline-flex items-center px-2 py-1 rounded-full text-xs font-medium bg-green-50 text-green-700 border border-green-200">
Active
</span>
</td>
<td class="p-4 text-right">
<div class="flex items-center justify-end gap-2 opacity-0 group-hover:opacity-100 transition-opacity">
<button class="p-1.5 rounded-md hover:bg-primary-light hover:text-primary text-text-secondary transition-colors">
<span class="material-symbols-outlined text-[18px]">edit</span>
</button>
<button class="p-1.5 rounded-md hover:bg-red-50 hover:text-red-600 text-text-secondary transition-colors">
<span class="material-symbols-outlined text-[18px]">delete</span>
</button>
</div>
</td>
</tr>
<tr class="group hover:bg-surface-highlight/50 transition-colors">
<td class="p-4">
<input class="rounded bg-surface border-gray-300 text-primary focus:ring-primary focus:ring-offset-white" type="checkbox"/>
</td>
<td class="p-4">
<div class="flex items-center gap-3">
<div class="size-10 rounded-lg bg-cover bg-center shrink-0 border border-border-subtle" data-alt="Icon of Midjourney AI" style='background-image: url("https://lh3.googleusercontent.com/aida-public/AB6AXuA-IWUA_5FInPrqeTtZDcb85HR0X3jABxlDeG811Bd-_2x4ISEl8huiNp-kAG75fBmopT3dd5nSp1E0wCtOxXMFoP6HR9nrg4PfNDxjLao4ulneOOrJMgL4LdtuXgKxrQHm1WDoG4TviJ-lIYXzLVI9Yw-9mjDFihMurEOdJlarrMZekyEVrogy_jliQPo3oXvcemTUiJAZz9UCwq8LOEr09EqzU25kXCXx3Z3B0XvdM6VDUrQN8mKtKU1eU9CVwsLJfOKZ09CpJIwx");'></div>
<div>
<h3 class="text-sm font-bold text-text-main group-hover:text-primary transition-colors">Midjourney</h3>
<p class="text-xs text-text-secondary truncate max-w-[200px]">midjourney.com</p>
</div>
</div>
</td>
<td class="p-4">
<div class="flex flex-wrap gap-1">
<span class="px-2 py-0.5 rounded text-[10px] font-medium bg-surface-highlight text-text-secondary border border-border-subtle">Image Gen</span>
<span class="px-2 py-0.5 rounded text-[10px] font-medium bg-surface-highlight text-text-secondary border border-border-subtle">Art</span>
</div>
</td>
<td class="p-4">
<span class="inline-flex items-center px-2 py-1 rounded-full text-xs font-medium bg-green-50 text-green-700 border border-green-200">
Active
</span>
</td>
<td class="p-4 text-right">
<div class="flex items-center justify-end gap-2 opacity-0 group-hover:opacity-100 transition-opacity">
<button class="p-1.5 rounded-md hover:bg-primary-light hover:text-primary text-text-secondary transition-colors">
<span class="material-symbols-outlined text-[18px]">edit</span>
</button>
<button class="p-1.5 rounded-md hover:bg-red-50 hover:text-red-600 text-text-secondary transition-colors">
<span class="material-symbols-outlined text-[18px]">delete</span>
</button>
</div>
</td>
</tr>
<tr class="group hover:bg-surface-highlight/50 transition-colors">
<td class="p-4">
<input class="rounded bg-surface border-gray-300 text-primary focus:ring-primary focus:ring-offset-white" type="checkbox"/>
</td>
<td class="p-4">
<div class="flex items-center gap-3">
<div class="size-10 rounded-lg bg-cover bg-center shrink-0 border border-border-subtle" data-alt="Icon of Copy AI Tool" style='background-image: url("https://lh3.googleusercontent.com/aida-public/AB6AXuB6XdW_8pAKAs4YPwwYM4bcub_ilJ2i6L7baANaKdNNmDP5k1TRngBOsHE0aTy6sBuBJG49alX-4PRkz55qTcxsrRy6_S8rqD5gCTWLquEDcjqfSai-s7GEdZcBCNJ6G5hEFJM7JFli5r5ETcPq2QBz_NQT0W94XYhzaYpZQVEX-su5ywWxAcrfOFrWrhwC5RCiZL0B7AinM4c1pRSfeDtDpVTzxTvx0MrDi6gBH-duuCUmbr0YjkqhyaZqpRpX-ZhW-JiJQ3sKPUCA");'></div>
<div>
<h3 class="text-sm font-bold text-text-main group-hover:text-primary transition-colors">Copy.ai</h3>
<p class="text-xs text-text-secondary truncate max-w-[200px]">copy.ai</p>
</div>
</div>
</td>
<td class="p-4">
<div class="flex flex-wrap gap-1">
<span class="px-2 py-0.5 rounded text-[10px] font-medium bg-surface-highlight text-text-secondary border border-border-subtle">Writing</span>
<span class="px-2 py-0.5 rounded text-[10px] font-medium bg-surface-highlight text-text-secondary border border-border-subtle">Marketing</span>
</div>
</td>
<td class="p-4">
<span class="inline-flex items-center px-2 py-1 rounded-full text-xs font-medium bg-yellow-50 text-yellow-700 border border-yellow-200">
Pending
</span>
</td>
<td class="p-4 text-right">
<div class="flex items-center justify-end gap-2 opacity-0 group-hover:opacity-100 transition-opacity">
<button class="p-1.5 rounded-md hover:bg-primary-light hover:text-primary text-text-secondary transition-colors">
<span class="material-symbols-outlined text-[18px]">edit</span>
</button>
<button class="p-1.5 rounded-md hover:bg-red-50 hover:text-red-600 text-text-secondary transition-colors">
<span class="material-symbols-outlined text-[18px]">delete</span>
</button>
</div>
</td>
</tr>
</tbody>
</table>
</div>
<div class="flex items-center justify-between px-4 py-3 border-t border-border-subtle bg-surface">
<div class="flex flex-1 justify-between sm:hidden">
<a class="relative inline-flex items-center rounded-md border border-border-subtle bg-surface px-4 py-2 text-sm font-medium text-text-secondary hover:bg-surface-highlight hover:text-text-main" href="#">Previous</a>
<a class="relative ml-3 inline-flex items-center rounded-md border border-border-subtle bg-surface px-4 py-2 text-sm font-medium text-text-secondary hover:bg-surface-highlight hover:text-text-main" href="#">Next</a>
</div>
<div class="hidden sm:flex sm:flex-1 sm:items-center sm:justify-end">
<nav aria-label="Pagination" class="isolate inline-flex -space-x-px rounded-md shadow-sm">
<a class="relative inline-flex items-center rounded-l-md px-2 py-2 text-text-secondary ring-1 ring-inset ring-border-subtle hover:bg-surface-highlight focus:z-20 focus:outline-offset-0" href="#">
<span class="material-symbols-outlined text-[20px]">chevron_left</span>
</a>
<a aria-current="page" class="relative z-10 inline-flex items-center bg-primary px-4 py-2 text-sm font-semibold text-white focus:z-20 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary" href="#">1</a>
<a class="relative inline-flex items-center px-4 py-2 text-sm font-semibold text-text-secondary ring-1 ring-inset ring-border-subtle hover:bg-surface-highlight focus:z-20 focus:outline-offset-0" href="#">2</a>
<a class="relative inline-flex items-center px-4 py-2 text-sm font-semibold text-text-secondary ring-1 ring-inset ring-border-subtle hover:bg-surface-highlight focus:z-20 focus:outline-offset-0" href="#">3</a>
<a class="relative inline-flex items-center rounded-r-md px-2 py-2 text-text-secondary ring-1 ring-inset ring-border-subtle hover:bg-surface-highlight focus:z-20 focus:outline-offset-0" href="#">
<span class="material-symbols-outlined text-[20px]">chevron_right</span>
</a>
</nav>
</div>
</div>
</div>
</div>
<div class="xl:col-span-1">
<div class="bg-surface border border-border-subtle rounded-xl shadow-lg shadow-slate-200/50 sticky top-8">
<div class="px-6 py-4 border-b border-border-subtle flex justify-between items-center bg-surface-highlight/30 rounded-t-xl">
<h2 class="text-lg font-bold text-text-main">Add New Website</h2>
<span class="material-symbols-outlined text-text-secondary cursor-pointer hover:text-text-main">close</span>
</div>
<div class="p-6 flex flex-col gap-6">
<div class="flex flex-col gap-3">
<label class="text-sm font-medium text-text-main">Website URL <span class="text-primary">*</span></label>
<div class="flex flex-col gap-2">
<div class="relative">
<input class="w-full bg-surface border border-border-subtle rounded-lg py-2.5 px-3 text-sm text-text-main placeholder-text-secondary focus:ring-1 focus:ring-primary focus:border-primary transition-all shadow-sm" placeholder="https://example.com" type="url"/>
<div class="absolute inset-y-0 right-3 flex items-center">
<span class="material-symbols-outlined text-green-500 text-[18px]" style="display:none;">check_circle</span>
</div>
</div>
<button class="flex items-center justify-center gap-2 w-full py-2 px-3 rounded-lg border border-primary/20 bg-primary-light/50 hover:bg-primary-light text-primary text-sm font-medium transition-colors group" type="button">
<span class="material-symbols-outlined text-[18px] group-hover:animate-spin">autorenew</span>
Auto-fetch Website Info
</button>
<p class="text-xs text-text-secondary mt-1">
Our AI will attempt to scrape metadata and descriptions automatically.
</p>
</div>
</div>
<div class="flex flex-col gap-3">
<label class="text-sm font-medium text-text-main">Tool Name</label>
<input class="w-full bg-surface border border-border-subtle rounded-lg py-2.5 px-3 text-sm text-text-main placeholder-text-secondary focus:ring-1 focus:ring-primary focus:border-primary shadow-sm" placeholder="e.g. ChatGPT" type="text"/>
</div>
<div class="flex flex-col gap-3">
<label class="text-sm font-medium text-text-main">Short Description</label>
<textarea class="w-full bg-surface border border-border-subtle rounded-lg py-2.5 px-3 text-sm text-text-main placeholder-text-secondary focus:ring-1 focus:ring-primary focus:border-primary resize-none shadow-sm" placeholder="Enter a brief description of the AI tool..." rows="4"></textarea>
</div>
<div class="flex flex-col gap-3">
<label class="text-sm font-medium text-text-main">Tags</label>
<div class="p-3 bg-surface border border-border-subtle rounded-lg min-h-[44px] flex flex-wrap gap-2 shadow-sm">
<span class="inline-flex items-center gap-1 px-2 py-1 rounded bg-surface-highlight text-xs text-text-main border border-border-subtle">
Productivity
<span class="material-symbols-outlined text-[14px] cursor-pointer hover:text-red-500 text-text-secondary">close</span>
</span>
<input class="bg-transparent border-none p-0 text-sm text-text-main placeholder-text-secondary focus:ring-0 w-24" placeholder="+ Add tag" type="text"/>
</div>
</div>
<div class="flex items-center justify-between py-2">
<label class="text-sm font-medium text-text-main">Active Status</label>
<button class="w-11 h-6 bg-primary rounded-full relative cursor-pointer shadow-inner">
<span class="absolute top-1 left-6 size-4 bg-white rounded-full shadow-sm transition-all"></span>
</button>
</div>
</div>
<div class="px-6 py-4 border-t border-border-subtle flex gap-3 bg-surface-highlight/30 rounded-b-xl">
<button class="flex-1 py-2.5 rounded-lg bg-primary hover:bg-primary-hover text-white text-sm font-bold shadow-lg shadow-primary/20 transition-all">
Save Website
</button>
<button class="px-4 py-2.5 rounded-lg border border-border-subtle bg-surface hover:bg-surface-highlight text-text-main text-sm font-medium transition-colors shadow-sm">
Cancel
</button>
</div>
</div>
</div>
</div>
</div>
</div>
</main>
</body></html>

Binary file not shown.

After

Width:  |  Height:  |  Size: 221 KiB

View File

@@ -0,0 +1,351 @@
<!DOCTYPE html>
<html lang="en"><head>
<meta charset="utf-8"/>
<meta content="width=device-width, initial-scale=1.0" name="viewport"/>
<title>AI Tool Detail Page (Light Theme)</title>
<link href="https://fonts.googleapis.com" rel="preconnect"/>
<link crossorigin="" href="https://fonts.gstatic.com" rel="preconnect"/>
<link href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@300;400;500;600;700&amp;family=Noto+Sans:wght@400;500;700&amp;display=swap" rel="stylesheet"/>
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&amp;display=swap" rel="stylesheet"/>
<script src="https://cdn.tailwindcss.com?plugins=forms,container-queries"></script>
<script id="tailwind-config">
tailwind.config = {
theme: {
extend: {
colors: {
"primary": "#0ea5e9", // Updated to sky-500 for better visibility on light
"primary-dark": "#0284c7", // Sky-600
"secondary": "#8b5cf6", // Violet-500
"background": "#f8fafc", // Slate-50 - Light background
"surface": "#ffffff", // White cards
"surface-highlight": "#f1f5f9", // Slate-100 for inputs/tags
"border": "#e2e8f0", // Slate-200
"text-main": "#0f172a", // Slate-900
"text-muted": "#64748b", // Slate-500
"text-on-dark": "#ffffff",
},
fontFamily: {
"display": ["Space Grotesk", "sans-serif"],
"body": ["Noto Sans", "sans-serif"]
},
borderRadius: {"DEFAULT": "0.25rem", "lg": "0.5rem", "xl": "0.75rem", "2xl": "1rem", "full": "9999px"},
boxShadow: {
"soft": "0 4px 6px -1px rgba(0, 0, 0, 0.05), 0 2px 4px -1px rgba(0, 0, 0, 0.03)",
"card": "0 0 0 1px rgba(226, 232, 240, 1), 0 2px 8px rgba(0, 0, 0, 0.05)",
}
},
},
}
</script>
<style>
body {
font-family: 'Space Grotesk', sans-serif;
}
.glass-card {
background: rgba(255, 255, 255, 0.8);
backdrop-filter: blur(12px);
border: 1px solid rgba(226, 232, 240, 0.6);
box-shadow: 0 10px 30px -5px rgba(0, 0, 0, 0.05);
}
.glass-header {
background: rgba(255, 255, 255, 0.85);
backdrop-filter: blur(8px);
border-bottom: 1px solid rgba(226, 232, 240, 1);
}
</style>
</head>
<body class="bg-background text-text-main min-h-screen flex flex-col selection:bg-primary/20 selection:text-primary-dark">
<div class="w-full glass-header sticky top-0 z-50">
<div class="max-w-[1280px] mx-auto px-4 sm:px-6 lg:px-8">
<header class="flex items-center justify-between h-16">
<div class="flex items-center gap-8">
<div class="flex items-center gap-3">
<div class="size-8 text-primary">
<span class="material-symbols-outlined text-3xl">token</span>
</div>
<h2 class="text-text-main text-xl font-bold tracking-tight">AI Discovery</h2>
</div>
<div class="hidden md:flex items-center gap-6">
<a class="text-text-muted hover:text-primary text-sm font-medium transition-colors" href="#">Categories</a>
<a class="text-text-muted hover:text-primary text-sm font-medium transition-colors" href="#">Submit Tool</a>
<a class="text-text-muted hover:text-primary text-sm font-medium transition-colors" href="#">Blog</a>
</div>
</div>
<div class="flex items-center gap-4">
<div class="hidden sm:flex relative group">
<div class="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
<span class="material-symbols-outlined text-text-muted text-[20px]">search</span>
</div>
<input class="bg-surface-highlight hover:bg-white focus:bg-white text-text-main text-sm rounded-lg pl-10 pr-4 py-2 w-48 focus:w-64 transition-all outline-none ring-0 border border-transparent focus:border-primary/50 placeholder:text-text-muted/70 shadow-sm" placeholder="Search tools..."/>
</div>
<button class="flex items-center justify-center rounded-lg h-9 px-4 bg-primary hover:bg-primary-dark text-white text-sm font-bold transition-colors shadow-sm hover:shadow-md">
Log In
</button>
</div>
</header>
</div>
</div>
<main class="flex-grow w-full max-w-[1280px] mx-auto px-4 sm:px-6 lg:px-8 py-8">
<div class="mb-8">
<button class="group flex items-center gap-2 text-text-muted hover:text-primary transition-colors text-sm font-bold">
<span class="material-symbols-outlined text-[20px] group-hover:-translate-x-1 transition-transform">arrow_back</span>
<span>Back to Home</span>
</button>
</div>
<div class="grid grid-cols-1 lg:grid-cols-12 gap-8 lg:gap-12">
<div class="lg:col-span-8 flex flex-col gap-8">
<div class="flex flex-col sm:flex-row gap-6 items-start">
<div class="relative shrink-0">
<div class="absolute -inset-2 bg-gradient-to-br from-primary/30 via-purple-300/30 to-secondary/30 rounded-3xl opacity-60 blur-xl"></div>
<div class="relative size-[120px] rounded-xl bg-surface flex items-center justify-center overflow-hidden border border-border bg-cover bg-center shadow-soft" data-alt="Abstract neural network pattern in deep blue and purple" style="background-image: url('https://lh3.googleusercontent.com/aida-public/AB6AXuAFm-_1orioA8YwgKaNGCzxomQeon7z13YmJCs2VpIoXP6J2a5kLgu_mC9IFA-cRA5s6yNaDPiWRxhF8tTX4FFOTbngaHydfCmAMNxU2EuZ2burR-hLOggJYl24v1Ry7GaNu6ETcYui4SIfvWqNs43vwA4vEmoogV3TxVzuDRDHsy5nj9ab4TnW4c5mVgqHPttLIF1NKTm6rtEcPnWW1jtCihIAAqZVnst9o0Z-IyAYGJoPjNajRdJZGR5NDfiBf-tdr79Bo42KY_DX');">
</div>
</div>
<div class="flex flex-col gap-3 flex-1">
<div>
<h1 class="text-4xl font-bold text-text-main tracking-tight font-display mb-1">NeuroGen AI</h1>
<a class="text-primary hover:text-primary-dark hover:underline text-base font-medium flex items-center gap-1" href="#">
www.neurogen.ai
<span class="material-symbols-outlined text-[16px]">open_in_new</span>
</a>
</div>
<div class="flex flex-wrap gap-2">
<span class="inline-flex items-center px-3 py-1 rounded-full text-xs font-medium bg-surface-highlight text-text-muted border border-border hover:border-primary/30 transition-colors cursor-default">Generative AI</span>
<span class="inline-flex items-center px-3 py-1 rounded-full text-xs font-medium bg-surface-highlight text-text-muted border border-border hover:border-primary/30 transition-colors cursor-default">Copywriting</span>
<span class="inline-flex items-center px-3 py-1 rounded-full text-xs font-medium bg-surface-highlight text-text-muted border border-border hover:border-primary/30 transition-colors cursor-default">Productivity</span>
<span class="inline-flex items-center px-3 py-1 rounded-full text-xs font-medium bg-purple-50 text-secondary border border-purple-100">Free Trial</span>
</div>
<div class="flex items-center gap-6 mt-1 text-text-muted text-sm">
<div class="flex items-center gap-2" title="Total Views">
<span class="material-symbols-outlined text-[18px]">visibility</span>
<span>12,450 Views</span>
</div>
<div class="flex items-center gap-2" title="Date Added">
<span class="material-symbols-outlined text-[18px]">calendar_today</span>
<span>Added Oct 24, 2023</span>
</div>
</div>
</div>
</div>
<hr class="border-border"/>
<div class="space-y-10">
<section>
<h3 class="text-xl font-bold text-text-main mb-4 flex items-center gap-2">
<span class="material-symbols-outlined text-primary">info</span>
Product Overview
</h3>
<p class="text-text-muted leading-relaxed text-lg font-body">
NeuroGen AI is an advanced copywriting assistant designed to help marketers, writers, and businesses generate high-converting content in seconds. By leveraging state-of-the-art natural language processing models, it understands context, tone, and brand voice to deliver tailored outputs.
</p>
</section>
<div class="rounded-xl overflow-hidden border border-border relative group aspect-video w-full bg-surface shadow-md">
<div class="absolute inset-0 bg-cover bg-center" data-alt="Dashboard interface of NeuroGen AI showing analytics and text editor" style="background-image: url('https://lh3.googleusercontent.com/aida-public/AB6AXuCtwM-4OkGIPG4sQqABpRcLvOgKvLzuyVlQJUVcU8SqFsVod_jck_vFm-q7UX70vUaaF7UPEa--rkf18dMi5_qYcXYsjTzd5ehM3ga_OoIr1r6wnRTnRxKcrUUC5USP72vdsFJkRFeKmH6epH1Avi5tglJ732VTvoNiuys499jctwZ67d4Yu8Slfn0EO4Ny8zIjzPRGLer8lMaEeZPMagNbgn4asMLvmDtTep3mgPzwTscfzoOFVChGqNak0ZmLyOiVY7vKIapdHyoz');">
</div>
<div class="absolute inset-0 bg-gradient-to-t from-black/80 via-transparent to-transparent opacity-60"></div>
<div class="absolute bottom-4 left-4">
<p class="text-white font-medium text-sm bg-black/40 backdrop-blur-md px-3 py-1 rounded-lg border border-white/10">Dashboard Interface</p>
</div>
</div>
<section>
<h3 class="text-xl font-bold text-text-main mb-4">Detailed Description</h3>
<div class="text-text-muted space-y-4 leading-relaxed font-body">
<p>
Writing high-quality content consistently is a challenge for modern businesses. NeuroGen AI bridges the gap between human creativity and machine speed. Unlike generic text generators, NeuroGen allows users to fine-tune specific parameters such as emotional resonance, sentence structure complexity, and SEO keyword density.
</p>
<p>
The platform includes specialized templates for:
</p>
<ul class="list-disc pl-5 space-y-2 text-text-muted marker:text-primary">
<li>Social media captions (Instagram, LinkedIn, Twitter)</li>
<li>Long-form blog posts with automatic formatting</li>
<li>Email marketing sequences</li>
<li>Ad copy variants for A/B testing</li>
</ul>
<p>
Security is paramount; NeuroGen ensures that your proprietary data is never used to train public models. Enterprise-grade encryption and team collaboration features make it a suitable choice for large organizations looking to scale their content operations.
</p>
</div>
</section>
<section>
<h3 class="text-xl font-bold text-text-main mb-4 flex items-center gap-2">
<span class="material-symbols-outlined text-secondary">newspaper</span>
Related News
</h3>
<div class="grid gap-4">
<a class="group block p-5 rounded-xl border border-border bg-surface hover:shadow-soft hover:border-primary/30 transition-all" href="#">
<div class="flex justify-between items-start mb-2">
<span class="inline-flex items-center px-2 py-0.5 rounded text-[10px] font-bold uppercase tracking-wider bg-primary/10 text-primary-dark">Product Update</span>
<span class="text-xs text-text-muted">2 days ago</span>
</div>
<h4 class="text-text-main font-bold text-base group-hover:text-primary transition-colors mb-2">NeuroGen 2.0 Released: Now with Multimodal capabilities</h4>
<p class="text-sm text-text-muted line-clamp-2">The latest version introduces image analysis and generation alongside text, marking a significant step forward for content creators looking for all-in-one solutions.</p>
</a>
<a class="group block p-5 rounded-xl border border-border bg-surface hover:shadow-soft hover:border-primary/30 transition-all" href="#">
<div class="flex justify-between items-start mb-2">
<span class="inline-flex items-center px-2 py-0.5 rounded text-[10px] font-bold uppercase tracking-wider bg-purple-50 text-secondary">Industry News</span>
<span class="text-xs text-text-muted">Oct 12, 2023</span>
</div>
<h4 class="text-text-main font-bold text-base group-hover:text-primary transition-colors mb-2">AI Copywriting Market Analysis Q3 2023</h4>
<p class="text-sm text-text-muted line-clamp-2">NeuroGen AI has been named a "High Performer" in the latest G2 Grid Report for AI Writing Assistants, recognized for its ease of use and ROI.</p>
</a>
</div>
</section>
<section>
<h3 class="text-xl font-bold text-text-main mb-4 flex items-center gap-2">
<span class="material-symbols-outlined text-primary">auto_awesome</span>
Similar Recommendations
</h3>
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
<a class="flex flex-col p-4 rounded-xl border border-border bg-surface hover:border-primary/50 hover:shadow-soft transition-all group h-full" href="#">
<div class="flex items-start justify-between mb-3">
<div class="size-10 rounded-lg bg-gradient-to-br from-blue-500 to-indigo-500 flex items-center justify-center shadow-lg shadow-blue-500/20">
<span class="text-white font-bold text-lg">J</span>
</div>
<div class="size-8 rounded-full bg-surface-highlight flex items-center justify-center text-text-muted group-hover:text-white group-hover:bg-primary group-hover:scale-110 transition-all">
<span class="material-symbols-outlined text-[18px]">arrow_forward</span>
</div>
</div>
<div>
<h4 class="text-text-main font-bold mb-1">Jasper AI</h4>
<p class="text-xs text-text-muted line-clamp-2 mb-3">Enterprise-grade AI content platform for marketing teams and creators.</p>
<div class="flex gap-2">
<span class="px-2 py-1 rounded bg-surface-highlight text-[10px] text-text-muted font-medium border border-border">Writing</span>
<span class="px-2 py-1 rounded bg-surface-highlight text-[10px] text-text-muted font-medium border border-border">Marketing</span>
</div>
</div>
</a>
<a class="flex flex-col p-4 rounded-xl border border-border bg-surface hover:border-primary/50 hover:shadow-soft transition-all group h-full" href="#">
<div class="flex items-start justify-between mb-3">
<div class="size-10 rounded-lg bg-gradient-to-br from-emerald-400 to-teal-500 flex items-center justify-center shadow-lg shadow-emerald-500/20">
<span class="text-white font-bold text-lg">C</span>
</div>
<div class="size-8 rounded-full bg-surface-highlight flex items-center justify-center text-text-muted group-hover:text-white group-hover:bg-primary group-hover:scale-110 transition-all">
<span class="material-symbols-outlined text-[18px]">arrow_forward</span>
</div>
</div>
<div>
<h4 class="text-text-main font-bold mb-1">Copy.ai</h4>
<p class="text-xs text-text-muted line-clamp-2 mb-3">Generates high-quality copy for your business in seconds.</p>
<div class="flex gap-2">
<span class="px-2 py-1 rounded bg-surface-highlight text-[10px] text-text-muted font-medium border border-border">Free Plan</span>
<span class="px-2 py-1 rounded bg-surface-highlight text-[10px] text-text-muted font-medium border border-border">Social</span>
</div>
</div>
</a>
<a class="flex flex-col p-4 rounded-xl border border-border bg-surface hover:border-primary/50 hover:shadow-soft transition-all group h-full" href="#">
<div class="flex items-start justify-between mb-3">
<div class="size-10 rounded-lg bg-gradient-to-br from-orange-400 to-red-500 flex items-center justify-center shadow-lg shadow-orange-500/20">
<span class="text-white font-bold text-lg">W</span>
</div>
<div class="size-8 rounded-full bg-surface-highlight flex items-center justify-center text-text-muted group-hover:text-white group-hover:bg-primary group-hover:scale-110 transition-all">
<span class="material-symbols-outlined text-[18px]">arrow_forward</span>
</div>
</div>
<div>
<h4 class="text-text-main font-bold mb-1">Writesonic</h4>
<p class="text-xs text-text-muted line-clamp-2 mb-3">AI writer that creates SEO-friendly content for blogs and websites.</p>
<div class="flex gap-2">
<span class="px-2 py-1 rounded bg-surface-highlight text-[10px] text-text-muted font-medium border border-border">SEO</span>
<span class="px-2 py-1 rounded bg-surface-highlight text-[10px] text-text-muted font-medium border border-border">Blogs</span>
</div>
</div>
</a>
<a class="flex flex-col p-4 rounded-xl border border-border bg-surface hover:border-primary/50 hover:shadow-soft transition-all group h-full" href="#">
<div class="flex items-start justify-between mb-3">
<div class="size-10 rounded-lg bg-gradient-to-br from-purple-400 to-fuchsia-500 flex items-center justify-center shadow-lg shadow-purple-500/20">
<span class="text-white font-bold text-lg">R</span>
</div>
<div class="size-8 rounded-full bg-surface-highlight flex items-center justify-center text-text-muted group-hover:text-white group-hover:bg-primary group-hover:scale-110 transition-all">
<span class="material-symbols-outlined text-[18px]">arrow_forward</span>
</div>
</div>
<div>
<h4 class="text-text-main font-bold mb-1">Rytr</h4>
<p class="text-xs text-text-muted line-clamp-2 mb-3">A better, faster way to write profile bios, facebook ads and more.</p>
<div class="flex gap-2">
<span class="px-2 py-1 rounded bg-surface-highlight text-[10px] text-text-muted font-medium border border-border">Emails</span>
<span class="px-2 py-1 rounded bg-surface-highlight text-[10px] text-text-muted font-medium border border-border">Ads</span>
</div>
</div>
</a>
</div>
</section>
</div>
</div>
<div class="lg:col-span-4 space-y-6">
<div class="glass-card rounded-2xl p-6 flex flex-col gap-4 sticky top-24">
<div class="flex items-center justify-between mb-2">
<span class="text-text-main font-bold text-lg">Try it now</span>
<span class="px-2 py-1 rounded bg-green-50 text-green-600 text-xs font-bold border border-green-200">ONLINE</span>
</div>
<a class="group relative flex items-center justify-center w-full overflow-hidden rounded-xl bg-gradient-to-r from-primary via-primary-dark to-secondary p-[1px] focus:outline-none focus:ring-2 focus:ring-primary focus:ring-offset-2 transition-all hover:scale-[1.02] shadow-sm hover:shadow-md" href="#">
<div class="relative flex h-12 w-full items-center justify-center rounded-xl bg-surface group-hover:bg-transparent px-8 py-1 transition-colors">
<span class="flex items-center gap-2 text-primary group-hover:text-white font-bold text-base uppercase tracking-wide transition-colors">
Visit Website
<span class="material-symbols-outlined text-[20px] font-bold">arrow_outward</span>
</span>
</div>
</a>
<p class="text-xs text-center text-text-muted">
Opens in a new tab • <span class="text-text-main font-medium">neurogen.ai</span>
</p>
</div>
<div class="rounded-2xl border border-border bg-surface p-6 shadow-card">
<h3 class="text-lg font-bold text-text-main mb-5 flex items-center gap-2">
<span class="material-symbols-outlined text-secondary">verified</span>
Main Features
</h3>
<ul class="space-y-4">
<li class="flex items-start gap-3">
<div class="mt-0.5 rounded-full bg-primary/10 p-1">
<span class="material-symbols-outlined text-primary text-[16px] block">check</span>
</div>
<div>
<p class="text-sm font-bold text-text-main">Contextual Awareness</p>
<p class="text-xs text-text-muted mt-0.5">Understands previous inputs to maintain thread continuity.</p>
</div>
</li>
<li class="flex items-start gap-3">
<div class="mt-0.5 rounded-full bg-primary/10 p-1">
<span class="material-symbols-outlined text-primary text-[16px] block">check</span>
</div>
<div>
<p class="text-sm font-bold text-text-main">Multi-language Support</p>
<p class="text-xs text-text-muted mt-0.5">Generate content in over 25 languages natively.</p>
</div>
</li>
<li class="flex items-start gap-3">
<div class="mt-0.5 rounded-full bg-primary/10 p-1">
<span class="material-symbols-outlined text-primary text-[16px] block">check</span>
</div>
<div>
<p class="text-sm font-bold text-text-main">SEO Optimization</p>
<p class="text-xs text-text-muted mt-0.5">Built-in keyword analyzer and suggestion tool.</p>
</div>
</li>
<li class="flex items-start gap-3">
<div class="mt-0.5 rounded-full bg-primary/10 p-1">
<span class="material-symbols-outlined text-primary text-[16px] block">check</span>
</div>
<div>
<p class="text-sm font-bold text-text-main">Export to CMS</p>
<p class="text-xs text-text-muted mt-0.5">Direct integration with WordPress and Ghost.</p>
</div>
</li>
</ul>
</div>
<div class="flex gap-4">
<button class="flex-1 py-3 rounded-lg border border-border bg-surface text-text-muted hover:text-text-main hover:bg-surface-highlight hover:border-gray-300 transition-colors text-sm font-medium flex items-center justify-center gap-2 shadow-sm">
<span class="material-symbols-outlined text-[18px]">share</span>
Share
</button>
<button class="flex-1 py-3 rounded-lg border border-border bg-surface text-text-muted hover:text-red-600 hover:border-red-200 hover:bg-red-50 transition-colors text-sm font-medium flex items-center justify-center gap-2 shadow-sm">
<span class="material-symbols-outlined text-[18px]">flag</span>
Report
</button>
</div>
</div>
</div>
<div class="h-20"></div>
</main>
</body></html>

Binary file not shown.

After

Width:  |  Height:  |  Size: 389 KiB

View File

@@ -0,0 +1,357 @@
<!DOCTYPE html>
<html lang="en"><head>
<meta charset="utf-8"/>
<meta content="width=device-width, initial-scale=1.0" name="viewport"/>
<title>ZJPB - AI Tool Discovery</title>
<link href="https://fonts.googleapis.com" rel="preconnect"/>
<link crossorigin="" href="https://fonts.gstatic.com" rel="preconnect"/>
<link href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@300;400;500;600;700&amp;display=swap" rel="stylesheet"/>
<link href="https://fonts.googleapis.com/css2?family=Noto+Sans:wght@400;500;700&amp;display=swap" rel="stylesheet"/>
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&amp;display=swap" rel="stylesheet"/>
<script src="https://cdn.tailwindcss.com?plugins=forms,container-queries"></script>
<script id="tailwind-config">
tailwind.config = {
darkMode: "class",
theme: {
extend: {
colors: {
primary: "#0ea5e9", // Sky Blue - Fresh and Techy
secondary: "#8b5cf6", // Violet - Softened purple
"page-bg": "#f8fafc", // Slate 50 - Very light grey/white
"surface": "#ffffff", // Pure white
"border-color": "#e2e8f0", // Slate 200 - Subtle borders
"text-main": "#0f172a", // Slate 900 - High contrast text
"text-muted": "#64748b", // Slate 500 - Secondary text
},
fontFamily: {
"display": ["Space Grotesk", "sans-serif"],
"body": ["Noto Sans", "sans-serif"],
},
backgroundImage: {
'gradient-tech': 'linear-gradient(135deg, #0ea5e9 0%, #8b5cf6 100%)',
'gradient-text': 'linear-gradient(to right, #0ea5e9, #8b5cf6)',
}
},
},
}
</script>
<style>
body {
font-family: 'Space Grotesk', sans-serif;
}
.text-gradient {
background: linear-gradient(to right, #0ea5e9, #a855f7);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
}
.card-hover:hover {
transform: translateY(-4px);
box-shadow: 0 10px 30px -10px rgba(14, 165, 233, 0.15);
border-color: #0ea5e9;
}
</style>
</head>
<body class="bg-page-bg text-text-main antialiased selection:bg-primary selection:text-white">
<div class="relative flex min-h-screen w-full flex-col overflow-x-hidden">
<div class="fixed top-0 left-0 w-full h-96 bg-primary/10 blur-[120px] rounded-full pointer-events-none -translate-y-1/2"></div>
<div class="fixed top-20 right-0 w-96 h-96 bg-secondary/10 blur-[100px] rounded-full pointer-events-none translate-x-1/3"></div>
<header class="sticky top-0 z-50 w-full border-b border-solid border-border-color bg-surface/80 backdrop-blur-md">
<div class="mx-auto flex h-16 max-w-[1440px] items-center justify-between px-6 lg:px-10">
<div class="flex items-center gap-8">
<a class="flex items-center gap-3 text-text-main hover:opacity-80 transition-opacity" href="#">
<div class="flex items-center justify-center size-8 rounded-lg bg-gradient-tech text-white">
<span class="material-symbols-outlined" style="font-size: 20px; font-weight: 700;">bolt</span>
</div>
<h2 class="text-text-main text-lg font-bold leading-tight tracking-tight">ZJPB</h2>
</a>
<nav class="hidden md:flex items-center gap-8">
<a class="text-text-muted hover:text-primary text-sm font-medium transition-colors" href="#">Home</a>
<a class="text-text-muted hover:text-primary text-sm font-medium transition-colors" href="#">Categories</a>
<a class="text-text-muted hover:text-primary text-sm font-medium transition-colors" href="#">Community</a>
<a class="text-text-muted hover:text-primary text-sm font-medium transition-colors" href="#">About</a>
</nav>
</div>
<div class="flex items-center gap-4">
<div class="hidden lg:flex items-center bg-slate-100 border border-transparent rounded-full h-10 px-4 w-64 focus-within:border-primary focus-within:bg-white transition-all">
<span class="material-symbols-outlined text-slate-400" style="font-size: 20px;">search</span>
<input class="bg-transparent border-none text-text-main text-sm placeholder-slate-400 focus:ring-0 w-full ml-2" placeholder="Search AI tools..." type="text"/>
</div>
<button class="hidden sm:flex h-9 px-4 items-center justify-center rounded-lg text-sm font-bold text-text-muted hover:bg-slate-100 transition-colors">
Log In
</button>
<button class="flex h-9 px-5 items-center justify-center rounded-lg bg-primary hover:bg-primary/90 text-white text-sm font-bold transition-colors shadow-[0_4px_14px_rgba(14,165,233,0.3)]">
Sign Up
</button>
<button class="md:hidden text-text-main">
<span class="material-symbols-outlined">menu</span>
</button>
</div>
</div>
</header>
<main class="flex-1 flex flex-col items-center w-full px-6 lg:px-10 py-8">
<div class="w-full max-w-[1200px] flex flex-col gap-10">
<div class="flex flex-col md:flex-row items-start md:items-end justify-between gap-6 pb-6 border-b border-border-color">
<div class="flex flex-col gap-2 max-w-2xl">
<h1 class="text-4xl md:text-5xl lg:text-6xl font-black tracking-tighter text-text-main mb-2">
ZJPB - <span class="text-gradient">焦提示词</span>
</h1>
<p class="text-text-muted text-lg md:text-xl font-light">
发现最新最好用的AI工具和产品. Discover the best AI tools tailored for your workflow.
</p>
</div>
<button class="flex items-center gap-2 bg-surface border border-border-color hover:border-primary text-text-main px-5 py-2.5 rounded-lg transition-all group whitespace-nowrap shadow-sm hover:shadow-md">
<span class="material-symbols-outlined text-primary group-hover:scale-110 transition-transform">add_circle</span>
<span class="font-bold text-sm">Submit a Tool</span>
</button>
</div>
<div class="w-full overflow-x-auto pb-2 scrollbar-hide">
<div class="flex gap-3 min-w-max">
<button class="flex items-center h-9 px-5 rounded-full bg-primary text-white font-bold text-sm shadow-[0_2px_10px_rgba(14,165,233,0.3)]">
All Tools
</button>
<button class="flex items-center gap-2 h-9 px-5 rounded-full bg-surface border border-border-color text-text-muted hover:text-primary hover:border-primary font-medium text-sm transition-all shadow-sm">
<span class="material-symbols-outlined text-sm">chat</span> AI Chat
</button>
<button class="flex items-center gap-2 h-9 px-5 rounded-full bg-surface border border-border-color text-text-muted hover:text-primary hover:border-primary font-medium text-sm transition-all shadow-sm">
<span class="material-symbols-outlined text-sm">image</span> Image Gen
</button>
<button class="flex items-center gap-2 h-9 px-5 rounded-full bg-surface border border-border-color text-text-muted hover:text-primary hover:border-primary font-medium text-sm transition-all shadow-sm">
<span class="material-symbols-outlined text-sm">movie</span> Video
</button>
<button class="flex items-center gap-2 h-9 px-5 rounded-full bg-surface border border-border-color text-text-muted hover:text-primary hover:border-primary font-medium text-sm transition-all shadow-sm">
<span class="material-symbols-outlined text-sm">edit_note</span> Writing
</button>
<button class="flex items-center gap-2 h-9 px-5 rounded-full bg-surface border border-border-color text-text-muted hover:text-primary hover:border-primary font-medium text-sm transition-all shadow-sm">
<span class="material-symbols-outlined text-sm">code</span> Coding
</button>
<button class="flex items-center gap-2 h-9 px-5 rounded-full bg-surface border border-border-color text-text-muted hover:text-primary hover:border-primary font-medium text-sm transition-all shadow-sm">
<span class="material-symbols-outlined text-sm">graphic_eq</span> Audio
</button>
<button class="flex items-center gap-2 h-9 px-5 rounded-full bg-surface border border-border-color text-text-muted hover:text-primary hover:border-primary font-medium text-sm transition-all shadow-sm">
<span class="material-symbols-outlined text-sm">view_in_ar</span> 3D Assets
</button>
</div>
</div>
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-6">
<div class="group relative flex flex-col gap-4 rounded-xl border border-border-color bg-surface p-5 card-hover transition-all duration-300 cursor-pointer overflow-hidden shadow-sm">
<div class="absolute top-0 left-0 w-full h-1 bg-gradient-to-r from-primary to-secondary opacity-0 group-hover:opacity-100 transition-opacity"></div>
<div class="flex items-start justify-between">
<div class="size-12 rounded-lg bg-cover bg-center shadow-md ring-1 ring-black/5" data-alt="ChatGPT logo icon green background" style="background-image: url('https://lh3.googleusercontent.com/aida-public/AB6AXuBStb1HsLpCc4Hlu8FzN0ecAT6pMnnu_pzF4cUUQDG6FdYLA65ua60D-3NxKDOHqkuVsgsy9ku-vUZ0_Jyc5O9Xi1NlwR5L7oC9gt_jretgJqsEeOk1dm2yo4GvB26KgmMWzpUKeCtIg1NyZDWRNJF3gfIVxF95sT29iy6tKSXsUdfVsOo-o_5kEpB3qCFKZdMo4fhOe8DVh4JnmmdZuS8z3PflupzZpru6F0QK3xL407vfIULuQ3L5NRtFdTfZs7O-elYl2FrwjJ_x');"></div>
<div class="flex items-center justify-center size-8 rounded-full bg-slate-100 text-slate-400 group-hover:text-primary group-hover:bg-primary/10 transition-colors">
<span class="material-symbols-outlined text-lg">arrow_outward</span>
</div>
</div>
<div>
<h3 class="text-text-main text-lg font-bold leading-tight group-hover:text-primary transition-colors">ChatGPT</h3>
<p class="text-text-muted text-sm mt-2 line-clamp-2">OpenAI's advanced conversational model capable of understanding and generating human-like text.</p>
</div>
<div class="mt-auto flex items-center justify-between pt-4 border-t border-border-color">
<div class="flex gap-2">
<span class="px-2 py-0.5 rounded text-[10px] font-bold uppercase tracking-wider bg-slate-100 text-slate-600 border border-slate-200">Chat</span>
<span class="px-2 py-0.5 rounded text-[10px] font-bold uppercase tracking-wider bg-slate-100 text-slate-600 border border-slate-200">NLP</span>
</div>
<div class="flex items-center gap-1 text-slate-400 text-xs">
<span class="material-symbols-outlined text-[14px]">visibility</span>
<span>1.2M</span>
</div>
</div>
</div>
<div class="group relative flex flex-col gap-4 rounded-xl border border-border-color bg-surface p-5 card-hover transition-all duration-300 cursor-pointer overflow-hidden shadow-sm">
<div class="absolute top-0 left-0 w-full h-1 bg-gradient-to-r from-primary to-secondary opacity-0 group-hover:opacity-100 transition-opacity"></div>
<div class="flex items-start justify-between">
<div class="size-12 rounded-lg bg-cover bg-center shadow-md ring-1 ring-black/5" data-alt="Midjourney logo icon blue background" style="background-image: url('https://lh3.googleusercontent.com/aida-public/AB6AXuBW8nuPBGShgvw9aCK5p0mKlki3EMfhi8ZZKvLTaM-QupUQDzsBx5wkIvN_Unyt30H2WHXBw5GB4Kz8gL68peWqK99Cpo6gRM-Bk3f94xWdYji9osPrreu5UMVtu2W1Rn8IyBCfPDUu3LJnbDd_viZz4nb04tliq9O_ezo8OCYMede7GZYxIxHTrEkhNBADzC2Z7KjeF-DR7fRBf4dxHYz5rJUGakf1qyEouaAlEDWMnBymOKFkQGK19UvizQiQIUeDFMGMxgL0ng_x');"></div>
<div class="flex items-center justify-center size-8 rounded-full bg-slate-100 text-slate-400 group-hover:text-primary group-hover:bg-primary/10 transition-colors">
<span class="material-symbols-outlined text-lg">arrow_outward</span>
</div>
</div>
<div>
<h3 class="text-text-main text-lg font-bold leading-tight group-hover:text-primary transition-colors">Midjourney</h3>
<p class="text-text-muted text-sm mt-2 line-clamp-2">Hyper-realistic AI image generator that creates stunning visuals from text prompts.</p>
</div>
<div class="mt-auto flex items-center justify-between pt-4 border-t border-border-color">
<div class="flex gap-2">
<span class="px-2 py-0.5 rounded text-[10px] font-bold uppercase tracking-wider bg-slate-100 text-slate-600 border border-slate-200">Image</span>
<span class="px-2 py-0.5 rounded text-[10px] font-bold uppercase tracking-wider bg-slate-100 text-slate-600 border border-slate-200">Art</span>
</div>
<div class="flex items-center gap-1 text-slate-400 text-xs">
<span class="material-symbols-outlined text-[14px]">visibility</span>
<span>850k</span>
</div>
</div>
</div>
<div class="group relative flex flex-col gap-4 rounded-xl border border-border-color bg-surface p-5 card-hover transition-all duration-300 cursor-pointer overflow-hidden shadow-sm">
<div class="absolute top-0 left-0 w-full h-1 bg-gradient-to-r from-primary to-secondary opacity-0 group-hover:opacity-100 transition-opacity"></div>
<div class="flex items-start justify-between">
<div class="size-12 rounded-lg bg-cover bg-center shadow-md ring-1 ring-black/5" data-alt="Jasper logo icon yellow background" style="background-image: url('https://lh3.googleusercontent.com/aida-public/AB6AXuAQv96KYBZ3uV5NpDJyuDzc5J-mGo3fTWcBzVv00wC062ApfHezk6XkvKI6lsad5hFopxB7Qqhbqifto971lBo_7ASfOFUvtSU-Z3omU0q4PGcmea1YR_Va7vKPBtpA1DS_uq779QNTY5oF_kjapJrp7-Nfi5tI0NfQ3kU5KOd-dwBrN2T1Md6nT1jUEeOKG3zEgmAfEYan4mQXQXpz7Ywh2ZaoFCOY0OTiJP9RAnzIYL0Tv-ywt3_r66CSTY5pyzfqqP7sUk4vegAv');"></div>
<div class="flex items-center justify-center size-8 rounded-full bg-slate-100 text-slate-400 group-hover:text-primary group-hover:bg-primary/10 transition-colors">
<span class="material-symbols-outlined text-lg">arrow_outward</span>
</div>
</div>
<div>
<h3 class="text-text-main text-lg font-bold leading-tight group-hover:text-primary transition-colors">Jasper AI</h3>
<p class="text-text-muted text-sm mt-2 line-clamp-2">AI copywriter for marketing content, blog posts, and social media captions.</p>
</div>
<div class="mt-auto flex items-center justify-between pt-4 border-t border-border-color">
<div class="flex gap-2">
<span class="px-2 py-0.5 rounded text-[10px] font-bold uppercase tracking-wider bg-slate-100 text-slate-600 border border-slate-200">Writing</span>
<span class="px-2 py-0.5 rounded text-[10px] font-bold uppercase tracking-wider bg-slate-100 text-slate-600 border border-slate-200">Marketing</span>
</div>
<div class="flex items-center gap-1 text-slate-400 text-xs">
<span class="material-symbols-outlined text-[14px]">visibility</span>
<span>300k</span>
</div>
</div>
</div>
<div class="group relative flex flex-col gap-4 rounded-xl border border-border-color bg-surface p-5 card-hover transition-all duration-300 cursor-pointer overflow-hidden shadow-sm">
<div class="absolute top-0 left-0 w-full h-1 bg-gradient-to-r from-primary to-secondary opacity-0 group-hover:opacity-100 transition-opacity"></div>
<div class="flex items-start justify-between">
<div class="size-12 rounded-lg bg-cover bg-center shadow-md ring-1 ring-black/5" data-alt="Runway logo icon pink background" style="background-image: url('https://lh3.googleusercontent.com/aida-public/AB6AXuAkmxk1Ar1KxgHHM5pKMer4rOra0KUieDAXhm9nE4sQp5WjsnGYENKmbM-EN-hRjhV7OC2ha7Go-Cl90HgfNpbgt--grIpJYKoUjgvsp0juLIDgX_fN4rKrjdkezxRU8YVJBuTaM3lWZJCptm3I6isDO10xidrChOc5kWV9SQny79KEVKENXxOJXEVT3c0m16M3JnGRTRmu-6EY8XU68-On78-7SXxLyP5TESi-ooZ2wHaOwxqJUwFb-oQyQoKAkXnzXAqGwwITBiLS');"></div>
<div class="flex items-center justify-center size-8 rounded-full bg-slate-100 text-slate-400 group-hover:text-primary group-hover:bg-primary/10 transition-colors">
<span class="material-symbols-outlined text-lg">arrow_outward</span>
</div>
</div>
<div>
<h3 class="text-text-main text-lg font-bold leading-tight group-hover:text-primary transition-colors">Runway Gen-2</h3>
<p class="text-text-muted text-sm mt-2 line-clamp-2">Next-generation video creation tool that turns text into high-quality video clips.</p>
</div>
<div class="mt-auto flex items-center justify-between pt-4 border-t border-border-color">
<div class="flex gap-2">
<span class="px-2 py-0.5 rounded text-[10px] font-bold uppercase tracking-wider bg-slate-100 text-slate-600 border border-slate-200">Video</span>
<span class="px-2 py-0.5 rounded text-[10px] font-bold uppercase tracking-wider bg-slate-100 text-slate-600 border border-slate-200">Gen-AI</span>
</div>
<div class="flex items-center gap-1 text-slate-400 text-xs">
<span class="material-symbols-outlined text-[14px]">visibility</span>
<span>420k</span>
</div>
</div>
</div>
<div class="group relative flex flex-col gap-4 rounded-xl border border-border-color bg-surface p-5 card-hover transition-all duration-300 cursor-pointer overflow-hidden shadow-sm">
<div class="absolute top-0 left-0 w-full h-1 bg-gradient-to-r from-primary to-secondary opacity-0 group-hover:opacity-100 transition-opacity"></div>
<div class="flex items-start justify-between">
<div class="size-12 rounded-lg bg-cover bg-center shadow-md ring-1 ring-black/5" data-alt="Copilot logo icon purple background" style="background-image: url('https://lh3.googleusercontent.com/aida-public/AB6AXuBki2piUPdnWstozizUU63ouakjjA3B4xN5uQs7rA84yX4NsYFIaNybtfVRy0bXUKfUBYdixo22jb3bbN6TlKindW5iQU0sJc3FF2GXS_CRpgLoNMLziOyf4rLArNmM2VoycHJ3DyrIsI8847xlO3BHrK8kyemNc9mBfaXyPnWsJ0hnF2lXfljCOcYNQTtY1qgzw5RfOHwA0NUnLwuRG53tU-mnJPPejbDyzW7YWdBnh9yTj3dnJJDOfzYiEx252wgf7Q6GBjTGrQnG');"></div>
<div class="flex items-center justify-center size-8 rounded-full bg-slate-100 text-slate-400 group-hover:text-primary group-hover:bg-primary/10 transition-colors">
<span class="material-symbols-outlined text-lg">arrow_outward</span>
</div>
</div>
<div>
<h3 class="text-text-main text-lg font-bold leading-tight group-hover:text-primary transition-colors">GitHub Copilot</h3>
<p class="text-text-muted text-sm mt-2 line-clamp-2">Your AI pair programmer that helps you write better code faster.</p>
</div>
<div class="mt-auto flex items-center justify-between pt-4 border-t border-border-color">
<div class="flex gap-2">
<span class="px-2 py-0.5 rounded text-[10px] font-bold uppercase tracking-wider bg-slate-100 text-slate-600 border border-slate-200">Dev</span>
<span class="px-2 py-0.5 rounded text-[10px] font-bold uppercase tracking-wider bg-slate-100 text-slate-600 border border-slate-200">Coding</span>
</div>
<div class="flex items-center gap-1 text-slate-400 text-xs">
<span class="material-symbols-outlined text-[14px]">visibility</span>
<span>1.5M</span>
</div>
</div>
</div>
<div class="group relative flex flex-col gap-4 rounded-xl border border-border-color bg-surface p-5 card-hover transition-all duration-300 cursor-pointer overflow-hidden shadow-sm">
<div class="absolute top-0 left-0 w-full h-1 bg-gradient-to-r from-primary to-secondary opacity-0 group-hover:opacity-100 transition-opacity"></div>
<div class="flex items-start justify-between">
<div class="size-12 rounded-lg bg-cover bg-center shadow-md ring-1 ring-black/5" data-alt="ElevenLabs logo icon red background" style="background-image: url('https://lh3.googleusercontent.com/aida-public/AB6AXuBVqan6FEM01JlSNs0aKYTXdgzFAXn1xT_Yx0S3VszDGf5x0hsptwFAxMNtCEtJZOW65aZMncYKyPpzdAoIHLaBDk_ORHur89Redtuublqbd51Cr6sKVdMJ2VAAihNuVPsvpNBdIiLGuf40kXbe3HJrdYVNAKi27xcxBICheI4OzkFF7uJvChUyDbumqLRFUDMssCuxZIQYTeDKbTT628ZBFUN2H8u0RfVSPEyEhJVSE2NCAD4mH-dWGmsQzMWjmeZkwXJKpw1TGHFp');"></div>
<div class="flex items-center justify-center size-8 rounded-full bg-slate-100 text-slate-400 group-hover:text-primary group-hover:bg-primary/10 transition-colors">
<span class="material-symbols-outlined text-lg">arrow_outward</span>
</div>
</div>
<div>
<h3 class="text-text-main text-lg font-bold leading-tight group-hover:text-primary transition-colors">ElevenLabs</h3>
<p class="text-text-muted text-sm mt-2 line-clamp-2">The most realistic AI voice generator and text-to-speech software.</p>
</div>
<div class="mt-auto flex items-center justify-between pt-4 border-t border-border-color">
<div class="flex gap-2">
<span class="px-2 py-0.5 rounded text-[10px] font-bold uppercase tracking-wider bg-slate-100 text-slate-600 border border-slate-200">Audio</span>
<span class="px-2 py-0.5 rounded text-[10px] font-bold uppercase tracking-wider bg-slate-100 text-slate-600 border border-slate-200">TTS</span>
</div>
<div class="flex items-center gap-1 text-slate-400 text-xs">
<span class="material-symbols-outlined text-[14px]">visibility</span>
<span>210k</span>
</div>
</div>
</div>
<div class="group relative flex flex-col gap-4 rounded-xl border border-border-color bg-surface p-5 card-hover transition-all duration-300 cursor-pointer overflow-hidden shadow-sm">
<div class="absolute top-0 left-0 w-full h-1 bg-gradient-to-r from-primary to-secondary opacity-0 group-hover:opacity-100 transition-opacity"></div>
<div class="flex items-start justify-between">
<div class="size-12 rounded-lg bg-cover bg-center shadow-md ring-1 ring-black/5" data-alt="Stable Diffusion logo icon teal background" style="background-image: url('https://lh3.googleusercontent.com/aida-public/AB6AXuB4zzlwlZK6dJVjV4vvFGvRmtsvd7dvQMCC1mAzFfEqA2sYnJnEdTDAF76lfVYkaKzibwuppHRIcEgbT8xaoWOBF1qdMiV3Qs3P9fEOLJ2jApPPqJyBnPXsvxFsI6FTlZ0SS_Rpblvjln_n_YLh8Wi_VWh8lCJirMM5vMCuyrNGK9Crxv3bnJzlbH34i9vmxSxSQ5uCmoAE5GPxkD23vzun9BBOKjRK-Ln7DhQ_bGhnVxkxEtM8Z-DVuGg2dNE7WnD7BEBXTOBh5XQa');"></div>
<div class="flex items-center justify-center size-8 rounded-full bg-slate-100 text-slate-400 group-hover:text-primary group-hover:bg-primary/10 transition-colors">
<span class="material-symbols-outlined text-lg">arrow_outward</span>
</div>
</div>
<div>
<h3 class="text-text-main text-lg font-bold leading-tight group-hover:text-primary transition-colors">Stable Diffusion</h3>
<p class="text-text-muted text-sm mt-2 line-clamp-2">Open source latent text-to-image diffusion model for image generation.</p>
</div>
<div class="mt-auto flex items-center justify-between pt-4 border-t border-border-color">
<div class="flex gap-2">
<span class="px-2 py-0.5 rounded text-[10px] font-bold uppercase tracking-wider bg-slate-100 text-slate-600 border border-slate-200">Open Source</span>
<span class="px-2 py-0.5 rounded text-[10px] font-bold uppercase tracking-wider bg-slate-100 text-slate-600 border border-slate-200">Image</span>
</div>
<div class="flex items-center gap-1 text-slate-400 text-xs">
<span class="material-symbols-outlined text-[14px]">visibility</span>
<span>950k</span>
</div>
</div>
</div>
<div class="group relative flex flex-col gap-4 rounded-xl border border-border-color bg-surface p-5 card-hover transition-all duration-300 cursor-pointer overflow-hidden shadow-sm">
<div class="absolute top-0 left-0 w-full h-1 bg-gradient-to-r from-primary to-secondary opacity-0 group-hover:opacity-100 transition-opacity"></div>
<div class="flex items-start justify-between">
<div class="size-12 rounded-lg bg-cover bg-center shadow-md ring-1 ring-black/5" data-alt="Notion AI logo icon indigo background" style="background-image: url('https://lh3.googleusercontent.com/aida-public/AB6AXuAgnpQqxiQhRd6xj43Q0hY2EQTna5khOzvl_NxO3OJN_7_nsfC0wP1x7ZmfBEvEEOoGi7NHVx6pvOpJb7Zk7dvUQWD64Y7Nrhc0mi0wsVIB1U8OfG1wioEusBqkI6zJQQh8W0om8gi2ubnJn2McHrvANulIAVnRxn1zPcy-SAnW1rLynAdVFKurBX5qK3BFBi_knAMoL8bTL_1LTxIdhdCggw6WeCGsFgUu6vqwjq9prGC-j94Fr8GibbjDSABY36P1OoZg59Aiv1M4');"></div>
<div class="flex items-center justify-center size-8 rounded-full bg-slate-100 text-slate-400 group-hover:text-primary group-hover:bg-primary/10 transition-colors">
<span class="material-symbols-outlined text-lg">arrow_outward</span>
</div>
</div>
<div>
<h3 class="text-text-main text-lg font-bold leading-tight group-hover:text-primary transition-colors">Notion AI</h3>
<p class="text-text-muted text-sm mt-2 line-clamp-2">Access the limitless power of AI, right inside your Notion workspace.</p>
</div>
<div class="mt-auto flex items-center justify-between pt-4 border-t border-border-color">
<div class="flex gap-2">
<span class="px-2 py-0.5 rounded text-[10px] font-bold uppercase tracking-wider bg-slate-100 text-slate-600 border border-slate-200">Productivity</span>
</div>
<div class="flex items-center gap-1 text-slate-400 text-xs">
<span class="material-symbols-outlined text-[14px]">visibility</span>
<span>600k</span>
</div>
</div>
</div>
</div>
<div class="flex items-center justify-center py-4">
<nav class="flex items-center gap-2">
<a class="flex size-10 items-center justify-center rounded-full border border-border-color text-text-muted hover:text-primary hover:border-primary transition-colors bg-surface" href="#">
<span class="material-symbols-outlined text-base">chevron_left</span>
</a>
<a class="flex size-10 items-center justify-center rounded-full bg-primary text-white text-sm font-bold shadow-[0_4px_10px_rgba(14,165,233,0.3)]" href="#">1</a>
<a class="flex size-10 items-center justify-center rounded-full border border-transparent hover:bg-slate-200 text-text-muted hover:text-text-main text-sm font-medium transition-colors" href="#">2</a>
<a class="flex size-10 items-center justify-center rounded-full border border-transparent hover:bg-slate-200 text-text-muted hover:text-text-main text-sm font-medium transition-colors" href="#">3</a>
<span class="flex size-10 items-center justify-center text-slate-400">...</span>
<a class="flex size-10 items-center justify-center rounded-full border border-transparent hover:bg-slate-200 text-text-muted hover:text-text-main text-sm font-medium transition-colors" href="#">12</a>
<a class="flex size-10 items-center justify-center rounded-full border border-border-color text-text-muted hover:text-primary hover:border-primary transition-colors bg-surface" href="#">
<span class="material-symbols-outlined text-base">chevron_right</span>
</a>
</nav>
</div>
</div>
</main>
<footer class="w-full border-t border-border-color bg-surface py-8 mt-auto">
<div class="mx-auto max-w-[1200px] flex flex-col md:flex-row items-center justify-between gap-6 px-6 lg:px-10">
<div class="flex items-center gap-2 text-text-muted text-sm">
<span>© 2023 ZJPB AI Directory. All rights reserved.</span>
</div>
<div class="flex gap-6">
<a class="text-text-muted hover:text-primary transition-colors" href="#">
<span class="text-sm font-medium">Twitter</span>
</a>
<a class="text-text-muted hover:text-primary transition-colors" href="#">
<span class="text-sm font-medium">Discord</span>
</a>
<a class="text-text-muted hover:text-primary transition-colors" href="#">
<span class="text-sm font-medium">Privacy Policy</span>
</a>
</div>
</div>
</footer>
</div>
</body></html>

Binary file not shown.

After

Width:  |  Height:  |  Size: 341 KiB

View File

@@ -1,126 +0,0 @@
<!DOCTYPE html>
<html class="dark" lang="en"><head>
<meta charset="utf-8"/>
<meta content="width=device-width, initial-scale=1.0" name="viewport"/>
<title>Admin Login - AI Discovery</title>
<!-- Fonts -->
<link href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@300;400;500;600;700&amp;family=Noto+Sans:wght@300;400;500;600;700&amp;display=swap" rel="stylesheet"/>
<!-- Material Symbols -->
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&amp;display=swap" rel="stylesheet"/>
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&amp;display=swap" rel="stylesheet"/>
<!-- Tailwind CSS -->
<script src="https://cdn.tailwindcss.com?plugins=forms,container-queries"></script>
<script id="tailwind-config">
tailwind.config = {
darkMode: "class",
theme: {
extend: {
colors: {
"primary": "#25c0f4",
"background-light": "#f5f8f8",
"background-dark": "#101e22",
"surface-dark": "#162125",
"input-bg": "#1b2427",
"input-border": "#3b4e54",
"text-muted": "#9cb2ba",
},
fontFamily: {
"display": ["Space Grotesk", "sans-serif"],
"body": ["Noto Sans", "sans-serif"],
},
borderRadius: {"DEFAULT": "0.25rem", "lg": "0.5rem", "xl": "0.75rem", "full": "9999px"},
backgroundImage: {
'tech-grid': "radial-gradient(circle at center, rgba(37, 192, 244, 0.05) 0%, transparent 70%), linear-gradient(rgba(37, 192, 244, 0.03) 1px, transparent 1px), linear-gradient(90deg, rgba(37, 192, 244, 0.03) 1px, transparent 1px)",
}
},
},
}
</script>
<style>
.glass-panel {
background: rgba(22, 33, 37, 0.7);
backdrop-filter: blur(12px);
-webkit-backdrop-filter: blur(12px);
}
.tech-bg-size {
background-size: 100% 100%, 40px 40px, 40px 40px;
}
</style>
</head>
<body class="bg-background-light dark:bg-background-dark font-display text-slate-900 dark:text-white min-h-screen flex flex-col items-center justify-center relative overflow-hidden selection:bg-primary/30">
<!-- Ambient Background -->
<div class="absolute inset-0 z-0 bg-tech-grid tech-bg-size pointer-events-none" data-alt="Abstract subtle grid pattern background representing technology"></div>
<div class="absolute top-[-20%] right-[-10%] w-[600px] h-[600px] bg-primary/10 rounded-full blur-[120px] pointer-events-none"></div>
<div class="absolute bottom-[-10%] left-[-10%] w-[500px] h-[500px] bg-purple-600/10 rounded-full blur-[100px] pointer-events-none"></div>
<!-- Main Content Wrapper -->
<div class="w-full max-w-[480px] px-4 z-10 flex flex-col gap-6">
<!-- Back Navigation -->
<a class="group flex items-center gap-2 text-text-muted hover:text-white transition-colors w-fit" href="#">
<div class="flex items-center justify-center w-8 h-8 rounded-full border border-input-border bg-surface-dark group-hover:border-primary/50 transition-colors">
<span class="material-symbols-outlined text-sm">arrow_back</span>
</div>
<span class="text-sm font-medium">Back to Home</span>
</a>
<!-- Login Card -->
<div class="glass-panel border border-white/5 dark:border-input-border/50 rounded-xl shadow-2xl p-8 md:p-10 relative overflow-hidden">
<!-- Decorative Accent -->
<div class="absolute top-0 left-0 w-full h-[2px] bg-gradient-to-r from-transparent via-primary to-transparent opacity-70"></div>
<!-- Header -->
<div class="flex flex-col gap-2 mb-8">
<div class="flex items-center gap-3 mb-2">
<div class="p-2 rounded-lg bg-primary/10 text-primary">
<span class="material-symbols-outlined text-2xl">shield_person</span>
</div>
<span class="text-xs font-bold tracking-widest uppercase text-primary/80">System Access</span>
</div>
<h1 class="text-3xl font-black tracking-tight text-white leading-tight">管理员登录</h1>
<p class="text-text-muted text-base font-normal">Enter your credentials to access the AI Control Panel.</p>
</div>
<!-- Error Message Area (Static Example) -->
<!-- <div class="mb-6 p-3 rounded-lg bg-red-500/10 border border-red-500/20 flex items-start gap-3">
<span class="material-symbols-outlined text-red-400 text-sm mt-0.5">error</span>
<p class="text-red-400 text-sm">Invalid credentials provided.</p>
</div> -->
<!-- Form -->
<form class="flex flex-col gap-5">
<!-- Username Field -->
<div class="space-y-2">
<label class="text-white text-sm font-medium leading-normal" for="username">Username</label>
<div class="relative group">
<input class="form-input flex w-full h-14 pl-12 pr-4 rounded-lg text-white focus:outline-0 focus:ring-0 border border-input-border bg-input-bg focus:border-primary placeholder:text-text-muted text-base font-normal leading-normal transition-colors" id="username" placeholder="Enter your username or email" type="text"/>
<div class="absolute left-4 top-1/2 -translate-y-1/2 text-text-muted group-focus-within:text-primary transition-colors flex items-center justify-center pointer-events-none">
<span class="material-symbols-outlined text-[20px]">person</span>
</div>
</div>
</div>
<!-- Password Field -->
<div class="space-y-2">
<div class="flex justify-between items-end">
<label class="text-white text-sm font-medium leading-normal" for="password">Password</label>
<a class="text-xs text-primary hover:text-white transition-colors mb-1" href="#">Forgot password?</a>
</div>
<div class="flex w-full items-stretch rounded-lg group relative">
<input class="form-input flex w-full h-14 pl-12 pr-12 rounded-lg text-white focus:outline-0 focus:ring-0 border border-input-border bg-input-bg focus:border-primary placeholder:text-text-muted text-base font-normal leading-normal transition-colors z-10" id="password" placeholder="Enter your password" type="password"/>
<div class="absolute left-4 top-1/2 -translate-y-1/2 text-text-muted group-focus-within:text-primary transition-colors flex items-center justify-center pointer-events-none z-20">
<span class="material-symbols-outlined text-[20px]">lock</span>
</div>
<button class="absolute right-0 top-0 h-full px-4 text-text-muted hover:text-white transition-colors flex items-center justify-center z-20 focus:outline-none" type="button">
<span class="material-symbols-outlined text-[20px]">visibility_off</span>
</button>
</div>
</div>
<!-- Login Button -->
<button class="mt-4 flex w-full h-12 cursor-pointer items-center justify-center overflow-hidden rounded-lg bg-primary text-background-dark text-base font-bold leading-normal tracking-[0.015em] hover:bg-primary/90 transition-all active:scale-[0.98] shadow-[0_0_20px_rgba(37,192,244,0.3)] hover:shadow-[0_0_30px_rgba(37,192,244,0.5)]" type="button">
<span class="truncate">Login</span>
</button>
</form>
<!-- Footer Meta -->
<div class="mt-8 flex justify-center gap-6 border-t border-input-border/30 pt-6">
<p class="text-text-muted text-xs text-center">
Protected by reCAPTCHA. <a class="underline hover:text-primary" href="#">Privacy</a> &amp; <a class="underline hover:text-primary" href="#">Terms</a>.
</p>
</div>
</div>
</div>
</body></html>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 518 KiB

View File

@@ -1,394 +0,0 @@
<!DOCTYPE html>
<html class="dark" lang="en"><head>
<meta charset="utf-8"/>
<meta content="width=device-width, initial-scale=1.0" name="viewport"/>
<title>Admin Management Interface</title>
<!-- Fonts -->
<link href="https://fonts.googleapis.com" rel="preconnect"/>
<link crossorigin="" href="https://fonts.gstatic.com" rel="preconnect"/>
<link href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@300;400;500;700&amp;family=Noto+Sans:wght@400;500;700&amp;display=swap" rel="stylesheet"/>
<!-- Material Symbols -->
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&amp;display=swap" rel="stylesheet"/>
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&amp;display=swap" rel="stylesheet"/>
<!-- Tailwind CSS -->
<script src="https://cdn.tailwindcss.com?plugins=forms,container-queries"></script>
<!-- Theme Configuration -->
<script id="tailwind-config">
tailwind.config = {
darkMode: "class",
theme: {
extend: {
colors: {
"primary": "#25c0f4",
"primary-hover": "#0ea5e9",
"background-light": "#f5f8f8",
"background-dark": "#111618", /* Matched to snippets */
"surface-dark": "#192226",
"surface-highlight": "#283539",
"text-secondary": "#9cb2ba",
},
fontFamily: {
"display": ["Space Grotesk", "sans-serif"],
"body": ["Noto Sans", "sans-serif"],
},
borderRadius: {"DEFAULT": "0.25rem", "lg": "0.5rem", "xl": "0.75rem", "2xl": "1rem", "full": "9999px"},
},
},
}
</script>
<style>
body {
font-family: "Noto Sans", sans-serif;
}
h1, h2, h3, h4, h5, h6, .font-display {
font-family: "Space Grotesk", sans-serif;
}
/* Custom Scrollbar for dark theme */
::-webkit-scrollbar {
width: 8px;
height: 8px;
}
::-webkit-scrollbar-track {
background: #111618;
}
::-webkit-scrollbar-thumb {
background: #283539;
border-radius: 4px;
}
::-webkit-scrollbar-thumb:hover {
background: #3a4b50;
}
</style>
</head>
<body class="bg-background-light dark:bg-background-dark text-slate-900 dark:text-white overflow-hidden flex h-screen w-full">
<!-- Sidebar -->
<aside class="flex flex-col w-64 h-full border-r border-[#283539] bg-background-dark flex-shrink-0 transition-all duration-300">
<div class="p-6 flex items-center gap-3">
<div class="size-8 text-primary">
<svg fill="none" viewbox="0 0 48 48" xmlns="http://www.w3.org/2000/svg">
<path clip-rule="evenodd" d="M24 18.4228L42 11.475V34.3663C42 34.7796 41.7457 35.1504 41.3601 35.2992L24 42V18.4228Z" fill="currentColor" fill-rule="evenodd"></path>
<path clip-rule="evenodd" d="M24 8.18819L33.4123 11.574L24 15.2071L14.5877 11.574L24 8.18819ZM9 15.8487L21 20.4805V37.6263L9 32.9945V15.8487ZM27 37.6263V20.4805L39 15.8487V32.9945L27 37.6263ZM25.354 2.29885C24.4788 1.98402 23.5212 1.98402 22.646 2.29885L4.98454 8.65208C3.7939 9.08038 3 10.2097 3 11.475V34.3663C3 36.0196 4.01719 37.5026 5.55962 38.098L22.9197 44.7987C23.6149 45.0671 24.3851 45.0671 25.0803 44.7987L42.4404 38.098C43.9828 37.5026 45 36.0196 45 34.3663V11.475C45 10.2097 44.2061 9.08038 43.0155 8.65208L25.354 2.29885Z" fill="currentColor" fill-rule="evenodd"></path>
</svg>
</div>
<h1 class="text-white text-xl font-bold tracking-tight">AI Discovery</h1>
</div>
<nav class="flex-1 px-4 flex flex-col gap-2 mt-4">
<div class="px-3 py-2">
<p class="text-text-secondary text-xs font-bold uppercase tracking-wider mb-2">Main Menu</p>
<a class="flex items-center gap-3 px-3 py-2.5 rounded-lg bg-surface-highlight text-white group transition-colors" href="#">
<span class="material-symbols-outlined text-primary group-hover:text-white transition-colors">language</span>
<span class="text-sm font-medium">Website Management</span>
</a>
<a class="flex items-center gap-3 px-3 py-2.5 rounded-lg text-text-secondary hover:bg-surface-highlight hover:text-white transition-colors mt-1" href="#">
<span class="material-symbols-outlined">label</span>
<span class="text-sm font-medium">Tag Management</span>
</a>
<a class="flex items-center gap-3 px-3 py-2.5 rounded-lg text-text-secondary hover:bg-surface-highlight hover:text-white transition-colors mt-1" href="#">
<span class="material-symbols-outlined">group</span>
<span class="text-sm font-medium">Administrators</span>
</a>
</div>
<div class="px-3 py-2 mt-auto">
<p class="text-text-secondary text-xs font-bold uppercase tracking-wider mb-2">System</p>
<a class="flex items-center gap-3 px-3 py-2.5 rounded-lg text-text-secondary hover:bg-surface-highlight hover:text-white transition-colors" href="#">
<span class="material-symbols-outlined">settings</span>
<span class="text-sm font-medium">Settings</span>
</a>
</div>
</nav>
<div class="p-4 border-t border-[#283539]">
<div class="flex items-center gap-3">
<div class="bg-center bg-no-repeat bg-cover rounded-full size-10 border border-[#283539]" data-alt="User profile picture showing a minimal avatar" style='background-image: url("https://lh3.googleusercontent.com/aida-public/AB6AXuDlD4gGno1M8YZQOQ6QyLdUZtaU7klHxTjeH2Kf3aH39GkQ56ld-iSPZ6RCbkxzCeXJzMq6_29atBkPks_wP-Q7pvpyNAAZW2FJKxOFTSrcQZp2O30dVhn-DPYELqG1fdqM-hPfluduBN-I0k_Q8cJrJ3LKVUUgnxsE8bWD4CSo8CoYvXmtK03j86TehszyrfWfKpoBcu8XaHpjh10EGoDHFYeSJ6AKJQDsp_0iz5LC4bOXvyg0_Nef5mQOBtQ9zKuKSbCYhRuu7Wyo");'></div>
<div class="flex flex-col overflow-hidden">
<h2 class="text-white text-sm font-medium truncate">Admin User</h2>
<p class="text-text-secondary text-xs truncate">admin@ai-discovery.com</p>
</div>
</div>
</div>
</aside>
<!-- Main Content -->
<main class="flex-1 flex flex-col h-full overflow-hidden bg-background-dark relative">
<!-- Top Header -->
<header class="h-16 border-b border-[#283539] bg-background-dark/95 backdrop-blur px-8 flex items-center justify-between shrink-0 z-20">
<!-- Breadcrumbs -->
<div class="flex items-center gap-2 text-sm">
<a class="text-text-secondary hover:text-white transition-colors" href="#">Dashboard</a>
<span class="text-text-secondary">/</span>
<span class="text-white font-medium">Website Management</span>
</div>
<!-- Global Actions -->
<div class="flex items-center gap-6">
<!-- Search -->
<div class="relative w-64 hidden md:block">
<span class="absolute inset-y-0 left-0 flex items-center pl-3 text-text-secondary">
<span class="material-symbols-outlined text-[20px]">search</span>
</span>
<input class="w-full bg-surface-highlight border-none rounded-lg py-2 pl-10 pr-4 text-sm text-white placeholder-text-secondary focus:ring-1 focus:ring-primary focus:bg-[#1f292d] transition-all" placeholder="Global search..." type="text"/>
</div>
<!-- Notifications -->
<button class="relative text-text-secondary hover:text-white transition-colors">
<span class="material-symbols-outlined">notifications</span>
<span class="absolute top-0 right-0 size-2 bg-primary rounded-full"></span>
</button>
<!-- Logout -->
<button class="text-text-secondary hover:text-white transition-colors">
<span class="material-symbols-outlined">logout</span>
</button>
</div>
</header>
<!-- Scrollable Content Area -->
<div class="flex-1 overflow-y-auto overflow-x-hidden p-8 scroll-smooth">
<div class="max-w-[1400px] mx-auto flex flex-col gap-8 pb-10">
<!-- Page Title & Toolbar -->
<div class="flex flex-col md:flex-row md:items-end justify-between gap-4">
<div>
<h1 class="text-3xl md:text-4xl font-bold text-white mb-2">Website Management</h1>
<p class="text-text-secondary text-base">Manage and curate AI tools for the discovery platform.</p>
</div>
<div class="flex gap-3">
<button class="flex items-center gap-2 h-10 px-4 rounded-lg bg-surface-highlight hover:bg-[#344247] text-white text-sm font-medium transition-colors border border-transparent hover:border-[#4a5a60]">
<span class="material-symbols-outlined text-[20px]">filter_list</span>
<span>Filter</span>
</button>
<button class="flex items-center gap-2 h-10 px-4 rounded-lg bg-surface-highlight hover:bg-[#344247] text-white text-sm font-medium transition-colors border border-transparent hover:border-[#4a5a60]">
<span class="material-symbols-outlined text-[20px]">download</span>
<span>Export</span>
</button>
</div>
</div>
<!-- Content Grid: Table & Quick Add Form -->
<div class="grid grid-cols-1 xl:grid-cols-3 gap-8 items-start">
<!-- Left Column: Data Table -->
<div class="xl:col-span-2 flex flex-col gap-4">
<!-- Table Toolbar -->
<div class="bg-surface-dark border border-[#283539] rounded-t-xl p-4 flex flex-col sm:flex-row justify-between items-center gap-4">
<div class="relative w-full sm:w-72">
<span class="absolute inset-y-0 left-0 flex items-center pl-3 text-text-secondary">
<span class="material-symbols-outlined text-[20px]">search</span>
</span>
<input class="w-full bg-[#111618] border border-[#283539] rounded-lg py-2 pl-10 pr-4 text-sm text-white placeholder-text-secondary focus:ring-1 focus:ring-primary" placeholder="Search websites..." type="text"/>
</div>
<div class="text-sm text-text-secondary">
Showing <span class="text-white font-medium">1-10</span> of <span class="text-white font-medium">128</span>
</div>
</div>
<!-- Table -->
<div class="bg-surface-dark border border-[#283539] rounded-b-xl overflow-hidden shadow-xl">
<div class="overflow-x-auto">
<table class="w-full text-left border-collapse">
<thead>
<tr class="bg-[#1e272c] border-b border-[#283539]">
<th class="p-4 text-xs font-semibold uppercase tracking-wider text-text-secondary w-10">
<input class="rounded bg-[#111618] border-[#283539] text-primary focus:ring-offset-[#111618]" type="checkbox"/>
</th>
<th class="p-4 text-xs font-semibold uppercase tracking-wider text-text-secondary">Website / Tool</th>
<th class="p-4 text-xs font-semibold uppercase tracking-wider text-text-secondary">Tags</th>
<th class="p-4 text-xs font-semibold uppercase tracking-wider text-text-secondary">Status</th>
<th class="p-4 text-xs font-semibold uppercase tracking-wider text-text-secondary text-right">Actions</th>
</tr>
</thead>
<tbody class="divide-y divide-[#283539]">
<!-- Row 1 -->
<tr class="group hover:bg-[#1e272c] transition-colors">
<td class="p-4">
<input class="rounded bg-[#111618] border-[#283539] text-primary focus:ring-offset-[#111618]" type="checkbox"/>
</td>
<td class="p-4">
<div class="flex items-center gap-3">
<div class="size-10 rounded-lg bg-cover bg-center shrink-0 border border-[#283539]" data-alt="Icon of Chat AI Tool" style='background-image: url("https://lh3.googleusercontent.com/aida-public/AB6AXuB_TTlnbkP1ygA1GNesXOhx_H9yup1gOKwumL9Qnh8VcPEUlwDRA89LRKhO8tyDtOtvtfrGh7CRSnP5xMOyb_GvJgQ-FQToY20cgHuTpVeI97COVlA4e2wOp04SH5ogk98S2SFF4jtSoS7RldXDdaafrysnsulA7_euKFIDf42NTPk_YwR4aglKFOeiQCQT-ITD6hCZjE8kqEfam8E6onBbJ14KZt2sE8gn0VP-pzzgAgPlr3htq5j6T26Tijk8V7SIFQGlB20GjSsb");'></div>
<div>
<h3 class="text-sm font-bold text-white group-hover:text-primary transition-colors">ChatGPT</h3>
<p class="text-xs text-text-secondary truncate max-w-[200px]">openai.com/chatgpt</p>
</div>
</div>
</td>
<td class="p-4">
<div class="flex flex-wrap gap-1">
<span class="px-2 py-0.5 rounded text-[10px] font-medium bg-[#283539] text-white border border-[#3a4b50]">LLM</span>
<span class="px-2 py-0.5 rounded text-[10px] font-medium bg-[#283539] text-white border border-[#3a4b50]">Chatbot</span>
</div>
</td>
<td class="p-4">
<span class="inline-flex items-center px-2 py-1 rounded-full text-xs font-medium bg-green-500/10 text-green-400 border border-green-500/20">
Active
</span>
</td>
<td class="p-4 text-right">
<div class="flex items-center justify-end gap-2 opacity-0 group-hover:opacity-100 transition-opacity">
<button class="p-1.5 rounded-md hover:bg-primary/20 hover:text-primary text-text-secondary transition-colors">
<span class="material-symbols-outlined text-[18px]">edit</span>
</button>
<button class="p-1.5 rounded-md hover:bg-red-500/20 hover:text-red-400 text-text-secondary transition-colors">
<span class="material-symbols-outlined text-[18px]">delete</span>
</button>
</div>
</td>
</tr>
<!-- Row 2 -->
<tr class="group hover:bg-[#1e272c] transition-colors">
<td class="p-4">
<input class="rounded bg-[#111618] border-[#283539] text-primary focus:ring-offset-[#111618]" type="checkbox"/>
</td>
<td class="p-4">
<div class="flex items-center gap-3">
<div class="size-10 rounded-lg bg-cover bg-center shrink-0 border border-[#283539]" data-alt="Icon of Midjourney AI" style='background-image: url("https://lh3.googleusercontent.com/aida-public/AB6AXuA-IWUA_5FInPrqeTtZDcb85HR0X3jABxlDeG811Bd-_2x4ISEl8huiNp-kAG75fBmopT3dd5nSp1E0wCtOxXMFoP6HR9nrg4PfNDxjLao4ulneOOrJMgL4LdtuXgKxrQHm1WDoG4TviJ-lIYXzLVI9Yw-9mjDFihMurEOdJlarrMZekyEVrogy_jliQPo3oXvcemTUiJAZz9UCwq8LOEr09EqzU25kXCXx3Z3B0XvdM6VDUrQN8mKtKU1eU9CVwsLJfOKZ09CpJIwx");'></div>
<div>
<h3 class="text-sm font-bold text-white group-hover:text-primary transition-colors">Midjourney</h3>
<p class="text-xs text-text-secondary truncate max-w-[200px]">midjourney.com</p>
</div>
</div>
</td>
<td class="p-4">
<div class="flex flex-wrap gap-1">
<span class="px-2 py-0.5 rounded text-[10px] font-medium bg-[#283539] text-white border border-[#3a4b50]">Image Gen</span>
<span class="px-2 py-0.5 rounded text-[10px] font-medium bg-[#283539] text-white border border-[#3a4b50]">Art</span>
</div>
</td>
<td class="p-4">
<span class="inline-flex items-center px-2 py-1 rounded-full text-xs font-medium bg-green-500/10 text-green-400 border border-green-500/20">
Active
</span>
</td>
<td class="p-4 text-right">
<div class="flex items-center justify-end gap-2 opacity-0 group-hover:opacity-100 transition-opacity">
<button class="p-1.5 rounded-md hover:bg-primary/20 hover:text-primary text-text-secondary transition-colors">
<span class="material-symbols-outlined text-[18px]">edit</span>
</button>
<button class="p-1.5 rounded-md hover:bg-red-500/20 hover:text-red-400 text-text-secondary transition-colors">
<span class="material-symbols-outlined text-[18px]">delete</span>
</button>
</div>
</td>
</tr>
<!-- Row 3 -->
<tr class="group hover:bg-[#1e272c] transition-colors">
<td class="p-4">
<input class="rounded bg-[#111618] border-[#283539] text-primary focus:ring-offset-[#111618]" type="checkbox"/>
</td>
<td class="p-4">
<div class="flex items-center gap-3">
<div class="size-10 rounded-lg bg-cover bg-center shrink-0 border border-[#283539]" data-alt="Icon of Copy AI Tool" style='background-image: url("https://lh3.googleusercontent.com/aida-public/AB6AXuB6XdW_8pAKAs4YPwwYM4bcub_ilJ2i6L7baANaKdNNmDP5k1TRngBOsHE0aTy6sBuBJG49alX-4PRkz55qTcxsrRy6_S8rqD5gCTWLquEDcjqfSai-s7GEdZcBCNJ6G5hEFJM7JFli5r5ETcPq2QBz_NQT0W94XYhzaYpZQVEX-su5ywWxAcrfOFrWrhwC5RCiZL0B7AinM4c1pRSfeDtDpVTzxTvx0MrDi6gBH-duuCUmbr0YjkqhyaZqpRpX-ZhW-JiJQ3sKPUCA");'></div>
<div>
<h3 class="text-sm font-bold text-white group-hover:text-primary transition-colors">Copy.ai</h3>
<p class="text-xs text-text-secondary truncate max-w-[200px]">copy.ai</p>
</div>
</div>
</td>
<td class="p-4">
<div class="flex flex-wrap gap-1">
<span class="px-2 py-0.5 rounded text-[10px] font-medium bg-[#283539] text-white border border-[#3a4b50]">Writing</span>
<span class="px-2 py-0.5 rounded text-[10px] font-medium bg-[#283539] text-white border border-[#3a4b50]">Marketing</span>
</div>
</td>
<td class="p-4">
<span class="inline-flex items-center px-2 py-1 rounded-full text-xs font-medium bg-yellow-500/10 text-yellow-400 border border-yellow-500/20">
Pending
</span>
</td>
<td class="p-4 text-right">
<div class="flex items-center justify-end gap-2 opacity-0 group-hover:opacity-100 transition-opacity">
<button class="p-1.5 rounded-md hover:bg-primary/20 hover:text-primary text-text-secondary transition-colors">
<span class="material-symbols-outlined text-[18px]">edit</span>
</button>
<button class="p-1.5 rounded-md hover:bg-red-500/20 hover:text-red-400 text-text-secondary transition-colors">
<span class="material-symbols-outlined text-[18px]">delete</span>
</button>
</div>
</td>
</tr>
</tbody>
</table>
</div>
<!-- Pagination -->
<div class="flex items-center justify-between px-4 py-3 border-t border-[#283539]">
<div class="flex flex-1 justify-between sm:hidden">
<a class="relative inline-flex items-center rounded-md border border-[#283539] bg-[#111618] px-4 py-2 text-sm font-medium text-text-secondary hover:bg-[#1e272c] hover:text-white" href="#">Previous</a>
<a class="relative ml-3 inline-flex items-center rounded-md border border-[#283539] bg-[#111618] px-4 py-2 text-sm font-medium text-text-secondary hover:bg-[#1e272c] hover:text-white" href="#">Next</a>
</div>
<div class="hidden sm:flex sm:flex-1 sm:items-center sm:justify-end">
<nav aria-label="Pagination" class="isolate inline-flex -space-x-px rounded-md shadow-sm">
<a class="relative inline-flex items-center rounded-l-md px-2 py-2 text-text-secondary ring-1 ring-inset ring-[#283539] hover:bg-[#1e272c] focus:z-20 focus:outline-offset-0" href="#">
<span class="material-symbols-outlined text-[20px]">chevron_left</span>
</a>
<a aria-current="page" class="relative z-10 inline-flex items-center bg-primary px-4 py-2 text-sm font-semibold text-[#111618] focus:z-20 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary" href="#">1</a>
<a class="relative inline-flex items-center px-4 py-2 text-sm font-semibold text-text-secondary ring-1 ring-inset ring-[#283539] hover:bg-[#1e272c] focus:z-20 focus:outline-offset-0" href="#">2</a>
<a class="relative inline-flex items-center px-4 py-2 text-sm font-semibold text-text-secondary ring-1 ring-inset ring-[#283539] hover:bg-[#1e272c] focus:z-20 focus:outline-offset-0" href="#">3</a>
<a class="relative inline-flex items-center rounded-r-md px-2 py-2 text-text-secondary ring-1 ring-inset ring-[#283539] hover:bg-[#1e272c] focus:z-20 focus:outline-offset-0" href="#">
<span class="material-symbols-outlined text-[20px]">chevron_right</span>
</a>
</nav>
</div>
</div>
</div>
</div>
<!-- Right Column: Add/Edit Form -->
<div class="xl:col-span-1">
<div class="bg-surface-dark border border-[#283539] rounded-xl shadow-xl sticky top-8">
<div class="px-6 py-4 border-b border-[#283539] flex justify-between items-center bg-[#1e272c] rounded-t-xl">
<h2 class="text-lg font-bold text-white">Add New Website</h2>
<span class="material-symbols-outlined text-text-secondary cursor-pointer hover:text-white">close</span>
</div>
<div class="p-6 flex flex-col gap-6">
<!-- URL Input + Auto Fetch -->
<div class="flex flex-col gap-3">
<label class="text-sm font-medium text-white">Website URL <span class="text-primary">*</span></label>
<div class="flex flex-col gap-2">
<div class="relative">
<input class="w-full bg-[#111618] border border-[#283539] rounded-lg py-2.5 px-3 text-sm text-white placeholder-text-secondary focus:ring-1 focus:ring-primary focus:border-primary transition-all" placeholder="https://example.com" type="url"/>
<div class="absolute inset-y-0 right-3 flex items-center">
<span class="material-symbols-outlined text-green-500 text-[18px]" style="display:none;">check_circle</span>
</div>
</div>
<!-- CRITICAL: Auto-fetch Button -->
<button class="flex items-center justify-center gap-2 w-full py-2 px-3 rounded-lg border border-primary/30 bg-primary/5 hover:bg-primary/10 text-primary text-sm font-medium transition-colors group" type="button">
<span class="material-symbols-outlined text-[18px] group-hover:animate-spin">autorenew</span>
Auto-fetch Website Info
</button>
<p class="text-xs text-text-secondary mt-1">
Our AI will attempt to scrape metadata and descriptions automatically.
</p>
</div>
</div>
<!-- Other Fields -->
<div class="flex flex-col gap-3">
<label class="text-sm font-medium text-white">Tool Name</label>
<input class="w-full bg-[#111618] border border-[#283539] rounded-lg py-2.5 px-3 text-sm text-white placeholder-text-secondary focus:ring-1 focus:ring-primary focus:border-primary" placeholder="e.g. ChatGPT" type="text"/>
</div>
<div class="flex flex-col gap-3">
<label class="text-sm font-medium text-white">Short Description</label>
<textarea class="w-full bg-[#111618] border border-[#283539] rounded-lg py-2.5 px-3 text-sm text-white placeholder-text-secondary focus:ring-1 focus:ring-primary focus:border-primary resize-none" placeholder="Enter a brief description of the AI tool..." rows="4"></textarea>
</div>
<div class="flex flex-col gap-3">
<label class="text-sm font-medium text-white">Tags</label>
<div class="p-3 bg-[#111618] border border-[#283539] rounded-lg min-h-[44px] flex flex-wrap gap-2">
<span class="inline-flex items-center gap-1 px-2 py-1 rounded bg-[#283539] text-xs text-white">
Productivity
<span class="material-symbols-outlined text-[14px] cursor-pointer hover:text-red-400">close</span>
</span>
<input class="bg-transparent border-none p-0 text-sm text-white placeholder-text-secondary focus:ring-0 w-24" placeholder="+ Add tag" type="text"/>
</div>
</div>
<!-- Status Toggle -->
<div class="flex items-center justify-between py-2">
<label class="text-sm font-medium text-white">Active Status</label>
<button class="w-11 h-6 bg-primary rounded-full relative cursor-pointer">
<span class="absolute top-1 left-6 size-4 bg-white rounded-full shadow-sm transition-all"></span>
</button>
</div>
</div>
<div class="px-6 py-4 border-t border-[#283539] flex gap-3 bg-[#1e272c] rounded-b-xl">
<button class="flex-1 py-2.5 rounded-lg bg-primary hover:bg-primary-hover text-[#111618] text-sm font-bold shadow-lg shadow-primary/20 transition-all">
Save Website
</button>
<button class="px-4 py-2.5 rounded-lg border border-[#283539] bg-transparent hover:bg-[#283539] text-white text-sm font-medium transition-colors">
Cancel
</button>
</div>
</div>
</div>
</div>
</div>
</div>
</main>
</body></html>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 249 KiB

View File

@@ -1,270 +0,0 @@
<!DOCTYPE html>
<html class="dark" lang="en"><head>
<meta charset="utf-8"/>
<meta content="width=device-width, initial-scale=1.0" name="viewport"/>
<title>AI Tool Detail Page</title>
<!-- Google Fonts -->
<link href="https://fonts.googleapis.com" rel="preconnect"/>
<link crossorigin="" href="https://fonts.gstatic.com" rel="preconnect"/>
<link href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@300;400;500;600;700&amp;family=Noto+Sans:wght@400;500;700&amp;display=swap" rel="stylesheet"/>
<!-- Material Symbols -->
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&amp;display=swap" rel="stylesheet"/>
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&amp;display=swap" rel="stylesheet"/>
<!-- Tailwind CSS -->
<script src="https://cdn.tailwindcss.com?plugins=forms,container-queries"></script>
<!-- Theme Config -->
<script id="tailwind-config">
tailwind.config = {
darkMode: "class",
theme: {
extend: {
colors: {
"primary": "#25c0f4",
"primary-dark": "#0ea5e9",
"secondary": "#a855f7", // Adding purple for the gradient request
"background-light": "#f5f8f8",
"background-dark": "#101e22",
"surface-dark": "#1a262b",
"surface-border": "#283539",
"text-secondary": "#9cb2ba"
},
fontFamily: {
"display": ["Space Grotesk", "sans-serif"],
"body": ["Noto Sans", "sans-serif"]
},
borderRadius: {"DEFAULT": "0.25rem", "lg": "0.5rem", "xl": "0.75rem", "2xl": "1rem", "full": "9999px"},
},
},
}
</script>
<style>
body {
font-family: 'Space Grotesk', sans-serif;
}
.glass-card {
background: rgba(26, 38, 43, 0.6);
backdrop-filter: blur(12px);
border: 1px solid rgba(40, 53, 57, 0.5);
}
</style>
</head>
<body class="bg-background-light dark:bg-background-dark text-slate-900 dark:text-white min-h-screen flex flex-col selection:bg-primary selection:text-background-dark">
<!-- Top Navigation -->
<div class="w-full border-b border-surface-border bg-background-dark/95 backdrop-blur sticky top-0 z-50">
<div class="max-w-[1280px] mx-auto px-4 sm:px-6 lg:px-8">
<header class="flex items-center justify-between h-16">
<!-- Logo Area -->
<div class="flex items-center gap-8">
<div class="flex items-center gap-3 text-white">
<div class="size-8 text-primary">
<span class="material-symbols-outlined text-3xl">token</span>
</div>
<h2 class="text-white text-xl font-bold tracking-tight">AI Discovery</h2>
</div>
<!-- Desktop Nav -->
<div class="hidden md:flex items-center gap-6">
<a class="text-text-secondary hover:text-white text-sm font-medium transition-colors" href="#">Categories</a>
<a class="text-text-secondary hover:text-white text-sm font-medium transition-colors" href="#">Submit Tool</a>
<a class="text-text-secondary hover:text-white text-sm font-medium transition-colors" href="#">Blog</a>
</div>
</div>
<!-- Right Actions -->
<div class="flex items-center gap-4">
<div class="hidden sm:flex relative group">
<div class="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
<span class="material-symbols-outlined text-text-secondary text-[20px]">search</span>
</div>
<input class="bg-surface-border/50 hover:bg-surface-border focus:bg-surface-border text-white text-sm rounded-lg pl-10 pr-4 py-2 w-48 focus:w-64 transition-all outline-none ring-0 border border-transparent focus:border-primary/50 placeholder:text-text-secondary/70" placeholder="Search tools..."/>
</div>
<button class="flex items-center justify-center rounded-lg h-9 px-4 bg-primary hover:bg-primary-dark text-background-dark text-sm font-bold transition-colors">
Log In
</button>
</div>
</header>
</div>
</div>
<!-- Main Layout -->
<main class="flex-grow w-full max-w-[1280px] mx-auto px-4 sm:px-6 lg:px-8 py-8">
<!-- Back Button -->
<div class="mb-8">
<button class="group flex items-center gap-2 text-text-secondary hover:text-primary transition-colors text-sm font-bold">
<span class="material-symbols-outlined text-[20px] group-hover:-translate-x-1 transition-transform">arrow_back</span>
<span>Back to Home</span>
</button>
</div>
<div class="grid grid-cols-1 lg:grid-cols-12 gap-8 lg:gap-12">
<!-- Left Column: Header Info + Content -->
<div class="lg:col-span-8 flex flex-col gap-8">
<!-- Header Section -->
<div class="flex flex-col sm:flex-row gap-6 items-start">
<!-- Logo Container -->
<div class="relative shrink-0">
<div class="absolute -inset-1 bg-gradient-to-br from-primary via-purple-500 to-secondary rounded-2xl opacity-30 blur-md"></div>
<div class="relative size-[120px] rounded-xl bg-surface-border flex items-center justify-center overflow-hidden border border-surface-border bg-cover bg-center" data-alt="Abstract neural network pattern in deep blue and purple" style="background-image: url('https://lh3.googleusercontent.com/aida-public/AB6AXuAFm-_1orioA8YwgKaNGCzxomQeon7z13YmJCs2VpIoXP6J2a5kLgu_mC9IFA-cRA5s6yNaDPiWRxhF8tTX4FFOTbngaHydfCmAMNxU2EuZ2burR-hLOggJYl24v1Ry7GaNu6ETcYui4SIfvWqNs43vwA4vEmoogV3TxVzuDRDHsy5nj9ab4TnW4c5mVgqHPttLIF1NKTm6rtEcPnWW1jtCihIAAqZVnst9o0Z-IyAYGJoPjNajRdJZGR5NDfiBf-tdr79Bo42KY_DX');">
</div>
</div>
<!-- Title & Meta -->
<div class="flex flex-col gap-3 flex-1">
<div>
<h1 class="text-4xl font-bold text-white tracking-tight font-display mb-1">NeuroGen AI</h1>
<a class="text-primary hover:text-primary-dark hover:underline text-base font-medium flex items-center gap-1" href="#">
www.neurogen.ai
<span class="material-symbols-outlined text-[16px]">open_in_new</span>
</a>
</div>
<!-- Tags -->
<div class="flex flex-wrap gap-2">
<span class="inline-flex items-center px-3 py-1 rounded-full text-xs font-medium bg-surface-border text-text-secondary border border-transparent hover:border-primary/30 transition-colors cursor-default">Generative AI</span>
<span class="inline-flex items-center px-3 py-1 rounded-full text-xs font-medium bg-surface-border text-text-secondary border border-transparent hover:border-primary/30 transition-colors cursor-default">Copywriting</span>
<span class="inline-flex items-center px-3 py-1 rounded-full text-xs font-medium bg-surface-border text-text-secondary border border-transparent hover:border-primary/30 transition-colors cursor-default">Productivity</span>
<span class="inline-flex items-center px-3 py-1 rounded-full text-xs font-medium bg-purple-500/10 text-purple-400 border border-purple-500/20">Free Trial</span>
</div>
<!-- Stats Row -->
<div class="flex items-center gap-6 mt-1 text-text-secondary text-sm">
<div class="flex items-center gap-2" title="Total Views">
<span class="material-symbols-outlined text-[18px]">visibility</span>
<span>12,450 Views</span>
</div>
<div class="flex items-center gap-2" title="Date Added">
<span class="material-symbols-outlined text-[18px]">calendar_today</span>
<span>Added Oct 24, 2023</span>
</div>
</div>
</div>
</div>
<hr class="border-surface-border/60"/>
<!-- Main Content Body -->
<div class="space-y-8">
<!-- Overview -->
<section>
<h3 class="text-xl font-bold text-white mb-4 flex items-center gap-2">
<span class="material-symbols-outlined text-primary">info</span>
Product Overview
</h3>
<p class="text-text-secondary leading-relaxed text-lg">
NeuroGen AI is an advanced copywriting assistant designed to help marketers, writers, and businesses generate high-converting content in seconds. By leveraging state-of-the-art natural language processing models, it understands context, tone, and brand voice to deliver tailored outputs.
</p>
</section>
<!-- Image Gallery / Preview (Optional visual break) -->
<div class="rounded-xl overflow-hidden border border-surface-border relative group aspect-video w-full bg-surface-dark">
<div class="absolute inset-0 bg-cover bg-center" data-alt="Dashboard interface of NeuroGen AI showing analytics and text editor" style="background-image: url('https://lh3.googleusercontent.com/aida-public/AB6AXuCtwM-4OkGIPG4sQqABpRcLvOgKvLzuyVlQJUVcU8SqFsVod_jck_vFm-q7UX70vUaaF7UPEa--rkf18dMi5_qYcXYsjTzd5ehM3ga_OoIr1r6wnRTnRxKcrUUC5USP72vdsFJkRFeKmH6epH1Avi5tglJ732VTvoNiuys499jctwZ67d4Yu8Slfn0EO4Ny8zIjzPRGLer8lMaEeZPMagNbgn4asMLvmDtTep3mgPzwTscfzoOFVChGqNak0ZmLyOiVY7vKIapdHyoz');">
</div>
<div class="absolute inset-0 bg-gradient-to-t from-background-dark via-transparent to-transparent opacity-60"></div>
<div class="absolute bottom-4 left-4">
<p class="text-white font-medium text-sm bg-black/50 backdrop-blur px-3 py-1 rounded-lg">Dashboard Interface</p>
</div>
</div>
<!-- Detailed Description -->
<section>
<h3 class="text-xl font-bold text-white mb-4">Detailed Description</h3>
<div class="text-text-secondary space-y-4 leading-relaxed">
<p>
Writing high-quality content consistently is a challenge for modern businesses. NeuroGen AI bridges the gap between human creativity and machine speed. Unlike generic text generators, NeuroGen allows users to fine-tune specific parameters such as emotional resonance, sentence structure complexity, and SEO keyword density.
</p>
<p>
The platform includes specialized templates for:
</p>
<ul class="list-disc pl-5 space-y-2 text-gray-400 marker:text-primary">
<li>Social media captions (Instagram, LinkedIn, Twitter)</li>
<li>Long-form blog posts with automatic formatting</li>
<li>Email marketing sequences</li>
<li>Ad copy variants for A/B testing</li>
</ul>
<p>
Security is paramount; NeuroGen ensures that your proprietary data is never used to train public models. Enterprise-grade encryption and team collaboration features make it a suitable choice for large organizations looking to scale their content operations.
</p>
</div>
</section>
</div>
</div>
<!-- Right Column: Sidebar -->
<div class="lg:col-span-4 space-y-6">
<!-- CTA Card -->
<div class="glass-card rounded-2xl p-6 flex flex-col gap-4 shadow-xl shadow-black/20 sticky top-24">
<div class="flex items-center justify-between mb-2">
<span class="text-white font-bold text-lg">Try it now</span>
<span class="px-2 py-1 rounded bg-green-500/20 text-green-400 text-xs font-bold border border-green-500/20">ONLINE</span>
</div>
<a class="group relative flex items-center justify-center w-full overflow-hidden rounded-xl bg-gradient-to-r from-primary via-primary to-purple-500 p-[1px] focus:outline-none focus:ring-2 focus:ring-slate-400 focus:ring-offset-2 focus:ring-offset-slate-50 transition-all hover:scale-[1.02]" href="#">
<span class="absolute inset-[-1000%] animate-[spin_2s_linear_infinite] bg-[conic-gradient(from_90deg_at_50%_50%,#E2CBFF_0%,#393BB2_50%,#E2CBFF_100%)] opacity-0 group-hover:opacity-100 transition-opacity"></span>
<div class="relative flex h-12 w-full items-center justify-center rounded-xl bg-background-dark/10 group-hover:bg-transparent px-8 py-1 backdrop-blur-3xl transition-colors">
<span class="flex items-center gap-2 text-background-dark font-bold text-base uppercase tracking-wide">
Visit Website
<span class="material-symbols-outlined text-[20px] font-bold">arrow_outward</span>
</span>
</div>
<!-- Overlay for text color correction on hover/default since bg changes -->
<div class="absolute inset-0 flex items-center justify-center pointer-events-none">
<span class="flex items-center gap-2 text-white font-bold text-base uppercase tracking-wide group-hover:text-white transition-colors">
Visit Website
<span class="material-symbols-outlined text-[20px] font-bold">arrow_outward</span>
</span>
</div>
</a>
<p class="text-xs text-center text-text-secondary">
Opens in a new tab • <span class="text-white">neurogen.ai</span>
</p>
</div>
<!-- Features Box -->
<div class="rounded-2xl border border-surface-border bg-surface-dark/50 p-6">
<h3 class="text-lg font-bold text-white mb-5 flex items-center gap-2">
<span class="material-symbols-outlined text-secondary">verified</span>
Main Features
</h3>
<ul class="space-y-4">
<li class="flex items-start gap-3">
<div class="mt-0.5 rounded-full bg-primary/20 p-1">
<span class="material-symbols-outlined text-primary text-[16px] block">check</span>
</div>
<div>
<p class="text-sm font-bold text-white">Contextual Awareness</p>
<p class="text-xs text-text-secondary mt-0.5">Understands previous inputs to maintain thread continuity.</p>
</div>
</li>
<li class="flex items-start gap-3">
<div class="mt-0.5 rounded-full bg-primary/20 p-1">
<span class="material-symbols-outlined text-primary text-[16px] block">check</span>
</div>
<div>
<p class="text-sm font-bold text-white">Multi-language Support</p>
<p class="text-xs text-text-secondary mt-0.5">Generate content in over 25 languages natively.</p>
</div>
</li>
<li class="flex items-start gap-3">
<div class="mt-0.5 rounded-full bg-primary/20 p-1">
<span class="material-symbols-outlined text-primary text-[16px] block">check</span>
</div>
<div>
<p class="text-sm font-bold text-white">SEO Optimization</p>
<p class="text-xs text-text-secondary mt-0.5">Built-in keyword analyzer and suggestion tool.</p>
</div>
</li>
<li class="flex items-start gap-3">
<div class="mt-0.5 rounded-full bg-primary/20 p-1">
<span class="material-symbols-outlined text-primary text-[16px] block">check</span>
</div>
<div>
<p class="text-sm font-bold text-white">Export to CMS</p>
<p class="text-xs text-text-secondary mt-0.5">Direct integration with WordPress and Ghost.</p>
</div>
</li>
</ul>
</div>
<!-- Share / Report -->
<div class="flex gap-4">
<button class="flex-1 py-3 rounded-lg border border-surface-border bg-transparent text-text-secondary hover:text-white hover:bg-surface-border transition-colors text-sm font-medium flex items-center justify-center gap-2">
<span class="material-symbols-outlined text-[18px]">share</span>
Share
</button>
<button class="flex-1 py-3 rounded-lg border border-surface-border bg-transparent text-text-secondary hover:text-red-400 hover:border-red-400/30 hover:bg-red-400/10 transition-colors text-sm font-medium flex items-center justify-center gap-2">
<span class="material-symbols-outlined text-[18px]">flag</span>
Report
</button>
</div>
</div>
</div>
<!-- Footer Spacer -->
<div class="h-20"></div>
</main>
</body></html>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 654 KiB

View File

@@ -1,383 +0,0 @@
<!DOCTYPE html>
<html class="dark" lang="en"><head>
<meta charset="utf-8"/>
<meta content="width=device-width, initial-scale=1.0" name="viewport"/>
<title>ZJPB - AI Tool Discovery</title>
<!-- Google Fonts -->
<link href="https://fonts.googleapis.com" rel="preconnect"/>
<link crossorigin="" href="https://fonts.gstatic.com" rel="preconnect"/>
<link href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@300;400;500;600;700&amp;display=swap" rel="stylesheet"/>
<link href="https://fonts.googleapis.com/css2?family=Noto+Sans:wght@400;500;700&amp;display=swap" rel="stylesheet"/>
<!-- Material Symbols -->
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&amp;display=swap" rel="stylesheet"/>
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&amp;display=swap" rel="stylesheet"/>
<!-- Tailwind CSS -->
<script src="https://cdn.tailwindcss.com?plugins=forms,container-queries"></script>
<script id="tailwind-config">
tailwind.config = {
darkMode: "class",
theme: {
extend: {
colors: {
primary: "#25c0f4",
secondary: "#7c3aed", // Adding purple for the gradient mix
"background-light": "#f5f8f8",
"background-dark": "#111618",
"surface-dark": "#1b2427",
"border-dark": "#283539",
},
fontFamily: {
"display": ["Space Grotesk", "sans-serif"],
"body": ["Noto Sans", "sans-serif"],
},
backgroundImage: {
'gradient-tech': 'linear-gradient(135deg, #25c0f4 0%, #7c3aed 100%)',
'gradient-text': 'linear-gradient(to right, #25c0f4, #a855f7)',
}
},
},
}
</script>
<style>
body {
font-family: 'Space Grotesk', sans-serif;
}
.text-gradient {
background: linear-gradient(to right, #25c0f4, #c084fc);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
}
.card-hover:hover {
transform: translateY(-4px);
box-shadow: 0 10px 30px -10px rgba(37, 192, 244, 0.15);
border-color: #25c0f4;
}
</style>
</head>
<body class="bg-background-light dark:bg-background-dark text-slate-900 dark:text-white antialiased selection:bg-primary selection:text-black">
<div class="relative flex min-h-screen w-full flex-col overflow-x-hidden">
<!-- Background Gradient Elements for Tech Vibe -->
<div class="fixed top-0 left-0 w-full h-96 bg-primary/5 blur-[120px] rounded-full pointer-events-none -translate-y-1/2"></div>
<div class="fixed top-20 right-0 w-96 h-96 bg-secondary/10 blur-[100px] rounded-full pointer-events-none translate-x-1/3"></div>
<!-- Top Navigation -->
<header class="sticky top-0 z-50 w-full border-b border-solid border-border-dark bg-background-dark/80 backdrop-blur-md">
<div class="mx-auto flex h-16 max-w-[1440px] items-center justify-between px-6 lg:px-10">
<div class="flex items-center gap-8">
<!-- Logo -->
<a class="flex items-center gap-3 text-white hover:opacity-90 transition-opacity" href="#">
<div class="flex items-center justify-center size-8 rounded-lg bg-gradient-tech text-black">
<span class="material-symbols-outlined" style="font-size: 20px; font-weight: 700;">bolt</span>
</div>
<h2 class="text-white text-lg font-bold leading-tight tracking-tight">ZJPB</h2>
</a>
<!-- Desktop Nav -->
<nav class="hidden md:flex items-center gap-8">
<a class="text-gray-300 hover:text-primary text-sm font-medium transition-colors" href="#">Home</a>
<a class="text-gray-300 hover:text-primary text-sm font-medium transition-colors" href="#">Categories</a>
<a class="text-gray-300 hover:text-primary text-sm font-medium transition-colors" href="#">Community</a>
<a class="text-gray-300 hover:text-primary text-sm font-medium transition-colors" href="#">About</a>
</nav>
</div>
<!-- Actions -->
<div class="flex items-center gap-4">
<!-- Search -->
<div class="hidden lg:flex items-center bg-surface-dark border border-border-dark rounded-full h-10 px-4 w-64 focus-within:border-primary transition-colors">
<span class="material-symbols-outlined text-gray-400" style="font-size: 20px;">search</span>
<input class="bg-transparent border-none text-white text-sm placeholder-gray-500 focus:ring-0 w-full ml-2" placeholder="Search AI tools..." type="text"/>
</div>
<!-- Auth Buttons -->
<button class="hidden sm:flex h-9 px-4 items-center justify-center rounded-lg text-sm font-bold text-white hover:bg-white/5 transition-colors">
Log In
</button>
<button class="flex h-9 px-5 items-center justify-center rounded-lg bg-primary hover:bg-primary/90 text-background-dark text-sm font-bold transition-colors shadow-[0_0_15px_rgba(37,192,244,0.3)]">
Sign Up
</button>
<!-- Mobile Menu Toggle -->
<button class="md:hidden text-white">
<span class="material-symbols-outlined">menu</span>
</button>
</div>
</div>
</header>
<!-- Main Content -->
<main class="flex-1 flex flex-col items-center w-full px-6 lg:px-10 py-8">
<div class="w-full max-w-[1200px] flex flex-col gap-10">
<!-- Hero Section -->
<div class="flex flex-col md:flex-row items-start md:items-end justify-between gap-6 pb-6 border-b border-border-dark/50">
<div class="flex flex-col gap-2 max-w-2xl">
<h1 class="text-4xl md:text-5xl lg:text-6xl font-black tracking-tighter text-white mb-2">
ZJPB - <span class="text-gradient">焦提示词</span>
</h1>
<p class="text-gray-400 text-lg md:text-xl font-light">
发现最新最好用的AI工具和产品. Discover the best AI tools tailored for your workflow.
</p>
</div>
<button class="flex items-center gap-2 bg-surface-dark border border-border-dark hover:border-primary text-white px-5 py-2.5 rounded-lg transition-all group whitespace-nowrap">
<span class="material-symbols-outlined text-primary group-hover:scale-110 transition-transform">add_circle</span>
<span class="font-bold text-sm">Submit a Tool</span>
</button>
</div>
<!-- Filter Chips -->
<div class="w-full overflow-x-auto pb-2 scrollbar-hide">
<div class="flex gap-3 min-w-max">
<button class="flex items-center h-9 px-5 rounded-full bg-primary text-background-dark font-bold text-sm shadow-[0_0_10px_rgba(37,192,244,0.4)]">
All Tools
</button>
<button class="flex items-center gap-2 h-9 px-5 rounded-full bg-surface-dark border border-border-dark text-gray-300 hover:text-white hover:border-gray-500 font-medium text-sm transition-all">
<span class="material-symbols-outlined text-sm">chat</span> AI Chat
</button>
<button class="flex items-center gap-2 h-9 px-5 rounded-full bg-surface-dark border border-border-dark text-gray-300 hover:text-white hover:border-gray-500 font-medium text-sm transition-all">
<span class="material-symbols-outlined text-sm">image</span> Image Gen
</button>
<button class="flex items-center gap-2 h-9 px-5 rounded-full bg-surface-dark border border-border-dark text-gray-300 hover:text-white hover:border-gray-500 font-medium text-sm transition-all">
<span class="material-symbols-outlined text-sm">movie</span> Video
</button>
<button class="flex items-center gap-2 h-9 px-5 rounded-full bg-surface-dark border border-border-dark text-gray-300 hover:text-white hover:border-gray-500 font-medium text-sm transition-all">
<span class="material-symbols-outlined text-sm">edit_note</span> Writing
</button>
<button class="flex items-center gap-2 h-9 px-5 rounded-full bg-surface-dark border border-border-dark text-gray-300 hover:text-white hover:border-gray-500 font-medium text-sm transition-all">
<span class="material-symbols-outlined text-sm">code</span> Coding
</button>
<button class="flex items-center gap-2 h-9 px-5 rounded-full bg-surface-dark border border-border-dark text-gray-300 hover:text-white hover:border-gray-500 font-medium text-sm transition-all">
<span class="material-symbols-outlined text-sm">graphic_eq</span> Audio
</button>
<button class="flex items-center gap-2 h-9 px-5 rounded-full bg-surface-dark border border-border-dark text-gray-300 hover:text-white hover:border-gray-500 font-medium text-sm transition-all">
<span class="material-symbols-outlined text-sm">view_in_ar</span> 3D Assets
</button>
</div>
</div>
<!-- Tool Grid -->
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-6">
<!-- Card 1 -->
<div class="group relative flex flex-col gap-4 rounded-xl border border-border-dark bg-surface-dark p-5 card-hover transition-all duration-300 cursor-pointer overflow-hidden">
<div class="absolute top-0 left-0 w-full h-1 bg-gradient-to-r from-primary to-secondary opacity-0 group-hover:opacity-100 transition-opacity"></div>
<div class="flex items-start justify-between">
<div class="size-12 rounded-lg bg-cover bg-center shadow-lg" data-alt="ChatGPT logo icon green background" style="background-image: url('https://lh3.googleusercontent.com/aida-public/AB6AXuBStb1HsLpCc4Hlu8FzN0ecAT6pMnnu_pzF4cUUQDG6FdYLA65ua60D-3NxKDOHqkuVsgsy9ku-vUZ0_Jyc5O9Xi1NlwR5L7oC9gt_jretgJqsEeOk1dm2yo4GvB26KgmMWzpUKeCtIg1NyZDWRNJF3gfIVxF95sT29iy6tKSXsUdfVsOo-o_5kEpB3qCFKZdMo4fhOe8DVh4JnmmdZuS8z3PflupzZpru6F0QK3xL407vfIULuQ3L5NRtFdTfZs7O-elYl2FrwjJ_x');"></div>
<div class="flex items-center justify-center size-8 rounded-full bg-background-dark text-gray-400 group-hover:text-primary group-hover:bg-primary/10 transition-colors">
<span class="material-symbols-outlined text-lg">arrow_outward</span>
</div>
</div>
<div>
<h3 class="text-white text-lg font-bold leading-tight group-hover:text-primary transition-colors">ChatGPT</h3>
<p class="text-gray-400 text-sm mt-2 line-clamp-2">OpenAI's advanced conversational model capable of understanding and generating human-like text.</p>
</div>
<div class="mt-auto flex items-center justify-between pt-4 border-t border-border-dark">
<div class="flex gap-2">
<span class="px-2 py-0.5 rounded text-[10px] font-bold uppercase tracking-wider bg-white/5 text-gray-300 border border-white/10">Chat</span>
<span class="px-2 py-0.5 rounded text-[10px] font-bold uppercase tracking-wider bg-white/5 text-gray-300 border border-white/10">NLP</span>
</div>
<div class="flex items-center gap-1 text-gray-500 text-xs">
<span class="material-symbols-outlined text-[14px]">visibility</span>
<span>1.2M</span>
</div>
</div>
</div>
<!-- Card 2 -->
<div class="group relative flex flex-col gap-4 rounded-xl border border-border-dark bg-surface-dark p-5 card-hover transition-all duration-300 cursor-pointer overflow-hidden">
<div class="absolute top-0 left-0 w-full h-1 bg-gradient-to-r from-primary to-secondary opacity-0 group-hover:opacity-100 transition-opacity"></div>
<div class="flex items-start justify-between">
<div class="size-12 rounded-lg bg-cover bg-center shadow-lg" data-alt="Midjourney logo icon blue background" style="background-image: url('https://lh3.googleusercontent.com/aida-public/AB6AXuBW8nuPBGShgvw9aCK5p0mKlki3EMfhi8ZZKvLTaM-QupUQDzsBx5wkIvN_Unyt30H2WHXBw5GB4Kz8gL68peWqK99Cpo6gRM-Bk3f94xWdYji9osPrreu5UMVtu2W1Rn8IyBCfPDUu3LJnbDd_viZz4nb04tliq9O_ezo8OCYMede7GZYxIxHTrEkhNBADzC2Z7KjeF-DR7fRBf4dxHYz5rJUGakf1qyEouaAlEDWMnBymOKFkQGK19UvizQiQIUeDFMGMxgL0ng_x');"></div>
<div class="flex items-center justify-center size-8 rounded-full bg-background-dark text-gray-400 group-hover:text-primary group-hover:bg-primary/10 transition-colors">
<span class="material-symbols-outlined text-lg">arrow_outward</span>
</div>
</div>
<div>
<h3 class="text-white text-lg font-bold leading-tight group-hover:text-primary transition-colors">Midjourney</h3>
<p class="text-gray-400 text-sm mt-2 line-clamp-2">Hyper-realistic AI image generator that creates stunning visuals from text prompts.</p>
</div>
<div class="mt-auto flex items-center justify-between pt-4 border-t border-border-dark">
<div class="flex gap-2">
<span class="px-2 py-0.5 rounded text-[10px] font-bold uppercase tracking-wider bg-white/5 text-gray-300 border border-white/10">Image</span>
<span class="px-2 py-0.5 rounded text-[10px] font-bold uppercase tracking-wider bg-white/5 text-gray-300 border border-white/10">Art</span>
</div>
<div class="flex items-center gap-1 text-gray-500 text-xs">
<span class="material-symbols-outlined text-[14px]">visibility</span>
<span>850k</span>
</div>
</div>
</div>
<!-- Card 3 -->
<div class="group relative flex flex-col gap-4 rounded-xl border border-border-dark bg-surface-dark p-5 card-hover transition-all duration-300 cursor-pointer overflow-hidden">
<div class="absolute top-0 left-0 w-full h-1 bg-gradient-to-r from-primary to-secondary opacity-0 group-hover:opacity-100 transition-opacity"></div>
<div class="flex items-start justify-between">
<div class="size-12 rounded-lg bg-cover bg-center shadow-lg" data-alt="Jasper logo icon yellow background" style="background-image: url('https://lh3.googleusercontent.com/aida-public/AB6AXuAQv96KYBZ3uV5NpDJyuDzc5J-mGo3fTWcBzVv00wC062ApfHezk6XkvKI6lsad5hFopxB7Qqhbqifto971lBo_7ASfOFUvtSU-Z3omU0q4PGcmea1YR_Va7vKPBtpA1DS_uq779QNTY5oF_kjapJrp7-Nfi5tI0NfQ3kU5KOd-dwBrN2T1Md6nT1jUEeOKG3zEgmAfEYan4mQXQXpz7Ywh2ZaoFCOY0OTiJP9RAnzIYL0Tv-ywt3_r66CSTY5pyzfqqP7sUk4vegAv');"></div>
<div class="flex items-center justify-center size-8 rounded-full bg-background-dark text-gray-400 group-hover:text-primary group-hover:bg-primary/10 transition-colors">
<span class="material-symbols-outlined text-lg">arrow_outward</span>
</div>
</div>
<div>
<h3 class="text-white text-lg font-bold leading-tight group-hover:text-primary transition-colors">Jasper AI</h3>
<p class="text-gray-400 text-sm mt-2 line-clamp-2">AI copywriter for marketing content, blog posts, and social media captions.</p>
</div>
<div class="mt-auto flex items-center justify-between pt-4 border-t border-border-dark">
<div class="flex gap-2">
<span class="px-2 py-0.5 rounded text-[10px] font-bold uppercase tracking-wider bg-white/5 text-gray-300 border border-white/10">Writing</span>
<span class="px-2 py-0.5 rounded text-[10px] font-bold uppercase tracking-wider bg-white/5 text-gray-300 border border-white/10">Marketing</span>
</div>
<div class="flex items-center gap-1 text-gray-500 text-xs">
<span class="material-symbols-outlined text-[14px]">visibility</span>
<span>300k</span>
</div>
</div>
</div>
<!-- Card 4 -->
<div class="group relative flex flex-col gap-4 rounded-xl border border-border-dark bg-surface-dark p-5 card-hover transition-all duration-300 cursor-pointer overflow-hidden">
<div class="absolute top-0 left-0 w-full h-1 bg-gradient-to-r from-primary to-secondary opacity-0 group-hover:opacity-100 transition-opacity"></div>
<div class="flex items-start justify-between">
<div class="size-12 rounded-lg bg-cover bg-center shadow-lg" data-alt="Runway logo icon pink background" style="background-image: url('https://lh3.googleusercontent.com/aida-public/AB6AXuAkmxk1Ar1KxgHHM5pKMer4rOra0KUieDAXhm9nE4sQp5WjsnGYENKmbM-EN-hRjhV7OC2ha7Go-Cl90HgfNpbgt--grIpJYKoUjgvsp0juLIDgX_fN4rKrjdkezxRU8YVJBuTaM3lWZJCptm3I6isDO10xidrChOc5kWV9SQny79KEVKENXxOJXEVT3c0m16M3JnGRTRmu-6EY8XU68-On78-7SXxLyP5TESi-ooZ2wHaOwxqJUwFb-oQyQoKAkXnzXAqGwwITBiLS');"></div>
<div class="flex items-center justify-center size-8 rounded-full bg-background-dark text-gray-400 group-hover:text-primary group-hover:bg-primary/10 transition-colors">
<span class="material-symbols-outlined text-lg">arrow_outward</span>
</div>
</div>
<div>
<h3 class="text-white text-lg font-bold leading-tight group-hover:text-primary transition-colors">Runway Gen-2</h3>
<p class="text-gray-400 text-sm mt-2 line-clamp-2">Next-generation video creation tool that turns text into high-quality video clips.</p>
</div>
<div class="mt-auto flex items-center justify-between pt-4 border-t border-border-dark">
<div class="flex gap-2">
<span class="px-2 py-0.5 rounded text-[10px] font-bold uppercase tracking-wider bg-white/5 text-gray-300 border border-white/10">Video</span>
<span class="px-2 py-0.5 rounded text-[10px] font-bold uppercase tracking-wider bg-white/5 text-gray-300 border border-white/10">Gen-AI</span>
</div>
<div class="flex items-center gap-1 text-gray-500 text-xs">
<span class="material-symbols-outlined text-[14px]">visibility</span>
<span>420k</span>
</div>
</div>
</div>
<!-- Card 5 -->
<div class="group relative flex flex-col gap-4 rounded-xl border border-border-dark bg-surface-dark p-5 card-hover transition-all duration-300 cursor-pointer overflow-hidden">
<div class="absolute top-0 left-0 w-full h-1 bg-gradient-to-r from-primary to-secondary opacity-0 group-hover:opacity-100 transition-opacity"></div>
<div class="flex items-start justify-between">
<div class="size-12 rounded-lg bg-cover bg-center shadow-lg" data-alt="Copilot logo icon purple background" style="background-image: url('https://lh3.googleusercontent.com/aida-public/AB6AXuBki2piUPdnWstozizUU63ouakjjA3B4xN5uQs7rA84yX4NsYFIaNybtfVRy0bXUKfUBYdixo22jb3bbN6TlKindW5iQU0sJc3FF2GXS_CRpgLoNMLziOyf4rLArNmM2VoycHJ3DyrIsI8847xlO3BHrK8kyemNc9mBfaXyPnWsJ0hnF2lXfljCOcYNQTtY1qgzw5RfOHwA0NUnLwuRG53tU-mnJPPejbDyzW7YWdBnh9yTj3dnJJDOfzYiEx252wgf7Q6GBjTGrQnG');"></div>
<div class="flex items-center justify-center size-8 rounded-full bg-background-dark text-gray-400 group-hover:text-primary group-hover:bg-primary/10 transition-colors">
<span class="material-symbols-outlined text-lg">arrow_outward</span>
</div>
</div>
<div>
<h3 class="text-white text-lg font-bold leading-tight group-hover:text-primary transition-colors">GitHub Copilot</h3>
<p class="text-gray-400 text-sm mt-2 line-clamp-2">Your AI pair programmer that helps you write better code faster.</p>
</div>
<div class="mt-auto flex items-center justify-between pt-4 border-t border-border-dark">
<div class="flex gap-2">
<span class="px-2 py-0.5 rounded text-[10px] font-bold uppercase tracking-wider bg-white/5 text-gray-300 border border-white/10">Dev</span>
<span class="px-2 py-0.5 rounded text-[10px] font-bold uppercase tracking-wider bg-white/5 text-gray-300 border border-white/10">Coding</span>
</div>
<div class="flex items-center gap-1 text-gray-500 text-xs">
<span class="material-symbols-outlined text-[14px]">visibility</span>
<span>1.5M</span>
</div>
</div>
</div>
<!-- Card 6 -->
<div class="group relative flex flex-col gap-4 rounded-xl border border-border-dark bg-surface-dark p-5 card-hover transition-all duration-300 cursor-pointer overflow-hidden">
<div class="absolute top-0 left-0 w-full h-1 bg-gradient-to-r from-primary to-secondary opacity-0 group-hover:opacity-100 transition-opacity"></div>
<div class="flex items-start justify-between">
<div class="size-12 rounded-lg bg-cover bg-center shadow-lg" data-alt="ElevenLabs logo icon red background" style="background-image: url('https://lh3.googleusercontent.com/aida-public/AB6AXuBVqan6FEM01JlSNs0aKYTXdgzFAXn1xT_Yx0S3VszDGf5x0hsptwFAxMNtCEtJZOW65aZMncYKyPpzdAoIHLaBDk_ORHur89Redtuublqbd51Cr6sKVdMJ2VAAihNuVPsvpNBdIiLGuf40kXbe3HJrdYVNAKi27xcxBICheI4OzkFF7uJvChUyDbumqLRFUDMssCuxZIQYTeDKbTT628ZBFUN2H8u0RfVSPEyEhJVSE2NCAD4mH-dWGmsQzMWjmeZkwXJKpw1TGHFp');"></div>
<div class="flex items-center justify-center size-8 rounded-full bg-background-dark text-gray-400 group-hover:text-primary group-hover:bg-primary/10 transition-colors">
<span class="material-symbols-outlined text-lg">arrow_outward</span>
</div>
</div>
<div>
<h3 class="text-white text-lg font-bold leading-tight group-hover:text-primary transition-colors">ElevenLabs</h3>
<p class="text-gray-400 text-sm mt-2 line-clamp-2">The most realistic AI voice generator and text-to-speech software.</p>
</div>
<div class="mt-auto flex items-center justify-between pt-4 border-t border-border-dark">
<div class="flex gap-2">
<span class="px-2 py-0.5 rounded text-[10px] font-bold uppercase tracking-wider bg-white/5 text-gray-300 border border-white/10">Audio</span>
<span class="px-2 py-0.5 rounded text-[10px] font-bold uppercase tracking-wider bg-white/5 text-gray-300 border border-white/10">TTS</span>
</div>
<div class="flex items-center gap-1 text-gray-500 text-xs">
<span class="material-symbols-outlined text-[14px]">visibility</span>
<span>210k</span>
</div>
</div>
</div>
<!-- Card 7 -->
<div class="group relative flex flex-col gap-4 rounded-xl border border-border-dark bg-surface-dark p-5 card-hover transition-all duration-300 cursor-pointer overflow-hidden">
<div class="absolute top-0 left-0 w-full h-1 bg-gradient-to-r from-primary to-secondary opacity-0 group-hover:opacity-100 transition-opacity"></div>
<div class="flex items-start justify-between">
<div class="size-12 rounded-lg bg-cover bg-center shadow-lg" data-alt="Stable Diffusion logo icon teal background" style="background-image: url('https://lh3.googleusercontent.com/aida-public/AB6AXuB4zzlwlZK6dJVjV4vvFGvRmtsvd7dvQMCC1mAzFfEqA2sYnJnEdTDAF76lfVYkaKzibwuppHRIcEgbT8xaoWOBF1qdMiV3Qs3P9fEOLJ2jApPPqJyBnPXsvxFsI6FTlZ0SS_Rpblvjln_n_YLh8Wi_VWh8lCJirMM5vMCuyrNGK9Crxv3bnJzlbH34i9vmxSxSQ5uCmoAE5GPxkD23vzun9BBOKjRK-Ln7DhQ_bGhnVxkxEtM8Z-DVuGg2dNE7WnD7BEBXTOBh5XQa');"></div>
<div class="flex items-center justify-center size-8 rounded-full bg-background-dark text-gray-400 group-hover:text-primary group-hover:bg-primary/10 transition-colors">
<span class="material-symbols-outlined text-lg">arrow_outward</span>
</div>
</div>
<div>
<h3 class="text-white text-lg font-bold leading-tight group-hover:text-primary transition-colors">Stable Diffusion</h3>
<p class="text-gray-400 text-sm mt-2 line-clamp-2">Open source latent text-to-image diffusion model for image generation.</p>
</div>
<div class="mt-auto flex items-center justify-between pt-4 border-t border-border-dark">
<div class="flex gap-2">
<span class="px-2 py-0.5 rounded text-[10px] font-bold uppercase tracking-wider bg-white/5 text-gray-300 border border-white/10">Open Source</span>
<span class="px-2 py-0.5 rounded text-[10px] font-bold uppercase tracking-wider bg-white/5 text-gray-300 border border-white/10">Image</span>
</div>
<div class="flex items-center gap-1 text-gray-500 text-xs">
<span class="material-symbols-outlined text-[14px]">visibility</span>
<span>950k</span>
</div>
</div>
</div>
<!-- Card 8 -->
<div class="group relative flex flex-col gap-4 rounded-xl border border-border-dark bg-surface-dark p-5 card-hover transition-all duration-300 cursor-pointer overflow-hidden">
<div class="absolute top-0 left-0 w-full h-1 bg-gradient-to-r from-primary to-secondary opacity-0 group-hover:opacity-100 transition-opacity"></div>
<div class="flex items-start justify-between">
<div class="size-12 rounded-lg bg-cover bg-center shadow-lg" data-alt="Notion AI logo icon indigo background" style="background-image: url('https://lh3.googleusercontent.com/aida-public/AB6AXuAgnpQqxiQhRd6xj43Q0hY2EQTna5khOzvl_NxO3OJN_7_nsfC0wP1x7ZmfBEvEEOoGi7NHVx6pvOpJb7Zk7dvUQWD64Y7Nrhc0mi0wsVIB1U8OfG1wioEusBqkI6zJQQh8W0om8gi2ubnJn2McHrvANulIAVnRxn1zPcy-SAnW1rLynAdVFKurBX5qK3BFBi_knAMoL8bTL_1LTxIdhdCggw6WeCGsFgUu6vqwjq9prGC-j94Fr8GibbjDSABY36P1OoZg59Aiv1M4');"></div>
<div class="flex items-center justify-center size-8 rounded-full bg-background-dark text-gray-400 group-hover:text-primary group-hover:bg-primary/10 transition-colors">
<span class="material-symbols-outlined text-lg">arrow_outward</span>
</div>
</div>
<div>
<h3 class="text-white text-lg font-bold leading-tight group-hover:text-primary transition-colors">Notion AI</h3>
<p class="text-gray-400 text-sm mt-2 line-clamp-2">Access the limitless power of AI, right inside your Notion workspace.</p>
</div>
<div class="mt-auto flex items-center justify-between pt-4 border-t border-border-dark">
<div class="flex gap-2">
<span class="px-2 py-0.5 rounded text-[10px] font-bold uppercase tracking-wider bg-white/5 text-gray-300 border border-white/10">Productivity</span>
</div>
<div class="flex items-center gap-1 text-gray-500 text-xs">
<span class="material-symbols-outlined text-[14px]">visibility</span>
<span>600k</span>
</div>
</div>
</div>
</div>
<!-- Pagination -->
<div class="flex items-center justify-center py-4">
<nav class="flex items-center gap-2">
<a class="flex size-10 items-center justify-center rounded-full border border-border-dark text-gray-400 hover:text-white hover:border-primary transition-colors" href="#">
<span class="material-symbols-outlined text-base">chevron_left</span>
</a>
<a class="flex size-10 items-center justify-center rounded-full bg-primary text-background-dark text-sm font-bold shadow-[0_0_10px_rgba(37,192,244,0.3)]" href="#">1</a>
<a class="flex size-10 items-center justify-center rounded-full border border-transparent hover:bg-surface-dark text-gray-400 hover:text-white text-sm font-medium transition-colors" href="#">2</a>
<a class="flex size-10 items-center justify-center rounded-full border border-transparent hover:bg-surface-dark text-gray-400 hover:text-white text-sm font-medium transition-colors" href="#">3</a>
<span class="flex size-10 items-center justify-center text-gray-600">...</span>
<a class="flex size-10 items-center justify-center rounded-full border border-transparent hover:bg-surface-dark text-gray-400 hover:text-white text-sm font-medium transition-colors" href="#">12</a>
<a class="flex size-10 items-center justify-center rounded-full border border-border-dark text-gray-400 hover:text-white hover:border-primary transition-colors" href="#">
<span class="material-symbols-outlined text-base">chevron_right</span>
</a>
</nav>
</div>
</div>
</main>
<!-- Simple Footer -->
<footer class="w-full border-t border-border-dark bg-background-dark py-8 mt-auto">
<div class="mx-auto max-w-[1200px] flex flex-col md:flex-row items-center justify-between gap-6 px-6 lg:px-10">
<div class="flex items-center gap-2 text-gray-400 text-sm">
<span>© 2023 ZJPB AI Directory. All rights reserved.</span>
</div>
<div class="flex gap-6">
<a class="text-gray-500 hover:text-primary transition-colors" href="#">
<i class="fab fa-twitter text-lg"></i> <!-- Placeholder for social icon -->
<span class="text-sm">Twitter</span>
</a>
<a class="text-gray-500 hover:text-primary transition-colors" href="#">
<span class="text-sm">Discord</span>
</a>
<a class="text-gray-500 hover:text-primary transition-colors" href="#">
<span class="text-sm">Privacy Policy</span>
</a>
</div>
</div>
</footer>
</div>
</body></html>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 354 KiB

View File

@@ -0,0 +1,363 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>批量导入 - ZJPB 焦提示词</title>
<!-- Google Fonts -->
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@300;400;500;600;700&family=Noto+Sans:wght@400;500;700&display=swap" rel="stylesheet">
<!-- Google Material Symbols -->
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@20..48,100..700,0..1,-50..200" />
<!-- Bootstrap CSS -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@4.6.0/dist/css/bootstrap.min.css" rel="stylesheet">
<!-- Custom Admin Theme -->
<link href="{{ url_for('static', filename='css/admin-sidebar.css') }}" rel="stylesheet">
<link href="{{ url_for('static', filename='css/admin-actions.css') }}" rel="stylesheet">
</head>
<body class="admin-sidebar-layout">
<!-- 左侧菜单栏 -->
<aside class="admin-sidebar">
<!-- Logo -->
<div class="sidebar-logo">
<span class="material-symbols-outlined logo-icon">blur_on</span>
<span class="logo-text">ZJPB 焦提示词</span>
</div>
<!-- 主菜单 -->
<nav class="sidebar-nav">
<div class="nav-section">
<div class="nav-section-title">主菜单</div>
<ul class="nav-menu">
<li class="nav-item">
<a href="{{ url_for('admin.index') }}" class="nav-link">
<span class="material-symbols-outlined nav-icon">dashboard</span>
<span class="nav-text">控制台</span>
</a>
</li>
<li class="nav-item">
<a href="{{ url_for('site.index_view') }}" class="nav-link">
<span class="material-symbols-outlined nav-icon">public</span>
<span class="nav-text">网站管理</span>
</a>
</li>
<li class="nav-item">
<a href="{{ url_for('tag.index_view') }}" class="nav-link">
<span class="material-symbols-outlined nav-icon">label</span>
<span class="nav-text">标签管理</span>
</a>
</li>
<li class="nav-item">
<a href="{{ url_for('news.index_view') }}" class="nav-link">
<span class="material-symbols-outlined nav-icon">newspaper</span>
<span class="nav-text">新闻管理</span>
</a>
</li>
<li class="nav-item">
<a href="{{ url_for('admin_users.index_view') }}" class="nav-link">
<span class="material-symbols-outlined nav-icon">admin_panel_settings</span>
<span class="nav-text">管理员</span>
</a>
</li>
</ul>
</div>
<!-- 系统菜单 -->
<div class="nav-section">
<div class="nav-section-title">系统</div>
<ul class="nav-menu">
<li class="nav-item active">
<a href="{{ url_for('batch_import') }}" class="nav-link">
<span class="material-symbols-outlined nav-icon">upload_file</span>
<span class="nav-text">批量导入</span>
</a>
</li>
<li class="nav-item">
<a href="{{ url_for('index') }}" class="nav-link" target="_blank">
<span class="material-symbols-outlined nav-icon">open_in_new</span>
<span class="nav-text">查看网站</span>
</a>
</li>
<li class="nav-item">
<a href="{{ url_for('admin_logout') }}" class="nav-link">
<span class="material-symbols-outlined nav-icon">logout</span>
<span class="nav-text">退出登录</span>
</a>
</li>
</ul>
</div>
</nav>
<!-- 用户信息 -->
<div class="sidebar-user">
<div class="user-avatar">
<span class="material-symbols-outlined">account_circle</span>
</div>
<div class="user-info">
<div class="user-name">{{ current_user.username }}</div>
<div class="user-email">{{ current_user.email or 'admin@zjpb.com' }}</div>
</div>
</div>
</aside>
<!-- 右侧主内容区 -->
<div class="admin-main">
<!-- 顶部导航栏 -->
<header class="admin-header">
<div class="header-breadcrumb">
<a href="{{ url_for('admin.index') }}" class="breadcrumb-link">控制台</a>
<span class="breadcrumb-separator">/</span>
<span class="breadcrumb-current">批量导入</span>
</div>
<div class="header-actions">
<div class="search-box">
<span class="material-symbols-outlined search-icon">search</span>
<input type="text" placeholder="全局搜索..." class="search-input">
</div>
<button class="header-btn">
<span class="material-symbols-outlined">notifications</span>
</button>
<button class="header-btn">
<span class="material-symbols-outlined">settings</span>
</button>
</div>
</header>
<!-- 页面内容 -->
<main class="admin-content">
<div class="page-header">
<div>
<h1 class="page-title">批量导入网站</h1>
<p class="page-description">支持通过URL列表或Chrome书签文件批量导入网站</p>
</div>
</div>
{% with messages = get_flashed_messages(with_categories=true) %}
{% if messages %}
{% for category, message in messages %}
<div class="alert alert-{{ 'success' if category == 'success' else 'danger' }} alert-dismissible fade show" role="alert">
{{ message }}
<button type="button" class="close" data-dismiss="alert">
<span>&times;</span>
</button>
</div>
{% endfor %}
{% endif %}
{% endwith %}
<div class="card">
<div class="card-body">
<ul class="nav nav-tabs mb-4" role="tablist">
<li class="nav-item">
<a class="nav-link active" data-toggle="tab" href="#url-list">URL列表导入</a>
</li>
<li class="nav-item">
<a class="nav-link" data-toggle="tab" href="#bookmark-file">Chrome书签导入</a>
</li>
</ul>
<div class="tab-content">
<!-- URL列表导入 -->
<div class="tab-pane fade show active" id="url-list">
<form method="POST" action="{{ url_for('batch_import') }}">
<input type="hidden" name="import_type" value="url_list">
<div class="form-group">
<label for="url-textarea">网站URL列表</label>
<textarea class="form-control" id="url-textarea" name="url_list" rows="10"
placeholder="每行一个URL例如&#10;https://www.example.com&#10;https://www.google.com&#10;https://github.com"></textarea>
<small class="form-text text-muted">
每行输入一个网站URL系统将自动抓取网站名称、描述和Logo
</small>
</div>
<div class="form-group">
<div class="custom-control custom-checkbox">
<input type="checkbox" class="custom-control-input" id="auto-activate" name="auto_activate" checked>
<label class="custom-control-label" for="auto-activate">自动启用导入的网站</label>
</div>
</div>
<button type="submit" class="btn btn-primary">
<span class="material-symbols-outlined" style="font-size: 18px; vertical-align: middle;">upload_file</span>
开始导入
</button>
</form>
</div>
<!-- Chrome书签导入 -->
<div class="tab-pane fade" id="bookmark-file">
<form method="POST" action="{{ url_for('batch_import') }}" enctype="multipart/form-data">
<input type="hidden" name="import_type" value="bookmark_file">
<div class="alert alert-info">
<strong>如何导出Chrome书签</strong>
<ol class="mb-0 mt-2">
<li>打开Chrome浏览器</li>
<li><kbd>Ctrl + Shift + O</kbd> 打开书签管理器</li>
<li>点击右上角的 <strong></strong> 菜单</li>
<li>选择 <strong>导出书签</strong></li>
<li>保存为HTML文件</li>
</ol>
</div>
<div class="form-group">
<label for="bookmark-file-input">选择书签文件</label>
<div class="custom-file">
<input type="file" class="custom-file-input" id="bookmark-file-input"
name="bookmark_file" accept=".html,.htm" required>
<label class="custom-file-label" for="bookmark-file-input">选择文件...</label>
</div>
<small class="form-text text-muted">
仅支持HTML格式的Chrome书签导出文件
</small>
</div>
<div class="form-group">
<label for="folder-filter">筛选文件夹(可选)</label>
<input type="text" class="form-control" id="folder-filter" name="folder_filter"
placeholder="例如AI工具">
<small class="form-text text-muted">
留空则导入所有书签,填写文件夹名称则只导入该文件夹下的书签
</small>
</div>
<div class="form-group">
<div class="custom-control custom-checkbox">
<input type="checkbox" class="custom-control-input" id="auto-activate-2" name="auto_activate" checked>
<label class="custom-control-label" for="auto-activate-2">自动启用导入的网站</label>
</div>
</div>
<button type="submit" class="btn btn-primary">
<span class="material-symbols-outlined" style="font-size: 18px; vertical-align: middle;">upload_file</span>
开始导入
</button>
</form>
</div>
</div>
</div>
</div>
{% if results %}
<div class="card mt-4">
<div class="card-header">
<h5 class="mb-0">导入结果</h5>
</div>
<div class="card-body">
<div class="alert alert-success">
<strong>导入完成!</strong>
成功: {{ results.success_count }},
失败: {{ results.failed_count }},
总计: {{ results.total_count }}
</div>
{% if results.success_list %}
<h6>成功导入 ({{ results.success_count }})</h6>
<ul class="list-group mb-3">
{% for item in results.success_list %}
<li class="list-group-item">
<span class="material-symbols-outlined text-success" style="font-size: 18px; vertical-align: middle;">check_circle</span>
<strong>{{ item.name }}</strong> - {{ item.url }}
</li>
{% endfor %}
</ul>
{% endif %}
{% if results.failed_list %}
<h6 class="text-danger">导入失败 ({{ results.failed_count }})</h6>
<div class="alert alert-warning">
<small><strong>提示:</strong>失败的URL不会影响其他URL的导入您可以稍后手动添加这些网站。</small>
</div>
<div class="table-responsive">
<table class="table table-sm table-hover">
<thead class="thead-light">
<tr>
<th style="width: 40px;">#</th>
<th style="width: 30%;">网站名称</th>
<th style="width: 35%;">URL</th>
<th style="width: 35%;">失败原因</th>
</tr>
</thead>
<tbody>
{% for item in results.failed_list %}
<tr>
<td>
<span class="material-symbols-outlined text-danger" style="font-size: 20px;">cancel</span>
</td>
<td>
<strong>{{ item.name or '未知' }}</strong>
</td>
<td>
<small class="text-muted" style="word-break: break-all;">{{ item.url }}</small>
</td>
<td>
<span class="badge badge-danger">{{ item.error }}</span>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% endif %}
</div>
</div>
{% endif %}
</main>
</div>
<!-- Bootstrap JS -->
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@4.6.0/dist/js/bootstrap.bundle.min.js"></script>
<style>
.page-title {
font-size: 24px;
font-weight: 600;
margin-bottom: 8px;
}
.page-description {
color: #666;
margin-bottom: 24px;
}
.card {
border-radius: 8px;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
border: 1px solid #e0e0e0;
}
.card-header {
background: #f8f9fa;
border-bottom: 1px solid #dee2e6;
}
kbd {
background-color: #f7f7f7;
border: 1px solid #ccc;
border-radius: 3px;
box-shadow: 0 1px 0 rgba(0,0,0,0.2);
color: #333;
display: inline-block;
font-family: monospace;
font-size: 12px;
padding: 2px 6px;
}
</style>
<script>
// 文件选择器显示文件名
document.querySelector('.custom-file-input').addEventListener('change', function(e) {
var fileName = e.target.files[0].name;
var label = e.target.nextElementSibling;
label.textContent = fileName;
});
</script>
</body>
</html>

View File

@@ -8,10 +8,14 @@
<link href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@300;400;500;600;700&family=Noto+Sans:wght@400;500;700&display=swap" rel="stylesheet">
<!-- Custom Admin Theme -->
<link href="{{ url_for('static', filename='css/admin-theme.css') }}" rel="stylesheet">
<style>
/* 强制应用亮色主题到body */
body {
background: #F3F3F3 !important;
color: #000000 !important;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, 'Noto Sans', sans-serif !important;
}
</style>
{% endblock %}
{% block body %}
<body class="admin-theme">
{{ super() }}
</body>
{% endblock %}
{% block body_class %}admin-theme{% endblock %}

239
templates/admin/index.html Normal file
View File

@@ -0,0 +1,239 @@
{% extends 'admin/master.html' %}
{% block body %}
<div class="dashboard-container">
<div class="row">
<!-- 统计卡片 -->
<div class="col-md-3 mb-4">
<div class="stat-card">
<div class="stat-icon" style="background: rgba(0, 82, 217, 0.1); color: #0052D9;">
<span class="material-symbols-outlined">public</span>
</div>
<div class="stat-info">
<div class="stat-value">{{ stats.sites_count or 0 }}</div>
<div class="stat-label">AI工具总数</div>
</div>
</div>
</div>
<div class="col-md-3 mb-4">
<div class="stat-card">
<div class="stat-icon" style="background: rgba(0, 168, 112, 0.1); color: #00A870;">
<span class="material-symbols-outlined">label</span>
</div>
<div class="stat-info">
<div class="stat-value">{{ stats.tags_count or 0 }}</div>
<div class="stat-label">标签分类</div>
</div>
</div>
</div>
<div class="col-md-3 mb-4">
<div class="stat-card">
<div class="stat-icon" style="background: rgba(227, 115, 24, 0.1); color: #E37318;">
<span class="material-symbols-outlined">newspaper</span>
</div>
<div class="stat-info">
<div class="stat-value">{{ stats.news_count or 0 }}</div>
<div class="stat-label">新闻动态</div>
</div>
</div>
</div>
<div class="col-md-3 mb-4">
<div class="stat-card">
<div class="stat-icon" style="background: rgba(213, 73, 65, 0.1); color: #D54941;">
<span class="material-symbols-outlined">visibility</span>
</div>
<div class="stat-info">
<div class="stat-value">{{ stats.total_views or 0 }}</div>
<div class="stat-label">总浏览量</div>
</div>
</div>
</div>
</div>
<!-- 快捷操作 -->
<div class="row">
<div class="col-md-12 mb-4">
<div class="card">
<div class="card-header">
<h5 class="mb-0">快捷操作</h5>
</div>
<div class="card-body">
<div class="quick-actions">
<a href="{{ url_for('site.create_view') }}" class="quick-action-btn">
<span class="material-symbols-outlined">add_circle</span>
<span>添加新工具</span>
</a>
<a href="{{ url_for('tag.index_view') }}" class="quick-action-btn">
<span class="material-symbols-outlined">label</span>
<span>管理标签</span>
</a>
<a href="{{ url_for('news.create_view') }}" class="quick-action-btn">
<span class="material-symbols-outlined">post_add</span>
<span>发布新闻</span>
</a>
<a href="{{ url_for('index') }}" class="quick-action-btn" target="_blank">
<span class="material-symbols-outlined">open_in_new</span>
<span>查看网站</span>
</a>
</div>
</div>
</div>
</div>
</div>
<!-- 最近添加的工具 -->
<div class="row">
<div class="col-md-12">
<div class="card">
<div class="card-header">
<h5 class="mb-0">最近添加的工具</h5>
</div>
<div class="card-body">
{% if recent_sites %}
<div class="table-responsive">
<table class="table">
<thead>
<tr>
<th>名称</th>
<th>URL</th>
<th>浏览量</th>
<th>状态</th>
<th>添加时间</th>
</tr>
</thead>
<tbody>
{% for site in recent_sites %}
<tr>
<td>
<div class="d-flex align-items-center">
{% if site.logo %}
<img src="{{ site.logo }}" alt="{{ site.name }}" style="width: 32px; height: 32px; border-radius: 4px; margin-right: 12px; object-fit: cover;">
{% endif %}
<strong>{{ site.name }}</strong>
</div>
</td>
<td>
<a href="{{ site.url }}" target="_blank" style="color: #0052D9; text-decoration: none;">
{{ site.url[:50] }}...
</a>
</td>
<td>{{ site.view_count }}</td>
<td>
{% if site.is_active %}
<span class="badge badge-success">已启用</span>
{% else %}
<span class="badge badge-secondary">已禁用</span>
{% endif %}
</td>
<td>{{ site.created_at.strftime('%Y-%m-%d %H:%M') }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% else %}
<p class="text-muted text-center mb-0">暂无数据</p>
{% endif %}
</div>
</div>
</div>
</div>
</div>
<style>
.dashboard-container {
max-width: 1400px;
}
.stat-card {
background: white;
border: 1px solid #DCDFE6;
border-radius: 6px;
padding: 20px;
display: flex;
align-items: center;
gap: 16px;
box-shadow: 0 1px 4px rgba(0, 0, 0, .05);
}
.stat-icon {
width: 56px;
height: 56px;
border-radius: 8px;
display: flex;
align-items: center;
justify-content: center;
}
.stat-icon .material-symbols-outlined {
font-size: 28px;
}
.stat-info {
flex: 1;
}
.stat-value {
font-size: 28px;
font-weight: 600;
color: #000000;
line-height: 1;
margin-bottom: 4px;
}
.stat-label {
font-size: 14px;
color: #606266;
}
.quick-actions {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
gap: 12px;
}
.quick-action-btn {
display: flex;
align-items: center;
gap: 12px;
padding: 16px;
background: #F5F7FA;
border: 1px solid #DCDFE6;
border-radius: 6px;
color: #000000;
text-decoration: none;
transition: all 0.2s;
}
.quick-action-btn:hover {
background: #ECF2FE;
border-color: #0052D9;
color: #0052D9;
text-decoration: none;
transform: translateY(-2px);
}
.quick-action-btn .material-symbols-outlined {
font-size: 24px;
}
.badge {
padding: 4px 8px;
font-size: 12px;
border-radius: 4px;
}
.badge-success {
background: rgba(0, 168, 112, 0.1);
color: #00A870;
}
.badge-secondary {
background: #F5F5F5;
color: #606266;
}
</style>
{% endblock %}

176
templates/admin/master.html Normal file
View File

@@ -0,0 +1,176 @@
{% import 'admin/layout.html' as layout with context -%}
{% import 'admin/static.html' as admin_static with context %}
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{% block title %}{% if admin_view.category %}{{ admin_view.category }} - {% endif %}{{ admin_view.name }} - {{ admin_view.admin.name }}{% endblock %}</title>
<!-- Google Fonts -->
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@300;400;500;600;700&family=Noto+Sans:wght@400;500;700&display=swap" rel="stylesheet">
<!-- Google Material Symbols -->
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@20..48,100..700,0..1,-50..200" />
<!-- Bootstrap CSS -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@4.6.0/dist/css/bootstrap.min.css" rel="stylesheet">
<!-- Custom Admin Theme -->
<link href="{{ url_for('static', filename='css/admin-sidebar.css') }}" rel="stylesheet">
<link href="{{ url_for('static', filename='css/admin-actions.css') }}" rel="stylesheet">
<!-- 暂时禁用中文化CSS等待精确调整 -->
<!-- <link href="{{ url_for('static', filename='css/admin-i18n.css') }}" rel="stylesheet"> -->
{% block head_css %}{% endblock %}
</head>
<body class="admin-sidebar-layout">
<!-- 左侧菜单栏 -->
<aside class="admin-sidebar">
<!-- Logo -->
<div class="sidebar-logo">
<span class="material-symbols-outlined logo-icon">blur_on</span>
<span class="logo-text">ZJPB 焦提示词</span>
</div>
<!-- 主菜单 -->
<nav class="sidebar-nav">
<div class="nav-section">
<div class="nav-section-title">主菜单</div>
<ul class="nav-menu">
{% set active_category = admin_view.category if admin_view.category else '' %}
{% set active_name = admin_view.name if admin_view.name else '' %}
{% for item in admin_view.admin._menu %}
{% if item.is_category() %}
{# 分类菜单 #}
{% for child in item.get_children() %}
{% set is_active = (child.name == active_name) %}
<li class="nav-item {% if is_active %}active{% endif %}">
<a href="{{ child.get_url() }}" class="nav-link">
<span class="material-symbols-outlined nav-icon">
{% if '网站' in child.name %}public
{% elif '标签' in child.name %}label
{% elif '新闻' in child.name %}newspaper
{% elif '管理员' in child.name %}admin_panel_settings
{% else %}circle{% endif %}
</span>
<span class="nav-text">{{ child.name }}</span>
</a>
</li>
{% endfor %}
{% else %}
{# 单独菜单项 #}
{% set is_active = (item.name == active_name) %}
<li class="nav-item {% if is_active %}active{% endif %}">
<a href="{{ item.get_url() }}" class="nav-link">
<span class="material-symbols-outlined nav-icon">dashboard</span>
<span class="nav-text">{{ item.name }}</span>
</a>
</li>
{% endif %}
{% endfor %}
</ul>
</div>
<!-- 系统菜单 -->
<div class="nav-section">
<div class="nav-section-title">系统</div>
<ul class="nav-menu">
<li class="nav-item">
<a href="{{ url_for('batch_import') }}" class="nav-link">
<span class="material-symbols-outlined nav-icon">upload_file</span>
<span class="nav-text">批量导入</span>
</a>
</li>
<li class="nav-item">
<a href="{{ url_for('index') }}" class="nav-link" target="_blank">
<span class="material-symbols-outlined nav-icon">open_in_new</span>
<span class="nav-text">查看网站</span>
</a>
</li>
<li class="nav-item">
<a href="{{ url_for('admin_logout') }}" class="nav-link">
<span class="material-symbols-outlined nav-icon">logout</span>
<span class="nav-text">退出登录</span>
</a>
</li>
</ul>
</div>
</nav>
<!-- 用户信息 -->
<div class="sidebar-user">
<div class="user-avatar">
<span class="material-symbols-outlined">account_circle</span>
</div>
<div class="user-info">
<div class="user-name">{{ current_user.username }}</div>
<div class="user-email">{{ current_user.email or 'admin@zjpb.com' }}</div>
</div>
</div>
</aside>
<!-- 右侧主内容区 -->
<div class="admin-main">
<!-- 顶部导航栏 -->
<header class="admin-header">
<div class="header-breadcrumb">
<a href="{{ url_for('admin.index') }}" class="breadcrumb-link">控制台</a>
<span class="breadcrumb-separator">/</span>
<span class="breadcrumb-current">{{ admin_view.name }}</span>
</div>
<div class="header-actions">
<div class="search-box">
<span class="material-symbols-outlined search-icon">search</span>
<input type="text" placeholder="全局搜索..." class="search-input">
</div>
<button class="header-btn">
<span class="material-symbols-outlined">notifications</span>
</button>
<button class="header-btn">
<span class="material-symbols-outlined">settings</span>
</button>
</div>
</header>
<!-- 页面内容 -->
<main class="admin-content">
{% block page_body %}
<div class="page-header">
<div>
<h1 class="page-title">{% block brand %}{{ admin_view.name }}{% endblock %}</h1>
{% if admin_view.name == '网站管理' %}
<p class="page-description">管理和维护AI工具导航平台的所有工具。</p>
{% elif admin_view.name == '标签管理' %}
<p class="page-description">管理工具分类标签,优化内容组织。</p>
{% elif admin_view.name == '新闻管理' %}
<p class="page-description">管理工具相关新闻和更新动态。</p>
{% elif admin_view.name == '管理员' %}
<p class="page-description">管理后台管理员账号和权限。</p>
{% else %}
<p class="page-description">{{ admin_view.category or '' }}</p>
{% endif %}
</div>
{% block page_actions %}{% endblock %}
</div>
{% block messages %}
{{ layout.messages() }}
{% endblock %}
{% block body %}{% endblock %}
{% endblock %}
</main>
</div>
<!-- Bootstrap JS -->
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@4.6.0/dist/js/bootstrap.bundle.min.js"></script>
{% block tail_js %}{% endblock %}
{% block tail %}{% endblock %}
</body>
</html>

View File

@@ -7,30 +7,40 @@
margin-top: 10px;
margin-bottom: 15px;
}
.generate-tags-btn {
margin-top: 10px;
margin-bottom: 15px;
}
.fetch-status {
margin-top: 10px;
padding: 10px;
border-radius: 8px;
display: none;
}
.fetch-status.success {
.tags-status {
margin-top: 10px;
padding: 10px;
border-radius: 8px;
display: none;
}
.fetch-status.success, .tags-status.success {
background-color: rgba(34, 197, 94, 0.1);
border: 1px solid rgba(34, 197, 94, 0.3);
color: #4ade80;
}
.fetch-status.error {
.fetch-status.error, .tags-status.error {
background-color: rgba(239, 68, 68, 0.1);
border: 1px solid rgba(239, 68, 68, 0.3);
color: #f87171;
}
.auto-fetch-btn .loading-icon {
.auto-fetch-btn .loading-icon, .generate-tags-btn .loading-icon {
display: none;
}
.auto-fetch-btn.loading .loading-icon {
.auto-fetch-btn.loading .loading-icon, .generate-tags-btn.loading .loading-icon {
display: inline-block;
animation: spin 1s linear infinite;
}
.auto-fetch-btn.loading .normal-icon {
.auto-fetch-btn.loading .normal-icon, .generate-tags-btn.loading .normal-icon {
display: none;
}
@keyframes spin {
@@ -119,6 +129,75 @@ document.addEventListener('DOMContentLoaded', function() {
statusDiv.style.display = 'block';
}
}
// 在标签字段后添加"AI生成标签"按钮
const tagsField = document.querySelector('select[name="tags"]');
if (tagsField) {
const generateBtn = document.createElement('button');
generateBtn.type = 'button';
generateBtn.className = 'btn btn-success generate-tags-btn';
generateBtn.innerHTML = '<span class="normal-icon">✨</span><span class="loading-icon">↻</span> AI生成标签';
const tagsStatusDiv = document.createElement('div');
tagsStatusDiv.className = 'tags-status';
tagsField.parentNode.appendChild(generateBtn);
tagsField.parentNode.appendChild(tagsStatusDiv);
generateBtn.addEventListener('click', function() {
const nameField = document.querySelector('input[name="name"]');
const descriptionField = document.querySelector('textarea[name="description"]');
const name = nameField ? nameField.value.trim() : '';
const description = descriptionField ? descriptionField.value.trim() : '';
if (!name || !description) {
showTagsStatus('请先填写网站名称和描述', 'error');
return;
}
// 显示加载状态
generateBtn.disabled = true;
generateBtn.classList.add('loading');
tagsStatusDiv.style.display = 'none';
// 调用API生成标签
fetch('/api/generate-tags', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
name: name,
description: description
})
})
.then(response => response.json())
.then(data => {
if (data.success && data.tags && data.tags.length > 0) {
// 显示生成的标签
const tagsText = data.tags.join(', ');
showTagsStatus('✓ AI生成的标签建议: ' + tagsText + '\n请在标签字段中手动选择或创建这些标签', 'success');
} else {
showTagsStatus('✗ ' + (data.message || '标签生成失败'), 'error');
}
})
.catch(error => {
console.error('Error:', error);
showTagsStatus('✗ 网络请求失败,请重试', 'error');
})
.finally(() => {
generateBtn.disabled = false;
generateBtn.classList.remove('loading');
});
});
function showTagsStatus(message, type) {
tagsStatusDiv.textContent = message;
tagsStatusDiv.className = 'tags-status ' + type;
tagsStatusDiv.style.display = 'block';
}
}
});
</script>
{% endblock %}

View File

@@ -7,30 +7,40 @@
margin-top: 10px;
margin-bottom: 15px;
}
.generate-tags-btn {
margin-top: 10px;
margin-bottom: 15px;
}
.fetch-status {
margin-top: 10px;
padding: 10px;
border-radius: 8px;
display: none;
}
.fetch-status.success {
.tags-status {
margin-top: 10px;
padding: 10px;
border-radius: 8px;
display: none;
}
.fetch-status.success, .tags-status.success {
background-color: rgba(34, 197, 94, 0.1);
border: 1px solid rgba(34, 197, 94, 0.3);
color: #4ade80;
}
.fetch-status.error {
.fetch-status.error, .tags-status.error {
background-color: rgba(239, 68, 68, 0.1);
border: 1px solid rgba(239, 68, 68, 0.3);
color: #f87171;
}
.auto-fetch-btn .loading-icon {
.auto-fetch-btn .loading-icon, .generate-tags-btn .loading-icon {
display: none;
}
.auto-fetch-btn.loading .loading-icon {
.auto-fetch-btn.loading .loading-icon, .generate-tags-btn.loading .loading-icon {
display: inline-block;
animation: spin 1s linear infinite;
}
.auto-fetch-btn.loading .normal-icon {
.auto-fetch-btn.loading .normal-icon, .generate-tags-btn.loading .normal-icon {
display: none;
}
@keyframes spin {
@@ -119,6 +129,75 @@ document.addEventListener('DOMContentLoaded', function() {
statusDiv.style.display = 'block';
}
}
// 在标签字段后添加"AI生成标签"按钮
const tagsField = document.querySelector('select[name="tags"]');
if (tagsField) {
const generateBtn = document.createElement('button');
generateBtn.type = 'button';
generateBtn.className = 'btn btn-success generate-tags-btn';
generateBtn.innerHTML = '<span class="normal-icon">✨</span><span class="loading-icon">↻</span> AI生成标签';
const tagsStatusDiv = document.createElement('div');
tagsStatusDiv.className = 'tags-status';
tagsField.parentNode.appendChild(generateBtn);
tagsField.parentNode.appendChild(tagsStatusDiv);
generateBtn.addEventListener('click', function() {
const nameField = document.querySelector('input[name="name"]');
const descriptionField = document.querySelector('textarea[name="description"]');
const name = nameField ? nameField.value.trim() : '';
const description = descriptionField ? descriptionField.value.trim() : '';
if (!name || !description) {
showTagsStatus('请先填写网站名称和描述', 'error');
return;
}
// 显示加载状态
generateBtn.disabled = true;
generateBtn.classList.add('loading');
tagsStatusDiv.style.display = 'none';
// 调用API生成标签
fetch('/api/generate-tags', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
name: name,
description: description
})
})
.then(response => response.json())
.then(data => {
if (data.success && data.tags && data.tags.length > 0) {
// 显示生成的标签
const tagsText = data.tags.join(', ');
showTagsStatus('✓ AI生成的标签建议: ' + tagsText + '\n请在标签字段中手动选择或创建这些标签', 'success');
} else {
showTagsStatus('✗ ' + (data.message || '标签生成失败'), 'error');
}
})
.catch(error => {
console.error('Error:', error);
showTagsStatus('✗ 网络请求失败,请重试', 'error');
})
.finally(() => {
generateBtn.disabled = false;
generateBtn.classList.remove('loading');
});
});
function showTagsStatus(message, type) {
tagsStatusDiv.textContent = message;
tagsStatusDiv.className = 'tags-status ' + type;
tagsStatusDiv.style.display = 'block';
}
}
});
</script>
{% endblock %}

View File

@@ -56,6 +56,12 @@
box-shadow: 0 10px 30px -10px rgba(37, 192, 244, 0.15);
border-color: #25c0f4;
}
.line-clamp-2 {
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
}
</style>
{% block extra_css %}{% endblock %}
</head>

278
templates/base_new.html Normal file
View File

@@ -0,0 +1,278 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{% block title %}ZJPB - 焦提示词 | AI工具导航{% endblock %}</title>
<!-- Google Fonts -->
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800&display=swap" rel="stylesheet">
<!-- Material Symbols -->
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@20..48,100..700,0..1,-50..200" />
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
:root {
--primary-blue: #0ea5e9;
--primary-dark: #0284c7;
--text-primary: #1e293b;
--text-secondary: #64748b;
--text-muted: #94a3b8;
--bg-page: #f8fafc;
--bg-white: #ffffff;
--border-color: #e2e8f0;
--shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
--shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
--shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.1);
--radius-sm: 6px;
--radius-md: 8px;
--radius-lg: 12px;
}
body {
font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
background: var(--bg-page);
color: var(--text-primary);
line-height: 1.6;
}
/* 导航栏 */
.navbar {
background: var(--bg-white);
border-bottom: 1px solid var(--border-color);
padding: 0;
position: sticky;
top: 0;
z-index: 1000;
box-shadow: var(--shadow-sm);
}
.nav-container {
max-width: 1280px;
margin: 0 auto;
padding: 0 24px;
display: flex;
align-items: center;
justify-content: space-between;
height: 64px;
}
.nav-left {
display: flex;
align-items: center;
gap: 40px;
}
.nav-logo {
display: flex;
align-items: center;
gap: 8px;
text-decoration: none;
color: var(--text-primary);
font-weight: 700;
font-size: 18px;
}
.nav-logo-icon {
width: 32px;
height: 32px;
background: linear-gradient(135deg, #0ea5e9 0%, #0284c7 100%);
border-radius: 8px;
display: flex;
align-items: center;
justify-content: center;
color: white;
}
.nav-links {
display: flex;
align-items: center;
gap: 32px;
list-style: none;
}
.nav-links a {
color: var(--text-secondary);
text-decoration: none;
font-size: 14px;
font-weight: 500;
transition: color 0.2s;
}
.nav-links a:hover {
color: var(--text-primary);
}
.nav-right {
display: flex;
align-items: center;
gap: 16px;
}
.search-box {
position: relative;
display: flex;
align-items: center;
}
.search-box input {
width: 280px;
padding: 8px 12px 8px 36px;
border: 1px solid var(--border-color);
border-radius: var(--radius-sm);
font-size: 14px;
background: var(--bg-page);
transition: all 0.2s;
}
.search-box input:focus {
outline: none;
border-color: var(--primary-blue);
background: var(--bg-white);
}
.search-box .material-symbols-outlined {
position: absolute;
left: 10px;
top: 50%;
transform: translateY(-50%);
font-size: 20px;
color: var(--text-muted);
}
.btn {
padding: 8px 16px;
border-radius: var(--radius-sm);
font-size: 14px;
font-weight: 500;
text-decoration: none;
cursor: pointer;
transition: all 0.2s;
border: none;
display: inline-flex;
align-items: center;
gap: 6px;
}
.btn-secondary {
background: transparent;
color: var(--text-secondary);
}
.btn-secondary:hover {
color: var(--text-primary);
}
.btn-primary {
background: var(--primary-blue);
color: white;
}
.btn-primary:hover {
background: var(--primary-dark);
}
/* 主内容区 */
.main-content {
max-width: 1280px;
margin: 0 auto;
padding: 0 24px;
}
/* 页脚 */
.footer {
background: var(--bg-white);
border-top: 1px solid var(--border-color);
margin-top: 80px;
padding: 32px 0;
}
.footer-container {
max-width: 1280px;
margin: 0 auto;
padding: 0 24px;
display: flex;
align-items: center;
justify-content: space-between;
}
.footer-text {
color: var(--text-secondary);
font-size: 14px;
}
.footer-links {
display: flex;
gap: 24px;
}
.footer-links a {
color: var(--text-secondary);
text-decoration: none;
font-size: 14px;
transition: color 0.2s;
}
.footer-links a:hover {
color: var(--text-primary);
}
{% block extra_css %}{% endblock %}
</style>
</head>
<body>
<!-- 导航栏 -->
<nav class="navbar">
<div class="nav-container">
<div class="nav-left">
<a href="/" class="nav-logo">
<div class="nav-logo-icon">
<span class="material-symbols-outlined" style="font-size: 20px;">blur_on</span>
</div>
<span>ZJPB</span>
</a>
<ul class="nav-links">
<li><a href="/">Home</a></li>
<li><a href="/#categories">Categories</a></li>
<li><a href="/admin/login">Admin</a></li>
</ul>
</div>
<div class="nav-right">
<form action="/" method="get" class="search-box">
<span class="material-symbols-outlined">search</span>
<input type="text" name="q" placeholder="搜索 AI 工具..." value="{{ search_query or '' }}">
</form>
<a href="/admin/login" class="btn btn-secondary">登录</a>
<a href="/admin/login" class="btn btn-primary">注册</a>
</div>
</div>
</nav>
<!-- 主内容 -->
{% block content %}{% endblock %}
<!-- 页脚 -->
<footer class="footer">
<div class="footer-container">
<div class="footer-text">
© 2023 ZJPB AI Directory. All rights reserved.
</div>
<div class="footer-links">
<a href="#">Twitter</a>
<a href="#">Discord</a>
<a href="#">Privacy Policy</a>
</div>
</div>
</footer>
{% block extra_js %}{% endblock %}
</body>
</html>

View File

@@ -106,6 +106,82 @@
</div>
</section>
{% endif %}
<!-- Related News -->
{% if news_list %}
<section>
<h3 class="text-xl font-bold text-white mb-4 flex items-center gap-2">
<span class="material-symbols-outlined text-secondary\">newspaper</span>
相关新闻
</h3>
<div class="grid gap-4">
{% for news in news_list %}
<a class="group block p-5 rounded-xl border border-border-dark bg-surface-dark/40 hover:bg-border-dark/60 hover:border-primary/30 transition-all" href="{{ news.url if news.url else '#' }}" {{ 'target=\"_blank\"' if news.url else '' }}>
<div class="flex justify-between items-start mb-2">
{% if news.news_type == 'Product Update' %}
<span class="inline-flex items-center px-2 py-0.5 rounded text-[10px] font-bold uppercase tracking-wider bg-primary/10 text-primary">{{ news.news_type }}</span>
{% elif news.news_type == 'Industry News' %}
<span class="inline-flex items-center px-2 py-0.5 rounded text-[10px] font-bold uppercase tracking-wider bg-purple-500/10 text-purple-400">{{ news.news_type }}</span>
{% else %}
<span class="inline-flex items-center px-2 py-0.5 rounded text-[10px] font-bold uppercase tracking-wider bg-gray-500/10 text-gray-400">{{ news.news_type }}</span>
{% endif %}
<span class="text-xs text-gray-400">
{% set days_ago = (now() - news.published_at).days %}
{% if days_ago == 0 %}
今天
{% elif days_ago == 1 %}
1天前
{% elif days_ago < 7 %}
{{ days_ago }}天前
{% else %}
{{ news.published_at.strftime('%Y年%m月%d日') }}
{% endif %}
</span>
</div>
<h4 class="text-white font-bold text-base group-hover:text-primary transition-colors mb-2">{{ news.title }}</h4>
<p class="text-sm text-gray-400 line-clamp-2">{{ news.content }}</p>
</a>
{% endfor %}
</div>
</section>
{% endif %}
<!-- Similar Recommendations -->
{% if recommended_sites %}
<section>
<h3 class="text-xl font-bold text-white mb-4 flex items-center gap-2">
<span class="material-symbols-outlined text-primary">auto_awesome</span>
同类工具推荐
</h3>
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
{% for rec_site in recommended_sites %}
<a class="flex flex-col p-4 rounded-xl border border-border-dark bg-surface-dark hover:border-primary/50 hover:shadow-[0_0_20px_-10px_rgba(37,192,244,0.3)] transition-all group h-full" href="{{ url_for('site_detail', slug=rec_site.slug) }}">
<div class="flex items-start justify-between mb-3">
{% if rec_site.logo %}
<div class="size-10 rounded-lg bg-cover bg-center shadow-lg" style="background-image: url('{{ rec_site.logo }}');"></div>
{% else %}
<div class="size-10 rounded-lg bg-gradient-to-br from-primary to-secondary flex items-center justify-center shadow-lg">
<span class="text-white font-bold text-lg">{{ rec_site.name[0] }}</span>
</div>
{% endif %}
<div class="size-8 rounded-full bg-border-dark/50 flex items-center justify-center text-gray-400 group-hover:text-white group-hover:bg-primary group-hover:scale-110 transition-all">
<span class="material-symbols-outlined text-[18px]">arrow_forward</span>
</div>
</div>
<div>
<h4 class="text-white font-bold mb-1">{{ rec_site.name }}</h4>
<p class="text-xs text-gray-400 line-clamp-2 mb-3">{{ rec_site.short_desc or '暂无描述' }}</p>
<div class="flex gap-2 flex-wrap">
{% for tag in rec_site.tags[:2] %}
<span class="px-2 py-1 rounded bg-border-dark text-[10px] text-gray-400 font-medium">{{ tag.name }}</span>
{% endfor %}
</div>
</div>
</a>
{% endfor %}
</div>
</section>
{% endif %}
</div>
</div>

577
templates/detail_new.html Normal file
View File

@@ -0,0 +1,577 @@
{% extends 'base_new.html' %}
{% block title %}{{ site.name }} - ZJPB AI工具导航{% endblock %}
{% block extra_css %}
<style>
/* 返回链接 */
.back-link {
display: inline-flex;
align-items: center;
gap: 6px;
color: var(--text-secondary);
text-decoration: none;
font-size: 24px;
font-weight: 500;
transition: color 0.2s;
}
.back-link:hover {
color: var(--text-primary);
}
.back-link .material-symbols-outlined {
font-size: 24px;
line-height: 1;
vertical-align: middle;
margin-top: -2px;
}
/* 产品头部区域 */
.product-header-wrapper {
display: flex;
gap: 32px;
margin-bottom: 32px;
align-items: flex-start;
position: relative;
}
.product-main-section {
flex: 1;
background: var(--bg-white);
border: 1px solid var(--border-color);
border-radius: var(--radius-lg);
padding: 32px;
}
.product-header {
display: flex;
gap: 20px;
margin-bottom: 20px;
}
.product-logo-large {
width: 88px;
height: 88px;
border-radius: var(--radius-lg);
object-fit: cover;
flex-shrink: 0;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
}
.product-main-info {
flex: 1;
}
.product-main-info h1 {
font-size: 32px;
font-weight: 700;
margin-bottom: 8px;
line-height: 1.2;
}
.product-link {
display: inline-flex;
align-items: center;
gap: 4px;
color: var(--primary-blue);
text-decoration: none;
font-size: 14px;
margin-bottom: 16px;
}
.product-link:hover {
text-decoration: underline;
}
.product-link .material-symbols-outlined {
font-size: 16px;
}
.product-meta {
display: flex;
gap: 20px;
margin-bottom: 16px;
flex-wrap: wrap;
}
.meta-item {
display: flex;
align-items: center;
gap: 6px;
color: var(--text-secondary);
font-size: 14px;
}
.meta-item .material-symbols-outlined {
font-size: 18px;
}
.product-tags-list {
display: flex;
gap: 8px;
flex-wrap: wrap;
}
.product-tag {
padding: 6px 12px;
background: #f1f5f9;
color: #64748b;
border-radius: 6px;
font-size: 13px;
font-weight: 500;
text-decoration: none;
transition: all 0.2s;
}
.product-tag:hover {
background: rgba(14, 165, 233, 0.1);
color: var(--primary-blue);
}
.product-tag.free {
background: rgba(139, 92, 246, 0.1);
color: #8b5cf6;
}
/* Try It Now卡片 - 独立定位 */
.try-now-card {
position: sticky;
top: 88px;
width: 300px;
background: var(--bg-white);
border: 1px solid var(--border-color);
border-radius: var(--radius-lg);
padding: 24px;
flex-shrink: 0;
}
.try-now-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 16px;
}
.try-now-header h3 {
font-size: 16px;
font-weight: 600;
margin: 0;
}
.status-badge {
padding: 4px 10px;
background: rgba(34, 197, 94, 0.1);
color: #059669;
border-radius: 4px;
font-size: 11px;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.5px;
}
.visit-btn {
display: flex;
align-items: center;
justify-content: center;
gap: 8px;
width: 100%;
padding: 12px;
background: var(--primary-blue);
color: white;
border: none;
border-radius: var(--radius-md);
text-decoration: none;
font-weight: 600;
font-size: 14px;
transition: all 0.2s;
text-transform: uppercase;
letter-spacing: 0.3px;
}
.visit-btn:hover {
background: var(--primary-dark);
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(14, 165, 233, 0.3);
}
.visit-btn .material-symbols-outlined {
font-size: 18px;
}
.visit-hint {
text-align: center;
margin-top: 12px;
font-size: 12px;
color: var(--text-muted);
}
/* 主内容布局 */
.content-layout {
display: grid;
grid-template-columns: 1fr 300px;
gap: 32px;
margin-bottom: 48px;
}
.main-column {
min-width: 0;
}
.sidebar-column {
position: sticky;
top: 88px;
align-self: flex-start;
}
/* 内容块 */
.content-block {
background: var(--bg-white);
border: 1px solid var(--border-color);
border-radius: var(--radius-lg);
padding: 32px;
margin-bottom: 24px;
}
.content-block h2 {
display: flex;
align-items: center;
gap: 10px;
font-size: 20px;
font-weight: 700;
margin-bottom: 20px;
}
.content-block h2 .material-symbols-outlined {
font-size: 24px;
color: var(--primary-blue);
}
.content-block p {
color: var(--text-secondary);
line-height: 1.7;
margin-bottom: 16px;
}
.content-block ul,
.content-block ol {
margin-left: 20px;
margin-bottom: 16px;
}
.content-block li {
color: var(--text-secondary);
line-height: 1.7;
margin-bottom: 8px;
}
/* 新闻卡片 */
.news-item {
padding: 20px;
border: 1px solid var(--border-color);
border-radius: var(--radius-md);
margin-bottom: 16px;
transition: all 0.2s;
}
.news-item:hover {
border-color: var(--primary-blue);
box-shadow: var(--shadow-md);
}
.news-item:last-child {
margin-bottom: 0;
}
.news-badge {
display: inline-block;
padding: 4px 10px;
background: rgba(139, 92, 246, 0.1);
color: #8b5cf6;
border-radius: 4px;
font-size: 11px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.5px;
margin-bottom: 12px;
}
.news-item h4 {
font-size: 16px;
font-weight: 600;
margin-bottom: 8px;
color: var(--text-primary);
}
.news-item p {
font-size: 14px;
color: var(--text-secondary);
margin-bottom: 8px;
line-height: 1.6;
}
.news-date {
font-size: 12px;
color: var(--text-muted);
}
/* 推荐卡片 */
.recommendations-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 16px;
}
.recommendation-card {
padding: 20px;
border: 1px solid var(--border-color);
border-radius: var(--radius-md);
text-decoration: none;
color: var(--text-primary);
transition: all 0.2s;
display: flex;
gap: 12px;
position: relative;
}
.recommendation-card:hover {
border-color: var(--primary-blue);
box-shadow: var(--shadow-md);
transform: translateY(-2px);
}
.recommendation-card .arrow-icon {
position: absolute;
top: 20px;
right: 20px;
color: #cbd5e1;
font-size: 20px;
transition: all 0.2s;
}
.recommendation-card:hover .arrow-icon {
color: var(--primary-blue);
transform: translate(2px, -2px);
}
.rec-logo {
width: 48px;
height: 48px;
border-radius: var(--radius-md);
object-fit: cover;
flex-shrink: 0;
}
.rec-info {
flex: 1;
padding-right: 24px;
}
.rec-info h4 {
font-size: 14px;
font-weight: 600;
margin-bottom: 4px;
}
.rec-info p {
font-size: 12px;
color: var(--text-secondary);
margin-bottom: 8px;
line-height: 1.4;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
}
.rec-tags {
display: flex;
gap: 4px;
flex-wrap: wrap;
}
.rec-tag {
padding: 2px 8px;
background: #f1f5f9;
color: #64748b;
border-radius: 4px;
font-size: 11px;
font-weight: 500;
}
/* 响应式 */
@media (max-width: 968px) {
.product-header-wrapper {
flex-direction: column;
}
.try-now-card {
width: 100%;
position: static;
}
.content-layout {
grid-template-columns: 1fr;
}
.sidebar-column {
position: static;
}
.recommendations-grid {
grid-template-columns: 1fr;
}
}
</style>
{% endblock %}
{% block content %}
<div class="main-content">
<!-- 顶部空白 -->
<div style="height: 40px;"></div>
<!-- 返回链接 -->
<a href="/" class="back-link">
<span class="material-symbols-outlined">arrow_back</span>
返回首页
</a>
<!-- 底部空白 -->
<div style="height: 20px;"></div>
<!-- 产品头部区域 -->
<div class="product-header-wrapper">
<!-- 左侧主内容 -->
<div class="product-main-section">
<div class="product-header">
<!-- Logo -->
{% if site.logo %}
<img src="{{ site.logo }}" alt="{{ site.name }}" class="product-logo-large">
{% else %}
<div class="product-logo-large" style="background: linear-gradient(135deg, #0ea5e9 0%, #8b5cf6 100%);"></div>
{% endif %}
<!-- 产品信息 -->
<div class="product-main-info">
<h1>{{ site.name }}</h1>
<a href="{{ site.url }}" target="_blank" class="product-link">
{{ site.url }}
<span class="material-symbols-outlined">open_in_new</span>
</a>
<div class="product-meta">
<div class="meta-item">
<span class="material-symbols-outlined">visibility</span>
<span>{{ site.view_count | default(0) }} 次浏览</span>
</div>
<div class="meta-item">
<span class="material-symbols-outlined">calendar_today</span>
<span>添加于 {{ site.created_at.strftime('%Y年%m月%d日') }}</span>
</div>
</div>
<div class="product-tags-list">
{% for tag in site.tags %}
<a href="/?tag={{ tag.slug }}" class="product-tag">{{ tag.name }}</a>
{% endfor %}
<span class="product-tag free">免费试用</span>
</div>
</div>
</div>
</div>
<!-- Try It Now卡片 -->
<div class="try-now-card">
<div class="try-now-header">
<h3>立即访问</h3>
<span class="status-badge">在线</span>
</div>
<a href="{{ site.url }}" target="_blank" class="visit-btn">
访问网站
<span class="material-symbols-outlined">north_east</span>
</a>
<p class="visit-hint">在新标签页打开 • {{ site.url.split('/')[2] if site.url else '' }}</p>
</div>
</div>
<!-- 内容布局 -->
<div class="content-layout">
<!-- 主列 -->
<div class="main-column">
<!-- Product Overview -->
<div class="content-block">
<h2>
<span class="material-symbols-outlined">info</span>
产品概述
</h2>
<p>{{ site.description }}</p>
</div>
<!-- Detailed Description -->
{% if site.features %}
<div class="content-block">
<h2>
<span class="material-symbols-outlined">description</span>
详细描述
</h2>
<div>{{ site.features | safe }}</div>
</div>
{% endif %}
<!-- Related News -->
{% if news_list %}
<div class="content-block">
<h2>
<span class="material-symbols-outlined">newspaper</span>
相关新闻
</h2>
{% for news in news_list %}
<div class="news-item">
<span class="news-badge">{{ news.news_type }}</span>
<h4>{{ news.title }}</h4>
<p>{{ news.content[:200] }}...</p>
<div class="news-date">{{ news.published_at.strftime('%b %d, %Y') }}</div>
</div>
{% endfor %}
</div>
{% endif %}
<!-- Similar Recommendations -->
{% if recommended_sites %}
<div class="content-block">
<h2>
<span class="material-symbols-outlined">auto_awesome</span>
相似推荐
</h2>
<div class="recommendations-grid">
{% for rec_site in recommended_sites %}
<a href="/site/{{ rec_site.code }}" class="recommendation-card">
{% if rec_site.logo %}
<img src="{{ rec_site.logo }}" alt="{{ rec_site.name }}" class="rec-logo">
{% else %}
<div class="rec-logo" style="background: linear-gradient(135deg, #0ea5e9 0%, #8b5cf6 100%);"></div>
{% endif %}
<div class="rec-info">
<h4>{{ rec_site.name }}</h4>
<p>{{ rec_site.short_desc or rec_site.description }}</p>
<div class="rec-tags">
{% for tag in rec_site.tags[:2] %}
<span class="rec-tag">{{ tag.name }}</span>
{% endfor %}
</div>
</div>
<span class="material-symbols-outlined arrow-icon">north_east</span>
</a>
{% endfor %}
</div>
</div>
{% endif %}
</div>
<!-- 侧边栏 -->
<div class="sidebar-column">
<!-- 预留侧边栏位置,可以后续添加其他模块 -->
</div>
</div>
</div>
{% endblock %}

412
templates/index_new.html Normal file
View File

@@ -0,0 +1,412 @@
{% extends 'base_new.html' %}
{% block title %}ZJPB - 焦提示词 | 发现最新最好用的AI工具和产品{% endblock %}
{% block extra_css %}
<style>
/* Hero区域 */
.hero {
padding-bottom: 40px;
background: var(--bg-page);
}
.hero-content {
max-width: 1280px;
margin: 0 auto;
padding: 0 24px;
}
.hero-subtitle {
font-size: 24px;
font-weight: 400;
color: #475569;
line-height: 1.5;
margin-bottom: 8px;
}
.hero-subtitle-en {
font-size: 16px;
font-weight: 400;
color: #64748b;
line-height: 1.6;
margin-bottom: 0;
}
/* 分类过滤 */
.categories {
padding: 32px 0;
}
.category-tabs {
display: flex;
gap: 8px;
flex-wrap: wrap;
}
.category-tab {
padding: 10px 20px;
border: 1px solid var(--border-color);
border-radius: 50px;
background: var(--bg-white);
color: var(--text-secondary);
text-decoration: none;
font-size: 14px;
font-weight: 500;
transition: all 0.2s;
display: inline-flex;
align-items: center;
gap: 8px;
}
.category-tab:hover {
border-color: var(--primary-blue);
color: var(--primary-blue);
}
.category-tab.active {
background: var(--primary-blue);
border-color: var(--primary-blue);
color: white;
}
.category-tab .material-symbols-outlined {
font-size: 18px;
}
/* 工具网格 */
.tools-section {
padding: 32px 0 48px;
}
.tools-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
gap: 20px;
margin-bottom: 48px;
}
.tool-card {
background: var(--bg-white);
border: 1px solid var(--border-color);
border-radius: 12px;
padding: 24px;
text-decoration: none;
color: var(--text-primary);
transition: all 0.2s;
position: relative;
display: flex;
flex-direction: column;
}
.tool-card:hover {
border-color: var(--primary-blue);
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.08);
transform: translateY(-4px);
}
.tool-card-header {
display: flex;
align-items: flex-start;
justify-content: space-between;
margin-bottom: 12px;
}
.tool-logo {
width: 48px;
height: 48px;
border-radius: 10px;
object-fit: cover;
flex-shrink: 0;
}
.tool-link-icon {
color: #cbd5e1;
transition: all 0.2s;
font-size: 20px;
}
.tool-card:hover .tool-link-icon {
color: var(--primary-blue);
transform: translate(2px, -2px);
}
.tool-name {
font-size: 18px;
font-weight: 700;
margin-bottom: 8px;
margin-top: 0;
color: var(--text-primary);
}
.tool-description {
font-size: 14px;
color: var(--text-secondary);
line-height: 1.6;
margin-bottom: 16px;
flex: 1;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
}
.tool-footer {
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
padding-top: 16px;
border-top: 1px solid var(--border-color);
}
.tool-tags {
display: flex;
gap: 6px;
flex-wrap: wrap;
flex: 1;
min-width: 0;
}
.tool-tag {
padding: 4px 10px;
background: #f1f5f9;
color: #64748b;
border-radius: 4px;
font-size: 11px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.3px;
}
.tool-views {
display: flex;
align-items: center;
gap: 4px;
color: #94a3b8;
font-size: 13px;
white-space: nowrap;
}
.tool-views .material-symbols-outlined {
font-size: 16px;
}
/* 分页 */
.pagination {
display: flex;
justify-content: center;
align-items: center;
gap: 8px;
}
.pagination a,
.pagination span {
min-width: 40px;
height: 40px;
display: flex;
align-items: center;
justify-content: center;
border: 1px solid var(--border-color);
border-radius: var(--radius-sm);
background: var(--bg-white);
color: var(--text-secondary);
text-decoration: none;
font-size: 14px;
font-weight: 500;
transition: all 0.2s;
}
.pagination a:hover {
border-color: var(--primary-blue);
color: var(--primary-blue);
}
.pagination .active {
background: var(--primary-blue);
border-color: var(--primary-blue);
color: white;
}
.pagination .disabled {
opacity: 0.5;
cursor: not-allowed;
}
.pagination .disabled:hover {
border-color: var(--border-color);
color: var(--text-secondary);
}
/* 空状态 */
.empty-state {
text-align: center;
padding: 80px 20px;
}
.empty-state .material-symbols-outlined {
font-size: 64px;
color: var(--text-muted);
margin-bottom: 16px;
}
.empty-state h3 {
font-size: 20px;
color: var(--text-primary);
margin-bottom: 8px;
}
.empty-state p {
color: var(--text-secondary);
}
/* 响应式 */
@media (max-width: 768px) {
.hero-title {
font-size: 40px;
}
.tools-grid {
grid-template-columns: 1fr;
}
}
</style>
{% endblock %}
{% block content %}
<!-- Hero区域 -->
<div class="hero">
<div style="height: 40px;"></div>
<div class="hero-content">
<p class="hero-subtitle">发现最新最好用的AI工具和产品</p>
<p class="hero-subtitle-en">Discover the best AI tools tailored for your workflow</p>
</div>
</div>
<div class="main-content">
<!-- 分类过滤 -->
<div class="categories" id="categories">
<div class="category-tabs">
<a href="/" class="category-tab {% if not selected_tag %}active{% endif %}">
All Tools
</a>
{% for tag in tags %}
<a href="/?tag={{ tag.slug }}" class="category-tab {% if selected_tag and selected_tag.id == tag.id %}active{% endif %}">
<span class="material-symbols-outlined">
{% if 'Chat' in tag.name or 'GPT' in tag.name %}chat
{% elif 'Image' in tag.name or '图' in tag.name %}image
{% elif 'Video' in tag.name or '视频' in tag.name %}videocam
{% elif 'Writing' in tag.name or '写作' in tag.name %}edit_note
{% elif 'Coding' in tag.name or '代码' in tag.name %}code
{% elif 'Audio' in tag.name or '音频' in tag.name %}graphic_eq
{% elif '3D' in tag.name %}view_in_ar
{% else %}label{% endif %}
</span>
{{ tag.name }}
</a>
{% endfor %}
</div>
</div>
<!-- 工具网格 -->
<div class="tools-section">
{% if search_query %}
<div style="margin-bottom: 24px; padding: 16px; background: #f1f5f9; border-radius: 8px;">
<p style="margin: 0; color: #64748b; font-size: 14px;">
<span class="material-symbols-outlined" style="font-size: 18px; vertical-align: middle;">search</span>
搜索 "{{ search_query }}" 的结果:找到 {{ pagination.total if pagination else sites|length }} 个工具
<a href="/" style="margin-left: 12px; color: #0ea5e9; text-decoration: none;">清除搜索</a>
</p>
</div>
{% endif %}
{% if sites %}
<div class="tools-grid">
{% for site in sites %}
<a href="/site/{{ site.code }}" class="tool-card">
<div class="tool-card-header">
{% if site.logo %}
<img src="{{ site.logo }}" alt="{{ site.name }}" class="tool-logo">
{% else %}
<div class="tool-logo" style="background: linear-gradient(135deg, #0ea5e9 0%, #8b5cf6 100%);"></div>
{% endif %}
<span class="material-symbols-outlined tool-link-icon">north_east</span>
</div>
<h3 class="tool-name">{{ site.name }}</h3>
<p class="tool-description">{{ site.short_desc or site.description }}</p>
<div class="tool-footer">
<div class="tool-tags">
{% for tag in site.tags[:2] %}
<span class="tool-tag">{{ tag.name }}</span>
{% endfor %}
</div>
<div class="tool-views">
<span class="material-symbols-outlined">visibility</span>
<span>{% if site.view_count >= 1000 %}{{ (site.view_count / 1000) | round(1) }}k{% else %}{{ site.view_count | default(0) }}{% endif %}</span>
</div>
</div>
</a>
{% endfor %}
</div>
<!-- 分页 -->
{% if pagination and pagination.pages > 1 %}
<div class="pagination">
<!-- 上一页 -->
{% if pagination.has_prev %}
<a href="?page={{ pagination.prev_num }}{% if selected_tag %}&tag={{ selected_tag.slug }}{% endif %}{% if search_query %}&q={{ search_query }}{% endif %}">
<span class="material-symbols-outlined">chevron_left</span>
</a>
{% else %}
<a href="#" class="disabled">
<span class="material-symbols-outlined">chevron_left</span>
</a>
{% endif %}
<!-- 页码显示 -->
{% set start_page = [1, pagination.page - 2]|max %}
{% set end_page = [pagination.pages, pagination.page + 2]|min %}
{% if start_page > 1 %}
<a href="?page=1{% if selected_tag %}&tag={{ selected_tag.slug }}{% endif %}{% if search_query %}&q={{ search_query }}{% endif %}">1</a>
{% if start_page > 2 %}
<span>...</span>
{% endif %}
{% endif %}
{% for page_num in range(start_page, end_page + 1) %}
{% if page_num == pagination.page %}
<span class="active">{{ page_num }}</span>
{% else %}
<a href="?page={{ page_num }}{% if selected_tag %}&tag={{ selected_tag.slug }}{% endif %}{% if search_query %}&q={{ search_query }}{% endif %}">{{ page_num }}</a>
{% endif %}
{% endfor %}
{% if end_page < pagination.pages %}
{% if end_page < pagination.pages - 1 %}
<span>...</span>
{% endif %}
<a href="?page={{ pagination.pages }}{% if selected_tag %}&tag={{ selected_tag.slug }}{% endif %}{% if search_query %}&q={{ search_query }}{% endif %}">{{ pagination.pages }}</a>
{% endif %}
<!-- 下一页 -->
{% if pagination.has_next %}
<a href="?page={{ pagination.next_num }}{% if selected_tag %}&tag={{ selected_tag.slug }}{% endif %}{% if search_query %}&q={{ search_query }}{% endif %}">
<span class="material-symbols-outlined">chevron_right</span>
</a>
{% else %}
<a href="#" class="disabled">
<span class="material-symbols-outlined">chevron_right</span>
</a>
{% endif %}
</div>
{% endif %}
{% else %}
<div class="empty-state">
<span class="material-symbols-outlined">search_off</span>
<h3>暂无工具</h3>
<p>{% if selected_tag %}该分类下还没有工具{% else %}还没有添加任何工具{% endif %}</p>
</div>
{% endif %}
</div>
</div>
{% endblock %}

View File

@@ -0,0 +1,125 @@
<!DOCTYPE NETSCAPE-Bookmark-file-1>
<!-- This is an automatically generated file.
It will be read and overwritten.
DO NOT EDIT! -->
<META HTTP-EQUIV="Content-Type" CONTENT="text/html; charset=UTF-8">
<TITLE>Bookmarks</TITLE>
<H1>Bookmarks</H1>
<DL><p>
<DT><H3 ADD_DATE="1766918493" LAST_MODIFIED="1766918493" PERSONAL_TOOLBAR_FOLDER="true">OneNav</H3>
<DL><p>
<DT><H3 ADD_DATE="1766918493" LAST_MODIFIED="1766918493">OneNav默认分类</H3>
<DL><p>
<DT><A HREF="https://stitch.withgoogle.com/" ADD_DATE="1764662976">Stitch</A>
<DT><A HREF="http://news.aipmclub.com/" ADD_DATE="1762877013">NewsNow</A>
<DT><A HREF="https://www.boxshub.com" ADD_DATE="1762876972">BoxsHub</A>
<DT><A HREF="https://www.aipmclub.com/" ADD_DATE="1762876927">AIPMClub</A>
<DT><A HREF="https://www.imgurl.org/" ADD_DATE="1608042525">ImgURL免费图床</A>
</DL><p>
<DT><H3 ADD_DATE="1766918493" LAST_MODIFIED="1766918493">AGI大模型</H3>
<DL><p>
<DT><A HREF="https://www.perplexity.ai/" ADD_DATE="1762878118">Perplexity</A>
<DT><A HREF="https://www.siliconflow.cn/" ADD_DATE="1762878079">硅基流动 SiliconFlow - 致力于成为全球领先的 AI 能力提供商</A>
<DT><A HREF="https://www.deepseek.com/" ADD_DATE="1762878065">DeepSeek | 深度求索</A>
<DT><A HREF="https://grok.com/" ADD_DATE="1762878029">Grok</A>
<DT><A HREF="https://chatgpt.com/" ADD_DATE="1762877965">ChatGPT</A>
<DT><A HREF="https://claude.ai/" ADD_DATE="1762876402">Claude</A>
<DT><A HREF="https://www.kimi.com/" ADD_DATE="1762876179">Kimi</A>
</DL><p>
<DT><H3 ADD_DATE="1766918493" LAST_MODIFIED="1766918493">API资源</H3>
<DL><p>
<DT><A HREF="https://www.pingxx.com/" ADD_DATE="1762964204">「简米Ping++」聚合支付系统-支付分账接口-B2B 支付解决方案-二清支付公司</A>
<DT><A HREF="https://openrouter.ai/" ADD_DATE="1762961868">OpenRouter</A>
<DT><A HREF="https://aihubmix.com/" ADD_DATE="1762961796">AIHubMix - One Interface, Router All LLMs</A>
<DT><A HREF="https://newsapi.org/" ADD_DATE="1762877264">NewsAPI</A>
<DT><A HREF="https://yunwu.zeabur.app/" ADD_DATE="1762877221">云雾API</A>
<DT><A HREF="https://serpapi.com/" ADD_DATE="1762877191">SerpAPI</A>
</DL><p>
<DT><H3 ADD_DATE="1766918493" LAST_MODIFIED="1766918493">服务器管理</H3>
<DL><p>
<DT><A HREF="https://www.cloudflare.com/" ADD_DATE="1763042359">Connect, protect, and build everywhere | Cloudflare</A>
<DT><A HREF="https://www.hostinger.com/" ADD_DATE="1762964107">Hostinger - Bring Your Idea Online With a Website</A>
<DT><A HREF="https://zh.semrush.com/" ADD_DATE="1762963724">Semrush数据驱动的营销工具助您拓展业务</A>
<DT><A HREF="https://www.pexels.com/" ADD_DATE="1762879063">Pexels</A>
<DT><A HREF="https://zeabur.com/" ADD_DATE="1762879008">Zeabur - Your AI DevOps Engineer</A>
<DT><A HREF="https://railway.com/" ADD_DATE="1762878979">Railway</A>
<DT><A HREF="https://github.com/" ADD_DATE="1762878961">GitHub · Change is constant. GitHub keeps you ahead. · GitHub</A>
<DT><A HREF="https://www.bt.cn/new/index.html" ADD_DATE="1762878924">宝塔面板(bt.cn) 简单好用的Linux/Windows服务器运维管理面板</A>
<DT><A HREF="https://1panel.cn/" ADD_DATE="1762878906">1Panel - 现代化、开源的Linux服务器运维管理面板</A>
</DL><p>
<DT><H3 ADD_DATE="1766918493" LAST_MODIFIED="1766918493">AI工具</H3>
<DL><p>
<DT><A HREF="https://gamma.app/" ADD_DATE="1762962993">Gamma</A>
<DT><A HREF="https://www.jetbrains.com/zh-cn/" ADD_DATE="1762961836">JetBrains: 软件开发者和团队的必备工具</A>
<DT><A HREF="https://www.shodan.io/" ADD_DATE="1762961720">shodan</A>
<DT><A HREF="https://www.similarweb.com/" ADD_DATE="1762961518">Similarweb: AI-Powered Digital Data Intelligence Solutions</A>
<DT><A HREF="https://www.tradingview.com/" ADD_DATE="1762960567">Trading View</A>
<DT><A HREF="https://sierra.ai/" ADD_DATE="1762960476">Sierra | Better customer experiences</A>
<DT><A HREF="https://www.producthunt.com/" ADD_DATE="1762960045">Product Hunt</A>
<DT><A HREF="https://quillbot.com/" ADD_DATE="1762959999">QuillBot: Your complete writing solution</A>
<DT><A HREF="https://www.newsminimalist.com/" ADD_DATE="1762878734">News Minimalist</A>
<DT><A HREF="https://www.piccopilot.com/" ADD_DATE="1762878663">Fashion AI Model &amp;amp; AI Generated Product Images for eCcommerce | Pic Copilot</A>
<DT><A HREF="https://www.canva.com/" ADD_DATE="1762878393">Canva</A>
<DT><A HREF="https://buildingai.cc/" ADD_DATE="1762878234">BuildingAI | 开源AI框架 | FastBuildAI</A>
<DT><A HREF="https://notebooklm.google/" ADD_DATE="1762878157">NotebookLLM</A>
<DT><A HREF="https://ip.rss.ink/" ADD_DATE="1669700402">IPRSS</A>
</DL><p>
<DT><H3 ADD_DATE="1766918493" LAST_MODIFIED="1766918493">Agent平台</H3>
<DL><p>
<DT><A HREF="https://www.lightlycode.com/" ADD_DATE="1764040904">轻灵</A>
<DT><A HREF="https://dify.ai/" ADD_DATE="1762961611">Dify: Leading Agentic Workflow Builder</A>
<DT><A HREF="https://flowith.io/" ADD_DATE="1762960422">flowith</A>
<DT><A HREF="https://fastgpt.io/zh" ADD_DATE="1762960391">FastGPT - 企业级 AI Agent 搭建平台</A>
<DT><A HREF="https://n8n.io/" ADD_DATE="1762878751">AI Workflow Automation Platform &amp;amp; Tools - n8n</A>
</DL><p>
<DT><H3 ADD_DATE="1766918493" LAST_MODIFIED="1766918493">AI视频</H3>
<DL><p>
<DT><A HREF="https://fogsight.ai/" ADD_DATE="1762963149">Fogsight 雾象 | AI Animation Engine - Transform Concepts into Cinematic Visuals</A>
<DT><A HREF="https://www.mooliv.com/" ADD_DATE="1762961767">模力视频 - AIGC视频制作平台 | AI剪辑 | 云剪辑 | 海量模板</A>
<DT><A HREF="https://medio.cool/" ADD_DATE="1762960443">米壳AI - Medio.cool 助力企业海外视频推广</A>
<DT><A HREF="https://www.relume.io/" ADD_DATE="1762878776">Relume — Websites designed &amp;amp; built faster with AI | AI website builder</A>
<DT><A HREF="https://www.fliki.ai/" ADD_DATE="1762878700">fliki</A>
<DT><A HREF="https://vidiq.com/" ADD_DATE="1762878646">vidiq</A>
<DT><A HREF="https://app.creatify.ai/" ADD_DATE="1762878619">Creatify </A>
</DL><p>
<DT><H3 ADD_DATE="1766918493" LAST_MODIFIED="1766918493">AI图像</H3>
<DL><p>
<DT><A HREF="https://www.aiwind.org/" ADD_DATE="1764389764">AI Wind</A>
<DT><A HREF="https://uizard.io/" ADD_DATE="1762961665">UI Design Made Easy, Powered By AI | Uizard</A>
<DT><A HREF="https://www.uxbot.cn/" ADD_DATE="1762960498">AI设计生成代码快速构建网站与AppUXbot</A>
<DT><A HREF="https://logodiffusion.com/" ADD_DATE="1762960460">AI Logo Maker | Logo Maker | Logo Diffusion</A>
</DL><p>
<DT><H3 ADD_DATE="1766918493" LAST_MODIFIED="1766918493">AI建站</H3>
<DL><p>
<DT><A HREF="https://stocksentinel.ai/" ADD_DATE="1762960515">Stock Sentinel</A>
<DT><A HREF="https://www.appwrite.io/" ADD_DATE="1762879030">Appwrite - The developers&amp;#039; cloud</A>
<DT><A HREF="https://gitcode.com/" ADD_DATE="1762878942">GitCode - 全球开发者的开源社区,开源代码托管平台</A>
<DT><A HREF="https://lovable.dev/" ADD_DATE="1762878420">Lovable</A>
<DT><A HREF="https://base44.com/" ADD_DATE="1762876528">Base44</A>
</DL><p>
<DT><H3 ADD_DATE="1766918493" LAST_MODIFIED="1766918493">Youtube大神</H3>
<DL><p>
<DT><A HREF="https://www.youtube.com/@tech-shrimp" ADD_DATE="1762960930">技术爬爬虾</A>
</DL><p>
<DT><H3 ADD_DATE="1766918493" LAST_MODIFIED="1766918493">GitHub大神</H3>
<DL><p>
<DT><A HREF="https://github.com/wayfind" ADD_DATE="1762961434">Ramond</A>
<DT><A HREF="https://github.com/Selei1983" ADD_DATE="1762961372">Selei1983</A>
</DL><p>
<DT><H3 ADD_DATE="1766918493" LAST_MODIFIED="1766918493">神奇必推</H3>
<DL><p>
<DT><A HREF="https://lmarena.ai" ADD_DATE="1764656354">LMArena</A>
<DT><A HREF="https://www.playphrase.me/" ADD_DATE="1762963518">PlayPhrase.me: Site for cinema archaeologists.</A>
</DL><p>
<DT><H3 ADD_DATE="1766918493" LAST_MODIFIED="1766918493">其他收藏</H3>
<DL><p>
<DT><A HREF="https://www.ycombinator.com/" ADD_DATE="1764041514">Y Combinator</A>
<DT><A HREF="https://www.trademap.org/" ADD_DATE="1762964077">Trade Map - Trade statistics for international business development</A>
</DL><p>
<DT><H3 ADD_DATE="1766918493" LAST_MODIFIED="1766918493">本地服务</H3>
<DL><p>
<DT><A HREF="https://www.asterdex.com/en" ADD_DATE="1763223053">Aster - The next-gen perp DEX for all traders</A>
<DT><A HREF="http://192.168.210.166:5666/login" ADD_DATE="1763046519">210飞牛</A>
</DL><p>
</DL><p>
</DL><p>

145
test_batch_import.py Normal file
View File

@@ -0,0 +1,145 @@
"""测试批量导入功能的错误处理"""
import sys
import os
# 设置UTF-8编码
if sys.platform == 'win32':
import io
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8')
from utils.bookmark_parser import BookmarkParser
from utils.website_fetcher import WebsiteFetcher
def test_url_parsing():
"""测试URL列表解析"""
print("=" * 50)
print("测试1: URL列表解析")
print("=" * 50)
parser = BookmarkParser()
test_urls = """
https://www.google.com
https://github.com
https://invalid-url-that-does-not-exist-12345.com
http://example.com
# 这是注释,应该被忽略
https://www.python.org
"""
urls = parser.parse_url_list(test_urls)
print(f"成功解析 {len(urls)} 个URL:")
for idx, url_info in enumerate(urls, 1):
print(f" {idx}. {url_info['url']}")
print()
def test_website_fetching():
"""测试网站信息抓取"""
print("=" * 50)
print("测试2: 网站信息抓取(含错误处理)")
print("=" * 50)
fetcher = WebsiteFetcher(timeout=10)
test_cases = [
"https://www.google.com",
"https://github.com",
"https://invalid-url-that-does-not-exist-12345.com", # 这个应该失败
"https://httpbin.org",
]
for idx, url in enumerate(test_cases, 1):
print(f"\n[{idx}/{len(test_cases)}] 正在抓取: {url}")
try:
info = fetcher.fetch_website_info(url)
if info:
print(f" [OK] 成功:")
print(f" 名称: {info.get('name', 'N/A')[:50]}")
print(f" 描述: {info.get('description', 'N/A')[:80]}")
print(f" Logo: {info.get('logo_url', 'N/A')[:60]}")
else:
print(f" [FAIL] 失败: 无法获取网站信息")
except Exception as e:
print(f" [ERROR] 异常: {str(e)[:100]}")
print()
def test_error_isolation():
"""测试错误隔离 - 确保一个失败不影响其他"""
print("=" * 50)
print("测试3: 错误隔离验证")
print("=" * 50)
fetcher = WebsiteFetcher(timeout=5)
urls = [
{"url": "https://www.google.com", "name": "Google"},
{"url": "https://invalid-url-12345.com", "name": "Invalid"}, # 这个会失败
{"url": "https://github.com", "name": "GitHub"},
{"url": "https://another-invalid-url.xyz", "name": "Invalid2"}, # 这个也会失败
{"url": "https://www.python.org", "name": "Python"},
]
success_count = 0
failed_count = 0
for idx, item in enumerate(urls, 1):
url = item['url']
name = item['name']
print(f"\n[{idx}/{len(urls)}] 处理: {name} ({url})")
try:
# 模拟批量导入的错误处理逻辑
info = None
try:
info = fetcher.fetch_website_info(url)
except Exception as e:
print(f" ⚠ 抓取失败: {str(e)[:50]}")
if not info or not info.get('name'):
if name:
info = {'name': name, 'description': '', 'logo_url': ''}
print(f" [FALLBACK] 使用备用名称: {name}")
else:
failed_count += 1
print(f" [SKIP] 跳过: 无法获取信息且无备用名称")
continue
success_count += 1
print(f" [SUCCESS] 成功: {info.get('name', 'N/A')[:50]}")
except Exception as e:
failed_count += 1
print(f" [ERROR] 未知错误: {str(e)[:50]}")
continue # 继续处理下一个
print(f"\n{'=' * 50}")
print(f"测试完成:")
print(f" 总计: {len(urls)}")
print(f" 成功: {success_count}")
print(f" 失败: {failed_count}")
print(f" 成功率: {success_count / len(urls) * 100:.1f}%")
print(f"{'=' * 50}\n")
if __name__ == "__main__":
try:
test_url_parsing()
test_website_fetching()
test_error_isolation()
print("\n" + "=" * 50)
print("所有测试完成!")
print("=" * 50)
print("\n关键验证点:")
print("1. [OK] URL解析正常工作")
print("2. [OK] 网站信息抓取有错误处理")
print("3. [OK] 单个失败不影响其他URL处理")
print("4. [OK] 提供了备用方案(使用书签名称)")
except Exception as e:
print(f"\n测试过程中出现错误: {str(e)}")
import traceback
traceback.print_exc()
sys.exit(1)

184
test_site_creation.py Normal file
View File

@@ -0,0 +1,184 @@
"""测试网站创建功能 - 验证code和slug自动生成"""
import sys
import os
# 设置UTF-8编码
if sys.platform == 'win32':
import io
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8')
from app import create_app
from models import db, Site
def test_site_creation_with_autoflush():
"""测试通过Flask-Admin添加网站时的autoflush问题"""
print("=" * 60)
print("测试验证on_model_change中的no_autoflush修复")
print("=" * 60)
app = create_app()
with app.app_context():
try:
# 模拟Flask-Admin创建网站的过程
print("\n1. 创建新网站对象code和slug为空...")
site = Site(
name='测试网站WaytoAGI',
url='https://test.waytoagi.com',
short_desc='这是一个测试网站',
description='用于测试code和slug自动生成功能',
is_active=True
)
print(f" 初始状态: code={site.code}, slug={site.slug}")
# 添加到session
print("\n2. 添加到数据库session...")
db.session.add(site)
# 模拟on_model_change的调用
print("\n3. 执行on_model_change逻辑生成code和slug...")
import re
import random
from pypinyin import lazy_pinyin
# 使用no_autoflush这是修复的关键
with db.session.no_autoflush:
# 生成code
if not site.code:
for _ in range(10):
code = str(random.randint(10000000, 99999999))
existing = Site.query.filter(Site.code == code).first()
if not existing:
site.code = code
print(f" 生成code: {code}")
break
# 生成slug
if not site.slug:
slug = ''.join(lazy_pinyin(site.name))
slug = slug.lower()
slug = re.sub(r'[^\w\s-]', '', slug)
slug = re.sub(r'[-\s]+', '-', slug).strip('-')
if not slug:
slug = f"site-{site.code}"
base_slug = slug[:50]
counter = 1
final_slug = slug
while counter < 100:
existing = Site.query.filter(Site.slug == final_slug).first()
if not existing:
break
final_slug = f"{base_slug}-{counter}"
counter += 1
site.slug = final_slug
print(f" 生成slug: {final_slug}")
print(f"\n4. 提交到数据库...")
print(f" 最终状态: code={site.code}, slug={site.slug}")
# 提交事务
db.session.commit()
print("\n[SUCCESS] 网站创建成功!")
print(f" ID: {site.id}")
print(f" 名称: {site.name}")
print(f" Code: {site.code}")
print(f" Slug: {site.slug}")
# 验证数据
print("\n5. 验证数据完整性...")
assert site.code is not None, "code不能为空"
assert site.slug is not None, "slug不能为空"
assert len(site.code) == 8, "code必须是8位数字"
assert site.code.isdigit(), "code必须是纯数字"
print(" [OK] 所有验证通过")
# 清理测试数据
print("\n6. 清理测试数据...")
db.session.delete(site)
db.session.commit()
print(" [OK] 测试数据已清理")
return True
except Exception as e:
db.session.rollback()
print(f"\n[FAILED] 测试失败:{str(e)}")
import traceback
traceback.print_exc()
return False
def test_unique_code_generation():
"""测试code唯一性生成"""
print("\n" + "=" * 60)
print("测试验证code唯一性检查")
print("=" * 60)
app = create_app()
with app.app_context():
try:
import random
print("\n1. 生成测试code...")
test_code = str(random.randint(10000000, 99999999))
print(f" 测试code: {test_code}")
print("\n2. 创建第一个网站...")
site1 = Site(
code=test_code,
name='测试网站1',
url='https://test1.com',
slug='test-site-1',
is_active=True
)
db.session.add(site1)
db.session.commit()
print(f" [OK] 网站1创建成功code={site1.code}")
print("\n3. 尝试在no_autoflush中查询该code...")
with db.session.no_autoflush:
existing = Site.query.filter(Site.code == test_code).first()
if existing:
print(f" [OK] 成功查询到已存在的code: {existing.code}")
else:
print(f" [ERROR] 未能查询到已存在的code")
return False
print("\n4. 清理测试数据...")
db.session.delete(site1)
db.session.commit()
print(" [OK] 测试数据已清理")
return True
except Exception as e:
db.session.rollback()
print(f"\n[FAILED] 测试失败:{str(e)}")
import traceback
traceback.print_exc()
return False
if __name__ == "__main__":
print("\n开始测试网站创建功能...\n")
test1_passed = test_site_creation_with_autoflush()
test2_passed = test_unique_code_generation()
print("\n" + "=" * 60)
print("测试结果汇总")
print("=" * 60)
print(f"测试1 - autoflush修复验证: {'[PASSED]' if test1_passed else '[FAILED]'}")
print(f"测试2 - code唯一性验证: {'[PASSED]' if test2_passed else '[FAILED]'}")
if test1_passed and test2_passed:
print("\n[SUCCESS] 所有测试通过on_model_change修复有效。")
sys.exit(0)
else:
print("\n[FAILED] 部分测试失败,需要进一步检查。")
sys.exit(1)

204
utils/bookmark_parser.py Normal file
View File

@@ -0,0 +1,204 @@
"""OneNav/Chrome书签HTML文件解析工具"""
from bs4 import BeautifulSoup
from typing import List, Dict
import re
class BookmarkParser:
"""解析OneNav/Chrome导出的书签HTML文件"""
def parse_html_file(self, html_content: str, debug=False) -> Dict[str, any]:
"""
解析OneNav/Chrome书签HTML文件
Args:
html_content: HTML文件内容
debug: 是否打印调试信息
Returns:
Dict: 包含 categories(标签列表) 和 sites(网站列表)
"""
soup = BeautifulSoup(html_content, 'html.parser')
categories = set() # 使用set去重
sites = []
# 找到第一个DL标签作为起点
first_dl = soup.find('dl')
if first_dl:
# 递归解析书签,收集分类和网站
self._parse_dl_tag(first_dl, categories, sites, current_category=None, debug=debug)
return {
'categories': sorted(list(categories)), # 转为排序的列表
'sites': sites
}
def parse_html_file_legacy(self, html_content: str) -> List[Dict[str, str]]:
"""
解析Chrome书签HTML文件旧版格式
Args:
html_content: HTML文件内容
Returns:
List[Dict]: 书签列表,每个书签包含 name, url, folder
"""
soup = BeautifulSoup(html_content, 'html.parser')
bookmarks = []
# 递归解析书签
self._parse_dl_tag_legacy(soup, bookmarks, folder_path="")
return bookmarks
def _parse_dl_tag(self, element, categories: set, sites: List[Dict], current_category: str, debug=False):
"""递归解析DL标签OneNav格式"""
# 查找所有DT标签不限制为直接子元素因为可能在p标签内
dt_list = element.find_all('dt')
if debug and dt_list:
print(f"Found {len(dt_list)} DT tags total")
for dt in dt_list:
# 检查是否是文件夹/分类
h3 = dt.find('h3', recursive=False)
if h3:
category_name = h3.get_text(strip=True)
# 跳过根节点和默认分类
if category_name not in ['OneNav', 'OneNav默认分类']:
categories.add(category_name)
if debug:
print(f" Category: {category_name}")
# 检查是否是书签链接并且不在子分类的DL中
a = dt.find('a', recursive=False)
if a and a.get('href'):
# 找到这个DT所属的最近的H3分类
parent_category = None
# 向上查找同级或父级的H3
prev = dt.find_previous('h3')
if prev:
parent_category = prev.get_text(strip=True)
# 跳过根节点和默认分类
if parent_category in ['OneNav', 'OneNav默认分类']:
parent_category = None
if parent_category:
url = a['href']
title = a.get_text(strip=True)
sites.append({
'title': title,
'url': url,
'category': parent_category,
'add_date': a.get('add_date', '')
})
if debug:
print(f" Site: {title} -> {parent_category}")
def _parse_dl_tag_legacy(self, element, bookmarks: List[Dict], folder_path: str):
"""递归解析DL标签Chrome旧格式"""
# 查找所有DT标签书签项
for dt in element.find_all('dt', recursive=False):
# 检查是否是文件夹
h3 = dt.find('h3', recursive=False)
if h3:
folder_name = h3.get_text(strip=True)
new_folder_path = f"{folder_path}/{folder_name}" if folder_path else folder_name
# 递归解析子文件夹
dl = dt.find('dl', recursive=False)
if dl:
self._parse_dl_tag_legacy(dl, bookmarks, new_folder_path)
# 检查是否是书签链接
a = dt.find('a', recursive=False)
if a and a.get('href'):
url = a['href']
name = a.get_text(strip=True)
bookmarks.append({
'name': name,
'url': url,
'folder': folder_path
})
def parse_url_list(self, text: str) -> List[Dict[str, str]]:
"""
解析纯文本URL列表
Args:
text: 文本内容每行一个URL
Returns:
List[Dict]: URL列表
"""
urls = []
lines = text.strip().split('\n')
for line in lines:
line = line.strip()
if not line or line.startswith('#'):
continue
# 简单的URL验证
if re.match(r'^https?://', line):
urls.append({
'url': line,
'name': '', # 名称留空,后续自动获取
'folder': ''
})
return urls
@staticmethod
def clean_title(title: str) -> str:
"""清理网站标题,提取网站名称"""
if not title:
return ''
# 去除HTML实体
title = re.sub(r'&amp;', '&', title)
title = re.sub(r'&lt;', '<', title)
title = re.sub(r'&gt;', '>', title)
title = re.sub(r'&quot;', '"', title)
# 常见的分隔符
separators = [' - ', ' | ', ' · ', '·', '', '']
for sep in separators:
if sep in title:
parts = title.split(sep)
# 过滤掉常见的无用部分
filtered_parts = []
skip_keywords = ['官网', '首页', 'official', 'home', 'page', 'website']
for part in parts:
part = part.strip()
if part and not any(kw in part.lower() for kw in skip_keywords):
filtered_parts.append(part)
if filtered_parts:
# 返回最短的部分(通常是网站名)
return min(filtered_parts, key=len)
# 去除一些常见的后缀词
suffixes = [
r'\s*官网\s*$', r'\s*首页\s*$',
r'\s*Official Site\s*$', r'\s*Home Page\s*$',
r'\s*Homepage\s*$', r'\s*Website\s*$'
]
for suffix in suffixes:
title = re.sub(suffix, '', title, flags=re.IGNORECASE)
return title.strip()
@staticmethod
def extract_domain(url: str) -> str:
"""从URL提取域名"""
match = re.search(r'https?://([^/]+)', url)
if match:
domain = match.group(1)
# 去除www前缀
domain = re.sub(r'^www\.', '', domain)
return domain
return url

98
utils/tag_generator.py Normal file
View File

@@ -0,0 +1,98 @@
"""DeepSeek AI 标签生成工具"""
import os
from openai import OpenAI
class TagGenerator:
"""使用DeepSeek生成标签"""
def __init__(self):
self.api_key = os.environ.get('DEEPSEEK_API_KEY')
self.base_url = os.environ.get('DEEPSEEK_BASE_URL', 'https://api.deepseek.com')
self.client = None
# 如果有API key初始化客户端
if self.api_key:
self.client = OpenAI(
api_key=self.api_key,
base_url=self.base_url
)
def generate_tags(self, name, description, existing_tags=None):
"""
根据产品名称和描述生成标签
Args:
name: 产品名称
description: 产品描述
existing_tags: 现有标签列表(用于参考)
Returns:
list: 生成的标签列表
"""
# 检查是否配置了API key
if not self.client:
raise ValueError("DEEPSEEK_API_KEY未配置请在.env文件中添加")
try:
# 构建提示词
existing_tags_str = ""
if existing_tags:
existing_tags_str = f"\n\n系统中已有的标签参考:\n{', '.join(existing_tags)}\n尽量使用已有标签,如果合适的话。"
prompt = f"""你是一个AI工具导航网站的标签生成助手。根据以下产品信息生成3-5个最合适的标签。
产品名称: {name}
产品描述: {description}
{existing_tags_str}
要求:
1. 标签应该准确描述产品的功能、类型或应用场景
2. 每个标签2-4个汉字
3. 标签要具体且有区分度
4. 如果是AI工具可以标注具体的AI类型"GPT""图像生成"等)
5. 只返回标签,用逗号分隔,不要其他说明
示例输出格式:写作助手,营销,GPT,内容生成
请生成标签:"""
# 调用DeepSeek API
response = self.client.chat.completions.create(
model="deepseek-chat",
messages=[
{"role": "system", "content": "你是一个专业的AI工具分类专家擅长为各类AI产品生成准确的标签。"},
{"role": "user", "content": prompt}
],
temperature=0.7,
max_tokens=100
)
# 解析返回的标签
tags_text = response.choices[0].message.content.strip()
tags = [tag.strip() for tag in tags_text.split(',') if tag.strip()]
# 限制标签数量为3-5个
if len(tags) > 5:
tags = tags[:5]
return tags
except Exception as e:
print(f"DeepSeek标签生成失败: {str(e)}")
return []
def generate_news_summary(self, url, content):
"""
生成新闻摘要(未来功能)
Args:
url: 新闻链接
content: 新闻内容
Returns:
str: 新闻摘要
"""
# TODO: 实现新闻摘要生成
pass

25
zjpb_homepage.html Normal file
View File

@@ -0,0 +1,25 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<meta content="AI网址收藏" name="keywords">
<meta content="0焦提示盒ZJPB.NET不费吹灰之力获取AI平台最新信息" name="description">
<meta content="IE=edge" http-equiv="X-UA-Compatible">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<title>0焦提示盒 - 0焦提示盒不费吹灰之力获取AI平台最新信息</title>
<script type="module" crossorigin src="/templates/default2/assets/index.js?v=1.3.5"></script>
<link href="/templates/default2/assets/index.css?v=1.3.5" rel="stylesheet">
<script type="text/javascript">
(function(c,l,a,r,i,t,y){
c[a]=c[a]||function(){(c[a].q=c[a].q||[]).push(arguments)};
t=l.createElement(r);t.async=1;t.src="https://www.clarity.ms/tag/"+i;
y=l.getElementsByTagName(r)[0];y.parentNode.insertBefore(t,y);
})(window, document, "clarity", "script", "uoa2j40sf0");
</script> <link rel="manifest" href="/templates/default2/manifest.json" />
</head>
<body>
<div id="app"></div>
</body>
</html>