feat: 初始化 Toolso.AI 开源 AI 工具导航站项目
🎯 项目概述 - 一个开源的 AI 工具导航站,帮助用户发现和探索优秀的 AI 工具 - 基于 Next.js 16 + React 19 构建的现代化全栈应用 ✨ 核心功能 - AI 工具目录:分类展示、搜索筛选、工具详情页 - 工具提交:用户可提交新工具,社区驱动 - 管理后台:工具审核、用户管理、角色权限控制 - 博客系统:MDX 驱动,AI 资讯和教程 - 国际化支持:中文/英文双语 (next-intl) - 用户认证:Better Auth + Google OAuth - 深色/浅色主题切换 🛠 技术栈 - 前端:Next.js 16 (App Router) + React 19 + TypeScript - 样式:Tailwind CSS + Framer Motion 动画 - UI 组件:自定义组件库 + Radix UI - 表单验证:React Hook Form + Zod - 数据库:PostgreSQL + Drizzle ORM - 认证:Better Auth (邮箱密码 + Google OAuth) - 邮件:Resend (验证邮件 + Newsletter) - 存储:Cloudflare R2 / AWS S3 兼容 📁 项目结构 - src/app/[locale]/(auth) - 认证页面 - src/app/[locale]/(marketing) - 营销页面 (首页、博客、工具目录) - src/app/[locale]/(protected) - 受保护页面 (仪表板、个人资料) - src/app/[locale]/(admin) - 管理后台 - src/features - 功能模块 - src/lib - 核心业务逻辑 - messages - 国际化翻译文件 🗄 数据库 - 包含 user, session, account, verification, passwordResetToken 等核心表 - 支持 Drizzle ORM 迁移管理 📝 文档 - 完整的 CLAUDE.md 项目指南 - 环境变量配置示例 (.env.example) - MIT 开源许可证
@@ -0,0 +1,85 @@
|
|||||||
|
# ====================================
|
||||||
|
# Database Configuration
|
||||||
|
# ====================================
|
||||||
|
# PostgreSQL connection string
|
||||||
|
# Format: postgresql://username:password@host:port/database?sslmode=require
|
||||||
|
DATABASE_URL="postgresql://username:password@host/database?sslmode=require"
|
||||||
|
|
||||||
|
# ====================================
|
||||||
|
# Authentication (Better Auth)
|
||||||
|
# ====================================
|
||||||
|
# Secret key for Better Auth (must be at least 32 characters in production)
|
||||||
|
# Generate with: openssl rand -base64 32
|
||||||
|
BETTER_AUTH_SECRET="your-super-secret-key-at-least-32-characters-long-for-production"
|
||||||
|
|
||||||
|
# The URL where your app is hosted
|
||||||
|
BETTER_AUTH_URL="http://localhost:3000"
|
||||||
|
|
||||||
|
# ====================================
|
||||||
|
# Google OAuth (Optional)
|
||||||
|
# ====================================
|
||||||
|
# Get these from: https://console.cloud.google.com/
|
||||||
|
# Guide: https://authjs.dev/getting-started/providers/google
|
||||||
|
AUTH_GOOGLE_ID="your-google-client-id"
|
||||||
|
AUTH_GOOGLE_SECRET="your-google-client-secret"
|
||||||
|
|
||||||
|
# Public Google OAuth settings
|
||||||
|
NEXT_PUBLIC_AUTH_GOOGLE_ENABLED="true"
|
||||||
|
NEXT_PUBLIC_AUTH_GOOGLE_ONE_TAP_ENABLED="true"
|
||||||
|
|
||||||
|
# ====================================
|
||||||
|
# Email Service (Required for email features)
|
||||||
|
# ====================================
|
||||||
|
# Resend API for transactional emails
|
||||||
|
# Get your API key from: https://resend.com
|
||||||
|
RESEND_API_KEY="re_your_api_key"
|
||||||
|
|
||||||
|
# Email Configuration - Choose one of the following options:
|
||||||
|
#
|
||||||
|
# Set a complete from email address (recommended, highest priority)
|
||||||
|
RESEND_FROM_EMAIL="Your App <noreply@yourdomain.com>"
|
||||||
|
|
||||||
|
# Optional: Resend Audience ID for newsletter/marketing
|
||||||
|
RESEND_AUDIENCE_ID="your-resend-audience-id"
|
||||||
|
|
||||||
|
# ====================================
|
||||||
|
# Analytics & Monitoring (Optional)
|
||||||
|
# ====================================
|
||||||
|
# Posthog Analytics
|
||||||
|
NEXT_PUBLIC_POSTHOG_KEY=""
|
||||||
|
NEXT_PUBLIC_POSTHOG_HOST=""
|
||||||
|
|
||||||
|
# Google Analytics
|
||||||
|
NEXT_PUBLIC_GOOGLE_ANALYTICS_ID=""
|
||||||
|
|
||||||
|
# Microsoft Clarity
|
||||||
|
NEXT_PUBLIC_CLARITY_PROJECT_ID=""
|
||||||
|
|
||||||
|
# Analytics Feature Flags(可选)
|
||||||
|
NEXT_PUBLIC_ANALYTICS_ENABLE_IN_DEVELOPMENT=""
|
||||||
|
|
||||||
|
# ====================================
|
||||||
|
# Storage Configuration (Optional - for file uploads)
|
||||||
|
# ====================================
|
||||||
|
# R2/S3-compatible storage for file uploads
|
||||||
|
# Works with Cloudflare R2, AWS S3, MinIO, etc.
|
||||||
|
STORAGE_ACCESS_KEY_ID=""
|
||||||
|
STORAGE_SECRET_ACCESS_KEY=""
|
||||||
|
STORAGE_ENDPOINT=""
|
||||||
|
STORAGE_BUCKET_NAME="uploads"
|
||||||
|
STORAGE_PUBLIC_URL=""
|
||||||
|
|
||||||
|
# ====================================
|
||||||
|
# Application Settings
|
||||||
|
# ====================================
|
||||||
|
# Public app URL (update in production)
|
||||||
|
NEXT_PUBLIC_APP_URL="http://localhost:3000"
|
||||||
|
|
||||||
|
# Node environment
|
||||||
|
NODE_ENV="development"
|
||||||
|
|
||||||
|
# ====================================
|
||||||
|
# Feature Flags (Optional)
|
||||||
|
# ====================================
|
||||||
|
# Enable/disable features
|
||||||
|
NEXT_PUBLIC_MAINTENANCE_MODE="false"
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"extends": "next/core-web-vitals"
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
# 自动检测文本文件并标准化行尾符
|
||||||
|
* text=auto
|
||||||
|
|
||||||
|
# 强制使用 LF 行尾符的文件类型 (防止 Windows CRLF 问题)
|
||||||
|
*.js text eol=lf
|
||||||
|
*.ts text eol=lf
|
||||||
|
*.tsx text eol=lf
|
||||||
|
*.jsx text eol=lf
|
||||||
|
*.mjs text eol=lf
|
||||||
|
*.cjs text eol=lf
|
||||||
|
*.json text eol=lf
|
||||||
|
*.md text eol=lf
|
||||||
|
*.mdx text eol=lf
|
||||||
|
*.yml text eol=lf
|
||||||
|
*.yaml text eol=lf
|
||||||
|
*.css text eol=lf
|
||||||
|
*.scss text eol=lf
|
||||||
|
*.html text eol=lf
|
||||||
|
*.sh text eol=lf
|
||||||
|
|
||||||
|
# 二进制文件
|
||||||
|
*.png binary
|
||||||
|
*.jpg binary
|
||||||
|
*.jpeg binary
|
||||||
|
*.gif binary
|
||||||
|
*.ico binary
|
||||||
|
*.svg binary
|
||||||
|
*.woff binary
|
||||||
|
*.woff2 binary
|
||||||
|
*.ttf binary
|
||||||
|
*.eot binary
|
||||||
|
*.pdf binary
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||||
|
|
||||||
|
# dependencies
|
||||||
|
/node_modules
|
||||||
|
/.pnp
|
||||||
|
.pnp.js
|
||||||
|
.yarn/install-state.gz
|
||||||
|
|
||||||
|
# testing
|
||||||
|
/coverage
|
||||||
|
|
||||||
|
# next.js
|
||||||
|
/.next/
|
||||||
|
/out/
|
||||||
|
|
||||||
|
# production
|
||||||
|
/build
|
||||||
|
|
||||||
|
# misc
|
||||||
|
.DS_Store
|
||||||
|
*.pem
|
||||||
|
|
||||||
|
# debug
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
|
||||||
|
# local env files
|
||||||
|
.env*.local
|
||||||
|
|
||||||
|
# vercel
|
||||||
|
.vercel
|
||||||
|
|
||||||
|
# typescript
|
||||||
|
*.tsbuildinfo
|
||||||
|
next-env.d.ts
|
||||||
|
|
||||||
|
# macOS
|
||||||
|
.DS_Store
|
||||||
|
.AppleDouble
|
||||||
|
.LSOverride
|
||||||
|
|
||||||
|
# Windows
|
||||||
|
Thumbs.db
|
||||||
|
ehthumbs.db
|
||||||
|
Desktop.ini
|
||||||
|
|
||||||
|
# Editor directories
|
||||||
|
.vscode/
|
||||||
|
.idea/
|
||||||
|
*.swp
|
||||||
|
*.swo
|
||||||
|
*~
|
||||||
|
.env
|
||||||
|
|
||||||
|
# local documentation
|
||||||
|
.local-docs/
|
||||||
|
|
||||||
|
# scripts
|
||||||
|
/scripts/manage-users.ts
|
||||||
@@ -0,0 +1,262 @@
|
|||||||
|
# CLAUDE.md
|
||||||
|
|
||||||
|
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||||||
|
|
||||||
|
## 项目概览
|
||||||
|
|
||||||
|
这是一个**开源的 AI 工具导航站 (Toolso.AI)**,帮助用户发现和探索各类优秀的 AI 工具。基于 Next.js 16 构建,拥有现代化的响应式界面、强大的搜索和筛选功能、多语言支持。
|
||||||
|
|
||||||
|
**核心特性**:
|
||||||
|
- AI 工具目录 (分类展示、搜索筛选、工具详情)
|
||||||
|
- 工具提交 (用户可提交新工具,社区驱动)
|
||||||
|
- 管理后台 (工具审核、用户管理、角色权限)
|
||||||
|
- 博客系统 (MDX 驱动,AI 资讯和教程)
|
||||||
|
- 国际化支持 (中文/英文)
|
||||||
|
- 用户认证 (Better Auth + Google OAuth)
|
||||||
|
|
||||||
|
## 技术栈
|
||||||
|
|
||||||
|
### 前端
|
||||||
|
- **框架**: Next.js 16 (App Router) + React 19
|
||||||
|
- **样式**: Tailwind CSS + Framer Motion 动画
|
||||||
|
- **UI 组件**: 自定义组件库 + Radix UI
|
||||||
|
- **表单**: React Hook Form + Zod 验证
|
||||||
|
- **主题**: next-themes (深色/浅色模式)
|
||||||
|
- **国际化**: next-intl (动态路由 `[locale]`)
|
||||||
|
|
||||||
|
### 后端
|
||||||
|
- **身份认证**: Better Auth (邮箱密码 + Google OAuth)
|
||||||
|
- **数据库**: PostgreSQL + Drizzle ORM
|
||||||
|
- **邮件**: Resend (验证邮件 + Newsletter)
|
||||||
|
- **存储**: Cloudflare R2 / AWS S3 兼容 (可选)
|
||||||
|
|
||||||
|
### 开发工具
|
||||||
|
- **类型安全**: TypeScript (strict mode)
|
||||||
|
- **代码规范**: ESLint
|
||||||
|
- **包管理**: pnpm
|
||||||
|
|
||||||
|
## 项目结构
|
||||||
|
|
||||||
|
```
|
||||||
|
├── src/ # 源代码目录
|
||||||
|
│ ├── app/[locale]/ # 国际化路由
|
||||||
|
│ │ ├── (auth)/ # 认证页面 (登录、注册、忘记密码)
|
||||||
|
│ │ ├── (marketing)/ # 营销页面 (首页、博客、联系、工具目录)
|
||||||
|
│ │ ├── (protected)/ # 需要认证的页面 (仪表板、个人资料)
|
||||||
|
│ │ ├── (admin)/ # 管理后台 (用户管理、工具管理)
|
||||||
|
│ │ ├── check-email/ # 邮箱验证提示页
|
||||||
|
│ │ └── verify-email/ # 邮箱验证处理页
|
||||||
|
│ ├── app/api/ # API 路由
|
||||||
|
│ │ ├── auth/ # Better Auth + 自定义认证端点
|
||||||
|
│ │ ├── admin/ # 管理员操作 API
|
||||||
|
│ │ ├── user/ # 用户信息 API
|
||||||
|
│ │ └── upload/ # 文件上传 API
|
||||||
|
│ ├── components/ # 可重用 UI 组件
|
||||||
|
│ ├── features/ # 功能模块
|
||||||
|
│ │ ├── auth/ # 认证组件和逻辑
|
||||||
|
│ │ ├── admin/ # 管理后台组件
|
||||||
|
│ │ ├── navigation/ # 导航组件
|
||||||
|
│ │ ├── tools/ # AI 工具目录模块
|
||||||
|
│ │ └── forms/ # 表单组件
|
||||||
|
│ ├── lib/ # 核心业务逻辑
|
||||||
|
│ │ ├── auth.ts # Better Auth 配置
|
||||||
|
│ │ ├── auth-client.ts # 前端认证客户端
|
||||||
|
│ │ ├── db/ # 数据库连接和 Schema
|
||||||
|
│ │ ├── email.ts # 邮件发送逻辑
|
||||||
|
│ │ ├── blog.ts # 博客系统逻辑
|
||||||
|
│ │ ├── r2-storage.ts # R2/S3 存储
|
||||||
|
│ │ ├── i18n.ts # 国际化配置
|
||||||
|
│ │ └── metadata.ts # SEO 元数据生成
|
||||||
|
│ ├── constants/ # 常量定义
|
||||||
|
│ │ └── website.ts # 网站基础信息
|
||||||
|
│ ├── context/ # React Context
|
||||||
|
│ ├── emails/ # 邮件模板 (React Email)
|
||||||
|
│ ├── layouts/ # 布局组件
|
||||||
|
│ └── drizzle/ # 数据库迁移文件
|
||||||
|
├── messages/ # 国际化翻译文件
|
||||||
|
│ ├── en.json # 英文 (含 SEO)
|
||||||
|
│ └── zh.json # 中文 (含 SEO)
|
||||||
|
├── public/ # 静态资源
|
||||||
|
└── scripts/ # 构建和管理脚本
|
||||||
|
```
|
||||||
|
|
||||||
|
## 数据库架构
|
||||||
|
|
||||||
|
当前包含 6 个核心表:
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- 用户表
|
||||||
|
user (id, name, email, emailVerified, image, role, banned, banReason, banExpires, createdAt, updatedAt)
|
||||||
|
|
||||||
|
-- 认证表
|
||||||
|
session (id, expiresAt, token, createdAt, updatedAt, ipAddress, userAgent, userId)
|
||||||
|
account (id, accountId, providerId, userId, accessToken, refreshToken, password, ...)
|
||||||
|
|
||||||
|
-- 验证表
|
||||||
|
verification (id, identifier, value, expiresAt, createdAt, updatedAt)
|
||||||
|
passwordResetToken (id, userId, token, expiresAt, createdAt)
|
||||||
|
|
||||||
|
-- 其他
|
||||||
|
newsletterSubscription (id, email, userId, status, unsubscribeToken, subscribedAt, ...)
|
||||||
|
```
|
||||||
|
|
||||||
|
完整 Schema 定义: `src/lib/db/schema.ts`
|
||||||
|
|
||||||
|
## 常用开发命令
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 启动开发服务器
|
||||||
|
pnpm dev
|
||||||
|
|
||||||
|
# 构建生产版本
|
||||||
|
pnpm build
|
||||||
|
|
||||||
|
# 启动生产服务器
|
||||||
|
pnpm start
|
||||||
|
|
||||||
|
# 代码检查
|
||||||
|
pnpm lint
|
||||||
|
|
||||||
|
# 数据库操作
|
||||||
|
pnpm db:generate # 生成 Drizzle 迁移文件
|
||||||
|
pnpm db:migrate # 执行数据库迁移
|
||||||
|
pnpm db:push # 推送 schema 到数据库 (开发环境)
|
||||||
|
pnpm db:studio # 启动 Drizzle Studio 数据库管理界面
|
||||||
|
|
||||||
|
# 管理员工具
|
||||||
|
pnpm admin:setup # 创建管理员账户
|
||||||
|
|
||||||
|
# 博客清单生成
|
||||||
|
pnpm generate:blog-manifest
|
||||||
|
```
|
||||||
|
|
||||||
|
## 环境变量配置
|
||||||
|
|
||||||
|
**必需的环境变量** (参考 `.env.example`):
|
||||||
|
|
||||||
|
```env
|
||||||
|
# 数据库
|
||||||
|
DATABASE_URL="postgresql://user:password@host/db?sslmode=require"
|
||||||
|
|
||||||
|
# Better Auth
|
||||||
|
BETTER_AUTH_SECRET="至少32字符的随机密钥"
|
||||||
|
BETTER_AUTH_URL="http://localhost:3000"
|
||||||
|
|
||||||
|
# Resend 邮件
|
||||||
|
RESEND_API_KEY="re_your_api_key"
|
||||||
|
RESEND_FROM_EMAIL="Your App <noreply@yourdomain.com>"
|
||||||
|
|
||||||
|
# 应用 URL
|
||||||
|
NEXT_PUBLIC_APP_URL="http://localhost:3000"
|
||||||
|
```
|
||||||
|
|
||||||
|
**可选的环境变量**:
|
||||||
|
```env
|
||||||
|
# Google OAuth
|
||||||
|
AUTH_GOOGLE_ID="your-google-client-id"
|
||||||
|
AUTH_GOOGLE_SECRET="your-google-client-secret"
|
||||||
|
|
||||||
|
# R2/S3 存储
|
||||||
|
STORAGE_ACCESS_KEY_ID="..."
|
||||||
|
STORAGE_SECRET_ACCESS_KEY="..."
|
||||||
|
STORAGE_ENDPOINT="..."
|
||||||
|
STORAGE_BUCKET_NAME="..."
|
||||||
|
STORAGE_PUBLIC_URL="..."
|
||||||
|
|
||||||
|
# 分析工具
|
||||||
|
NEXT_PUBLIC_POSTHOG_KEY="..."
|
||||||
|
NEXT_PUBLIC_GOOGLE_ANALYTICS_ID="..."
|
||||||
|
NEXT_PUBLIC_CLARITY_PROJECT_ID="..."
|
||||||
|
```
|
||||||
|
|
||||||
|
## 国际化 (i18n)
|
||||||
|
|
||||||
|
- 框架: `next-intl`
|
||||||
|
- 支持语言: 英文 (`en`), 中文 (`zh`)
|
||||||
|
- 翻译文件: `messages/en.json`, `messages/zh.json` (含 UI 文本和 SEO 配置)
|
||||||
|
- 路由格式: `/en/...`, `/zh/...`
|
||||||
|
|
||||||
|
**添加新语言**:
|
||||||
|
1. 复制 `messages/en.json` 到 `messages/新语言代码.json`
|
||||||
|
2. 翻译 JSON 文件内容
|
||||||
|
3. 更新 `i18n.config.ts` 添加新语言代码
|
||||||
|
|
||||||
|
## 路由权限控制
|
||||||
|
|
||||||
|
- **公开路由**: `(marketing)`, `(auth)`
|
||||||
|
- **受保护路由**: `(protected)` - 需要登录,通过 `SessionGuard` 组件保护
|
||||||
|
- **管理员路由**: `(admin)` - 需要 `role='admin'`,通过 `AdminGuard` 组件保护
|
||||||
|
|
||||||
|
## 认证流程
|
||||||
|
|
||||||
|
### 注册流程
|
||||||
|
1. 用户填写注册表单
|
||||||
|
2. 创建用户和账户记录
|
||||||
|
3. 发送验证邮件
|
||||||
|
4. 用户点击链接验证邮箱
|
||||||
|
|
||||||
|
### 密码重置流程
|
||||||
|
1. 用户输入邮箱
|
||||||
|
2. 生成重置令牌存入 `passwordResetToken` 表
|
||||||
|
3. 发送重置邮件
|
||||||
|
4. 用户点击链接设置新密码
|
||||||
|
|
||||||
|
## 管理后台
|
||||||
|
|
||||||
|
管理员权限通过 `user.role = 'admin'` 标识。
|
||||||
|
|
||||||
|
**功能**:
|
||||||
|
- 用户列表和搜索
|
||||||
|
- 查看用户详情
|
||||||
|
- 修改用户角色
|
||||||
|
- 禁用/解禁用户
|
||||||
|
|
||||||
|
**创建管理员账户**:
|
||||||
|
```bash
|
||||||
|
pnpm admin:setup
|
||||||
|
# 或手动在数据库中将 user.role 设为 'admin'
|
||||||
|
```
|
||||||
|
|
||||||
|
## 部署清单
|
||||||
|
|
||||||
|
1. **环境准备**
|
||||||
|
- PostgreSQL 数据库 (推荐 Supabase/Neon/Vercel Postgres)
|
||||||
|
- Resend API Key (https://resend.com)
|
||||||
|
- (可选) Google OAuth 凭据
|
||||||
|
- (可选) R2/S3 存储配置
|
||||||
|
|
||||||
|
2. **数据库初始化**
|
||||||
|
```bash
|
||||||
|
pnpm db:push
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **创建管理员账户**
|
||||||
|
```bash
|
||||||
|
pnpm admin:setup
|
||||||
|
```
|
||||||
|
|
||||||
|
4. **环境变量检查**
|
||||||
|
确保 `BETTER_AUTH_URL`, `NEXT_PUBLIC_APP_URL`, `RESEND_FROM_EMAIL` 使用正确的域名。
|
||||||
|
|
||||||
|
## 扩展指南
|
||||||
|
|
||||||
|
### 添加新功能模块
|
||||||
|
1. 在 `src/features/` 下创建新目录
|
||||||
|
2. 包含 `components/`, `actions/`, `schemas.ts` 等
|
||||||
|
3. 在 `src/app/` 下创建对应路由
|
||||||
|
|
||||||
|
### 添加新数据库表
|
||||||
|
1. 在 `src/lib/db/schema.ts` 定义新表
|
||||||
|
2. 运行 `pnpm db:generate` 生成迁移
|
||||||
|
3. 运行 `pnpm db:push` 或 `pnpm db:migrate` 应用更改
|
||||||
|
|
||||||
|
### 自定义邮件模板
|
||||||
|
编辑 `src/emails/` 目录下的 React Email 组件
|
||||||
|
|
||||||
|
## 相关文档
|
||||||
|
|
||||||
|
- [Better Auth 文档](https://better-auth.com/)
|
||||||
|
- [Drizzle ORM 文档](https://orm.drizzle.team/)
|
||||||
|
- [Next.js 文档](https://nextjs.org/docs)
|
||||||
|
- [next-intl 文档](https://next-intl-docs.vercel.app/)
|
||||||
|
- [Resend 文档](https://resend.com/docs)
|
||||||
@@ -0,0 +1,190 @@
|
|||||||
|
Apache License
|
||||||
|
Version 2.0, January 2004
|
||||||
|
http://www.apache.org/licenses/
|
||||||
|
|
||||||
|
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||||
|
|
||||||
|
1. Definitions.
|
||||||
|
|
||||||
|
"License" shall mean the terms and conditions for use, reproduction,
|
||||||
|
and distribution as defined by Sections 1 through 9 of this document.
|
||||||
|
|
||||||
|
"Licensor" shall mean the copyright owner or entity authorized by
|
||||||
|
the copyright owner that is granting the License.
|
||||||
|
|
||||||
|
"Legal Entity" shall mean the union of the acting entity and all
|
||||||
|
other entities that control, are controlled by, or are under common
|
||||||
|
control with that entity. For the purposes of this definition,
|
||||||
|
"control" means (i) the power, direct or indirect, to cause the
|
||||||
|
direction or management of such entity, whether by contract or
|
||||||
|
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||||
|
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||||
|
|
||||||
|
"You" (or "Your") shall mean an individual or Legal Entity
|
||||||
|
exercising permissions granted by this License.
|
||||||
|
|
||||||
|
"Source" form shall mean the preferred form for making modifications,
|
||||||
|
including but not limited to software source code, documentation
|
||||||
|
source, and configuration files.
|
||||||
|
|
||||||
|
"Object" form shall mean any form resulting from mechanical
|
||||||
|
transformation or translation of a Source form, including but
|
||||||
|
not limited to compiled object code, generated documentation,
|
||||||
|
and conversions to other media types.
|
||||||
|
|
||||||
|
"Work" shall mean the work of authorship, whether in Source or
|
||||||
|
Object form, made available under the License, as indicated by a
|
||||||
|
copyright notice that is included in or attached to the work
|
||||||
|
(an example is provided in the Appendix below).
|
||||||
|
|
||||||
|
"Derivative Works" shall mean any work, whether in Source or Object
|
||||||
|
form, that is based on (or derived from) the Work and for which the
|
||||||
|
editorial revisions, annotations, elaborations, or other modifications
|
||||||
|
represent, as a whole, an original work of authorship. For the purposes
|
||||||
|
of this License, Derivative Works shall not include works that remain
|
||||||
|
separable from, or merely link (or bind by name) to the interfaces of,
|
||||||
|
the Work and Derivative Works thereof.
|
||||||
|
|
||||||
|
"Contribution" shall mean any work of authorship, including
|
||||||
|
the original version of the Work and any modifications or additions
|
||||||
|
to that Work or Derivative Works thereof, that is intentionally
|
||||||
|
submitted to the Licensor for inclusion in the Work by the copyright owner
|
||||||
|
or by an individual or Legal Entity authorized to submit on behalf of
|
||||||
|
the copyright owner. For the purposes of this definition, "submitted"
|
||||||
|
means any form of electronic, verbal, or written communication sent
|
||||||
|
to the Licensor or its representatives, including but not limited to
|
||||||
|
communication on electronic mailing lists, source code control systems,
|
||||||
|
and issue tracking systems that are managed by, or on behalf of, the
|
||||||
|
Licensor for the purpose of discussing and improving the Work, but
|
||||||
|
excluding communication that is conspicuously marked or otherwise
|
||||||
|
designated in writing by the copyright owner as "Not a Contribution."
|
||||||
|
|
||||||
|
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||||
|
on behalf of whom a Contribution has been received by Licensor and
|
||||||
|
subsequently incorporated within the Work.
|
||||||
|
|
||||||
|
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
copyright license to reproduce, prepare Derivative Works of,
|
||||||
|
publicly display, publicly perform, sublicense, and distribute the
|
||||||
|
Work and such Derivative Works in Source or Object form.
|
||||||
|
|
||||||
|
3. Grant of Patent License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
(except as stated in this section) patent license to make, have made,
|
||||||
|
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||||
|
where such license applies only to those patent claims licensable
|
||||||
|
by such Contributor that are necessarily infringed by their
|
||||||
|
Contribution(s) alone or by combination of their Contribution(s)
|
||||||
|
with the Work to which such Contribution(s) was submitted. If You
|
||||||
|
institute patent litigation against any entity (including a
|
||||||
|
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||||
|
or a Contribution incorporated within the Work constitutes direct
|
||||||
|
or contributory patent infringement, then any patent licenses
|
||||||
|
granted to You under this License for that Work shall terminate
|
||||||
|
as of the date such litigation is filed.
|
||||||
|
|
||||||
|
4. Redistribution. You may reproduce and distribute copies of the
|
||||||
|
Work or Derivative Works thereof in any medium, with or without
|
||||||
|
modifications, and in Source or Object form, provided that You
|
||||||
|
meet the following conditions:
|
||||||
|
|
||||||
|
(a) You must give any other recipients of the Work or
|
||||||
|
Derivative Works a copy of this License; and
|
||||||
|
|
||||||
|
(b) You must cause any modified files to carry prominent notices
|
||||||
|
stating that You changed the files; and
|
||||||
|
|
||||||
|
(c) You must retain, in the Source form of any Derivative Works
|
||||||
|
that You distribute, all copyright, patent, trademark, and
|
||||||
|
attribution notices from the Source form of the Work,
|
||||||
|
excluding those notices that do not pertain to any part of
|
||||||
|
the Derivative Works; and
|
||||||
|
|
||||||
|
(d) If the Work includes a "NOTICE" text file as part of its
|
||||||
|
distribution, then any Derivative Works that You distribute must
|
||||||
|
include a readable copy of the attribution notices contained
|
||||||
|
within such NOTICE file, excluding those notices that do not
|
||||||
|
pertain to any part of the Derivative Works, in at least one
|
||||||
|
of the following places: within a NOTICE text file distributed
|
||||||
|
as part of the Derivative Works; within the Source form or
|
||||||
|
documentation, if provided along with the Derivative Works; or,
|
||||||
|
within a display generated by the Derivative Works, if and
|
||||||
|
wherever such third-party notices normally appear. The contents
|
||||||
|
of the NOTICE file are for informational purposes only and
|
||||||
|
do not modify the License. You may add Your own attribution
|
||||||
|
notices within Derivative Works that You distribute, alongside
|
||||||
|
or as an addendum to the NOTICE text from the Work, provided
|
||||||
|
that such additional attribution notices cannot be construed
|
||||||
|
as modifying the License.
|
||||||
|
|
||||||
|
You may add Your own copyright statement to Your modifications and
|
||||||
|
may provide additional or different license terms and conditions
|
||||||
|
for use, reproduction, or distribution of Your modifications, or
|
||||||
|
for any such Derivative Works as a whole, provided Your use,
|
||||||
|
reproduction, and distribution of the Work otherwise complies with
|
||||||
|
the conditions stated in this License.
|
||||||
|
|
||||||
|
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||||
|
any Contribution intentionally submitted for inclusion in the Work
|
||||||
|
by You to the Licensor shall be under the terms and conditions of
|
||||||
|
this License, without any additional terms or conditions.
|
||||||
|
Notwithstanding the above, nothing herein shall supersede or modify
|
||||||
|
the terms of any separate license agreement you may have executed
|
||||||
|
with Licensor regarding such Contributions.
|
||||||
|
|
||||||
|
6. Trademarks. This License does not grant permission to use the trade
|
||||||
|
names, trademarks, service marks, or product names of the Licensor,
|
||||||
|
except as required for reasonable and customary use in describing the
|
||||||
|
origin of the Work and reproducing the content of the NOTICE file.
|
||||||
|
|
||||||
|
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||||
|
agreed to in writing, Licensor provides the Work (and each
|
||||||
|
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||||
|
implied, including, without limitation, any warranties or conditions
|
||||||
|
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||||
|
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||||
|
appropriateness of using or redistributing the Work and assume any
|
||||||
|
risks associated with Your exercise of permissions under this License.
|
||||||
|
|
||||||
|
8. Limitation of Liability. In no event and under no legal theory,
|
||||||
|
whether in tort (including negligence), contract, or otherwise,
|
||||||
|
unless required by applicable law (such as deliberate and grossly
|
||||||
|
negligent acts) or agreed to in writing, shall any Contributor be
|
||||||
|
liable to You for damages, including any direct, indirect, special,
|
||||||
|
incidental, or consequential damages of any character arising as a
|
||||||
|
result of this License or out of the use or inability to use the
|
||||||
|
Work (including but not limited to damages for loss of goodwill,
|
||||||
|
work stoppage, computer failure or malfunction, or any and all
|
||||||
|
other commercial damages or losses), even if such Contributor
|
||||||
|
has been advised of the possibility of such damages.
|
||||||
|
|
||||||
|
9. Accepting Warranty or Additional Liability. While redistributing
|
||||||
|
the Work or Derivative Works thereof, You may choose to offer,
|
||||||
|
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||||
|
or other liability obligations and/or rights consistent with this
|
||||||
|
License. However, in accepting such obligations, You may act only
|
||||||
|
on Your own behalf and on Your sole responsibility, not on behalf
|
||||||
|
of any other Contributor, and only if You agree to indemnify,
|
||||||
|
defend, and hold each Contributor harmless for any liability
|
||||||
|
incurred by, or claims asserted against, such Contributor by reason
|
||||||
|
of your accepting any such warranty or additional liability.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
Copyright 2025 CoolChange.AI
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
@@ -0,0 +1,439 @@
|
|||||||
|
<div align="center">
|
||||||
|
|
||||||
|
# Toolso.AI
|
||||||
|
|
||||||
|
**An open-source AI tools directory to help you discover the best AI tools**
|
||||||
|
|
||||||
|
[](https://opensource.org/licenses/Apache-2.0)
|
||||||
|
[](https://nextjs.org/)
|
||||||
|
[](https://react.dev/)
|
||||||
|
[](https://www.typescriptlang.org/)
|
||||||
|
[](https://tailwindcss.com/)
|
||||||
|
[](https://www.postgresql.org/)
|
||||||
|
|
||||||
|
[English](#english) | [中文](#中文)
|
||||||
|
|
||||||
|
<br/>
|
||||||
|
|
||||||
|
**Discover, explore, and share the best AI tools.**
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
<a name="english"></a>
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
Toolso.AI is an open-source AI tools directory designed to help users discover the best AI tools across various categories. Built with Next.js 16, React 19, and TypeScript, it features a modern, responsive interface with powerful search and filtering capabilities, multilingual support, and a comprehensive admin panel for tool management.
|
||||||
|
|
||||||
|
### Key Features
|
||||||
|
|
||||||
|
| Feature | Description | Benefits |
|
||||||
|
|---------|-------------|----------|
|
||||||
|
| **AI Tools Directory** | Categorized AI tools listing | Easy discovery and exploration |
|
||||||
|
| **Search & Filter** | Powerful search with category filters | Find the right tool quickly |
|
||||||
|
| **Tool Submission** | User-contributed tool submissions | Community-driven content |
|
||||||
|
| **Admin Panel** | Tool & user management | Approve submissions, manage content |
|
||||||
|
| **Blog System** | MDX-powered multilingual blog | SEO-optimized AI insights |
|
||||||
|
| **i18n** | next-intl internationalization | English & Chinese out of the box |
|
||||||
|
| **Authentication** | Better Auth + Google OAuth | User accounts and submissions |
|
||||||
|
| **Theming** | Light/Dark mode | Comfortable browsing experience |
|
||||||
|
|
||||||
|
## Quick Start
|
||||||
|
|
||||||
|
### Prerequisites
|
||||||
|
|
||||||
|
- Node.js 18+
|
||||||
|
- pnpm 8+
|
||||||
|
- PostgreSQL database
|
||||||
|
|
||||||
|
### Installation
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Clone the repository
|
||||||
|
git clone https://github.com/coolchange-ai/toolso-ai-open.git
|
||||||
|
cd toolso-ai-open
|
||||||
|
|
||||||
|
# Install dependencies
|
||||||
|
pnpm install
|
||||||
|
|
||||||
|
# Set up environment variables
|
||||||
|
cp .env.example .env.local
|
||||||
|
|
||||||
|
# Push database schema
|
||||||
|
pnpm db:push
|
||||||
|
|
||||||
|
# Start development server
|
||||||
|
pnpm dev
|
||||||
|
```
|
||||||
|
|
||||||
|
Open [http://localhost:3000](http://localhost:3000) to see your app.
|
||||||
|
|
||||||
|
### Environment Configuration
|
||||||
|
|
||||||
|
Create `.env.local` with your configuration:
|
||||||
|
|
||||||
|
```env
|
||||||
|
# Required
|
||||||
|
DATABASE_URL="postgresql://user:password@host/db?sslmode=require"
|
||||||
|
BETTER_AUTH_SECRET="your-secret-key-at-least-32-characters"
|
||||||
|
BETTER_AUTH_URL="http://localhost:3000"
|
||||||
|
NEXT_PUBLIC_APP_URL="http://localhost:3000"
|
||||||
|
|
||||||
|
# Email (Required for auth features)
|
||||||
|
RESEND_API_KEY="re_your_api_key"
|
||||||
|
RESEND_FROM_EMAIL="Your App <noreply@yourdomain.com>"
|
||||||
|
|
||||||
|
# Optional: Google OAuth
|
||||||
|
AUTH_GOOGLE_ID="your-google-client-id"
|
||||||
|
AUTH_GOOGLE_SECRET="your-google-client-secret"
|
||||||
|
```
|
||||||
|
|
||||||
|
See [.env.example](.env.example) for all available options.
|
||||||
|
|
||||||
|
## Project Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
src/
|
||||||
|
├── app/
|
||||||
|
│ ├── [locale]/ # Internationalized routes
|
||||||
|
│ │ ├── (auth)/ # Authentication pages
|
||||||
|
│ │ ├── (marketing)/ # Public pages (home, blog, tools)
|
||||||
|
│ │ ├── (protected)/ # User dashboard, profile
|
||||||
|
│ │ └── (admin)/ # Admin panel
|
||||||
|
│ └── api/ # API routes
|
||||||
|
├── components/ # Reusable UI components
|
||||||
|
├── features/ # Feature modules
|
||||||
|
│ ├── auth/ # Authentication logic
|
||||||
|
│ ├── admin/ # Admin components
|
||||||
|
│ ├── tools/ # AI tools directory
|
||||||
|
│ └── navigation/ # Navigation components
|
||||||
|
├── lib/ # Core utilities
|
||||||
|
│ ├── auth.ts # Better Auth config
|
||||||
|
│ ├── db/ # Database schema & connection
|
||||||
|
│ └── email.ts # Email utilities
|
||||||
|
└── emails/ # React Email templates
|
||||||
|
|
||||||
|
messages/ # i18n translation files
|
||||||
|
public/ # Static assets
|
||||||
|
scripts/ # Build & admin scripts
|
||||||
|
```
|
||||||
|
|
||||||
|
## Tech Stack
|
||||||
|
|
||||||
|
### Frontend
|
||||||
|
- **Framework**: Next.js 16 (App Router)
|
||||||
|
- **UI Library**: React 19
|
||||||
|
- **Styling**: Tailwind CSS + Framer Motion
|
||||||
|
- **Components**: Custom components + Radix UI
|
||||||
|
- **Forms**: React Hook Form + Zod validation
|
||||||
|
- **Themes**: next-themes (dark/light mode)
|
||||||
|
|
||||||
|
### Backend
|
||||||
|
- **Authentication**: Better Auth
|
||||||
|
- **Database**: PostgreSQL + Drizzle ORM
|
||||||
|
- **Email**: Resend
|
||||||
|
- **Storage**: Cloudflare R2 / AWS S3
|
||||||
|
|
||||||
|
### Developer Experience
|
||||||
|
- **Language**: TypeScript (strict mode)
|
||||||
|
- **Linting**: ESLint
|
||||||
|
- **Package Manager**: pnpm
|
||||||
|
|
||||||
|
## Available Scripts
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pnpm dev # Start development server
|
||||||
|
pnpm build # Build for production
|
||||||
|
pnpm start # Start production server
|
||||||
|
pnpm lint # Run ESLint
|
||||||
|
|
||||||
|
# Database
|
||||||
|
pnpm db:generate # Generate Drizzle migrations
|
||||||
|
pnpm db:migrate # Run migrations
|
||||||
|
pnpm db:push # Push schema to database
|
||||||
|
pnpm db:studio # Open Drizzle Studio
|
||||||
|
|
||||||
|
# Admin
|
||||||
|
pnpm admin:setup # Create admin account
|
||||||
|
|
||||||
|
# Content
|
||||||
|
pnpm generate:blog-manifest # Generate blog manifest
|
||||||
|
```
|
||||||
|
|
||||||
|
## Deployment
|
||||||
|
|
||||||
|
### Vercel (Recommended)
|
||||||
|
|
||||||
|
1. Push your code to GitHub
|
||||||
|
2. Import project in [Vercel](https://vercel.com)
|
||||||
|
3. Add environment variables
|
||||||
|
4. Deploy
|
||||||
|
|
||||||
|
### Other Platforms
|
||||||
|
|
||||||
|
The template works with any platform supporting Node.js:
|
||||||
|
- Railway
|
||||||
|
- Render
|
||||||
|
- Docker
|
||||||
|
- Self-hosted
|
||||||
|
|
||||||
|
### Deployment Checklist
|
||||||
|
|
||||||
|
- [ ] Set up PostgreSQL database (Supabase, Neon, or Vercel Postgres)
|
||||||
|
- [ ] Configure Resend for email
|
||||||
|
- [ ] Update `BETTER_AUTH_URL` and `NEXT_PUBLIC_APP_URL` with production domain
|
||||||
|
- [ ] Run `pnpm db:push` to initialize database
|
||||||
|
- [ ] Create admin account with `pnpm admin:setup`
|
||||||
|
|
||||||
|
## Internationalization
|
||||||
|
|
||||||
|
The template supports multiple languages via `next-intl`:
|
||||||
|
|
||||||
|
- **Supported**: English (`en`), Chinese (`zh`)
|
||||||
|
- **Route Format**: `/en/...`, `/zh/...`
|
||||||
|
- **Translation Files**: `messages/en.json`, `messages/zh.json`
|
||||||
|
|
||||||
|
### Adding a New Language
|
||||||
|
|
||||||
|
1. Copy `messages/en.json` to `messages/{locale}.json`
|
||||||
|
2. Translate the content
|
||||||
|
3. Update `i18n.config.ts`
|
||||||
|
|
||||||
|
## Contributing
|
||||||
|
|
||||||
|
We welcome contributions! Please feel free to submit a Pull Request.
|
||||||
|
|
||||||
|
1. Fork the repository
|
||||||
|
2. Create your feature branch (`git checkout -b feature/amazing-feature`)
|
||||||
|
3. Commit your changes (`git commit -m 'Add some amazing feature'`)
|
||||||
|
4. Push to the branch (`git push origin feature/amazing-feature`)
|
||||||
|
5. Open a Pull Request
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
This project is licensed under the Apache License 2.0 - see the [LICENSE](LICENSE) file for details.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
<a name="中文"></a>
|
||||||
|
|
||||||
|
## 项目概览
|
||||||
|
|
||||||
|
Toolso.AI 是一个开源的 AI 工具导航站,旨在帮助用户发现各类优秀的 AI 工具。基于 Next.js 16、React 19 和 TypeScript 构建,拥有现代化的响应式界面、强大的搜索和筛选功能、多语言支持,以及完善的后台管理系统。
|
||||||
|
|
||||||
|
### 核心特性
|
||||||
|
|
||||||
|
| 特性 | 描述 | 优势 |
|
||||||
|
|------|------|------|
|
||||||
|
| **AI 工具目录** | 分类展示 AI 工具 | 轻松发现和探索 |
|
||||||
|
| **搜索与筛选** | 强大的搜索和分类筛选 | 快速找到合适的工具 |
|
||||||
|
| **工具提交** | 用户可提交新工具 | 社区驱动内容 |
|
||||||
|
| **管理后台** | 工具和用户管理 | 审核提交、管理内容 |
|
||||||
|
| **博客系统** | MDX 驱动的多语言博客 | SEO 优化的 AI 资讯 |
|
||||||
|
| **国际化** | next-intl 国际化 | 中英文开箱即用 |
|
||||||
|
| **身份认证** | Better Auth + Google OAuth | 用户账户和提交功能 |
|
||||||
|
| **主题切换** | 深色/浅色模式 | 舒适的浏览体验 |
|
||||||
|
|
||||||
|
## 快速开始
|
||||||
|
|
||||||
|
### 前置要求
|
||||||
|
|
||||||
|
- Node.js 18+
|
||||||
|
- pnpm 8+
|
||||||
|
- PostgreSQL 数据库
|
||||||
|
|
||||||
|
### 安装步骤
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 克隆仓库
|
||||||
|
git clone https://github.com/coolchange-ai/toolso-ai-open.git
|
||||||
|
cd toolso-ai-open
|
||||||
|
|
||||||
|
# 安装依赖
|
||||||
|
pnpm install
|
||||||
|
|
||||||
|
# 设置环境变量
|
||||||
|
cp .env.example .env.local
|
||||||
|
|
||||||
|
# 推送数据库结构
|
||||||
|
pnpm db:push
|
||||||
|
|
||||||
|
# 启动开发服务器
|
||||||
|
pnpm dev
|
||||||
|
```
|
||||||
|
|
||||||
|
打开 [http://localhost:3000](http://localhost:3000) 查看应用。
|
||||||
|
|
||||||
|
### 环境变量配置
|
||||||
|
|
||||||
|
创建 `.env.local` 并配置:
|
||||||
|
|
||||||
|
```env
|
||||||
|
# 必需配置
|
||||||
|
DATABASE_URL="postgresql://user:password@host/db?sslmode=require"
|
||||||
|
BETTER_AUTH_SECRET="至少32字符的密钥"
|
||||||
|
BETTER_AUTH_URL="http://localhost:3000"
|
||||||
|
NEXT_PUBLIC_APP_URL="http://localhost:3000"
|
||||||
|
|
||||||
|
# 邮件(认证功能必需)
|
||||||
|
RESEND_API_KEY="re_your_api_key"
|
||||||
|
RESEND_FROM_EMAIL="Your App <noreply@yourdomain.com>"
|
||||||
|
|
||||||
|
# 可选:Google OAuth
|
||||||
|
AUTH_GOOGLE_ID="your-google-client-id"
|
||||||
|
AUTH_GOOGLE_SECRET="your-google-client-secret"
|
||||||
|
```
|
||||||
|
|
||||||
|
完整配置请参考 [.env.example](.env.example)。
|
||||||
|
|
||||||
|
## 项目结构
|
||||||
|
|
||||||
|
```
|
||||||
|
src/
|
||||||
|
├── app/
|
||||||
|
│ ├── [locale]/ # 国际化路由
|
||||||
|
│ │ ├── (auth)/ # 认证页面
|
||||||
|
│ │ ├── (marketing)/ # 公开页面(首页、博客、工具)
|
||||||
|
│ │ ├── (protected)/ # 用户仪表板、个人资料
|
||||||
|
│ │ └── (admin)/ # 管理后台
|
||||||
|
│ └── api/ # API 路由
|
||||||
|
├── components/ # 可复用 UI 组件
|
||||||
|
├── features/ # 功能模块
|
||||||
|
│ ├── auth/ # 认证逻辑
|
||||||
|
│ ├── admin/ # 管理后台组件
|
||||||
|
│ ├── tools/ # AI 工具目录
|
||||||
|
│ └── navigation/ # 导航组件
|
||||||
|
├── lib/ # 核心工具库
|
||||||
|
│ ├── auth.ts # Better Auth 配置
|
||||||
|
│ ├── db/ # 数据库 Schema 和连接
|
||||||
|
│ └── email.ts # 邮件工具
|
||||||
|
└── emails/ # React Email 模板
|
||||||
|
|
||||||
|
messages/ # 国际化翻译文件
|
||||||
|
public/ # 静态资源
|
||||||
|
scripts/ # 构建和管理脚本
|
||||||
|
```
|
||||||
|
|
||||||
|
## 技术栈
|
||||||
|
|
||||||
|
### 前端
|
||||||
|
- **框架**: Next.js 16 (App Router)
|
||||||
|
- **UI 库**: React 19
|
||||||
|
- **样式**: Tailwind CSS + Framer Motion
|
||||||
|
- **组件**: 自定义组件 + Radix UI
|
||||||
|
- **表单**: React Hook Form + Zod 验证
|
||||||
|
- **主题**: next-themes (深色/浅色模式)
|
||||||
|
|
||||||
|
### 后端
|
||||||
|
- **身份认证**: Better Auth
|
||||||
|
- **数据库**: PostgreSQL + Drizzle ORM
|
||||||
|
- **邮件**: Resend
|
||||||
|
- **存储**: Cloudflare R2 / AWS S3
|
||||||
|
|
||||||
|
### 开发体验
|
||||||
|
- **语言**: TypeScript (严格模式)
|
||||||
|
- **代码检查**: ESLint
|
||||||
|
- **包管理器**: pnpm
|
||||||
|
|
||||||
|
## 可用脚本
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pnpm dev # 启动开发服务器
|
||||||
|
pnpm build # 构建生产版本
|
||||||
|
pnpm start # 启动生产服务器
|
||||||
|
pnpm lint # 运行 ESLint
|
||||||
|
|
||||||
|
# 数据库
|
||||||
|
pnpm db:generate # 生成 Drizzle 迁移
|
||||||
|
pnpm db:migrate # 运行迁移
|
||||||
|
pnpm db:push # 推送 schema 到数据库
|
||||||
|
pnpm db:studio # 打开 Drizzle Studio
|
||||||
|
|
||||||
|
# 管理员
|
||||||
|
pnpm admin:setup # 创建管理员账户
|
||||||
|
|
||||||
|
# 内容
|
||||||
|
pnpm generate:blog-manifest # 生成博客清单
|
||||||
|
```
|
||||||
|
|
||||||
|
## 部署指南
|
||||||
|
|
||||||
|
### Vercel(推荐)
|
||||||
|
|
||||||
|
1. 将代码推送到 GitHub
|
||||||
|
2. 在 [Vercel](https://vercel.com) 导入项目
|
||||||
|
3. 添加环境变量
|
||||||
|
4. 部署
|
||||||
|
|
||||||
|
### 其他平台
|
||||||
|
|
||||||
|
模板支持任何支持 Node.js 的平台:
|
||||||
|
- Railway
|
||||||
|
- Render
|
||||||
|
- Docker
|
||||||
|
- 自托管
|
||||||
|
|
||||||
|
### 部署清单
|
||||||
|
|
||||||
|
- [ ] 设置 PostgreSQL 数据库(Supabase、Neon 或 Vercel Postgres)
|
||||||
|
- [ ] 配置 Resend 邮件服务
|
||||||
|
- [ ] 更新 `BETTER_AUTH_URL` 和 `NEXT_PUBLIC_APP_URL` 为生产域名
|
||||||
|
- [ ] 运行 `pnpm db:push` 初始化数据库
|
||||||
|
- [ ] 使用 `pnpm admin:setup` 创建管理员账户
|
||||||
|
|
||||||
|
## 国际化
|
||||||
|
|
||||||
|
模板通过 `next-intl` 支持多语言:
|
||||||
|
|
||||||
|
- **已支持**: 英文 (`en`)、中文 (`zh`)
|
||||||
|
- **路由格式**: `/en/...`、`/zh/...`
|
||||||
|
- **翻译文件**: `messages/en.json`、`messages/zh.json`
|
||||||
|
|
||||||
|
### 添加新语言
|
||||||
|
|
||||||
|
1. 复制 `messages/en.json` 到 `messages/{语言代码}.json`
|
||||||
|
2. 翻译内容
|
||||||
|
3. 更新 `i18n.config.ts`
|
||||||
|
|
||||||
|
## 贡献指南
|
||||||
|
|
||||||
|
欢迎贡献!请随时提交 Pull Request。
|
||||||
|
|
||||||
|
1. Fork 仓库
|
||||||
|
2. 创建特性分支 (`git checkout -b feature/amazing-feature`)
|
||||||
|
3. 提交更改 (`git commit -m 'Add some amazing feature'`)
|
||||||
|
4. 推送到分支 (`git push origin feature/amazing-feature`)
|
||||||
|
5. 开启 Pull Request
|
||||||
|
|
||||||
|
## 开源协议
|
||||||
|
|
||||||
|
本项目基于 Apache License 2.0 开源 - 详情请查看 [LICENSE](LICENSE) 文件。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
<div align="center">
|
||||||
|
|
||||||
|
## Related Projects
|
||||||
|
|
||||||
|
| Project | Description | Link |
|
||||||
|
|---------|-------------|------|
|
||||||
|
| **Toolso.AI** | AI Tools Directory | [https://toolso.ai](https://toolso.ai) |
|
||||||
|
| **CoolChange.AI** | AI Development Studio | [https://coolchange.ai](https://coolchange.ai) |
|
||||||
|
| **chatPPT.app** | AI Presentation Generator | [https://chatppt.app](https://chatppt.app) |
|
||||||
|
| **AIHealthGuide** | AI Health Assistant | [https://aihealthguide.app](https://aihealthguide.app) |
|
||||||
|
|
||||||
|
<br/>
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
<br/>
|
||||||
|
|
||||||
|
**Built with love by [CoolChange.AI](https://coolchange.ai)**
|
||||||
|
|
||||||
|
<br/>
|
||||||
|
|
||||||
|
If you find this project helpful, please consider giving it a star!
|
||||||
|
|
||||||
|
</div>
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
import type { Config } from "drizzle-kit";
|
||||||
|
import * as dotenv from "dotenv";
|
||||||
|
|
||||||
|
// 加载环境变量
|
||||||
|
dotenv.config({ path: ".env.local" });
|
||||||
|
|
||||||
|
export default {
|
||||||
|
schema: "./src/lib/db/schema.ts",
|
||||||
|
out: "./src/drizzle",
|
||||||
|
dialect: "postgresql",
|
||||||
|
dbCredentials: {
|
||||||
|
url: process.env.DATABASE_URL!,
|
||||||
|
},
|
||||||
|
} satisfies Config;
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
import { type MDXComponents } from "mdx/types";
|
||||||
|
import Image, { type ImageProps } from "next/image";
|
||||||
|
|
||||||
|
export function useMDXComponents(components: MDXComponents) {
|
||||||
|
return {
|
||||||
|
...components,
|
||||||
|
Image: (props: ImageProps) => <Image {...props} />,
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,627 @@
|
|||||||
|
{
|
||||||
|
"common": {
|
||||||
|
"brand": {
|
||||||
|
"name": "Toolso.AI",
|
||||||
|
"tagline": "Discover the Best AI Tools",
|
||||||
|
"copyright": "Copyright © 2025",
|
||||||
|
"allRightsReserved": "All rights reserved"
|
||||||
|
},
|
||||||
|
"actions": {
|
||||||
|
"signIn": "Sign in",
|
||||||
|
"signUp": "Sign Up",
|
||||||
|
"signOut": "Sign Out",
|
||||||
|
"getStarted": "Get Started",
|
||||||
|
"startFree": "Start Free",
|
||||||
|
"learnMore": "Learn More",
|
||||||
|
"viewAll": "View All",
|
||||||
|
"submit": "Submit",
|
||||||
|
"cancel": "Cancel",
|
||||||
|
"save": "Save",
|
||||||
|
"delete": "Delete",
|
||||||
|
"edit": "Edit",
|
||||||
|
"update": "Update",
|
||||||
|
"continue": "Continue",
|
||||||
|
"back": "Back",
|
||||||
|
"next": "Next"
|
||||||
|
},
|
||||||
|
"status": {
|
||||||
|
"loading": "Loading...",
|
||||||
|
"verified": "Verified",
|
||||||
|
"pending": "Pending",
|
||||||
|
"active": "Active",
|
||||||
|
"inactive": "Inactive"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"navigation": {
|
||||||
|
"main": {
|
||||||
|
"tools": "Tools",
|
||||||
|
"blog": "Blog",
|
||||||
|
"contact": "Contact",
|
||||||
|
"dashboard": "Dashboard",
|
||||||
|
"profile": "Profile"
|
||||||
|
},
|
||||||
|
"footer": {
|
||||||
|
"legal": {
|
||||||
|
"terms": "Terms of Service",
|
||||||
|
"privacy": "Privacy Policy",
|
||||||
|
"cookies": "Cookie Policy"
|
||||||
|
},
|
||||||
|
"social": {
|
||||||
|
"twitter": "Twitter",
|
||||||
|
"linkedin": "LinkedIn",
|
||||||
|
"github": "GitHub"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"hero": {
|
||||||
|
"badge": "Discover the Best AI Tools",
|
||||||
|
"title": "Discover the Best AI Tools",
|
||||||
|
"description": "Explore our curated collection of AI tools. Find the perfect tools for productivity, creativity, development, and more.",
|
||||||
|
"cta": {
|
||||||
|
"primary": "Explore Tools",
|
||||||
|
"secondary": "Read Blog"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"features": {
|
||||||
|
"title": "Everything You Need to Build Fast",
|
||||||
|
"subtitle": "Stop wasting time on boilerplate. Focus on your unique features while this template handles the foundation.",
|
||||||
|
"items": {
|
||||||
|
"aiOptimized": {
|
||||||
|
"title": "Modern Tech Stack",
|
||||||
|
"description": "Built with Next.js 14, TypeScript, Tailwind CSS, and PostgreSQL. Production-ready from day one."
|
||||||
|
},
|
||||||
|
"zeroPipeline": {
|
||||||
|
"title": "Authentication Ready",
|
||||||
|
"description": "Email/password and Google OAuth configured. Session management and protected routes ready to use."
|
||||||
|
},
|
||||||
|
"nonTechnical": {
|
||||||
|
"title": "Admin Dashboard",
|
||||||
|
"description": "Complete admin panel for user management. Monitor your users and manage permissions easily."
|
||||||
|
},
|
||||||
|
"productionGrade": {
|
||||||
|
"title": "Blog System Included",
|
||||||
|
"description": "MDX-powered blog with i18n support. Create content in multiple languages with SEO optimization."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"testimonials": {
|
||||||
|
"title": "Trusted by AI Enthusiasts",
|
||||||
|
"subtitle": "Join thousands of users discovering the best AI tools for their workflows.",
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"name": "Sarah Chen",
|
||||||
|
"quote": "Toolso.AI helped me find the perfect AI writing assistant. The categorization makes discovery so easy!",
|
||||||
|
"designation": "Content Creator"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Michael Park",
|
||||||
|
"quote": "Finally, a comprehensive AI tools directory that's well-organized. Saved me hours of research.",
|
||||||
|
"designation": "Product Manager"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Emily Rodriguez",
|
||||||
|
"quote": "I discover new AI tools every week on Toolso.AI. It's become my go-to resource for AI exploration.",
|
||||||
|
"designation": "Startup Founder"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "David Kim",
|
||||||
|
"quote": "The search and filter features are incredibly helpful. Found exactly what I needed in minutes.",
|
||||||
|
"designation": "Software Engineer"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Lisa Wang",
|
||||||
|
"quote": "Love the multilingual support! Makes it easy to explore AI tools in both English and Chinese.",
|
||||||
|
"designation": "Digital Marketer"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "James Liu",
|
||||||
|
"quote": "Great resource for staying up-to-date with the latest AI tools. The blog section is also very informative.",
|
||||||
|
"designation": "AI Researcher"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"companies": {
|
||||||
|
"title": "Built with Industry-Leading Technologies",
|
||||||
|
"subtitle": "Built on modern stack trusted by the world's best teams"
|
||||||
|
},
|
||||||
|
"gridFeatures": {
|
||||||
|
"aiReady": {
|
||||||
|
"title": "Extensible Architecture",
|
||||||
|
"description": "Clean, modular code structure. Easy to add new features and integrations."
|
||||||
|
},
|
||||||
|
"creem": {
|
||||||
|
"title": "Database Ready",
|
||||||
|
"description": "PostgreSQL with Drizzle ORM configured. Type-safe queries and migrations ready."
|
||||||
|
},
|
||||||
|
"auth": {
|
||||||
|
"title": "Authentication System",
|
||||||
|
"description": "Better Auth with email/password and OAuth. Session management and protected routes ready."
|
||||||
|
},
|
||||||
|
"deploy": {
|
||||||
|
"title": "One-Click Deploy",
|
||||||
|
"description": "Deploy to Vercel, Railway, or any platform instantly. Production-ready from day one."
|
||||||
|
},
|
||||||
|
"typescript": {
|
||||||
|
"title": "TypeScript + Tailwind",
|
||||||
|
"description": "Modern tech stack with Next.js 14, TypeScript, Tailwind CSS, and shadcn/ui components."
|
||||||
|
},
|
||||||
|
"database": {
|
||||||
|
"title": "Admin Dashboard",
|
||||||
|
"description": "Complete admin panel for user management and system monitoring."
|
||||||
|
},
|
||||||
|
"seo": {
|
||||||
|
"title": "SEO & Marketing",
|
||||||
|
"description": "Meta tags, Open Graph, sitemap, and analytics. Everything for organic growth."
|
||||||
|
},
|
||||||
|
"vibeCoding": {
|
||||||
|
"title": "Blog System",
|
||||||
|
"description": "MDX-powered blog with multi-language support. Create content that ranks."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"cta": {
|
||||||
|
"title": "Find Your Perfect AI Tool",
|
||||||
|
"description": "Browse our curated collection of AI tools and discover what works best for you.",
|
||||||
|
"button": "Explore Tools"
|
||||||
|
},
|
||||||
|
"auth": {
|
||||||
|
"login": {
|
||||||
|
"title": "Sign in to your account",
|
||||||
|
"emailLabel": "Email address",
|
||||||
|
"emailPlaceholder": "hello@example.com",
|
||||||
|
"passwordLabel": "Password",
|
||||||
|
"passwordPlaceholder": "••••••••",
|
||||||
|
"forgotPassword": "Forgot password?",
|
||||||
|
"noAccount": "Don't have an account?",
|
||||||
|
"signUpLink": "Sign Up",
|
||||||
|
"signInButton": "Sign in",
|
||||||
|
"signingIn": "Signing in...",
|
||||||
|
"errors": {
|
||||||
|
"invalidEmail": "Please enter valid email",
|
||||||
|
"emailRequired": "Please enter email",
|
||||||
|
"passwordRequired": "Please enter password",
|
||||||
|
"loginFailed": "Login failed. Please try again.",
|
||||||
|
"googleLoginFailed": "Google login failed. Please try again."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"signup": {
|
||||||
|
"title": "Sign up for an account",
|
||||||
|
"nameLabel": "Full name",
|
||||||
|
"namePlaceholder": "John Doe",
|
||||||
|
"emailLabel": "Email address",
|
||||||
|
"emailPlaceholder": "hello@example.com",
|
||||||
|
"passwordLabel": "Password",
|
||||||
|
"passwordPlaceholder": "••••••••",
|
||||||
|
"hasAccount": "Already have an account?",
|
||||||
|
"signInLink": "Sign in",
|
||||||
|
"signUpButton": "Sign Up",
|
||||||
|
"signingUp": "Signing up...",
|
||||||
|
"errors": {
|
||||||
|
"nameRequired": "Please enter your name",
|
||||||
|
"invalidEmail": "Please enter valid email",
|
||||||
|
"emailRequired": "Please enter email",
|
||||||
|
"passwordRequired": "Please enter password",
|
||||||
|
"signupFailed": "Sign up failed. Please try again.",
|
||||||
|
"googleSignupFailed": "Google sign up failed. Please try again."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"social": {
|
||||||
|
"googleSignIn": "Continue with Google",
|
||||||
|
"or": "Or continue with"
|
||||||
|
},
|
||||||
|
"checkEmail": {
|
||||||
|
"title": "Check Your Email",
|
||||||
|
"description": "We've sent a verification link to your email address. Please click the link to verify your account and get started.",
|
||||||
|
"notReceived": "Didn't receive the email?",
|
||||||
|
"checkSpam": "Check your spam or junk folder",
|
||||||
|
"checkEmailCorrect": "Make sure you entered the correct email",
|
||||||
|
"waitAndCheck": "Wait a few minutes and check again",
|
||||||
|
"resendButton": "Resend Verification Email",
|
||||||
|
"resending": "Sending...",
|
||||||
|
"resendSuccess": "Verification email sent successfully! Please check your inbox.",
|
||||||
|
"resendError": "Failed to resend verification email.",
|
||||||
|
"backToLogin": "Back to Login",
|
||||||
|
"securityNote": "For your security, the verification link will expire in 24 hours."
|
||||||
|
},
|
||||||
|
"verifyEmail": {
|
||||||
|
"verifying": "Verifying Email",
|
||||||
|
"verifyingDescription": "Please wait while we verify your email address...",
|
||||||
|
"success": "Email Verified!",
|
||||||
|
"successDescription": "Your email has been successfully verified! Redirecting to login...",
|
||||||
|
"alreadyVerified": "Already Verified",
|
||||||
|
"failed": "Verification Failed",
|
||||||
|
"goToLogin": "Go to Login",
|
||||||
|
"backToSignUp": "Back to Sign Up",
|
||||||
|
"requestNew": "Request New Verification Email",
|
||||||
|
"checkSpamNote": "Check your spam folder if you don't see our emails"
|
||||||
|
},
|
||||||
|
"emailVerifiedGuard": {
|
||||||
|
"title": "Email Verification Required",
|
||||||
|
"description": "Please verify your email address to access this page. Check your inbox for the verification link we sent to:",
|
||||||
|
"resendButton": "Resend Verification Email",
|
||||||
|
"resending": "Sending...",
|
||||||
|
"resendSuccess": "Verification email sent! Please check your inbox.",
|
||||||
|
"resendError": "Failed to send verification email. Please try again.",
|
||||||
|
"signOutLink": "Sign out and try a different account"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Admin": {
|
||||||
|
"sidebar": {
|
||||||
|
"title": "Admin Panel",
|
||||||
|
"subtitle": "System Management",
|
||||||
|
"dashboard": "Dashboard",
|
||||||
|
"tools": "Tools",
|
||||||
|
"categories": "Categories",
|
||||||
|
"tags": "Tags",
|
||||||
|
"users": "Users",
|
||||||
|
"backToDashboard": "Back to User Dashboard"
|
||||||
|
},
|
||||||
|
"header": {
|
||||||
|
"welcome": "Welcome",
|
||||||
|
"adminBadge": "Admin"
|
||||||
|
},
|
||||||
|
"dashboard": {
|
||||||
|
"title": "Admin Dashboard",
|
||||||
|
"totalUsers": "Total Users",
|
||||||
|
"activeUsers": "Active Users",
|
||||||
|
"recentUsers": "Recent Users",
|
||||||
|
"viewAllUsers": "View All Users"
|
||||||
|
},
|
||||||
|
"toolsManagement": {
|
||||||
|
"title": "Tools Management",
|
||||||
|
"totalCount": "{count} tools",
|
||||||
|
"searchPlaceholder": "Search tool name or slug...",
|
||||||
|
"allStatus": "All Status",
|
||||||
|
"published": "Published",
|
||||||
|
"draft": "Draft",
|
||||||
|
"addTool": "Add Tool",
|
||||||
|
"tool": "Tool",
|
||||||
|
"slug": "Slug",
|
||||||
|
"status": "Status",
|
||||||
|
"createdAt": "Created",
|
||||||
|
"actions": "Actions",
|
||||||
|
"noData": "No tools data",
|
||||||
|
"previous": "Previous",
|
||||||
|
"next": "Next",
|
||||||
|
"confirmDelete": "Are you sure you want to delete \"{name}\"? This action cannot be undone.",
|
||||||
|
"deleted": "Tool deleted",
|
||||||
|
"deleteFailed": "Delete failed",
|
||||||
|
"statusUpdated": "Status updated",
|
||||||
|
"statusUpdateFailed": "Status update failed",
|
||||||
|
"toDraft": "Set to draft",
|
||||||
|
"publish": "Publish",
|
||||||
|
"edit": "Edit",
|
||||||
|
"visitWebsite": "Visit website",
|
||||||
|
"delete": "Delete",
|
||||||
|
"modal": {
|
||||||
|
"editTitle": "Edit Tool",
|
||||||
|
"addTitle": "Add Tool",
|
||||||
|
"basicInfo": "Basic Information",
|
||||||
|
"linksAndImages": "Links & Images",
|
||||||
|
"categories": "Categories",
|
||||||
|
"tags": "Tags",
|
||||||
|
"slug": "Slug",
|
||||||
|
"status": "Status",
|
||||||
|
"nameEn": "English Name",
|
||||||
|
"nameZh": "Chinese Name",
|
||||||
|
"descriptionEn": "English Description",
|
||||||
|
"descriptionZh": "Chinese Description",
|
||||||
|
"domain": "Domain",
|
||||||
|
"websiteUrl": "Website URL",
|
||||||
|
"logoUrl": "Logo URL",
|
||||||
|
"coverImageUrl": "Cover Image URL",
|
||||||
|
"cancel": "Cancel",
|
||||||
|
"saving": "Saving...",
|
||||||
|
"saveChanges": "Save Changes",
|
||||||
|
"createTool": "Create Tool",
|
||||||
|
"requiredField": "Please fill in required fields",
|
||||||
|
"updated": "Tool updated",
|
||||||
|
"created": "Tool created",
|
||||||
|
"updateFailed": "Update failed",
|
||||||
|
"createFailed": "Create failed"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"categoriesManagement": {
|
||||||
|
"title": "Categories Management",
|
||||||
|
"addCategory": "Add Category",
|
||||||
|
"order": "Order",
|
||||||
|
"category": "Category",
|
||||||
|
"slug": "Slug",
|
||||||
|
"toolCount": "Tools",
|
||||||
|
"toolCountLabel": "{count} tools",
|
||||||
|
"actions": "Actions",
|
||||||
|
"noData": "No categories data",
|
||||||
|
"edit": "Edit",
|
||||||
|
"delete": "Delete",
|
||||||
|
"cannotDelete": "This category has {count} tools, cannot delete",
|
||||||
|
"confirmDelete": "Are you sure you want to delete category \"{name}\"?",
|
||||||
|
"deleted": "Category deleted",
|
||||||
|
"deleteFailed": "Delete failed",
|
||||||
|
"modal": {
|
||||||
|
"editTitle": "Edit Category",
|
||||||
|
"addTitle": "Add Category",
|
||||||
|
"slug": "Slug",
|
||||||
|
"icon": "Icon (emoji)",
|
||||||
|
"nameEn": "English Name",
|
||||||
|
"nameZh": "Chinese Name",
|
||||||
|
"sortOrder": "Sort Order",
|
||||||
|
"descriptionEn": "English Description",
|
||||||
|
"descriptionZh": "Chinese Description",
|
||||||
|
"cancel": "Cancel",
|
||||||
|
"saving": "Saving...",
|
||||||
|
"saveChanges": "Save Changes",
|
||||||
|
"createCategory": "Create Category",
|
||||||
|
"requiredField": "Please fill in required fields",
|
||||||
|
"updated": "Category updated",
|
||||||
|
"created": "Category created",
|
||||||
|
"updateFailed": "Update failed",
|
||||||
|
"createFailed": "Create failed"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"tagsManagement": {
|
||||||
|
"title": "Tags Management",
|
||||||
|
"searchPlaceholder": "Search tags...",
|
||||||
|
"addTag": "Add Tag",
|
||||||
|
"toolCountLabel": "{count} tools",
|
||||||
|
"noData": "No tags data",
|
||||||
|
"edit": "Edit",
|
||||||
|
"delete": "Delete",
|
||||||
|
"confirmDeleteWithTools": "This tag is used by {count} tools. Deleting it will remove the tag from these tools. Continue?",
|
||||||
|
"confirmDelete": "Are you sure you want to delete tag \"{name}\"?",
|
||||||
|
"deleted": "Tag deleted",
|
||||||
|
"deleteFailed": "Delete failed",
|
||||||
|
"modal": {
|
||||||
|
"editTitle": "Edit Tag",
|
||||||
|
"addTitle": "Add Tag",
|
||||||
|
"slug": "Slug",
|
||||||
|
"nameEn": "English Name",
|
||||||
|
"nameZh": "Chinese Name",
|
||||||
|
"cancel": "Cancel",
|
||||||
|
"saving": "Saving...",
|
||||||
|
"saveChanges": "Save Changes",
|
||||||
|
"createTag": "Create Tag",
|
||||||
|
"requiredField": "Please fill in required fields",
|
||||||
|
"updated": "Tag updated",
|
||||||
|
"created": "Tag created",
|
||||||
|
"updateFailed": "Update failed",
|
||||||
|
"createFailed": "Create failed"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"users": {
|
||||||
|
"searchPlaceholder": "Search by name or email...",
|
||||||
|
"user": "User",
|
||||||
|
"role": "Role",
|
||||||
|
"status": "Status",
|
||||||
|
"joined": "Joined",
|
||||||
|
"actions": "Actions",
|
||||||
|
"admin": "Admin",
|
||||||
|
"banned": "Banned",
|
||||||
|
"active": "Active",
|
||||||
|
"unverified": "Unverified",
|
||||||
|
"verified": "Verified",
|
||||||
|
"emailVerified": "Email Verified",
|
||||||
|
"edit": "Edit",
|
||||||
|
"ban": "Ban",
|
||||||
|
"unban": "Unban",
|
||||||
|
"viewDetails": "View Details",
|
||||||
|
"banReason": "Ban Reason",
|
||||||
|
"banExpires": "Ban Expires",
|
||||||
|
"userDetails": "User Details",
|
||||||
|
"name": "Name",
|
||||||
|
"email": "Email",
|
||||||
|
"lastActive": "Last Active",
|
||||||
|
"close": "Close",
|
||||||
|
"roleUpdated": "Role updated successfully",
|
||||||
|
"roleUpdateFailed": "Failed to update role",
|
||||||
|
"userBanned": "User has been banned",
|
||||||
|
"userUnbanned": "User has been unbanned",
|
||||||
|
"banFailed": "Failed to ban/unban user",
|
||||||
|
"cancel": "Cancel",
|
||||||
|
"pagination": {
|
||||||
|
"previous": "Previous",
|
||||||
|
"next": "Next",
|
||||||
|
"page": "Page {current} of {total}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"dashboard": {
|
||||||
|
"title": "Dashboard",
|
||||||
|
"welcome": "Welcome back",
|
||||||
|
"cards": {
|
||||||
|
"personalInfo": {
|
||||||
|
"title": "Personal Info",
|
||||||
|
"labels": {
|
||||||
|
"name": "Name",
|
||||||
|
"email": "Email",
|
||||||
|
"status": "Status",
|
||||||
|
"notSet": "Not set"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"quickActions": {
|
||||||
|
"title": "Quick Actions",
|
||||||
|
"editProfile": "Edit Profile",
|
||||||
|
"accountSettings": "Account Settings",
|
||||||
|
"securitySettings": "Security Settings"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"profile": {
|
||||||
|
"title": "Profile",
|
||||||
|
"subtitle": "Manage your account settings and preferences",
|
||||||
|
"sections": {
|
||||||
|
"basicInfo": {
|
||||||
|
"title": "Basic Information",
|
||||||
|
"fullName": "Full Name",
|
||||||
|
"email": "Email Address",
|
||||||
|
"verificationStatus": "Verification Status",
|
||||||
|
"notSet": "Not set",
|
||||||
|
"nameNotSet": "Name not set",
|
||||||
|
"accountId": "Account ID"
|
||||||
|
},
|
||||||
|
"accountSettings": {
|
||||||
|
"title": "Account Settings",
|
||||||
|
"memberSince": "Member Since"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"contact": {
|
||||||
|
"title": "Contact Us",
|
||||||
|
"subtitle": "We'd love to hear from you. Send us a message and we'll respond as soon as possible.",
|
||||||
|
"description": "Please reach out to us and we will get back to you shortly.",
|
||||||
|
"testimonial": {
|
||||||
|
"title": "We're here to help",
|
||||||
|
"description": "Our team is ready to assist you with any questions or feedback."
|
||||||
|
},
|
||||||
|
"form": {
|
||||||
|
"nameLabel": "Full Name",
|
||||||
|
"namePlaceholder": "John Doe",
|
||||||
|
"emailLabel": "Email address",
|
||||||
|
"emailPlaceholder": "hello@example.com",
|
||||||
|
"companyLabel": "Company",
|
||||||
|
"companyPlaceholder": "Your Company",
|
||||||
|
"messageLabel": "Message",
|
||||||
|
"messagePlaceholder": "Enter your message here",
|
||||||
|
"submitButton": "Submit",
|
||||||
|
"submitting": "Sending...",
|
||||||
|
"submitSuccess": "Message sent successfully!",
|
||||||
|
"submitError": "Failed to send message. Please try again."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"blog": {
|
||||||
|
"title": "Blog",
|
||||||
|
"subtitle": "Latest insights and updates from our team",
|
||||||
|
"readMore": "Read More",
|
||||||
|
"minRead": "min read"
|
||||||
|
},
|
||||||
|
"errors": {
|
||||||
|
"404": {
|
||||||
|
"title": "404 - Page Not Found",
|
||||||
|
"message": "The page you are looking for doesn't exist.",
|
||||||
|
"backHome": "Go to Homepage"
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"title": "Something Went Wrong",
|
||||||
|
"message": "We're experiencing technical difficulties. Please try again later.",
|
||||||
|
"backHome": "Back to Home"
|
||||||
|
},
|
||||||
|
"general": {
|
||||||
|
"title": "An Error Occurred",
|
||||||
|
"message": "Something unexpected happened. Please try again.",
|
||||||
|
"tryAgain": "Try Again"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"featuredTestimonials": {
|
||||||
|
"people": [
|
||||||
|
{
|
||||||
|
"name": "John Doe",
|
||||||
|
"designation": "Software Engineer"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Jane Smith",
|
||||||
|
"designation": "Product Manager"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Bob Johnson",
|
||||||
|
"designation": "Designer"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Alice Brown",
|
||||||
|
"designation": "Developer"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"footer": {
|
||||||
|
"tagline": "Discover the best AI tools to boost your productivity.",
|
||||||
|
"product": {
|
||||||
|
"title": "Product",
|
||||||
|
"features": "Features",
|
||||||
|
"documentation": "Documentation"
|
||||||
|
},
|
||||||
|
"company": {
|
||||||
|
"title": "Company",
|
||||||
|
"about": "About",
|
||||||
|
"blog": "Blog"
|
||||||
|
},
|
||||||
|
"legal": {
|
||||||
|
"title": "Legal",
|
||||||
|
"privacy": "Privacy",
|
||||||
|
"terms": "Terms",
|
||||||
|
"cookies": "Cookies"
|
||||||
|
},
|
||||||
|
"social": {
|
||||||
|
"title": "Social",
|
||||||
|
"twitter": "Twitter",
|
||||||
|
"linkedin": "LinkedIn",
|
||||||
|
"github": "GitHub"
|
||||||
|
},
|
||||||
|
"newsletter": {
|
||||||
|
"title": "Subscribe to Newsletter",
|
||||||
|
"description": "Get the latest updates and news directly in your inbox.",
|
||||||
|
"placeholder": "Enter your email",
|
||||||
|
"subscribe": "Subscribe"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"tools": {
|
||||||
|
"title": "AI Tools Directory",
|
||||||
|
"subtitle": "Discover the best AI tools to boost your productivity",
|
||||||
|
"searchPlaceholder": "Search tools...",
|
||||||
|
"all": "All",
|
||||||
|
"foundTools": "Found {count} tools",
|
||||||
|
"visitWebsite": "Visit website",
|
||||||
|
"noTools": "No tools found",
|
||||||
|
"backToTools": "Back to tools",
|
||||||
|
"about": "About",
|
||||||
|
"categories": "Categories",
|
||||||
|
"tags": "Tags",
|
||||||
|
"visitButton": "Visit Website",
|
||||||
|
"toolInfo": "Tool Info",
|
||||||
|
"domain": "Domain",
|
||||||
|
"added": "Added",
|
||||||
|
"notFound": "Tool Not Found"
|
||||||
|
},
|
||||||
|
"seo": {
|
||||||
|
"home": {
|
||||||
|
"title": "Toolso.AI - Discover the Best AI Tools Directory",
|
||||||
|
"description": "Explore our curated collection of AI tools across various categories. Find the perfect AI tools for productivity, creativity, development, and more.",
|
||||||
|
"ogImage": "https://toolso.ai/banner.png"
|
||||||
|
},
|
||||||
|
"blog": {
|
||||||
|
"title": "Blog - Toolso.AI",
|
||||||
|
"description": "Latest insights, tutorials and updates about AI tools. Stay informed about the best AI tools and industry trends.",
|
||||||
|
"ogImage": "https://toolso.ai/banner.png"
|
||||||
|
},
|
||||||
|
"contact": {
|
||||||
|
"title": "Contact Us - Toolso.AI",
|
||||||
|
"description": "Get in touch with us. We'd love to hear your feedback and tool suggestions.",
|
||||||
|
"ogImage": "https://toolso.ai/banner.png"
|
||||||
|
},
|
||||||
|
"login": {
|
||||||
|
"title": "Login - Toolso.AI",
|
||||||
|
"description": "Sign in to your account to submit tools, save favorites, and contribute to our AI tools directory.",
|
||||||
|
"ogImage": "https://toolso.ai/banner.png"
|
||||||
|
},
|
||||||
|
"signup": {
|
||||||
|
"title": "Sign Up - Toolso.AI",
|
||||||
|
"description": "Create your account and start contributing to our AI tools directory. Submit tools and join our community.",
|
||||||
|
"ogImage": "https://toolso.ai/banner.png"
|
||||||
|
},
|
||||||
|
"terms": {
|
||||||
|
"title": "Terms of Service - Toolso.AI",
|
||||||
|
"description": "Read our terms of service to understand the rules and guidelines for using Toolso.AI.",
|
||||||
|
"ogImage": "https://toolso.ai/banner.png"
|
||||||
|
},
|
||||||
|
"privacy": {
|
||||||
|
"title": "Privacy Policy - Toolso.AI",
|
||||||
|
"description": "Learn how we collect, use, and protect your personal information and data on Toolso.AI.",
|
||||||
|
"ogImage": "https://toolso.ai/banner.png"
|
||||||
|
},
|
||||||
|
"cookies": {
|
||||||
|
"title": "Cookie Policy - Toolso.AI",
|
||||||
|
"description": "Understand how we use cookies and similar tracking technologies on Toolso.AI.",
|
||||||
|
"ogImage": "https://toolso.ai/banner.png"
|
||||||
|
},
|
||||||
|
"tools": {
|
||||||
|
"title": "AI Tools Directory - Discover the Best AI Tools",
|
||||||
|
"description": "Explore our curated collection of AI tools. Find the best AI tools for productivity, creativity, and more."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,649 @@
|
|||||||
|
{
|
||||||
|
"common": {
|
||||||
|
"brand": {
|
||||||
|
"name": "Toolso.AI",
|
||||||
|
"tagline": "发现最好的 AI 工具",
|
||||||
|
"copyright": "版权所有 © 2025",
|
||||||
|
"allRightsReserved": "保留所有权利"
|
||||||
|
},
|
||||||
|
"actions": {
|
||||||
|
"signIn": "登录",
|
||||||
|
"signUp": "注册",
|
||||||
|
"signOut": "退出登录",
|
||||||
|
"getStarted": "开始使用",
|
||||||
|
"startFree": "免费开始",
|
||||||
|
"startTrial": "开始试用",
|
||||||
|
"contactSales": "联系销售",
|
||||||
|
"learnMore": "了解更多",
|
||||||
|
"viewAll": "查看全部",
|
||||||
|
"submit": "提交",
|
||||||
|
"cancel": "取消",
|
||||||
|
"save": "保存",
|
||||||
|
"delete": "删除",
|
||||||
|
"edit": "编辑",
|
||||||
|
"update": "更新",
|
||||||
|
"continue": "继续",
|
||||||
|
"back": "返回",
|
||||||
|
"next": "下一步"
|
||||||
|
},
|
||||||
|
"status": {
|
||||||
|
"loading": "加载中...",
|
||||||
|
"verified": "已验证",
|
||||||
|
"pending": "待处理",
|
||||||
|
"active": "已激活",
|
||||||
|
"inactive": "未激活"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"navigation": {
|
||||||
|
"main": {
|
||||||
|
"tools": "工具库",
|
||||||
|
"blog": "博客",
|
||||||
|
"contact": "联系我们",
|
||||||
|
"dashboard": "仪表盘",
|
||||||
|
"profile": "个人资料"
|
||||||
|
},
|
||||||
|
"footer": {
|
||||||
|
"legal": {
|
||||||
|
"terms": "服务条款",
|
||||||
|
"privacy": "隐私政策",
|
||||||
|
"cookies": "Cookie 政策"
|
||||||
|
},
|
||||||
|
"social": {
|
||||||
|
"twitter": "Twitter",
|
||||||
|
"linkedin": "领英",
|
||||||
|
"github": "GitHub"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"hero": {
|
||||||
|
"badge": "发现最好的 AI 工具",
|
||||||
|
"title": "发现最好的AI工具",
|
||||||
|
"description": "精选优质 AI 工具集合,涵盖效率提升、创意设计、开发辅助等多个领域,帮你找到最适合的 AI 助手。",
|
||||||
|
"cta": {
|
||||||
|
"primary": "探索工具",
|
||||||
|
"secondary": "阅读博客"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"features": {
|
||||||
|
"title": "开始构建所需的一切",
|
||||||
|
"subtitle": "告别重复的样板代码。专注于你的独特功能,基础设施已为你准备就绪。",
|
||||||
|
"items": {
|
||||||
|
"aiOptimized": {
|
||||||
|
"title": "现代技术栈",
|
||||||
|
"description": "基于 Next.js 14、TypeScript、Tailwind CSS 和 PostgreSQL 构建。采用行业最佳实践和企业级架构。"
|
||||||
|
},
|
||||||
|
"zeroPipeline": {
|
||||||
|
"title": "开箱即用的认证",
|
||||||
|
"description": "Better Auth 驱动的完整认证系统,支持邮箱密码和 OAuth 登录、邮箱验证、密码重置和会话管理。"
|
||||||
|
},
|
||||||
|
"nonTechnical": {
|
||||||
|
"title": "管理后台",
|
||||||
|
"description": "功能齐全的管理面板,包含用户管理、角色权限控制和业务数据监控。轻松扩展满足你的需求。"
|
||||||
|
},
|
||||||
|
"productionGrade": {
|
||||||
|
"title": "博客系统",
|
||||||
|
"description": "内置 MDX 博客系统,支持语法高亮、SEO 优化和国际化。非常适合内容营销和产品更新。"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"testimonials": {
|
||||||
|
"title": "用户评价",
|
||||||
|
"subtitle": "来自使用 Toolso.AI 发现优质 AI 工具的真实用户反馈。",
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"name": "张伟",
|
||||||
|
"quote": "Toolso.AI 帮我找到了完美的 AI 写作助手,分类清晰让发现变得非常简单!",
|
||||||
|
"designation": "内容创作者"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "王强",
|
||||||
|
"quote": "终于有了一个组织良好的 AI 工具目录,节省了我大量的研究时间。",
|
||||||
|
"designation": "产品经理"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "李娜",
|
||||||
|
"quote": "每周都能在 Toolso.AI 上发现新的 AI 工具,已经成为我探索 AI 的首选资源。",
|
||||||
|
"designation": "创业者"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "陈婷",
|
||||||
|
"quote": "搜索和筛选功能非常好用,几分钟就找到了我需要的工具。",
|
||||||
|
"designation": "软件工程师"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "赵明",
|
||||||
|
"quote": "喜欢中英文双语支持!可以轻松探索各种 AI 工具。",
|
||||||
|
"designation": "数字营销专家"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "朱悦",
|
||||||
|
"quote": "了解最新 AI 工具的好资源,博客板块也很有价值。",
|
||||||
|
"designation": "AI 研究员"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"companies": {
|
||||||
|
"title": "采用行业领先技术构建",
|
||||||
|
"subtitle": "基于现代技术栈,被全球顶尖团队信赖"
|
||||||
|
},
|
||||||
|
"gridFeatures": {
|
||||||
|
"aiReady": {
|
||||||
|
"title": "可扩展架构",
|
||||||
|
"description": "模块化设计,轻松添加新功能。遵循 Next.js 最佳实践,便于维护。"
|
||||||
|
},
|
||||||
|
"creem": {
|
||||||
|
"title": "邮件系统",
|
||||||
|
"description": "集成 Resend 邮件服务,支持交易邮件、验证邮件和新闻通讯。"
|
||||||
|
},
|
||||||
|
"auth": {
|
||||||
|
"title": "身份认证",
|
||||||
|
"description": "Better Auth 提供邮箱密码和 OAuth 支持。会话管理和路由保护已就绪。"
|
||||||
|
},
|
||||||
|
"deploy": {
|
||||||
|
"title": "一键部署",
|
||||||
|
"description": "即刻部署到 Vercel、Railway 或任何平台。从第一天起即可投入生产。"
|
||||||
|
},
|
||||||
|
"typescript": {
|
||||||
|
"title": "TypeScript + Tailwind",
|
||||||
|
"description": "现代技术栈包括 Next.js 14、TypeScript、Tailwind CSS 和 shadcn/ui 组件。"
|
||||||
|
},
|
||||||
|
"database": {
|
||||||
|
"title": "数据库 + ORM",
|
||||||
|
"description": "PostgreSQL 配合 Drizzle ORM。类型安全的查询和迁移已配置。"
|
||||||
|
},
|
||||||
|
"seo": {
|
||||||
|
"title": "SEO 和营销",
|
||||||
|
"description": "元标签、Open Graph、站点地图和分析。为有机增长准备的一切。"
|
||||||
|
},
|
||||||
|
"vibeCoding": {
|
||||||
|
"title": "国际化支持",
|
||||||
|
"description": "内置中英文支持,使用 next-intl 实现。轻松添加更多语言。"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"cta": {
|
||||||
|
"title": "找到最适合你的 AI 工具",
|
||||||
|
"description": "浏览精选 AI 工具集合,发现能帮助你提升效率的最佳工具。",
|
||||||
|
"button": "探索工具"
|
||||||
|
},
|
||||||
|
"auth": {
|
||||||
|
"login": {
|
||||||
|
"title": "登录您的账户",
|
||||||
|
"emailLabel": "邮箱地址",
|
||||||
|
"emailPlaceholder": "hello@example.com",
|
||||||
|
"passwordLabel": "密码",
|
||||||
|
"passwordPlaceholder": "••••••••",
|
||||||
|
"forgotPassword": "忘记密码?",
|
||||||
|
"noAccount": "还没有账户?",
|
||||||
|
"signUpLink": "注册",
|
||||||
|
"signInButton": "登录",
|
||||||
|
"signingIn": "登录中...",
|
||||||
|
"errors": {
|
||||||
|
"invalidEmail": "请输入有效的邮箱地址",
|
||||||
|
"emailRequired": "请输入邮箱地址",
|
||||||
|
"passwordRequired": "请输入密码",
|
||||||
|
"loginFailed": "登录失败,请重试。",
|
||||||
|
"googleLoginFailed": "Google 登录失败,请重试。"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"signup": {
|
||||||
|
"title": "注册一个新账户",
|
||||||
|
"nameLabel": "姓名",
|
||||||
|
"namePlaceholder": "张三",
|
||||||
|
"emailLabel": "邮箱地址",
|
||||||
|
"emailPlaceholder": "hello@example.com",
|
||||||
|
"passwordLabel": "密码",
|
||||||
|
"passwordPlaceholder": "••••••••",
|
||||||
|
"hasAccount": "已经有账户了?",
|
||||||
|
"signInLink": "登录",
|
||||||
|
"signUpButton": "注册",
|
||||||
|
"signingUp": "注册中...",
|
||||||
|
"errors": {
|
||||||
|
"nameRequired": "请输入您的姓名",
|
||||||
|
"invalidEmail": "请输入有效的邮箱地址",
|
||||||
|
"emailRequired": "请输入邮箱地址",
|
||||||
|
"passwordRequired": "请输入密码",
|
||||||
|
"signupFailed": "注册失败,请重试。",
|
||||||
|
"googleSignupFailed": "Google 注册失败,请重试。"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"social": {
|
||||||
|
"googleSignIn": "使用 Google 继续",
|
||||||
|
"or": "或者继续使用"
|
||||||
|
},
|
||||||
|
"checkEmail": {
|
||||||
|
"title": "请查收您的邮件",
|
||||||
|
"description": "我们已向您的邮箱发送了验证链接。请点击链接验证您的账户并开始使用。",
|
||||||
|
"notReceived": "没有收到邮件?",
|
||||||
|
"checkSpam": "检查垃圾邮件文件夹",
|
||||||
|
"checkEmailCorrect": "确认输入的邮箱地址是否正确",
|
||||||
|
"waitAndCheck": "等待几分钟后再次查看",
|
||||||
|
"resendButton": "重新发送验证邮件",
|
||||||
|
"resending": "发送中...",
|
||||||
|
"resendSuccess": "验证邮件发送成功!请查收您的邮箱。",
|
||||||
|
"resendError": "发送验证邮件失败,请重试。",
|
||||||
|
"backToLogin": "返回登录",
|
||||||
|
"securityNote": "为了您的安全,验证链接将在24小时后失效。"
|
||||||
|
},
|
||||||
|
"verifyEmail": {
|
||||||
|
"verifying": "正在验证邮箱",
|
||||||
|
"verifyingDescription": "请稍候,我们正在验证您的邮箱地址...",
|
||||||
|
"success": "邮箱验证成功!",
|
||||||
|
"successDescription": "您的邮箱已成功验证!正在跳转到登录页面...",
|
||||||
|
"alreadyVerified": "已验证",
|
||||||
|
"failed": "验证失败",
|
||||||
|
"goToLogin": "前往登录",
|
||||||
|
"backToSignUp": "返回注册",
|
||||||
|
"requestNew": "重新发送验证邮件",
|
||||||
|
"checkSpamNote": "如果没有收到邮件,请检查垃圾邮件文件夹"
|
||||||
|
},
|
||||||
|
"emailVerifiedGuard": {
|
||||||
|
"title": "需要验证邮箱",
|
||||||
|
"description": "请验证您的邮箱地址以访问此页面。请查收我们发送到以下邮箱的验证链接:",
|
||||||
|
"resendButton": "重新发送验证邮件",
|
||||||
|
"resending": "发送中...",
|
||||||
|
"resendSuccess": "验证邮件已发送!请查收您的邮箱。",
|
||||||
|
"resendError": "发送验证邮件失败,请重试。",
|
||||||
|
"signOutLink": "退出并使用其他账户"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Admin": {
|
||||||
|
"sidebar": {
|
||||||
|
"title": "管理后台",
|
||||||
|
"subtitle": "系统管理",
|
||||||
|
"dashboard": "仪表板",
|
||||||
|
"tools": "工具管理",
|
||||||
|
"categories": "分类管理",
|
||||||
|
"tags": "标签管理",
|
||||||
|
"users": "用户管理",
|
||||||
|
"backToDashboard": "返回用户面板"
|
||||||
|
},
|
||||||
|
"header": {
|
||||||
|
"welcome": "欢迎",
|
||||||
|
"adminBadge": "管理员"
|
||||||
|
},
|
||||||
|
"dashboard": {
|
||||||
|
"title": "管理员仪表板",
|
||||||
|
"totalUsers": "总用户数",
|
||||||
|
"activeUsers": "活跃用户",
|
||||||
|
"recentUsers": "最近注册用户",
|
||||||
|
"viewAllUsers": "查看所有用户"
|
||||||
|
},
|
||||||
|
"toolsManagement": {
|
||||||
|
"title": "工具管理",
|
||||||
|
"totalCount": "共 {count} 个工具",
|
||||||
|
"searchPlaceholder": "搜索工具名称或 slug...",
|
||||||
|
"allStatus": "全部状态",
|
||||||
|
"published": "已发布",
|
||||||
|
"draft": "草稿",
|
||||||
|
"addTool": "添加工具",
|
||||||
|
"tool": "工具",
|
||||||
|
"slug": "Slug",
|
||||||
|
"status": "状态",
|
||||||
|
"createdAt": "创建时间",
|
||||||
|
"actions": "操作",
|
||||||
|
"noData": "暂无工具数据",
|
||||||
|
"previous": "上一页",
|
||||||
|
"next": "下一页",
|
||||||
|
"confirmDelete": "确定要删除工具 \"{name}\" 吗?此操作不可恢复。",
|
||||||
|
"deleted": "工具已删除",
|
||||||
|
"deleteFailed": "删除失败",
|
||||||
|
"statusUpdated": "状态已更新",
|
||||||
|
"statusUpdateFailed": "状态更新失败",
|
||||||
|
"toDraft": "转为草稿",
|
||||||
|
"publish": "发布",
|
||||||
|
"edit": "编辑",
|
||||||
|
"visitWebsite": "访问网站",
|
||||||
|
"delete": "删除",
|
||||||
|
"modal": {
|
||||||
|
"editTitle": "编辑工具",
|
||||||
|
"addTitle": "添加工具",
|
||||||
|
"basicInfo": "基本信息",
|
||||||
|
"linksAndImages": "链接和图片",
|
||||||
|
"categories": "分类",
|
||||||
|
"tags": "标签",
|
||||||
|
"slug": "Slug",
|
||||||
|
"status": "状态",
|
||||||
|
"nameEn": "英文名称",
|
||||||
|
"nameZh": "中文名称",
|
||||||
|
"descriptionEn": "英文描述",
|
||||||
|
"descriptionZh": "中文描述",
|
||||||
|
"domain": "域名",
|
||||||
|
"websiteUrl": "网站链接",
|
||||||
|
"logoUrl": "Logo URL",
|
||||||
|
"coverImageUrl": "封面图 URL",
|
||||||
|
"cancel": "取消",
|
||||||
|
"saving": "保存中...",
|
||||||
|
"saveChanges": "保存更改",
|
||||||
|
"createTool": "创建工具",
|
||||||
|
"requiredField": "请填写必填字段",
|
||||||
|
"updated": "工具已更新",
|
||||||
|
"created": "工具已创建",
|
||||||
|
"updateFailed": "更新失败",
|
||||||
|
"createFailed": "创建失败"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"categoriesManagement": {
|
||||||
|
"title": "分类管理",
|
||||||
|
"addCategory": "添加分类",
|
||||||
|
"order": "排序",
|
||||||
|
"category": "分类",
|
||||||
|
"slug": "Slug",
|
||||||
|
"toolCount": "工具数",
|
||||||
|
"toolCountLabel": "{count} 个工具",
|
||||||
|
"actions": "操作",
|
||||||
|
"noData": "暂无分类数据",
|
||||||
|
"edit": "编辑",
|
||||||
|
"delete": "删除",
|
||||||
|
"cannotDelete": "该分类下有 {count} 个工具,无法删除",
|
||||||
|
"confirmDelete": "确定要删除分类 \"{name}\" 吗?",
|
||||||
|
"deleted": "分类已删除",
|
||||||
|
"deleteFailed": "删除失败",
|
||||||
|
"modal": {
|
||||||
|
"editTitle": "编辑分类",
|
||||||
|
"addTitle": "添加分类",
|
||||||
|
"slug": "Slug",
|
||||||
|
"icon": "图标 (emoji)",
|
||||||
|
"nameEn": "英文名称",
|
||||||
|
"nameZh": "中文名称",
|
||||||
|
"sortOrder": "排序",
|
||||||
|
"descriptionEn": "英文描述",
|
||||||
|
"descriptionZh": "中文描述",
|
||||||
|
"cancel": "取消",
|
||||||
|
"saving": "保存中...",
|
||||||
|
"saveChanges": "保存更改",
|
||||||
|
"createCategory": "创建分类",
|
||||||
|
"requiredField": "请填写必填字段",
|
||||||
|
"updated": "分类已更新",
|
||||||
|
"created": "分类已创建",
|
||||||
|
"updateFailed": "更新失败",
|
||||||
|
"createFailed": "创建失败"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"tagsManagement": {
|
||||||
|
"title": "标签管理",
|
||||||
|
"searchPlaceholder": "搜索标签...",
|
||||||
|
"addTag": "添加标签",
|
||||||
|
"toolCountLabel": "{count} 个工具",
|
||||||
|
"noData": "暂无标签数据",
|
||||||
|
"edit": "编辑",
|
||||||
|
"delete": "删除",
|
||||||
|
"confirmDeleteWithTools": "该标签被 {count} 个工具使用,删除后这些工具将失去该标签。确定要删除吗?",
|
||||||
|
"confirmDelete": "确定要删除标签 \"{name}\" 吗?",
|
||||||
|
"deleted": "标签已删除",
|
||||||
|
"deleteFailed": "删除失败",
|
||||||
|
"modal": {
|
||||||
|
"editTitle": "编辑标签",
|
||||||
|
"addTitle": "添加标签",
|
||||||
|
"slug": "Slug",
|
||||||
|
"nameEn": "英文名称",
|
||||||
|
"nameZh": "中文名称",
|
||||||
|
"cancel": "取消",
|
||||||
|
"saving": "保存中...",
|
||||||
|
"saveChanges": "保存更改",
|
||||||
|
"createTag": "创建标签",
|
||||||
|
"requiredField": "请填写必填字段",
|
||||||
|
"updated": "标签已更新",
|
||||||
|
"created": "标签已创建",
|
||||||
|
"updateFailed": "更新失败",
|
||||||
|
"createFailed": "创建失败"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"users": {
|
||||||
|
"searchPlaceholder": "搜索用户姓名或邮箱...",
|
||||||
|
"user": "用户",
|
||||||
|
"role": "角色",
|
||||||
|
"status": "状态",
|
||||||
|
"joined": "加入时间",
|
||||||
|
"actions": "操作",
|
||||||
|
"admin": "管理员",
|
||||||
|
"banned": "已禁用",
|
||||||
|
"active": "活跃",
|
||||||
|
"unverified": "未验证",
|
||||||
|
"edit": "编辑",
|
||||||
|
"ban": "禁用",
|
||||||
|
"unban": "解禁",
|
||||||
|
"viewDetails": "查看详情",
|
||||||
|
"banReason": "禁用原因",
|
||||||
|
"banExpires": "禁用到期",
|
||||||
|
"userDetails": "用户详情",
|
||||||
|
"name": "姓名",
|
||||||
|
"email": "邮箱",
|
||||||
|
"emailVerified": "邮箱验证",
|
||||||
|
"verified": "已验证",
|
||||||
|
"lastActive": "最后活跃",
|
||||||
|
"close": "关闭",
|
||||||
|
"roleUpdated": "角色已更新",
|
||||||
|
"roleUpdateFailed": "角色更新失败",
|
||||||
|
"userBanned": "用户已禁用",
|
||||||
|
"userUnbanned": "用户已解禁",
|
||||||
|
"banFailed": "禁用操作失败",
|
||||||
|
"pagination": {
|
||||||
|
"previous": "上一页",
|
||||||
|
"next": "下一页",
|
||||||
|
"page": "第 {current} / {total} 页"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"dashboard": {
|
||||||
|
"title": "仪表盘",
|
||||||
|
"welcome": "欢迎回来",
|
||||||
|
"cards": {
|
||||||
|
"personalInfo": {
|
||||||
|
"title": "个人信息",
|
||||||
|
"labels": {
|
||||||
|
"name": "姓名",
|
||||||
|
"email": "邮箱",
|
||||||
|
"status": "状态",
|
||||||
|
"notSet": "未设置"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"quickActions": {
|
||||||
|
"title": "快捷操作",
|
||||||
|
"editProfile": "编辑个人资料",
|
||||||
|
"accountSettings": "账户设置",
|
||||||
|
"securitySettings": "安全设置"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"profile": {
|
||||||
|
"title": "个人资料",
|
||||||
|
"subtitle": "管理您的账户设置和偏好",
|
||||||
|
"sections": {
|
||||||
|
"basicInfo": {
|
||||||
|
"title": "基本信息",
|
||||||
|
"fullName": "姓名",
|
||||||
|
"email": "邮箱地址",
|
||||||
|
"verificationStatus": "验证状态",
|
||||||
|
"notSet": "未设置",
|
||||||
|
"nameNotSet": "姓名未设置",
|
||||||
|
"accountId": "账户 ID"
|
||||||
|
},
|
||||||
|
"accountSettings": {
|
||||||
|
"title": "账户详情",
|
||||||
|
"memberSince": "注册于",
|
||||||
|
"lastLogin": "最近登录",
|
||||||
|
"accountType": "账户类型"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"form": {
|
||||||
|
"nameLabel": "姓名",
|
||||||
|
"namePlaceholder": "请输入您的姓名",
|
||||||
|
"emailLabel": "邮箱",
|
||||||
|
"emailPlaceholder": "请输入您的邮箱地址",
|
||||||
|
"updateButton": "更新资料",
|
||||||
|
"updating": "更新中...",
|
||||||
|
"updateSuccess": "个人资料更新成功",
|
||||||
|
"updateError": "个人资料更新失败"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"contact": {
|
||||||
|
"title": "联系我们",
|
||||||
|
"subtitle": "我们很乐意倾听您的声音。给我们发送消息,我们会尽快回复。",
|
||||||
|
"description": "请联系我们,我们会尽快回复。",
|
||||||
|
"testimonial": {
|
||||||
|
"title": "帮助用户发现最佳 AI 工具",
|
||||||
|
"description": "Toolso.AI 致力于为用户提供精选的 AI 工具目录,帮助大家找到最适合的 AI 助手。"
|
||||||
|
},
|
||||||
|
"form": {
|
||||||
|
"nameLabel": "姓名",
|
||||||
|
"namePlaceholder": "张三",
|
||||||
|
"emailLabel": "邮箱地址",
|
||||||
|
"emailPlaceholder": "hello@example.com",
|
||||||
|
"companyLabel": "公司",
|
||||||
|
"companyPlaceholder": "某某科技有限公司",
|
||||||
|
"messageLabel": "消息",
|
||||||
|
"messagePlaceholder": "请在此处输入您的消息",
|
||||||
|
"submitButton": "提交",
|
||||||
|
"submitting": "发送中...",
|
||||||
|
"submitSuccess": "消息发送成功!",
|
||||||
|
"submitError": "消息发送失败,请重试。"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"blog": {
|
||||||
|
"title": "博客",
|
||||||
|
"subtitle": "来自我们团队的最新见解与更新",
|
||||||
|
"readMore": "阅读更多",
|
||||||
|
"minRead": "分钟阅读"
|
||||||
|
},
|
||||||
|
"errors": {
|
||||||
|
"404": {
|
||||||
|
"title": "404 - 页面未找到",
|
||||||
|
"message": "您正在寻找的页面不存在。",
|
||||||
|
"backHome": "返回首页"
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"title": "出错了",
|
||||||
|
"message": "我们遇到了一些技术问题,请稍后重试。",
|
||||||
|
"backHome": "返回首页"
|
||||||
|
},
|
||||||
|
"general": {
|
||||||
|
"title": "发生错误",
|
||||||
|
"message": "发生了一些意外情况,请重试。",
|
||||||
|
"tryAgain": "重试"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"featuredTestimonials": {
|
||||||
|
"people": [
|
||||||
|
{
|
||||||
|
"name": "张伟",
|
||||||
|
"designation": "软件工程师"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "王强",
|
||||||
|
"designation": "产品经理"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "李娜",
|
||||||
|
"designation": "数据科学家"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "陈婷",
|
||||||
|
"designation": "UX设计师"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "赵明",
|
||||||
|
"designation": "全栈开发者"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "朱悦",
|
||||||
|
"designation": "独立开发者"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"footer": {
|
||||||
|
"tagline": "发现最好的 AI 工具,提升你的工作效率。",
|
||||||
|
"product": {
|
||||||
|
"title": "产品",
|
||||||
|
"features": "功能",
|
||||||
|
"documentation": "文档"
|
||||||
|
},
|
||||||
|
"company": {
|
||||||
|
"title": "公司",
|
||||||
|
"about": "关于我们",
|
||||||
|
"blog": "博客",
|
||||||
|
"careers": "招聘"
|
||||||
|
},
|
||||||
|
"legal": {
|
||||||
|
"title": "法律",
|
||||||
|
"privacy": "隐私政策",
|
||||||
|
"terms": "服务条款",
|
||||||
|
"cookies": "Cookie 政策"
|
||||||
|
},
|
||||||
|
"social": {
|
||||||
|
"title": "社交",
|
||||||
|
"twitter": "Twitter",
|
||||||
|
"linkedin": "领英",
|
||||||
|
"github": "GitHub"
|
||||||
|
},
|
||||||
|
"newsletter": {
|
||||||
|
"title": "订阅邮件",
|
||||||
|
"description": "获取最新动态和资讯,直接发送到您的邮箱。",
|
||||||
|
"placeholder": "输入您的邮箱",
|
||||||
|
"subscribe": "订阅"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"tools": {
|
||||||
|
"title": "AI 工具库",
|
||||||
|
"subtitle": "发现最好的 AI 工具,提升您的工作效率",
|
||||||
|
"searchPlaceholder": "搜索工具...",
|
||||||
|
"all": "全部",
|
||||||
|
"foundTools": "共找到 {count} 个工具",
|
||||||
|
"visitWebsite": "访问网站",
|
||||||
|
"noTools": "暂无工具",
|
||||||
|
"backToTools": "返回工具列表",
|
||||||
|
"about": "介绍",
|
||||||
|
"categories": "分类",
|
||||||
|
"tags": "标签",
|
||||||
|
"visitButton": "访问网站",
|
||||||
|
"toolInfo": "工具信息",
|
||||||
|
"domain": "域名",
|
||||||
|
"added": "添加时间",
|
||||||
|
"notFound": "工具未找到"
|
||||||
|
},
|
||||||
|
"seo": {
|
||||||
|
"home": {
|
||||||
|
"title": "Toolso.AI - 发现最好的 AI 工具导航站",
|
||||||
|
"description": "探索精选的 AI 工具集合,涵盖多个类别。找到最适合效率提升、创意设计、开发辅助等场景的 AI 工具。",
|
||||||
|
"ogImage": "https://toolso.ai/banner.png"
|
||||||
|
},
|
||||||
|
"blog": {
|
||||||
|
"title": "博客 - Toolso.AI",
|
||||||
|
"description": "关于 AI 工具的最新资讯、教程和更新。了解最佳 AI 工具和行业趋势。",
|
||||||
|
"ogImage": "https://toolso.ai/banner.png"
|
||||||
|
},
|
||||||
|
"contact": {
|
||||||
|
"title": "联系我们 - Toolso.AI",
|
||||||
|
"description": "与我们取得联系。我们期待听到您的反馈和工具推荐建议。",
|
||||||
|
"ogImage": "https://toolso.ai/banner.png"
|
||||||
|
},
|
||||||
|
"login": {
|
||||||
|
"title": "登录 - Toolso.AI",
|
||||||
|
"description": "登录您的账户,提交工具、收藏喜爱的工具,为 AI 工具目录做贡献。",
|
||||||
|
"ogImage": "https://toolso.ai/banner.png"
|
||||||
|
},
|
||||||
|
"signup": {
|
||||||
|
"title": "注册 - Toolso.AI",
|
||||||
|
"description": "创建您的账户,开始为 AI 工具目录做贡献。提交工具、加入我们的社区。",
|
||||||
|
"ogImage": "https://toolso.ai/banner.png"
|
||||||
|
},
|
||||||
|
"terms": {
|
||||||
|
"title": "服务条款 - Toolso.AI",
|
||||||
|
"description": "阅读我们的服务条款,了解使用 Toolso.AI 的规则和指南。",
|
||||||
|
"ogImage": "https://toolso.ai/banner.png"
|
||||||
|
},
|
||||||
|
"privacy": {
|
||||||
|
"title": "隐私政策 - Toolso.AI",
|
||||||
|
"description": "了解我们如何在 Toolso.AI 上收集、使用和保护您的个人信息和数据。",
|
||||||
|
"ogImage": "https://toolso.ai/banner.png"
|
||||||
|
},
|
||||||
|
"cookies": {
|
||||||
|
"title": "Cookie 政策 - Toolso.AI",
|
||||||
|
"description": "了解我们如何在 Toolso.AI 上使用 cookies 和类似的跟踪技术。",
|
||||||
|
"ogImage": "https://toolso.ai/banner.png"
|
||||||
|
},
|
||||||
|
"tools": {
|
||||||
|
"title": "AI 工具库 - 发现最好的 AI 工具",
|
||||||
|
"description": "浏览精选的 AI 工具集合。找到提升效率、激发创意的最佳 AI 工具。"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
import fs from "node:fs";
|
||||||
|
import path from "node:path";
|
||||||
|
|
||||||
|
import rehypePrism from "@mapbox/rehype-prism";
|
||||||
|
import nextMDX from "@next/mdx";
|
||||||
|
import remarkGfm from "remark-gfm";
|
||||||
|
import createNextIntlPlugin from 'next-intl/plugin';
|
||||||
|
|
||||||
|
const withNextIntl = createNextIntlPlugin('./src/lib/i18n.ts');
|
||||||
|
|
||||||
|
const pnpmManagedPath = path.join(process.cwd(), "node_modules", ".pnpm");
|
||||||
|
const snapshotManagedPaths = fs.existsSync(pnpmManagedPath) ? [pnpmManagedPath] : [];
|
||||||
|
|
||||||
|
/** @type {import('next').NextConfig} */
|
||||||
|
const nextConfig = {
|
||||||
|
images: {
|
||||||
|
remotePatterns: [
|
||||||
|
{
|
||||||
|
protocol: "https",
|
||||||
|
hostname: "i.pravatar.cc",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
protocol: "https",
|
||||||
|
hostname: "images.unsplash.com",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
protocol: "https",
|
||||||
|
hostname: "a.offerget.pro",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
protocol: "https",
|
||||||
|
hostname: "lh3.googleusercontent.com",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
pageExtensions: ["ts", "tsx", "mdx"],
|
||||||
|
turbopack: {
|
||||||
|
rules: {
|
||||||
|
'*.mdx': {
|
||||||
|
loaders: ['@mdx-js/loader'],
|
||||||
|
as: '*.js',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
webpack(config) {
|
||||||
|
config.snapshot ??= {};
|
||||||
|
config.snapshot.managedPaths = snapshotManagedPaths;
|
||||||
|
return config;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const withMDX = nextMDX({
|
||||||
|
extension: /\.mdx?$/,
|
||||||
|
options: {
|
||||||
|
remarkPlugins: [remarkGfm],
|
||||||
|
rehypePlugins: [rehypePrism],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default withNextIntl(withMDX(nextConfig));
|
||||||
@@ -0,0 +1,111 @@
|
|||||||
|
{
|
||||||
|
"name": "toolso-ai",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "An open-source AI tools directory to help you discover the best AI tools across various categories.",
|
||||||
|
"private": false,
|
||||||
|
"keywords": [
|
||||||
|
"ai-tools",
|
||||||
|
"ai-directory",
|
||||||
|
"ai-tools-directory",
|
||||||
|
"artificial-intelligence",
|
||||||
|
"machine-learning",
|
||||||
|
"nextjs",
|
||||||
|
"typescript",
|
||||||
|
"tailwindcss",
|
||||||
|
"open-source"
|
||||||
|
],
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"homepage": "https://toolso.ai",
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/coolchange-ai/toolso-ai-open.git"
|
||||||
|
},
|
||||||
|
"bugs": {
|
||||||
|
"url": "https://github.com/coolchange-ai/toolso-ai-open/issues"
|
||||||
|
},
|
||||||
|
"author": {
|
||||||
|
"name": "CoolChange.AI",
|
||||||
|
"url": "https://coolchange.ai"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"dev": "node scripts/run-dev.mjs",
|
||||||
|
"build": "npm run generate:blog-manifest && next build",
|
||||||
|
"start": "next start",
|
||||||
|
"lint": "next lint",
|
||||||
|
"db:generate": "drizzle-kit generate",
|
||||||
|
"db:migrate": "drizzle-kit migrate",
|
||||||
|
"db:push": "drizzle-kit push",
|
||||||
|
"db:studio": "drizzle-kit studio",
|
||||||
|
"admin:setup": "tsx scripts/setup-admin.ts",
|
||||||
|
"seed:tools": "tsx scripts/seed-tools.ts",
|
||||||
|
"generate:blog-manifest": "node scripts/generate-blog-manifest.mjs"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@aws-sdk/client-s3": "^3.958.0",
|
||||||
|
"@better-auth/client": "0.0.2-alpha.3",
|
||||||
|
"@hookform/resolvers": "^3.4.2",
|
||||||
|
"@mapbox/rehype-prism": "^0.9.0",
|
||||||
|
"@mdx-js/loader": "^3.0.1",
|
||||||
|
"@mdx-js/react": "^3.0.1",
|
||||||
|
"@next/mdx": "^14.2.3",
|
||||||
|
"@radix-ui/react-label": "^2.0.2",
|
||||||
|
"@radix-ui/react-slot": "^1.0.2",
|
||||||
|
"@react-email/components": "^0.5.3",
|
||||||
|
"@tabler/icons-react": "^3.5.0",
|
||||||
|
"@tailwindcss/typography": "^0.5.13",
|
||||||
|
"@tanstack/react-store": "^0.7.5",
|
||||||
|
"@types/mdx": "^2.0.13",
|
||||||
|
"@vercel/analytics": "^1.5.0",
|
||||||
|
"@vercel/speed-insights": "^1.2.0",
|
||||||
|
"better-auth": "^1.4.10",
|
||||||
|
"class-variance-authority": "^0.7.0",
|
||||||
|
"clsx": "^2.1.1",
|
||||||
|
"cobe": "^0.6.3",
|
||||||
|
"date-fns": "^3.6.0",
|
||||||
|
"drizzle-kit": "^0.31.4",
|
||||||
|
"drizzle-orm": "^0.44.5",
|
||||||
|
"fast-glob": "^3.3.2",
|
||||||
|
"framer-motion": "^11.2.9",
|
||||||
|
"geist": "^1.3.0",
|
||||||
|
"lucide-react": "^0.562.0",
|
||||||
|
"nanoid": "^5.1.6",
|
||||||
|
"next": "16.1.1",
|
||||||
|
"next-intl": "^4.7.0",
|
||||||
|
"next-themes": "^0.4.6",
|
||||||
|
"next-view-transitions": "^0.3.5",
|
||||||
|
"postgres": "^3.4.7",
|
||||||
|
"prettier": "^3.7.4",
|
||||||
|
"prismjs": "^1.29.0",
|
||||||
|
"react": "^19.2.3",
|
||||||
|
"react-dom": "^19.2.3",
|
||||||
|
"react-email": "^4.2.11",
|
||||||
|
"react-hook-form": "^7.51.5",
|
||||||
|
"react-icons": "^5.2.1",
|
||||||
|
"react-markdown": "^10.1.0",
|
||||||
|
"react-wrap-balancer": "^1.1.1",
|
||||||
|
"reading-time": "^1.5.0",
|
||||||
|
"rehype-highlight": "^7.0.2",
|
||||||
|
"remark-gfm": "^4.0.0",
|
||||||
|
"resend": "^6.1.0",
|
||||||
|
"sonner": "^2.0.7",
|
||||||
|
"tailwind-merge": "^2.3.0",
|
||||||
|
"zod": "^3.23.8"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/node": "^20",
|
||||||
|
"@types/react": "^19.2.7",
|
||||||
|
"@types/react-dom": "^19.2.3",
|
||||||
|
"dotenv": "^17.2.2",
|
||||||
|
"eslint": "^8",
|
||||||
|
"eslint-config-next": "14.2.3",
|
||||||
|
"pg": "^8.16.3",
|
||||||
|
"playwright": "^1.57.0",
|
||||||
|
"png-to-ico": "^3.0.1",
|
||||||
|
"postcss": "^8",
|
||||||
|
"sharp": "^0.34.5",
|
||||||
|
"tailwindcss": "^3.4.1",
|
||||||
|
"tsx": "^4.20.5",
|
||||||
|
"typescript": "^5"
|
||||||
|
},
|
||||||
|
"packageManager": "pnpm@10.11.1+sha512.e519b9f7639869dc8d5c3c5dfef73b3f091094b0a006d7317353c72b124e80e1afd429732e28705ad6bfa1ee879c1fce46c128ccebd3192101f43dd67c667912"
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
/** @type {import('postcss-load-config').Config} */
|
||||||
|
const config = {
|
||||||
|
plugins: {
|
||||||
|
tailwindcss: {},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default config;
|
||||||
|
After Width: | Height: | Size: 22 KiB |
|
After Width: | Height: | Size: 293 KiB |
|
After Width: | Height: | Size: 715 KiB |
@@ -0,0 +1,24 @@
|
|||||||
|
# robots.txt for Toolso.AI - AI Tools Directory
|
||||||
|
# https://toolso.ai
|
||||||
|
|
||||||
|
User-agent: *
|
||||||
|
Allow: /
|
||||||
|
|
||||||
|
# Disallow admin and API pages
|
||||||
|
Disallow: /admin/
|
||||||
|
Disallow: /api/
|
||||||
|
|
||||||
|
# Disallow authentication pages
|
||||||
|
Disallow: /*/signup
|
||||||
|
Disallow: /*/login
|
||||||
|
Disallow: /*/check-email
|
||||||
|
Disallow: /*/verify-email
|
||||||
|
Disallow: /*/forgot-password
|
||||||
|
Disallow: /*/reset-password
|
||||||
|
|
||||||
|
# Disallow protected user pages
|
||||||
|
Disallow: /*/dashboard
|
||||||
|
Disallow: /*/profile
|
||||||
|
|
||||||
|
# Sitemap location
|
||||||
|
Sitemap: https://toolso.ai/sitemap.xml
|
||||||
|
After Width: | Height: | Size: 574 KiB |
|
After Width: | Height: | Size: 336 KiB |
@@ -0,0 +1,76 @@
|
|||||||
|
import fs from "node:fs";
|
||||||
|
import path from "node:path";
|
||||||
|
import glob from "fast-glob";
|
||||||
|
|
||||||
|
async function main() {
|
||||||
|
const blogRoot = path.join(process.cwd(), "src/app/[locale]/(marketing)/blog");
|
||||||
|
const entries = await glob("**/*.mdx", { cwd: blogRoot });
|
||||||
|
const manifest = new Map();
|
||||||
|
|
||||||
|
for (const entry of entries) {
|
||||||
|
const segments = entry.split("/");
|
||||||
|
|
||||||
|
if (segments.length < 2) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const [slug, filename] = segments;
|
||||||
|
|
||||||
|
if (!slug || slug.startsWith("[")) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const locale = filename.replace(/\.mdx$/, "");
|
||||||
|
|
||||||
|
if (!locale) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!manifest.has(slug)) {
|
||||||
|
manifest.set(slug, new Set());
|
||||||
|
}
|
||||||
|
|
||||||
|
manifest.get(slug).add(locale);
|
||||||
|
}
|
||||||
|
|
||||||
|
const sortedManifest = Array.from(manifest.entries()).sort(([a], [b]) =>
|
||||||
|
a.localeCompare(b)
|
||||||
|
);
|
||||||
|
|
||||||
|
const lines = [];
|
||||||
|
lines.push("// This file is auto-generated by scripts/generate-blog-manifest.mjs");
|
||||||
|
lines.push("// Do not edit manually.\n");
|
||||||
|
lines.push("export const blogModuleLoaders = {");
|
||||||
|
|
||||||
|
for (const [slug, locales] of sortedManifest) {
|
||||||
|
lines.push(` "${slug}": {`);
|
||||||
|
|
||||||
|
const sortedLocales = Array.from(locales).sort((a, b) => a.localeCompare(b));
|
||||||
|
|
||||||
|
for (const locale of sortedLocales) {
|
||||||
|
// 使用 path.posix 确保导入路径始终使用 / (跨平台兼容)
|
||||||
|
const importPath = path.posix.join(
|
||||||
|
"..",
|
||||||
|
"app",
|
||||||
|
"[locale]",
|
||||||
|
"(marketing)",
|
||||||
|
"blog",
|
||||||
|
slug,
|
||||||
|
`${locale}.mdx`
|
||||||
|
);
|
||||||
|
lines.push(` "${locale}": () => import("${importPath}"),`);
|
||||||
|
}
|
||||||
|
|
||||||
|
lines.push(" },");
|
||||||
|
}
|
||||||
|
|
||||||
|
lines.push("} as const;\n");
|
||||||
|
|
||||||
|
const targetPath = path.join(process.cwd(), "src/lib/blog-manifest.generated.ts");
|
||||||
|
fs.writeFileSync(targetPath, lines.join("\n"), "utf-8");
|
||||||
|
}
|
||||||
|
|
||||||
|
main().catch((error) => {
|
||||||
|
console.error(error);
|
||||||
|
process.exitCode = 1;
|
||||||
|
});
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
import { spawn } from "node:child_process";
|
||||||
|
import { platform } from "node:os";
|
||||||
|
|
||||||
|
const cleanEnv = { ...process.env };
|
||||||
|
|
||||||
|
for (const key of [
|
||||||
|
"npm_config_verify_deps_before_run",
|
||||||
|
"NPM_CONFIG_VERIFY_DEPS_BEFORE_RUN",
|
||||||
|
"npm_config__jsr_registry",
|
||||||
|
"NPM_CONFIG__JSR_REGISTRY",
|
||||||
|
]) {
|
||||||
|
if (key in cleanEnv) {
|
||||||
|
delete cleanEnv[key];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Windows 兼容性修复: Windows 需要 .cmd 扩展名和 shell
|
||||||
|
const isWindows = platform() === "win32";
|
||||||
|
const command = isWindows ? "next.cmd" : "next";
|
||||||
|
|
||||||
|
const child = spawn(command, ["dev"], {
|
||||||
|
stdio: "inherit",
|
||||||
|
env: cleanEnv,
|
||||||
|
shell: isWindows,
|
||||||
|
});
|
||||||
|
|
||||||
|
child.on("exit", (code, signal) => {
|
||||||
|
if (signal) {
|
||||||
|
process.kill(process.pid, signal);
|
||||||
|
} else {
|
||||||
|
process.exit(code ?? 0);
|
||||||
|
}
|
||||||
|
});
|
||||||
@@ -0,0 +1,469 @@
|
|||||||
|
-- =============================================
|
||||||
|
-- AI 工具导航站种子数据
|
||||||
|
-- 生成时间: 2025年1月
|
||||||
|
-- 包含: 10个分类, 30个标签, 50个工具
|
||||||
|
-- Logo 使用 Google Favicon 服务
|
||||||
|
-- =============================================
|
||||||
|
|
||||||
|
-- ========== 1. 分类数据 (10个) ==========
|
||||||
|
INSERT INTO category (id, slug, icon, sort_order, name_en, description_en, name_zh, description_zh) VALUES
|
||||||
|
('cat_chatbot', 'chatbot', '💬', 1, 'AI Chatbots', 'Conversational AI assistants and chatbots', 'AI 聊天机器人', '对话式AI助手和聊天机器人'),
|
||||||
|
('cat_writing', 'writing', '✍️', 2, 'Writing & Content', 'AI-powered writing assistants and content generation tools', 'AI 写作', 'AI驱动的写作助手和内容生成工具'),
|
||||||
|
('cat_image', 'image-generation', '🎨', 3, 'Image Generation', 'AI tools for creating and editing images', 'AI 图像生成', 'AI图像创作和编辑工具'),
|
||||||
|
('cat_video', 'video', '🎬', 4, 'Video & Animation', 'AI video generation, editing and animation tools', 'AI 视频', 'AI视频生成、编辑和动画工具'),
|
||||||
|
('cat_audio', 'audio-music', '🎵', 5, 'Audio & Music', 'AI music generation, voice synthesis and audio tools', 'AI 音频与音乐', 'AI音乐生成、语音合成和音频工具'),
|
||||||
|
('cat_coding', 'coding', '💻', 6, 'Coding & Development', 'AI coding assistants and developer tools', 'AI 编程', 'AI编程助手和开发者工具'),
|
||||||
|
('cat_productivity', 'productivity', '⚡', 7, 'Productivity', 'AI tools to boost your work efficiency', 'AI 效率工具', '提升工作效率的AI工具'),
|
||||||
|
('cat_research', 'research', '🔬', 8, 'Research & Analysis', 'AI research assistants and data analysis tools', 'AI 研究与分析', 'AI研究助手和数据分析工具'),
|
||||||
|
('cat_design', 'design', '🎯', 9, 'Design & Creative', 'AI tools for designers and creative professionals', 'AI 设计', '面向设计师和创意专业人士的AI工具'),
|
||||||
|
('cat_business', 'business', '📊', 10, 'Business & Marketing', 'AI tools for business operations and marketing', 'AI 商业与营销', '商业运营和营销AI工具');
|
||||||
|
|
||||||
|
-- ========== 2. 标签数据 (30个) ==========
|
||||||
|
INSERT INTO tag (id, slug, name_en, name_zh) VALUES
|
||||||
|
-- 功能标签
|
||||||
|
('tag_free', 'free', 'Free', '免费'),
|
||||||
|
('tag_freemium', 'freemium', 'Freemium', '免费增值'),
|
||||||
|
('tag_paid', 'paid', 'Paid', '付费'),
|
||||||
|
('tag_open_source', 'open-source', 'Open Source', '开源'),
|
||||||
|
('tag_api', 'api-available', 'API Available', '提供API'),
|
||||||
|
-- 技术标签
|
||||||
|
('tag_gpt', 'gpt-powered', 'GPT Powered', 'GPT驱动'),
|
||||||
|
('tag_claude', 'claude-powered', 'Claude Powered', 'Claude驱动'),
|
||||||
|
('tag_diffusion', 'diffusion-model', 'Diffusion Model', '扩散模型'),
|
||||||
|
('tag_llm', 'llm', 'Large Language Model', '大语言模型'),
|
||||||
|
-- 用途标签
|
||||||
|
('tag_beginner', 'beginner-friendly', 'Beginner Friendly', '新手友好'),
|
||||||
|
('tag_enterprise', 'enterprise', 'Enterprise', '企业级'),
|
||||||
|
('tag_creator', 'for-creators', 'For Creators', '创作者适用'),
|
||||||
|
('tag_developer', 'for-developers', 'For Developers', '开发者适用'),
|
||||||
|
('tag_marketer', 'for-marketers', 'For Marketers', '营销人员适用'),
|
||||||
|
('tag_student', 'for-students', 'For Students', '学生适用'),
|
||||||
|
-- 特性标签
|
||||||
|
('tag_realtime', 'real-time', 'Real-time', '实时'),
|
||||||
|
('tag_collaborative', 'collaborative', 'Collaborative', '协作'),
|
||||||
|
('tag_mobile', 'mobile-app', 'Mobile App', '移动应用'),
|
||||||
|
('tag_browser', 'browser-extension', 'Browser Extension', '浏览器插件'),
|
||||||
|
('tag_desktop', 'desktop-app', 'Desktop App', '桌面应用'),
|
||||||
|
-- 输出类型标签
|
||||||
|
('tag_text', 'text-output', 'Text Output', '文本输出'),
|
||||||
|
('tag_image_out', 'image-output', 'Image Output', '图像输出'),
|
||||||
|
('tag_video_out', 'video-output', 'Video Output', '视频输出'),
|
||||||
|
('tag_audio_out', 'audio-output', 'Audio Output', '音频输出'),
|
||||||
|
('tag_code_out', 'code-output', 'Code Output', '代码输出'),
|
||||||
|
-- 热门标签
|
||||||
|
('tag_trending', 'trending', 'Trending', '热门'),
|
||||||
|
('tag_new', 'new', 'New', '新上线'),
|
||||||
|
('tag_popular', 'popular', 'Popular', '受欢迎'),
|
||||||
|
('tag_editors_choice', 'editors-choice', 'Editor''s Choice', '编辑推荐'),
|
||||||
|
('tag_best_2025', 'best-of-2025', 'Best of 2025', '2025精选');
|
||||||
|
|
||||||
|
-- ========== 3. 工具数据 (50个) ==========
|
||||||
|
INSERT INTO tool (id, slug, domain, website_url, cover_image_url, logo_url, name_en, description_en, name_zh, description_zh, status) VALUES
|
||||||
|
-- AI 聊天机器人 (8个)
|
||||||
|
('tool_chatgpt', 'chatgpt', 'chat.openai.com', 'https://chat.openai.com', NULL, 'https://www.google.com/s2/favicons?domain=chat.openai.com&sz=128', 'ChatGPT', 'OpenAI''s advanced conversational AI assistant powered by GPT-4. Capable of natural conversations, code generation, creative writing, and complex reasoning.', 'ChatGPT', 'OpenAI推出的先进对话式AI助手,由GPT-4驱动。能够进行自然对话、代码生成、创意写作和复杂推理。', 'published'),
|
||||||
|
('tool_claude', 'claude', 'claude.ai', 'https://claude.ai', NULL, 'https://www.google.com/s2/favicons?domain=claude.ai&sz=128', 'Claude', 'Anthropic''s helpful, harmless, and honest AI assistant. Known for nuanced understanding, long context windows, and thoughtful responses.', 'Claude', 'Anthropic推出的有用、无害、诚实的AI助手。以细腻的理解、长上下文窗口和深思熟虑的回复著称。', 'published'),
|
||||||
|
('tool_gemini', 'gemini', 'gemini.google.com', 'https://gemini.google.com', NULL, 'https://www.google.com/s2/favicons?domain=gemini.google.com&sz=128', 'Google Gemini', 'Google''s multimodal AI assistant that can understand text, images, audio, and video. Integrated with Google services.', 'Google Gemini', 'Google的多模态AI助手,能够理解文本、图像、音频和视频。与Google服务深度集成。', 'published'),
|
||||||
|
('tool_perplexity', 'perplexity', 'perplexity.ai', 'https://perplexity.ai', NULL, 'https://www.google.com/s2/favicons?domain=perplexity.ai&sz=128', 'Perplexity AI', 'AI-powered search engine that provides direct answers with cited sources. Combines web search with language model capabilities.', 'Perplexity AI', 'AI驱动的搜索引擎,提供带引用来源的直接答案。结合网络搜索与语言模型能力。', 'published'),
|
||||||
|
('tool_poe', 'poe', 'poe.com', 'https://poe.com', NULL, 'https://www.google.com/s2/favicons?domain=poe.com&sz=128', 'Poe', 'Platform by Quora offering access to multiple AI models including GPT-4, Claude, and custom bots in one interface.', 'Poe', 'Quora推出的平台,在一个界面中提供多个AI模型访问,包括GPT-4、Claude和自定义机器人。', 'published'),
|
||||||
|
('tool_character', 'character-ai', 'character.ai', 'https://character.ai', NULL, 'https://www.google.com/s2/favicons?domain=character.ai&sz=128', 'Character.AI', 'Create and chat with AI characters. Popular for roleplay, creative storytelling, and entertainment purposes.', 'Character.AI', '创建AI角色并与之聊天。在角色扮演、创意故事讲述和娱乐方面广受欢迎。', 'published'),
|
||||||
|
('tool_coze', 'coze', 'coze.com', 'https://coze.com', NULL, 'https://www.google.com/s2/favicons?domain=coze.com&sz=128', 'Coze', 'ByteDance''s AI chatbot platform for creating custom AI bots with plugins, workflows, and knowledge bases.', 'Coze', '字节跳动的AI聊天机器人平台,可创建带插件、工作流和知识库的自定义AI机器人。', 'published'),
|
||||||
|
('tool_pi', 'pi-ai', 'pi.ai', 'https://pi.ai', NULL, 'https://www.google.com/s2/favicons?domain=pi.ai&sz=128', 'Pi', 'Personal AI assistant by Inflection AI, designed for supportive conversations and emotional intelligence.', 'Pi', 'Inflection AI的个人AI助手,专为支持性对话和情商设计。', 'published'),
|
||||||
|
|
||||||
|
-- AI 写作 (7个)
|
||||||
|
('tool_jasper', 'jasper', 'jasper.ai', 'https://jasper.ai', NULL, 'https://www.google.com/s2/favicons?domain=jasper.ai&sz=128', 'Jasper', 'AI content creation platform for marketing teams. Generate blog posts, ads, emails, and social media content at scale.', 'Jasper', '面向营销团队的AI内容创作平台。大规模生成博客文章、广告、邮件和社交媒体内容。', 'published'),
|
||||||
|
('tool_copy_ai', 'copy-ai', 'copy.ai', 'https://copy.ai', NULL, 'https://www.google.com/s2/favicons?domain=copy.ai&sz=128', 'Copy.ai', 'AI-powered copywriting tool for creating marketing copy, product descriptions, and sales content.', 'Copy.ai', 'AI驱动的文案工具,用于创建营销文案、产品描述和销售内容。', 'published'),
|
||||||
|
('tool_writesonic', 'writesonic', 'writesonic.com', 'https://writesonic.com', NULL, 'https://www.google.com/s2/favicons?domain=writesonic.com&sz=128', 'Writesonic', 'AI writer for creating SEO-optimized articles, blog posts, and marketing content with fact-checking capabilities.', 'Writesonic', 'AI写作工具,用于创建SEO优化的文章、博客和营销内容,具有事实核查功能。', 'published'),
|
||||||
|
('tool_notion_ai', 'notion-ai', 'notion.so', 'https://notion.so', NULL, 'https://www.google.com/s2/favicons?domain=notion.so&sz=128', 'Notion AI', 'AI writing assistant integrated into Notion. Helps with drafting, summarizing, brainstorming, and improving writing.', 'Notion AI', '集成在Notion中的AI写作助手。帮助起草、总结、头脑风暴和改进写作。', 'published'),
|
||||||
|
('tool_grammarly', 'grammarly', 'grammarly.com', 'https://grammarly.com', NULL, 'https://www.google.com/s2/favicons?domain=grammarly.com&sz=128', 'Grammarly', 'AI-powered writing assistant that checks grammar, spelling, punctuation, and offers style improvements.', 'Grammarly', 'AI驱动的写作助手,检查语法、拼写、标点符号,并提供风格改进建议。', 'published'),
|
||||||
|
('tool_quillbot', 'quillbot', 'quillbot.com', 'https://quillbot.com', NULL, 'https://www.google.com/s2/favicons?domain=quillbot.com&sz=128', 'QuillBot', 'AI paraphrasing and writing tool. Helps rephrase sentences, summarize text, and improve writing clarity.', 'QuillBot', 'AI改写和写作工具。帮助改述句子、总结文本和提高写作清晰度。', 'published'),
|
||||||
|
('tool_rytr', 'rytr', 'rytr.me', 'https://rytr.me', NULL, 'https://www.google.com/s2/favicons?domain=rytr.me&sz=128', 'Rytr', 'AI writing assistant for generating content in 30+ languages and 20+ tones. Affordable alternative for content creators.', 'Rytr', 'AI写作助手,支持30多种语言和20多种语气生成内容。创作者的经济实惠选择。', 'published'),
|
||||||
|
|
||||||
|
-- AI 图像生成 (8个)
|
||||||
|
('tool_midjourney', 'midjourney', 'midjourney.com', 'https://midjourney.com', NULL, 'https://www.google.com/s2/favicons?domain=midjourney.com&sz=128', 'Midjourney', 'Leading AI art generator known for stunning, artistic image generation. Creates images from text prompts via Discord.', 'Midjourney', '领先的AI艺术生成器,以惊艳的艺术图像生成著称。通过Discord从文本提示创建图像。', 'published'),
|
||||||
|
('tool_dalle', 'dall-e', 'openai.com', 'https://openai.com/dall-e', NULL, 'https://www.google.com/s2/favicons?domain=openai.com&sz=128', 'DALL·E 3', 'OpenAI''s latest image generation model with exceptional prompt understanding and photorealistic outputs.', 'DALL·E 3', 'OpenAI最新的图像生成模型,具有出色的提示理解能力和逼真的输出效果。', 'published'),
|
||||||
|
('tool_stable_diffusion', 'stable-diffusion', 'stability.ai', 'https://stability.ai', NULL, 'https://www.google.com/s2/favicons?domain=stability.ai&sz=128', 'Stable Diffusion', 'Open-source image generation model by Stability AI. Can be run locally with full customization.', 'Stable Diffusion', 'Stability AI的开源图像生成模型。可在本地运行并完全自定义。', 'published'),
|
||||||
|
('tool_leonardo', 'leonardo-ai', 'leonardo.ai', 'https://leonardo.ai', NULL, 'https://www.google.com/s2/favicons?domain=leonardo.ai&sz=128', 'Leonardo AI', 'AI image generator with fine-tuned models for game assets, concept art, and consistent character generation.', 'Leonardo AI', 'AI图像生成器,提供针对游戏资产、概念艺术和一致性角色生成的微调模型。', 'published'),
|
||||||
|
('tool_ideogram', 'ideogram', 'ideogram.ai', 'https://ideogram.ai', NULL, 'https://www.google.com/s2/favicons?domain=ideogram.ai&sz=128', 'Ideogram', 'AI image generator excelling at rendering text within images accurately. Great for logos and posters.', 'Ideogram', 'AI图像生成器,擅长准确渲染图像中的文字。适合制作Logo和海报。', 'published'),
|
||||||
|
('tool_playground', 'playground-ai', 'playground.com', 'https://playground.com', NULL, 'https://www.google.com/s2/favicons?domain=playground.com&sz=128', 'Playground AI', 'Free AI image generator with powerful editing features. Great for beginners with intuitive interface.', 'Playground AI', '免费AI图像生成器,具有强大的编辑功能。界面直观,适合新手。', 'published'),
|
||||||
|
('tool_canva_ai', 'canva-ai', 'canva.com', 'https://canva.com', NULL, 'https://www.google.com/s2/favicons?domain=canva.com&sz=128', 'Canva AI', 'AI-powered design features within Canva including Magic Design, text-to-image, and background removal.', 'Canva AI', 'Canva中的AI设计功能,包括Magic Design、文生图和背景移除。', 'published'),
|
||||||
|
('tool_adobe_firefly', 'adobe-firefly', 'firefly.adobe.com', 'https://firefly.adobe.com', NULL, 'https://www.google.com/s2/favicons?domain=firefly.adobe.com&sz=128', 'Adobe Firefly', 'Adobe''s generative AI for creative professionals. Trained on licensed content for commercial safety.', 'Adobe Firefly', 'Adobe面向创意专业人士的生成式AI。在授权内容上训练,商用安全。', 'published'),
|
||||||
|
|
||||||
|
-- AI 视频 (6个)
|
||||||
|
('tool_runway', 'runway', 'runway.com', 'https://runway.com', NULL, 'https://www.google.com/s2/favicons?domain=runway.com&sz=128', 'Runway', 'AI video generation and editing platform. Gen-2 creates videos from text or images with cinematic quality.', 'Runway', 'AI视频生成和编辑平台。Gen-2可从文本或图像创建电影级质量的视频。', 'published'),
|
||||||
|
('tool_pika', 'pika', 'pika.art', 'https://pika.art', NULL, 'https://www.google.com/s2/favicons?domain=pika.art&sz=128', 'Pika', 'AI video generator that creates and edits videos from text prompts. Known for smooth animations and effects.', 'Pika', 'AI视频生成器,从文本提示创建和编辑视频。以流畅的动画和效果著称。', 'published'),
|
||||||
|
('tool_heygen', 'heygen', 'heygen.com', 'https://heygen.com', NULL, 'https://www.google.com/s2/favicons?domain=heygen.com&sz=128', 'HeyGen', 'AI video generator for creating professional videos with AI avatars. Perfect for marketing and training content.', 'HeyGen', 'AI视频生成器,用AI数字人创建专业视频。适合营销和培训内容。', 'published'),
|
||||||
|
('tool_synthesia', 'synthesia', 'synthesia.io', 'https://synthesia.io', NULL, 'https://www.google.com/s2/favicons?domain=synthesia.io&sz=128', 'Synthesia', 'Create AI videos with realistic avatars in 120+ languages. Popular for corporate training and marketing.', 'Synthesia', '用逼真数字人创建120多种语言的AI视频。在企业培训和营销领域广受欢迎。', 'published'),
|
||||||
|
('tool_luma', 'luma-ai', 'lumalabs.ai', 'https://lumalabs.ai', NULL, 'https://www.google.com/s2/favicons?domain=lumalabs.ai&sz=128', 'Luma AI', 'AI video generation with Dream Machine. Creates cinematic videos from text and images with impressive quality.', 'Luma AI', 'Dream Machine AI视频生成。从文本和图像创建高质量的电影级视频。', 'published'),
|
||||||
|
('tool_kling', 'kling-ai', 'klingai.com', 'https://klingai.com', NULL, 'https://www.google.com/s2/favicons?domain=klingai.com&sz=128', 'Kling AI', 'Kuaishou''s AI video generator with impressive motion and physics understanding. Creates realistic videos.', 'Kling AI', '快手的AI视频生成器,具有出色的运动和物理理解能力。创建逼真视频。', 'published'),
|
||||||
|
|
||||||
|
-- AI 音频与音乐 (5个)
|
||||||
|
('tool_elevenlabs', 'elevenlabs', 'elevenlabs.io', 'https://elevenlabs.io', NULL, 'https://www.google.com/s2/favicons?domain=elevenlabs.io&sz=128', 'ElevenLabs', 'Leading AI voice synthesis platform. Creates ultra-realistic voiceovers, dubbing, and voice cloning.', 'ElevenLabs', '领先的AI语音合成平台。创建超逼真的配音、翻译配音和声音克隆。', 'published'),
|
||||||
|
('tool_suno', 'suno', 'suno.com', 'https://suno.com', NULL, 'https://www.google.com/s2/favicons?domain=suno.com&sz=128', 'Suno', 'AI music generator that creates complete songs with vocals from text prompts. Revolutionary for music creation.', 'Suno', 'AI音乐生成器,从文本提示创建带人声的完整歌曲。音乐创作的革命性工具。', 'published'),
|
||||||
|
('tool_udio', 'udio', 'udio.com', 'https://udio.com', NULL, 'https://www.google.com/s2/favicons?domain=udio.com&sz=128', 'Udio', 'AI music generation platform creating studio-quality songs with vocals. Strong competitor to Suno.', 'Udio', 'AI音乐生成平台,创建带人声的录音室品质歌曲。Suno的强劲竞争对手。', 'published'),
|
||||||
|
('tool_murf', 'murf', 'murf.ai', 'https://murf.ai', NULL, 'https://www.google.com/s2/favicons?domain=murf.ai&sz=128', 'Murf', 'AI voice generator for creating professional voiceovers for videos, podcasts, and presentations.', 'Murf', 'AI语音生成器,为视频、播客和演示文稿创建专业配音。', 'published'),
|
||||||
|
('tool_descript', 'descript', 'descript.com', 'https://descript.com', NULL, 'https://www.google.com/s2/favicons?domain=descript.com&sz=128', 'Descript', 'AI-powered audio and video editor. Edit audio by editing text transcripts. Includes Overdub voice cloning.', 'Descript', 'AI驱动的音视频编辑器。通过编辑文字稿来编辑音频。包含Overdub声音克隆功能。', 'published'),
|
||||||
|
|
||||||
|
-- AI 编程 (6个)
|
||||||
|
('tool_github_copilot', 'github-copilot', 'github.com', 'https://github.com/features/copilot', NULL, 'https://www.google.com/s2/favicons?domain=github.com&sz=128', 'GitHub Copilot', 'AI pair programmer by GitHub and OpenAI. Suggests code completions, functions, and entire files in your IDE.', 'GitHub Copilot', 'GitHub和OpenAI的AI结对编程助手。在IDE中建议代码补全、函数和完整文件。', 'published'),
|
||||||
|
('tool_cursor', 'cursor', 'cursor.sh', 'https://cursor.sh', NULL, 'https://www.google.com/s2/favicons?domain=cursor.sh&sz=128', 'Cursor', 'AI-first code editor built on VS Code. Deep AI integration for code generation, editing, and chat.', 'Cursor', '以AI为核心的代码编辑器,基于VS Code构建。深度AI集成,支持代码生成、编辑和对话。', 'published'),
|
||||||
|
('tool_replit', 'replit', 'replit.com', 'https://replit.com', NULL, 'https://www.google.com/s2/favicons?domain=replit.com&sz=128', 'Replit AI', 'Browser-based IDE with AI assistant. Code, deploy, and collaborate with AI-powered code generation.', 'Replit AI', '基于浏览器的IDE,内置AI助手。通过AI驱动的代码生成进行编码、部署和协作。', 'published'),
|
||||||
|
('tool_codeium', 'codeium', 'codeium.com', 'https://codeium.com', NULL, 'https://www.google.com/s2/favicons?domain=codeium.com&sz=128', 'Codeium', 'Free AI coding assistant supporting 70+ languages. Fast completions with IDE extensions for major editors.', 'Codeium', '免费AI编程助手,支持70多种语言。快速补全,为主流编辑器提供IDE插件。', 'published'),
|
||||||
|
('tool_tabnine', 'tabnine', 'tabnine.com', 'https://tabnine.com', NULL, 'https://www.google.com/s2/favicons?domain=tabnine.com&sz=128', 'Tabnine', 'AI code assistant with enterprise-grade privacy. Train on your codebase for personalized suggestions.', 'Tabnine', '企业级隐私的AI代码助手。在你的代码库上训练,提供个性化建议。', 'published'),
|
||||||
|
('tool_claude_code', 'claude-code', 'claude.ai', 'https://claude.ai/code', NULL, 'https://www.google.com/s2/favicons?domain=claude.ai&sz=128', 'Claude Code', 'Anthropic''s agentic coding tool. Natural language coding with full project context understanding.', 'Claude Code', 'Anthropic的代理式编程工具。支持自然语言编程,具有完整的项目上下文理解能力。', 'published'),
|
||||||
|
|
||||||
|
-- AI 效率工具 (4个)
|
||||||
|
('tool_otter', 'otter-ai', 'otter.ai', 'https://otter.ai', NULL, 'https://www.google.com/s2/favicons?domain=otter.ai&sz=128', 'Otter.ai', 'AI meeting assistant that transcribes, summarizes, and creates action items from meetings automatically.', 'Otter.ai', 'AI会议助手,自动转录、总结会议并创建待办事项。', 'published'),
|
||||||
|
('tool_fireflies', 'fireflies', 'fireflies.ai', 'https://fireflies.ai', NULL, 'https://www.google.com/s2/favicons?domain=fireflies.ai&sz=128', 'Fireflies.ai', 'AI notetaker for meetings. Records, transcribes, and summarizes meetings across all major platforms.', 'Fireflies.ai', '会议AI笔记助手。在所有主流平台上录制、转录和总结会议。', 'published'),
|
||||||
|
('tool_mem', 'mem-ai', 'mem.ai', 'https://mem.ai', NULL, 'https://www.google.com/s2/favicons?domain=mem.ai&sz=128', 'Mem', 'AI-powered note-taking app that organizes and surfaces relevant information automatically.', 'Mem', 'AI驱动的笔记应用,自动组织和呈现相关信息。', 'published'),
|
||||||
|
('tool_motion', 'motion', 'usemotion.com', 'https://usemotion.com', NULL, 'https://www.google.com/s2/favicons?domain=usemotion.com&sz=128', 'Motion', 'AI calendar and project management. Automatically schedules tasks and meetings for optimal productivity.', 'Motion', 'AI日历和项目管理工具。自动安排任务和会议以实现最佳生产力。', 'published'),
|
||||||
|
|
||||||
|
-- AI 研究与分析 (3个)
|
||||||
|
('tool_consensus', 'consensus', 'consensus.app', 'https://consensus.app', NULL, 'https://www.google.com/s2/favicons?domain=consensus.app&sz=128', 'Consensus', 'AI-powered academic search engine. Find and understand scientific research with AI summaries.', 'Consensus', 'AI驱动的学术搜索引擎。通过AI摘要查找和理解科学研究。', 'published'),
|
||||||
|
('tool_elicit', 'elicit', 'elicit.com', 'https://elicit.com', NULL, 'https://www.google.com/s2/favicons?domain=elicit.com&sz=128', 'Elicit', 'AI research assistant that helps find, summarize, and extract data from academic papers.', 'Elicit', 'AI研究助手,帮助查找、总结和提取学术论文中的数据。', 'published'),
|
||||||
|
('tool_scite', 'scite', 'scite.ai', 'https://scite.ai', NULL, 'https://www.google.com/s2/favicons?domain=scite.ai&sz=128', 'Scite', 'AI tool for evaluating scientific claims. Shows how papers cite each other with supporting or contrasting evidence.', 'Scite', 'AI科学声明评估工具。显示论文如何相互引用,包含支持或对比证据。', 'published'),
|
||||||
|
|
||||||
|
-- AI 设计 (3个)
|
||||||
|
('tool_figma_ai', 'figma-ai', 'figma.com', 'https://figma.com', NULL, 'https://www.google.com/s2/favicons?domain=figma.com&sz=128', 'Figma AI', 'AI features in Figma for generating designs, renaming layers, and creating content automatically.', 'Figma AI', 'Figma中的AI功能,用于生成设计、重命名图层和自动创建内容。', 'published'),
|
||||||
|
('tool_uizard', 'uizard', 'uizard.io', 'https://uizard.io', NULL, 'https://www.google.com/s2/favicons?domain=uizard.io&sz=128', 'Uizard', 'AI-powered UI design tool. Transform sketches and screenshots into editable designs.', 'Uizard', 'AI驱动的UI设计工具。将草图和截图转换为可编辑的设计。', 'published'),
|
||||||
|
('tool_magician', 'magician', 'magician.design', 'https://magician.design', NULL, 'https://www.google.com/s2/favicons?domain=magician.design&sz=128', 'Magician', 'AI design assistant plugin for Figma. Generate icons, images, and copy with AI.', 'Magician', 'Figma的AI设计助手插件。用AI生成图标、图像和文案。', 'published');
|
||||||
|
|
||||||
|
-- ========== 4. 工具-分类关联 ==========
|
||||||
|
INSERT INTO tool_category (tool_id, category_id) VALUES
|
||||||
|
-- 聊天机器人
|
||||||
|
('tool_chatgpt', 'cat_chatbot'),
|
||||||
|
('tool_claude', 'cat_chatbot'),
|
||||||
|
('tool_gemini', 'cat_chatbot'),
|
||||||
|
('tool_perplexity', 'cat_chatbot'),
|
||||||
|
('tool_perplexity', 'cat_research'),
|
||||||
|
('tool_poe', 'cat_chatbot'),
|
||||||
|
('tool_character', 'cat_chatbot'),
|
||||||
|
('tool_coze', 'cat_chatbot'),
|
||||||
|
('tool_pi', 'cat_chatbot'),
|
||||||
|
-- 写作
|
||||||
|
('tool_jasper', 'cat_writing'),
|
||||||
|
('tool_jasper', 'cat_business'),
|
||||||
|
('tool_copy_ai', 'cat_writing'),
|
||||||
|
('tool_copy_ai', 'cat_business'),
|
||||||
|
('tool_writesonic', 'cat_writing'),
|
||||||
|
('tool_notion_ai', 'cat_writing'),
|
||||||
|
('tool_notion_ai', 'cat_productivity'),
|
||||||
|
('tool_grammarly', 'cat_writing'),
|
||||||
|
('tool_quillbot', 'cat_writing'),
|
||||||
|
('tool_rytr', 'cat_writing'),
|
||||||
|
-- 图像生成
|
||||||
|
('tool_midjourney', 'cat_image'),
|
||||||
|
('tool_dalle', 'cat_image'),
|
||||||
|
('tool_stable_diffusion', 'cat_image'),
|
||||||
|
('tool_leonardo', 'cat_image'),
|
||||||
|
('tool_ideogram', 'cat_image'),
|
||||||
|
('tool_playground', 'cat_image'),
|
||||||
|
('tool_canva_ai', 'cat_image'),
|
||||||
|
('tool_canva_ai', 'cat_design'),
|
||||||
|
('tool_adobe_firefly', 'cat_image'),
|
||||||
|
('tool_adobe_firefly', 'cat_design'),
|
||||||
|
-- 视频
|
||||||
|
('tool_runway', 'cat_video'),
|
||||||
|
('tool_pika', 'cat_video'),
|
||||||
|
('tool_heygen', 'cat_video'),
|
||||||
|
('tool_heygen', 'cat_business'),
|
||||||
|
('tool_synthesia', 'cat_video'),
|
||||||
|
('tool_synthesia', 'cat_business'),
|
||||||
|
('tool_luma', 'cat_video'),
|
||||||
|
('tool_kling', 'cat_video'),
|
||||||
|
-- 音频
|
||||||
|
('tool_elevenlabs', 'cat_audio'),
|
||||||
|
('tool_suno', 'cat_audio'),
|
||||||
|
('tool_udio', 'cat_audio'),
|
||||||
|
('tool_murf', 'cat_audio'),
|
||||||
|
('tool_descript', 'cat_audio'),
|
||||||
|
('tool_descript', 'cat_video'),
|
||||||
|
-- 编程
|
||||||
|
('tool_github_copilot', 'cat_coding'),
|
||||||
|
('tool_cursor', 'cat_coding'),
|
||||||
|
('tool_replit', 'cat_coding'),
|
||||||
|
('tool_codeium', 'cat_coding'),
|
||||||
|
('tool_tabnine', 'cat_coding'),
|
||||||
|
('tool_claude_code', 'cat_coding'),
|
||||||
|
-- 效率
|
||||||
|
('tool_otter', 'cat_productivity'),
|
||||||
|
('tool_fireflies', 'cat_productivity'),
|
||||||
|
('tool_mem', 'cat_productivity'),
|
||||||
|
('tool_motion', 'cat_productivity'),
|
||||||
|
-- 研究
|
||||||
|
('tool_consensus', 'cat_research'),
|
||||||
|
('tool_elicit', 'cat_research'),
|
||||||
|
('tool_scite', 'cat_research'),
|
||||||
|
-- 设计
|
||||||
|
('tool_figma_ai', 'cat_design'),
|
||||||
|
('tool_uizard', 'cat_design'),
|
||||||
|
('tool_magician', 'cat_design');
|
||||||
|
|
||||||
|
-- ========== 5. 工具-标签关联 ==========
|
||||||
|
INSERT INTO tool_tag (tool_id, tag_id) VALUES
|
||||||
|
-- ChatGPT
|
||||||
|
('tool_chatgpt', 'tag_freemium'),
|
||||||
|
('tool_chatgpt', 'tag_gpt'),
|
||||||
|
('tool_chatgpt', 'tag_llm'),
|
||||||
|
('tool_chatgpt', 'tag_api'),
|
||||||
|
('tool_chatgpt', 'tag_popular'),
|
||||||
|
('tool_chatgpt', 'tag_mobile'),
|
||||||
|
('tool_chatgpt', 'tag_text'),
|
||||||
|
-- Claude
|
||||||
|
('tool_claude', 'tag_freemium'),
|
||||||
|
('tool_claude', 'tag_claude'),
|
||||||
|
('tool_claude', 'tag_llm'),
|
||||||
|
('tool_claude', 'tag_api'),
|
||||||
|
('tool_claude', 'tag_popular'),
|
||||||
|
('tool_claude', 'tag_text'),
|
||||||
|
('tool_claude', 'tag_code_out'),
|
||||||
|
-- Gemini
|
||||||
|
('tool_gemini', 'tag_free'),
|
||||||
|
('tool_gemini', 'tag_llm'),
|
||||||
|
('tool_gemini', 'tag_api'),
|
||||||
|
('tool_gemini', 'tag_mobile'),
|
||||||
|
('tool_gemini', 'tag_text'),
|
||||||
|
-- Perplexity
|
||||||
|
('tool_perplexity', 'tag_freemium'),
|
||||||
|
('tool_perplexity', 'tag_llm'),
|
||||||
|
('tool_perplexity', 'tag_realtime'),
|
||||||
|
('tool_perplexity', 'tag_trending'),
|
||||||
|
('tool_perplexity', 'tag_text'),
|
||||||
|
-- Poe
|
||||||
|
('tool_poe', 'tag_freemium'),
|
||||||
|
('tool_poe', 'tag_llm'),
|
||||||
|
('tool_poe', 'tag_beginner'),
|
||||||
|
('tool_poe', 'tag_mobile'),
|
||||||
|
-- Character.AI
|
||||||
|
('tool_character', 'tag_free'),
|
||||||
|
('tool_character', 'tag_llm'),
|
||||||
|
('tool_character', 'tag_creator'),
|
||||||
|
('tool_character', 'tag_mobile'),
|
||||||
|
-- Coze
|
||||||
|
('tool_coze', 'tag_free'),
|
||||||
|
('tool_coze', 'tag_llm'),
|
||||||
|
('tool_coze', 'tag_api'),
|
||||||
|
('tool_coze', 'tag_developer'),
|
||||||
|
-- Pi
|
||||||
|
('tool_pi', 'tag_free'),
|
||||||
|
('tool_pi', 'tag_llm'),
|
||||||
|
('tool_pi', 'tag_beginner'),
|
||||||
|
('tool_pi', 'tag_mobile'),
|
||||||
|
-- Jasper
|
||||||
|
('tool_jasper', 'tag_paid'),
|
||||||
|
('tool_jasper', 'tag_gpt'),
|
||||||
|
('tool_jasper', 'tag_enterprise'),
|
||||||
|
('tool_jasper', 'tag_marketer'),
|
||||||
|
('tool_jasper', 'tag_text'),
|
||||||
|
-- Copy.ai
|
||||||
|
('tool_copy_ai', 'tag_freemium'),
|
||||||
|
('tool_copy_ai', 'tag_gpt'),
|
||||||
|
('tool_copy_ai', 'tag_marketer'),
|
||||||
|
('tool_copy_ai', 'tag_text'),
|
||||||
|
-- Writesonic
|
||||||
|
('tool_writesonic', 'tag_freemium'),
|
||||||
|
('tool_writesonic', 'tag_gpt'),
|
||||||
|
('tool_writesonic', 'tag_marketer'),
|
||||||
|
('tool_writesonic', 'tag_text'),
|
||||||
|
-- Notion AI
|
||||||
|
('tool_notion_ai', 'tag_paid'),
|
||||||
|
('tool_notion_ai', 'tag_collaborative'),
|
||||||
|
('tool_notion_ai', 'tag_text'),
|
||||||
|
('tool_notion_ai', 'tag_popular'),
|
||||||
|
-- Grammarly
|
||||||
|
('tool_grammarly', 'tag_freemium'),
|
||||||
|
('tool_grammarly', 'tag_browser'),
|
||||||
|
('tool_grammarly', 'tag_desktop'),
|
||||||
|
('tool_grammarly', 'tag_popular'),
|
||||||
|
('tool_grammarly', 'tag_text'),
|
||||||
|
-- QuillBot
|
||||||
|
('tool_quillbot', 'tag_freemium'),
|
||||||
|
('tool_quillbot', 'tag_student'),
|
||||||
|
('tool_quillbot', 'tag_browser'),
|
||||||
|
('tool_quillbot', 'tag_text'),
|
||||||
|
-- Rytr
|
||||||
|
('tool_rytr', 'tag_freemium'),
|
||||||
|
('tool_rytr', 'tag_beginner'),
|
||||||
|
('tool_rytr', 'tag_text'),
|
||||||
|
-- Midjourney
|
||||||
|
('tool_midjourney', 'tag_paid'),
|
||||||
|
('tool_midjourney', 'tag_diffusion'),
|
||||||
|
('tool_midjourney', 'tag_creator'),
|
||||||
|
('tool_midjourney', 'tag_popular'),
|
||||||
|
('tool_midjourney', 'tag_image_out'),
|
||||||
|
('tool_midjourney', 'tag_editors_choice'),
|
||||||
|
-- DALL-E
|
||||||
|
('tool_dalle', 'tag_freemium'),
|
||||||
|
('tool_dalle', 'tag_diffusion'),
|
||||||
|
('tool_dalle', 'tag_api'),
|
||||||
|
('tool_dalle', 'tag_image_out'),
|
||||||
|
('tool_dalle', 'tag_popular'),
|
||||||
|
-- Stable Diffusion
|
||||||
|
('tool_stable_diffusion', 'tag_free'),
|
||||||
|
('tool_stable_diffusion', 'tag_open_source'),
|
||||||
|
('tool_stable_diffusion', 'tag_diffusion'),
|
||||||
|
('tool_stable_diffusion', 'tag_developer'),
|
||||||
|
('tool_stable_diffusion', 'tag_image_out'),
|
||||||
|
-- Leonardo AI
|
||||||
|
('tool_leonardo', 'tag_freemium'),
|
||||||
|
('tool_leonardo', 'tag_diffusion'),
|
||||||
|
('tool_leonardo', 'tag_creator'),
|
||||||
|
('tool_leonardo', 'tag_image_out'),
|
||||||
|
-- Ideogram
|
||||||
|
('tool_ideogram', 'tag_freemium'),
|
||||||
|
('tool_ideogram', 'tag_diffusion'),
|
||||||
|
('tool_ideogram', 'tag_image_out'),
|
||||||
|
('tool_ideogram', 'tag_trending'),
|
||||||
|
-- Playground AI
|
||||||
|
('tool_playground', 'tag_free'),
|
||||||
|
('tool_playground', 'tag_diffusion'),
|
||||||
|
('tool_playground', 'tag_beginner'),
|
||||||
|
('tool_playground', 'tag_image_out'),
|
||||||
|
-- Canva AI
|
||||||
|
('tool_canva_ai', 'tag_freemium'),
|
||||||
|
('tool_canva_ai', 'tag_beginner'),
|
||||||
|
('tool_canva_ai', 'tag_popular'),
|
||||||
|
('tool_canva_ai', 'tag_image_out'),
|
||||||
|
-- Adobe Firefly
|
||||||
|
('tool_adobe_firefly', 'tag_freemium'),
|
||||||
|
('tool_adobe_firefly', 'tag_diffusion'),
|
||||||
|
('tool_adobe_firefly', 'tag_enterprise'),
|
||||||
|
('tool_adobe_firefly', 'tag_image_out'),
|
||||||
|
-- Runway
|
||||||
|
('tool_runway', 'tag_freemium'),
|
||||||
|
('tool_runway', 'tag_diffusion'),
|
||||||
|
('tool_runway', 'tag_creator'),
|
||||||
|
('tool_runway', 'tag_video_out'),
|
||||||
|
('tool_runway', 'tag_editors_choice'),
|
||||||
|
-- Pika
|
||||||
|
('tool_pika', 'tag_freemium'),
|
||||||
|
('tool_pika', 'tag_diffusion'),
|
||||||
|
('tool_pika', 'tag_video_out'),
|
||||||
|
('tool_pika', 'tag_trending'),
|
||||||
|
-- HeyGen
|
||||||
|
('tool_heygen', 'tag_paid'),
|
||||||
|
('tool_heygen', 'tag_enterprise'),
|
||||||
|
('tool_heygen', 'tag_marketer'),
|
||||||
|
('tool_heygen', 'tag_video_out'),
|
||||||
|
-- Synthesia
|
||||||
|
('tool_synthesia', 'tag_paid'),
|
||||||
|
('tool_synthesia', 'tag_enterprise'),
|
||||||
|
('tool_synthesia', 'tag_video_out'),
|
||||||
|
('tool_synthesia', 'tag_popular'),
|
||||||
|
-- Luma AI
|
||||||
|
('tool_luma', 'tag_freemium'),
|
||||||
|
('tool_luma', 'tag_diffusion'),
|
||||||
|
('tool_luma', 'tag_video_out'),
|
||||||
|
('tool_luma', 'tag_trending'),
|
||||||
|
-- Kling AI
|
||||||
|
('tool_kling', 'tag_freemium'),
|
||||||
|
('tool_kling', 'tag_diffusion'),
|
||||||
|
('tool_kling', 'tag_video_out'),
|
||||||
|
('tool_kling', 'tag_new'),
|
||||||
|
-- ElevenLabs
|
||||||
|
('tool_elevenlabs', 'tag_freemium'),
|
||||||
|
('tool_elevenlabs', 'tag_api'),
|
||||||
|
('tool_elevenlabs', 'tag_creator'),
|
||||||
|
('tool_elevenlabs', 'tag_audio_out'),
|
||||||
|
('tool_elevenlabs', 'tag_popular'),
|
||||||
|
-- Suno
|
||||||
|
('tool_suno', 'tag_freemium'),
|
||||||
|
('tool_suno', 'tag_creator'),
|
||||||
|
('tool_suno', 'tag_audio_out'),
|
||||||
|
('tool_suno', 'tag_trending'),
|
||||||
|
('tool_suno', 'tag_editors_choice'),
|
||||||
|
-- Udio
|
||||||
|
('tool_udio', 'tag_freemium'),
|
||||||
|
('tool_udio', 'tag_creator'),
|
||||||
|
('tool_udio', 'tag_audio_out'),
|
||||||
|
('tool_udio', 'tag_new'),
|
||||||
|
-- Murf
|
||||||
|
('tool_murf', 'tag_freemium'),
|
||||||
|
('tool_murf', 'tag_marketer'),
|
||||||
|
('tool_murf', 'tag_audio_out'),
|
||||||
|
-- Descript
|
||||||
|
('tool_descript', 'tag_freemium'),
|
||||||
|
('tool_descript', 'tag_creator'),
|
||||||
|
('tool_descript', 'tag_audio_out'),
|
||||||
|
('tool_descript', 'tag_video_out'),
|
||||||
|
('tool_descript', 'tag_desktop'),
|
||||||
|
-- GitHub Copilot
|
||||||
|
('tool_github_copilot', 'tag_paid'),
|
||||||
|
('tool_github_copilot', 'tag_gpt'),
|
||||||
|
('tool_github_copilot', 'tag_developer'),
|
||||||
|
('tool_github_copilot', 'tag_code_out'),
|
||||||
|
('tool_github_copilot', 'tag_popular'),
|
||||||
|
('tool_github_copilot', 'tag_editors_choice'),
|
||||||
|
-- Cursor
|
||||||
|
('tool_cursor', 'tag_freemium'),
|
||||||
|
('tool_cursor', 'tag_claude'),
|
||||||
|
('tool_cursor', 'tag_developer'),
|
||||||
|
('tool_cursor', 'tag_code_out'),
|
||||||
|
('tool_cursor', 'tag_trending'),
|
||||||
|
('tool_cursor', 'tag_desktop'),
|
||||||
|
-- Replit
|
||||||
|
('tool_replit', 'tag_freemium'),
|
||||||
|
('tool_replit', 'tag_llm'),
|
||||||
|
('tool_replit', 'tag_developer'),
|
||||||
|
('tool_replit', 'tag_beginner'),
|
||||||
|
('tool_replit', 'tag_code_out'),
|
||||||
|
-- Codeium
|
||||||
|
('tool_codeium', 'tag_free'),
|
||||||
|
('tool_codeium', 'tag_llm'),
|
||||||
|
('tool_codeium', 'tag_developer'),
|
||||||
|
('tool_codeium', 'tag_code_out'),
|
||||||
|
-- Tabnine
|
||||||
|
('tool_tabnine', 'tag_freemium'),
|
||||||
|
('tool_tabnine', 'tag_enterprise'),
|
||||||
|
('tool_tabnine', 'tag_developer'),
|
||||||
|
('tool_tabnine', 'tag_code_out'),
|
||||||
|
-- Claude Code
|
||||||
|
('tool_claude_code', 'tag_paid'),
|
||||||
|
('tool_claude_code', 'tag_claude'),
|
||||||
|
('tool_claude_code', 'tag_developer'),
|
||||||
|
('tool_claude_code', 'tag_code_out'),
|
||||||
|
('tool_claude_code', 'tag_new'),
|
||||||
|
-- Otter.ai
|
||||||
|
('tool_otter', 'tag_freemium'),
|
||||||
|
('tool_otter', 'tag_collaborative'),
|
||||||
|
('tool_otter', 'tag_realtime'),
|
||||||
|
('tool_otter', 'tag_text'),
|
||||||
|
('tool_otter', 'tag_mobile'),
|
||||||
|
-- Fireflies.ai
|
||||||
|
('tool_fireflies', 'tag_freemium'),
|
||||||
|
('tool_fireflies', 'tag_collaborative'),
|
||||||
|
('tool_fireflies', 'tag_enterprise'),
|
||||||
|
('tool_fireflies', 'tag_text'),
|
||||||
|
-- Mem
|
||||||
|
('tool_mem', 'tag_freemium'),
|
||||||
|
('tool_mem', 'tag_llm'),
|
||||||
|
('tool_mem', 'tag_text'),
|
||||||
|
-- Motion
|
||||||
|
('tool_motion', 'tag_paid'),
|
||||||
|
('tool_motion', 'tag_collaborative'),
|
||||||
|
-- Consensus
|
||||||
|
('tool_consensus', 'tag_freemium'),
|
||||||
|
('tool_consensus', 'tag_llm'),
|
||||||
|
('tool_consensus', 'tag_student'),
|
||||||
|
('tool_consensus', 'tag_text'),
|
||||||
|
-- Elicit
|
||||||
|
('tool_elicit', 'tag_freemium'),
|
||||||
|
('tool_elicit', 'tag_llm'),
|
||||||
|
('tool_elicit', 'tag_student'),
|
||||||
|
('tool_elicit', 'tag_text'),
|
||||||
|
-- Scite
|
||||||
|
('tool_scite', 'tag_paid'),
|
||||||
|
('tool_scite', 'tag_student'),
|
||||||
|
('tool_scite', 'tag_text'),
|
||||||
|
-- Figma AI
|
||||||
|
('tool_figma_ai', 'tag_paid'),
|
||||||
|
('tool_figma_ai', 'tag_collaborative'),
|
||||||
|
('tool_figma_ai', 'tag_popular'),
|
||||||
|
('tool_figma_ai', 'tag_image_out'),
|
||||||
|
-- Uizard
|
||||||
|
('tool_uizard', 'tag_freemium'),
|
||||||
|
('tool_uizard', 'tag_beginner'),
|
||||||
|
('tool_uizard', 'tag_image_out'),
|
||||||
|
-- Magician
|
||||||
|
('tool_magician', 'tag_paid'),
|
||||||
|
('tool_magician', 'tag_image_out'),
|
||||||
|
('tool_magician', 'tag_text');
|
||||||
@@ -0,0 +1,626 @@
|
|||||||
|
#!/usr/bin/env tsx
|
||||||
|
/**
|
||||||
|
* AI 工具种子数据导入脚本
|
||||||
|
*
|
||||||
|
* 用法:
|
||||||
|
* pnpm seed:tools
|
||||||
|
*
|
||||||
|
* 功能:
|
||||||
|
* - 导入 10 个分类
|
||||||
|
* - 导入 30 个标签
|
||||||
|
* - 导入 50 个 AI 工具
|
||||||
|
* - 建立工具-分类、工具-标签关联
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { drizzle } from "drizzle-orm/postgres-js";
|
||||||
|
import postgres from "postgres";
|
||||||
|
import dotenv from "dotenv";
|
||||||
|
import { resolve } from "path";
|
||||||
|
import { pgTable, text, timestamp, integer, primaryKey, index } from "drizzle-orm/pg-core";
|
||||||
|
|
||||||
|
// 加载环境变量
|
||||||
|
dotenv.config({ path: resolve(process.cwd(), ".env.local") });
|
||||||
|
|
||||||
|
// ========== Schema 定义 ==========
|
||||||
|
const tool = pgTable("tool", {
|
||||||
|
id: text("id").primaryKey(),
|
||||||
|
slug: text("slug").notNull().unique(),
|
||||||
|
domain: text("domain"),
|
||||||
|
websiteUrl: text("website_url"),
|
||||||
|
coverImageUrl: text("cover_image_url"),
|
||||||
|
logoUrl: text("logo_url"),
|
||||||
|
nameEn: text("name_en").notNull(),
|
||||||
|
descriptionEn: text("description_en"),
|
||||||
|
nameZh: text("name_zh"),
|
||||||
|
descriptionZh: text("description_zh"),
|
||||||
|
status: text("status").notNull().default("draft"),
|
||||||
|
createdAt: timestamp("created_at").defaultNow().notNull(),
|
||||||
|
updatedAt: timestamp("updated_at").defaultNow().$onUpdate(() => new Date()).notNull(),
|
||||||
|
}, (table) => [
|
||||||
|
index("tool_slug_idx").on(table.slug),
|
||||||
|
index("tool_status_idx").on(table.status),
|
||||||
|
]);
|
||||||
|
|
||||||
|
const category = pgTable("category", {
|
||||||
|
id: text("id").primaryKey(),
|
||||||
|
slug: text("slug").notNull().unique(),
|
||||||
|
icon: text("icon"),
|
||||||
|
sortOrder: integer("sort_order").notNull().default(0),
|
||||||
|
nameEn: text("name_en").notNull(),
|
||||||
|
descriptionEn: text("description_en"),
|
||||||
|
nameZh: text("name_zh"),
|
||||||
|
descriptionZh: text("description_zh"),
|
||||||
|
createdAt: timestamp("created_at").defaultNow().notNull(),
|
||||||
|
updatedAt: timestamp("updated_at").defaultNow().$onUpdate(() => new Date()).notNull(),
|
||||||
|
}, (table) => [
|
||||||
|
index("category_slug_idx").on(table.slug),
|
||||||
|
index("category_sort_order_idx").on(table.sortOrder),
|
||||||
|
]);
|
||||||
|
|
||||||
|
const tag = pgTable("tag", {
|
||||||
|
id: text("id").primaryKey(),
|
||||||
|
slug: text("slug").notNull().unique(),
|
||||||
|
nameEn: text("name_en").notNull(),
|
||||||
|
nameZh: text("name_zh"),
|
||||||
|
createdAt: timestamp("created_at").defaultNow().notNull(),
|
||||||
|
updatedAt: timestamp("updated_at").defaultNow().$onUpdate(() => new Date()).notNull(),
|
||||||
|
}, (table) => [
|
||||||
|
index("tag_slug_idx").on(table.slug),
|
||||||
|
]);
|
||||||
|
|
||||||
|
const toolCategory = pgTable("tool_category", {
|
||||||
|
toolId: text("tool_id").notNull().references(() => tool.id, { onDelete: "cascade" }),
|
||||||
|
categoryId: text("category_id").notNull().references(() => category.id, { onDelete: "cascade" }),
|
||||||
|
}, (table) => [
|
||||||
|
primaryKey({ columns: [table.toolId, table.categoryId] }),
|
||||||
|
index("tool_category_tool_id_idx").on(table.toolId),
|
||||||
|
index("tool_category_category_id_idx").on(table.categoryId),
|
||||||
|
]);
|
||||||
|
|
||||||
|
const toolTag = pgTable("tool_tag", {
|
||||||
|
toolId: text("tool_id").notNull().references(() => tool.id, { onDelete: "cascade" }),
|
||||||
|
tagId: text("tag_id").notNull().references(() => tag.id, { onDelete: "cascade" }),
|
||||||
|
}, (table) => [
|
||||||
|
primaryKey({ columns: [table.toolId, table.tagId] }),
|
||||||
|
index("tool_tag_tool_id_idx").on(table.toolId),
|
||||||
|
index("tool_tag_tag_id_idx").on(table.tagId),
|
||||||
|
]);
|
||||||
|
|
||||||
|
// ========== 种子数据 ==========
|
||||||
|
|
||||||
|
const categoriesData = [
|
||||||
|
{ id: 'cat_chatbot', slug: 'chatbot', icon: '💬', sortOrder: 1, nameEn: 'AI Chatbots', descriptionEn: 'Conversational AI assistants and chatbots', nameZh: 'AI 聊天机器人', descriptionZh: '对话式AI助手和聊天机器人' },
|
||||||
|
{ id: 'cat_writing', slug: 'writing', icon: '✍️', sortOrder: 2, nameEn: 'Writing & Content', descriptionEn: 'AI-powered writing assistants and content generation tools', nameZh: 'AI 写作', descriptionZh: 'AI驱动的写作助手和内容生成工具' },
|
||||||
|
{ id: 'cat_image', slug: 'image-generation', icon: '🎨', sortOrder: 3, nameEn: 'Image Generation', descriptionEn: 'AI tools for creating and editing images', nameZh: 'AI 图像生成', descriptionZh: 'AI图像创作和编辑工具' },
|
||||||
|
{ id: 'cat_video', slug: 'video', icon: '🎬', sortOrder: 4, nameEn: 'Video & Animation', descriptionEn: 'AI video generation, editing and animation tools', nameZh: 'AI 视频', descriptionZh: 'AI视频生成、编辑和动画工具' },
|
||||||
|
{ id: 'cat_audio', slug: 'audio-music', icon: '🎵', sortOrder: 5, nameEn: 'Audio & Music', descriptionEn: 'AI music generation, voice synthesis and audio tools', nameZh: 'AI 音频与音乐', descriptionZh: 'AI音乐生成、语音合成和音频工具' },
|
||||||
|
{ id: 'cat_coding', slug: 'coding', icon: '💻', sortOrder: 6, nameEn: 'Coding & Development', descriptionEn: 'AI coding assistants and developer tools', nameZh: 'AI 编程', descriptionZh: 'AI编程助手和开发者工具' },
|
||||||
|
{ id: 'cat_productivity', slug: 'productivity', icon: '⚡', sortOrder: 7, nameEn: 'Productivity', descriptionEn: 'AI tools to boost your work efficiency', nameZh: 'AI 效率工具', descriptionZh: '提升工作效率的AI工具' },
|
||||||
|
{ id: 'cat_research', slug: 'research', icon: '🔬', sortOrder: 8, nameEn: 'Research & Analysis', descriptionEn: 'AI research assistants and data analysis tools', nameZh: 'AI 研究与分析', descriptionZh: 'AI研究助手和数据分析工具' },
|
||||||
|
{ id: 'cat_design', slug: 'design', icon: '🎯', sortOrder: 9, nameEn: 'Design & Creative', descriptionEn: 'AI tools for designers and creative professionals', nameZh: 'AI 设计', descriptionZh: '面向设计师和创意专业人士的AI工具' },
|
||||||
|
{ id: 'cat_business', slug: 'business', icon: '📊', sortOrder: 10, nameEn: 'Business & Marketing', descriptionEn: 'AI tools for business operations and marketing', nameZh: 'AI 商业与营销', descriptionZh: '商业运营和营销AI工具' },
|
||||||
|
];
|
||||||
|
|
||||||
|
const tagsData = [
|
||||||
|
// 功能标签
|
||||||
|
{ id: 'tag_free', slug: 'free', nameEn: 'Free', nameZh: '免费' },
|
||||||
|
{ id: 'tag_freemium', slug: 'freemium', nameEn: 'Freemium', nameZh: '免费增值' },
|
||||||
|
{ id: 'tag_paid', slug: 'paid', nameEn: 'Paid', nameZh: '付费' },
|
||||||
|
{ id: 'tag_open_source', slug: 'open-source', nameEn: 'Open Source', nameZh: '开源' },
|
||||||
|
{ id: 'tag_api', slug: 'api-available', nameEn: 'API Available', nameZh: '提供API' },
|
||||||
|
// 技术标签
|
||||||
|
{ id: 'tag_gpt', slug: 'gpt-powered', nameEn: 'GPT Powered', nameZh: 'GPT驱动' },
|
||||||
|
{ id: 'tag_claude', slug: 'claude-powered', nameEn: 'Claude Powered', nameZh: 'Claude驱动' },
|
||||||
|
{ id: 'tag_diffusion', slug: 'diffusion-model', nameEn: 'Diffusion Model', nameZh: '扩散模型' },
|
||||||
|
{ id: 'tag_llm', slug: 'llm', nameEn: 'Large Language Model', nameZh: '大语言模型' },
|
||||||
|
// 用途标签
|
||||||
|
{ id: 'tag_beginner', slug: 'beginner-friendly', nameEn: 'Beginner Friendly', nameZh: '新手友好' },
|
||||||
|
{ id: 'tag_enterprise', slug: 'enterprise', nameEn: 'Enterprise', nameZh: '企业级' },
|
||||||
|
{ id: 'tag_creator', slug: 'for-creators', nameEn: 'For Creators', nameZh: '创作者适用' },
|
||||||
|
{ id: 'tag_developer', slug: 'for-developers', nameEn: 'For Developers', nameZh: '开发者适用' },
|
||||||
|
{ id: 'tag_marketer', slug: 'for-marketers', nameEn: 'For Marketers', nameZh: '营销人员适用' },
|
||||||
|
{ id: 'tag_student', slug: 'for-students', nameEn: 'For Students', nameZh: '学生适用' },
|
||||||
|
// 特性标签
|
||||||
|
{ id: 'tag_realtime', slug: 'real-time', nameEn: 'Real-time', nameZh: '实时' },
|
||||||
|
{ id: 'tag_collaborative', slug: 'collaborative', nameEn: 'Collaborative', nameZh: '协作' },
|
||||||
|
{ id: 'tag_mobile', slug: 'mobile-app', nameEn: 'Mobile App', nameZh: '移动应用' },
|
||||||
|
{ id: 'tag_browser', slug: 'browser-extension', nameEn: 'Browser Extension', nameZh: '浏览器插件' },
|
||||||
|
{ id: 'tag_desktop', slug: 'desktop-app', nameEn: 'Desktop App', nameZh: '桌面应用' },
|
||||||
|
// 输出类型标签
|
||||||
|
{ id: 'tag_text', slug: 'text-output', nameEn: 'Text Output', nameZh: '文本输出' },
|
||||||
|
{ id: 'tag_image_out', slug: 'image-output', nameEn: 'Image Output', nameZh: '图像输出' },
|
||||||
|
{ id: 'tag_video_out', slug: 'video-output', nameEn: 'Video Output', nameZh: '视频输出' },
|
||||||
|
{ id: 'tag_audio_out', slug: 'audio-output', nameEn: 'Audio Output', nameZh: '音频输出' },
|
||||||
|
{ id: 'tag_code_out', slug: 'code-output', nameEn: 'Code Output', nameZh: '代码输出' },
|
||||||
|
// 热门标签
|
||||||
|
{ id: 'tag_trending', slug: 'trending', nameEn: 'Trending', nameZh: '热门' },
|
||||||
|
{ id: 'tag_new', slug: 'new', nameEn: 'New', nameZh: '新上线' },
|
||||||
|
{ id: 'tag_popular', slug: 'popular', nameEn: 'Popular', nameZh: '受欢迎' },
|
||||||
|
{ id: 'tag_editors_choice', slug: 'editors-choice', nameEn: "Editor's Choice", nameZh: '编辑推荐' },
|
||||||
|
{ id: 'tag_best_2025', slug: 'best-of-2025', nameEn: 'Best of 2025', nameZh: '2025精选' },
|
||||||
|
];
|
||||||
|
|
||||||
|
const toolsData = [
|
||||||
|
// AI 聊天机器人 (8个)
|
||||||
|
{ id: 'tool_chatgpt', slug: 'chatgpt', domain: 'chat.openai.com', websiteUrl: 'https://chat.openai.com', logoUrl: 'https://chat.openai.com/favicon.ico', nameEn: 'ChatGPT', descriptionEn: "OpenAI's advanced conversational AI assistant powered by GPT-4. Capable of natural conversations, code generation, creative writing, and complex reasoning.", nameZh: 'ChatGPT', descriptionZh: 'OpenAI推出的先进对话式AI助手,由GPT-4驱动。能够进行自然对话、代码生成、创意写作和复杂推理。', status: 'published' },
|
||||||
|
{ id: 'tool_claude', slug: 'claude', domain: 'claude.ai', websiteUrl: 'https://claude.ai', logoUrl: 'https://claude.ai/favicon.ico', nameEn: 'Claude', descriptionEn: "Anthropic's helpful, harmless, and honest AI assistant. Known for nuanced understanding, long context windows, and thoughtful responses.", nameZh: 'Claude', descriptionZh: 'Anthropic推出的有用、无害、诚实的AI助手。以细腻的理解、长上下文窗口和深思熟虑的回复著称。', status: 'published' },
|
||||||
|
{ id: 'tool_gemini', slug: 'gemini', domain: 'gemini.google.com', websiteUrl: 'https://gemini.google.com', logoUrl: 'https://gemini.google.com/favicon.ico', nameEn: 'Google Gemini', descriptionEn: "Google's multimodal AI assistant that can understand text, images, audio, and video. Integrated with Google services.", nameZh: 'Google Gemini', descriptionZh: 'Google的多模态AI助手,能够理解文本、图像、音频和视频。与Google服务深度集成。', status: 'published' },
|
||||||
|
{ id: 'tool_perplexity', slug: 'perplexity', domain: 'perplexity.ai', websiteUrl: 'https://perplexity.ai', logoUrl: 'https://perplexity.ai/favicon.ico', nameEn: 'Perplexity AI', descriptionEn: 'AI-powered search engine that provides direct answers with cited sources. Combines web search with language model capabilities.', nameZh: 'Perplexity AI', descriptionZh: 'AI驱动的搜索引擎,提供带引用来源的直接答案。结合网络搜索与语言模型能力。', status: 'published' },
|
||||||
|
{ id: 'tool_poe', slug: 'poe', domain: 'poe.com', websiteUrl: 'https://poe.com', logoUrl: 'https://poe.com/favicon.ico', nameEn: 'Poe', descriptionEn: 'Platform by Quora offering access to multiple AI models including GPT-4, Claude, and custom bots in one interface.', nameZh: 'Poe', descriptionZh: 'Quora推出的平台,在一个界面中提供多个AI模型访问,包括GPT-4、Claude和自定义机器人。', status: 'published' },
|
||||||
|
{ id: 'tool_character', slug: 'character-ai', domain: 'character.ai', websiteUrl: 'https://character.ai', logoUrl: 'https://character.ai/favicon.ico', nameEn: 'Character.AI', descriptionEn: 'Create and chat with AI characters. Popular for roleplay, creative storytelling, and entertainment purposes.', nameZh: 'Character.AI', descriptionZh: '创建AI角色并与之聊天。在角色扮演、创意故事讲述和娱乐方面广受欢迎。', status: 'published' },
|
||||||
|
{ id: 'tool_coze', slug: 'coze', domain: 'coze.com', websiteUrl: 'https://coze.com', logoUrl: 'https://coze.com/favicon.ico', nameEn: 'Coze', descriptionEn: "ByteDance's AI chatbot platform for creating custom AI bots with plugins, workflows, and knowledge bases.", nameZh: 'Coze', descriptionZh: '字节跳动的AI聊天机器人平台,可创建带插件、工作流和知识库的自定义AI机器人。', status: 'published' },
|
||||||
|
{ id: 'tool_pi', slug: 'pi-ai', domain: 'pi.ai', websiteUrl: 'https://pi.ai', logoUrl: 'https://pi.ai/favicon.ico', nameEn: 'Pi', descriptionEn: 'Personal AI assistant by Inflection AI, designed for supportive conversations and emotional intelligence.', nameZh: 'Pi', descriptionZh: 'Inflection AI的个人AI助手,专为支持性对话和情商设计。', status: 'published' },
|
||||||
|
|
||||||
|
// AI 写作 (7个)
|
||||||
|
{ id: 'tool_jasper', slug: 'jasper', domain: 'jasper.ai', websiteUrl: 'https://jasper.ai', logoUrl: 'https://jasper.ai/favicon.ico', nameEn: 'Jasper', descriptionEn: 'AI content creation platform for marketing teams. Generate blog posts, ads, emails, and social media content at scale.', nameZh: 'Jasper', descriptionZh: '面向营销团队的AI内容创作平台。大规模生成博客文章、广告、邮件和社交媒体内容。', status: 'published' },
|
||||||
|
{ id: 'tool_copy_ai', slug: 'copy-ai', domain: 'copy.ai', websiteUrl: 'https://copy.ai', logoUrl: 'https://copy.ai/favicon.ico', nameEn: 'Copy.ai', descriptionEn: 'AI-powered copywriting tool for creating marketing copy, product descriptions, and sales content.', nameZh: 'Copy.ai', descriptionZh: 'AI驱动的文案工具,用于创建营销文案、产品描述和销售内容。', status: 'published' },
|
||||||
|
{ id: 'tool_writesonic', slug: 'writesonic', domain: 'writesonic.com', websiteUrl: 'https://writesonic.com', logoUrl: 'https://writesonic.com/favicon.ico', nameEn: 'Writesonic', descriptionEn: 'AI writer for creating SEO-optimized articles, blog posts, and marketing content with fact-checking capabilities.', nameZh: 'Writesonic', descriptionZh: 'AI写作工具,用于创建SEO优化的文章、博客和营销内容,具有事实核查功能。', status: 'published' },
|
||||||
|
{ id: 'tool_notion_ai', slug: 'notion-ai', domain: 'notion.so', websiteUrl: 'https://notion.so', logoUrl: 'https://notion.so/favicon.ico', nameEn: 'Notion AI', descriptionEn: 'AI writing assistant integrated into Notion. Helps with drafting, summarizing, brainstorming, and improving writing.', nameZh: 'Notion AI', descriptionZh: '集成在Notion中的AI写作助手。帮助起草、总结、头脑风暴和改进写作。', status: 'published' },
|
||||||
|
{ id: 'tool_grammarly', slug: 'grammarly', domain: 'grammarly.com', websiteUrl: 'https://grammarly.com', logoUrl: 'https://grammarly.com/favicon.ico', nameEn: 'Grammarly', descriptionEn: 'AI-powered writing assistant that checks grammar, spelling, punctuation, and offers style improvements.', nameZh: 'Grammarly', descriptionZh: 'AI驱动的写作助手,检查语法、拼写、标点符号,并提供风格改进建议。', status: 'published' },
|
||||||
|
{ id: 'tool_quillbot', slug: 'quillbot', domain: 'quillbot.com', websiteUrl: 'https://quillbot.com', logoUrl: 'https://quillbot.com/favicon.ico', nameEn: 'QuillBot', descriptionEn: 'AI paraphrasing and writing tool. Helps rephrase sentences, summarize text, and improve writing clarity.', nameZh: 'QuillBot', descriptionZh: 'AI改写和写作工具。帮助改述句子、总结文本和提高写作清晰度。', status: 'published' },
|
||||||
|
{ id: 'tool_rytr', slug: 'rytr', domain: 'rytr.me', websiteUrl: 'https://rytr.me', logoUrl: 'https://rytr.me/favicon.ico', nameEn: 'Rytr', descriptionEn: 'AI writing assistant for generating content in 30+ languages and 20+ tones. Affordable alternative for content creators.', nameZh: 'Rytr', descriptionZh: 'AI写作助手,支持30多种语言和20多种语气生成内容。创作者的经济实惠选择。', status: 'published' },
|
||||||
|
|
||||||
|
// AI 图像生成 (8个)
|
||||||
|
{ id: 'tool_midjourney', slug: 'midjourney', domain: 'midjourney.com', websiteUrl: 'https://midjourney.com', logoUrl: 'https://midjourney.com/favicon.ico', nameEn: 'Midjourney', descriptionEn: 'Leading AI art generator known for stunning, artistic image generation. Creates images from text prompts via Discord.', nameZh: 'Midjourney', descriptionZh: '领先的AI艺术生成器,以惊艳的艺术图像生成著称。通过Discord从文本提示创建图像。', status: 'published' },
|
||||||
|
{ id: 'tool_dalle', slug: 'dall-e', domain: 'openai.com/dall-e', websiteUrl: 'https://openai.com/dall-e', logoUrl: 'https://openai.com/favicon.ico', nameEn: 'DALL·E 3', descriptionEn: "OpenAI's latest image generation model with exceptional prompt understanding and photorealistic outputs.", nameZh: 'DALL·E 3', descriptionZh: 'OpenAI最新的图像生成模型,具有出色的提示理解能力和逼真的输出效果。', status: 'published' },
|
||||||
|
{ id: 'tool_stable_diffusion', slug: 'stable-diffusion', domain: 'stability.ai', websiteUrl: 'https://stability.ai', logoUrl: 'https://stability.ai/favicon.ico', nameEn: 'Stable Diffusion', descriptionEn: 'Open-source image generation model by Stability AI. Can be run locally with full customization.', nameZh: 'Stable Diffusion', descriptionZh: 'Stability AI的开源图像生成模型。可在本地运行并完全自定义。', status: 'published' },
|
||||||
|
{ id: 'tool_leonardo', slug: 'leonardo-ai', domain: 'leonardo.ai', websiteUrl: 'https://leonardo.ai', logoUrl: 'https://leonardo.ai/favicon.ico', nameEn: 'Leonardo AI', descriptionEn: 'AI image generator with fine-tuned models for game assets, concept art, and consistent character generation.', nameZh: 'Leonardo AI', descriptionZh: 'AI图像生成器,提供针对游戏资产、概念艺术和一致性角色生成的微调模型。', status: 'published' },
|
||||||
|
{ id: 'tool_ideogram', slug: 'ideogram', domain: 'ideogram.ai', websiteUrl: 'https://ideogram.ai', logoUrl: 'https://ideogram.ai/favicon.ico', nameEn: 'Ideogram', descriptionEn: 'AI image generator excelling at rendering text within images accurately. Great for logos and posters.', nameZh: 'Ideogram', descriptionZh: 'AI图像生成器,擅长准确渲染图像中的文字。适合制作Logo和海报。', status: 'published' },
|
||||||
|
{ id: 'tool_playground', slug: 'playground-ai', domain: 'playground.com', websiteUrl: 'https://playground.com', logoUrl: 'https://playground.com/favicon.ico', nameEn: 'Playground AI', descriptionEn: 'Free AI image generator with powerful editing features. Great for beginners with intuitive interface.', nameZh: 'Playground AI', descriptionZh: '免费AI图像生成器,具有强大的编辑功能。界面直观,适合新手。', status: 'published' },
|
||||||
|
{ id: 'tool_canva_ai', slug: 'canva-ai', domain: 'canva.com', websiteUrl: 'https://canva.com', logoUrl: 'https://canva.com/favicon.ico', nameEn: 'Canva AI', descriptionEn: 'AI-powered design features within Canva including Magic Design, text-to-image, and background removal.', nameZh: 'Canva AI', descriptionZh: 'Canva中的AI设计功能,包括Magic Design、文生图和背景移除。', status: 'published' },
|
||||||
|
{ id: 'tool_adobe_firefly', slug: 'adobe-firefly', domain: 'firefly.adobe.com', websiteUrl: 'https://firefly.adobe.com', logoUrl: 'https://firefly.adobe.com/favicon.ico', nameEn: 'Adobe Firefly', descriptionEn: "Adobe's generative AI for creative professionals. Trained on licensed content for commercial safety.", nameZh: 'Adobe Firefly', descriptionZh: 'Adobe面向创意专业人士的生成式AI。在授权内容上训练,商用安全。', status: 'published' },
|
||||||
|
|
||||||
|
// AI 视频 (6个)
|
||||||
|
{ id: 'tool_runway', slug: 'runway', domain: 'runway.com', websiteUrl: 'https://runway.com', logoUrl: 'https://runway.com/favicon.ico', nameEn: 'Runway', descriptionEn: 'AI video generation and editing platform. Gen-2 creates videos from text or images with cinematic quality.', nameZh: 'Runway', descriptionZh: 'AI视频生成和编辑平台。Gen-2可从文本或图像创建电影级质量的视频。', status: 'published' },
|
||||||
|
{ id: 'tool_pika', slug: 'pika', domain: 'pika.art', websiteUrl: 'https://pika.art', logoUrl: 'https://pika.art/favicon.ico', nameEn: 'Pika', descriptionEn: 'AI video generator that creates and edits videos from text prompts. Known for smooth animations and effects.', nameZh: 'Pika', descriptionZh: 'AI视频生成器,从文本提示创建和编辑视频。以流畅的动画和效果著称。', status: 'published' },
|
||||||
|
{ id: 'tool_heygen', slug: 'heygen', domain: 'heygen.com', websiteUrl: 'https://heygen.com', logoUrl: 'https://heygen.com/favicon.ico', nameEn: 'HeyGen', descriptionEn: 'AI video generator for creating professional videos with AI avatars. Perfect for marketing and training content.', nameZh: 'HeyGen', descriptionZh: 'AI视频生成器,用AI数字人创建专业视频。适合营销和培训内容。', status: 'published' },
|
||||||
|
{ id: 'tool_synthesia', slug: 'synthesia', domain: 'synthesia.io', websiteUrl: 'https://synthesia.io', logoUrl: 'https://synthesia.io/favicon.ico', nameEn: 'Synthesia', descriptionEn: 'Create AI videos with realistic avatars in 120+ languages. Popular for corporate training and marketing.', nameZh: 'Synthesia', descriptionZh: '用逼真数字人创建120多种语言的AI视频。在企业培训和营销领域广受欢迎。', status: 'published' },
|
||||||
|
{ id: 'tool_luma', slug: 'luma-ai', domain: 'lumalabs.ai', websiteUrl: 'https://lumalabs.ai', logoUrl: 'https://lumalabs.ai/favicon.ico', nameEn: 'Luma AI', descriptionEn: 'AI video generation with Dream Machine. Creates cinematic videos from text and images with impressive quality.', nameZh: 'Luma AI', descriptionZh: 'Dream Machine AI视频生成。从文本和图像创建高质量的电影级视频。', status: 'published' },
|
||||||
|
{ id: 'tool_kling', slug: 'kling-ai', domain: 'klingai.com', websiteUrl: 'https://klingai.com', logoUrl: 'https://klingai.com/favicon.ico', nameEn: 'Kling AI', descriptionEn: "Kuaishou's AI video generator with impressive motion and physics understanding. Creates realistic videos.", nameZh: 'Kling AI', descriptionZh: '快手的AI视频生成器,具有出色的运动和物理理解能力。创建逼真视频。', status: 'published' },
|
||||||
|
|
||||||
|
// AI 音频与音乐 (5个)
|
||||||
|
{ id: 'tool_elevenlabs', slug: 'elevenlabs', domain: 'elevenlabs.io', websiteUrl: 'https://elevenlabs.io', logoUrl: 'https://elevenlabs.io/favicon.ico', nameEn: 'ElevenLabs', descriptionEn: 'Leading AI voice synthesis platform. Creates ultra-realistic voiceovers, dubbing, and voice cloning.', nameZh: 'ElevenLabs', descriptionZh: '领先的AI语音合成平台。创建超逼真的配音、翻译配音和声音克隆。', status: 'published' },
|
||||||
|
{ id: 'tool_suno', slug: 'suno', domain: 'suno.com', websiteUrl: 'https://suno.com', logoUrl: 'https://suno.com/favicon.ico', nameEn: 'Suno', descriptionEn: 'AI music generator that creates complete songs with vocals from text prompts. Revolutionary for music creation.', nameZh: 'Suno', descriptionZh: 'AI音乐生成器,从文本提示创建带人声的完整歌曲。音乐创作的革命性工具。', status: 'published' },
|
||||||
|
{ id: 'tool_udio', slug: 'udio', domain: 'udio.com', websiteUrl: 'https://udio.com', logoUrl: 'https://udio.com/favicon.ico', nameEn: 'Udio', descriptionEn: 'AI music generation platform creating studio-quality songs with vocals. Strong competitor to Suno.', nameZh: 'Udio', descriptionZh: 'AI音乐生成平台,创建带人声的录音室品质歌曲。Suno的强劲竞争对手。', status: 'published' },
|
||||||
|
{ id: 'tool_murf', slug: 'murf', domain: 'murf.ai', websiteUrl: 'https://murf.ai', logoUrl: 'https://murf.ai/favicon.ico', nameEn: 'Murf', descriptionEn: 'AI voice generator for creating professional voiceovers for videos, podcasts, and presentations.', nameZh: 'Murf', descriptionZh: 'AI语音生成器,为视频、播客和演示文稿创建专业配音。', status: 'published' },
|
||||||
|
{ id: 'tool_descript', slug: 'descript', domain: 'descript.com', websiteUrl: 'https://descript.com', logoUrl: 'https://descript.com/favicon.ico', nameEn: 'Descript', descriptionEn: 'AI-powered audio and video editor. Edit audio by editing text transcripts. Includes Overdub voice cloning.', nameZh: 'Descript', descriptionZh: 'AI驱动的音视频编辑器。通过编辑文字稿来编辑音频。包含Overdub声音克隆功能。', status: 'published' },
|
||||||
|
|
||||||
|
// AI 编程 (6个)
|
||||||
|
{ id: 'tool_github_copilot', slug: 'github-copilot', domain: 'github.com/features/copilot', websiteUrl: 'https://github.com/features/copilot', logoUrl: 'https://github.com/favicon.ico', nameEn: 'GitHub Copilot', descriptionEn: 'AI pair programmer by GitHub and OpenAI. Suggests code completions, functions, and entire files in your IDE.', nameZh: 'GitHub Copilot', descriptionZh: 'GitHub和OpenAI的AI结对编程助手。在IDE中建议代码补全、函数和完整文件。', status: 'published' },
|
||||||
|
{ id: 'tool_cursor', slug: 'cursor', domain: 'cursor.sh', websiteUrl: 'https://cursor.sh', logoUrl: 'https://cursor.sh/favicon.ico', nameEn: 'Cursor', descriptionEn: 'AI-first code editor built on VS Code. Deep AI integration for code generation, editing, and chat.', nameZh: 'Cursor', descriptionZh: '以AI为核心的代码编辑器,基于VS Code构建。深度AI集成,支持代码生成、编辑和对话。', status: 'published' },
|
||||||
|
{ id: 'tool_replit', slug: 'replit', domain: 'replit.com', websiteUrl: 'https://replit.com', logoUrl: 'https://replit.com/favicon.ico', nameEn: 'Replit AI', descriptionEn: 'Browser-based IDE with AI assistant. Code, deploy, and collaborate with AI-powered code generation.', nameZh: 'Replit AI', descriptionZh: '基于浏览器的IDE,内置AI助手。通过AI驱动的代码生成进行编码、部署和协作。', status: 'published' },
|
||||||
|
{ id: 'tool_codeium', slug: 'codeium', domain: 'codeium.com', websiteUrl: 'https://codeium.com', logoUrl: 'https://codeium.com/favicon.ico', nameEn: 'Codeium', descriptionEn: 'Free AI coding assistant supporting 70+ languages. Fast completions with IDE extensions for major editors.', nameZh: 'Codeium', descriptionZh: '免费AI编程助手,支持70多种语言。快速补全,为主流编辑器提供IDE插件。', status: 'published' },
|
||||||
|
{ id: 'tool_tabnine', slug: 'tabnine', domain: 'tabnine.com', websiteUrl: 'https://tabnine.com', logoUrl: 'https://tabnine.com/favicon.ico', nameEn: 'Tabnine', descriptionEn: 'AI code assistant with enterprise-grade privacy. Train on your codebase for personalized suggestions.', nameZh: 'Tabnine', descriptionZh: '企业级隐私的AI代码助手。在你的代码库上训练,提供个性化建议。', status: 'published' },
|
||||||
|
{ id: 'tool_claude_code', slug: 'claude-code', domain: 'claude.ai/code', websiteUrl: 'https://claude.ai/code', logoUrl: 'https://claude.ai/favicon.ico', nameEn: 'Claude Code', descriptionEn: "Anthropic's agentic coding tool. Natural language coding with full project context understanding.", nameZh: 'Claude Code', descriptionZh: 'Anthropic的代理式编程工具。支持自然语言编程,具有完整的项目上下文理解能力。', status: 'published' },
|
||||||
|
|
||||||
|
// AI 效率工具 (4个)
|
||||||
|
{ id: 'tool_otter', slug: 'otter-ai', domain: 'otter.ai', websiteUrl: 'https://otter.ai', logoUrl: 'https://otter.ai/favicon.ico', nameEn: 'Otter.ai', descriptionEn: 'AI meeting assistant that transcribes, summarizes, and creates action items from meetings automatically.', nameZh: 'Otter.ai', descriptionZh: 'AI会议助手,自动转录、总结会议并创建待办事项。', status: 'published' },
|
||||||
|
{ id: 'tool_fireflies', slug: 'fireflies', domain: 'fireflies.ai', websiteUrl: 'https://fireflies.ai', logoUrl: 'https://fireflies.ai/favicon.ico', nameEn: 'Fireflies.ai', descriptionEn: 'AI notetaker for meetings. Records, transcribes, and summarizes meetings across all major platforms.', nameZh: 'Fireflies.ai', descriptionZh: '会议AI笔记助手。在所有主流平台上录制、转录和总结会议。', status: 'published' },
|
||||||
|
{ id: 'tool_mem', slug: 'mem-ai', domain: 'mem.ai', websiteUrl: 'https://mem.ai', logoUrl: 'https://mem.ai/favicon.ico', nameEn: 'Mem', descriptionEn: 'AI-powered note-taking app that organizes and surfaces relevant information automatically.', nameZh: 'Mem', descriptionZh: 'AI驱动的笔记应用,自动组织和呈现相关信息。', status: 'published' },
|
||||||
|
{ id: 'tool_motion', slug: 'motion', domain: 'usemotion.com', websiteUrl: 'https://usemotion.com', logoUrl: 'https://usemotion.com/favicon.ico', nameEn: 'Motion', descriptionEn: 'AI calendar and project management. Automatically schedules tasks and meetings for optimal productivity.', nameZh: 'Motion', descriptionZh: 'AI日历和项目管理工具。自动安排任务和会议以实现最佳生产力。', status: 'published' },
|
||||||
|
|
||||||
|
// AI 研究与分析 (3个)
|
||||||
|
{ id: 'tool_consensus', slug: 'consensus', domain: 'consensus.app', websiteUrl: 'https://consensus.app', logoUrl: 'https://consensus.app/favicon.ico', nameEn: 'Consensus', descriptionEn: 'AI-powered academic search engine. Find and understand scientific research with AI summaries.', nameZh: 'Consensus', descriptionZh: 'AI驱动的学术搜索引擎。通过AI摘要查找和理解科学研究。', status: 'published' },
|
||||||
|
{ id: 'tool_elicit', slug: 'elicit', domain: 'elicit.com', websiteUrl: 'https://elicit.com', logoUrl: 'https://elicit.com/favicon.ico', nameEn: 'Elicit', descriptionEn: 'AI research assistant that helps find, summarize, and extract data from academic papers.', nameZh: 'Elicit', descriptionZh: 'AI研究助手,帮助查找、总结和提取学术论文中的数据。', status: 'published' },
|
||||||
|
{ id: 'tool_scite', slug: 'scite', domain: 'scite.ai', websiteUrl: 'https://scite.ai', logoUrl: 'https://scite.ai/favicon.ico', nameEn: 'Scite', descriptionEn: 'AI tool for evaluating scientific claims. Shows how papers cite each other with supporting or contrasting evidence.', nameZh: 'Scite', descriptionZh: 'AI科学声明评估工具。显示论文如何相互引用,包含支持或对比证据。', status: 'published' },
|
||||||
|
|
||||||
|
// AI 设计 (3个)
|
||||||
|
{ id: 'tool_figma_ai', slug: 'figma-ai', domain: 'figma.com', websiteUrl: 'https://figma.com', logoUrl: 'https://figma.com/favicon.ico', nameEn: 'Figma AI', descriptionEn: 'AI features in Figma for generating designs, renaming layers, and creating content automatically.', nameZh: 'Figma AI', descriptionZh: 'Figma中的AI功能,用于生成设计、重命名图层和自动创建内容。', status: 'published' },
|
||||||
|
{ id: 'tool_uizard', slug: 'uizard', domain: 'uizard.io', websiteUrl: 'https://uizard.io', logoUrl: 'https://uizard.io/favicon.ico', nameEn: 'Uizard', descriptionEn: 'AI-powered UI design tool. Transform sketches and screenshots into editable designs.', nameZh: 'Uizard', descriptionZh: 'AI驱动的UI设计工具。将草图和截图转换为可编辑的设计。', status: 'published' },
|
||||||
|
{ id: 'tool_magician', slug: 'magician', domain: 'magician.design', websiteUrl: 'https://magician.design', logoUrl: 'https://magician.design/favicon.ico', nameEn: 'Magician', descriptionEn: 'AI design assistant plugin for Figma. Generate icons, images, and copy with AI.', nameZh: 'Magician', descriptionZh: 'Figma的AI设计助手插件。用AI生成图标、图像和文案。', status: 'published' },
|
||||||
|
];
|
||||||
|
|
||||||
|
// 工具-分类关联数据
|
||||||
|
const toolCategoryData = [
|
||||||
|
// 聊天机器人
|
||||||
|
{ toolId: 'tool_chatgpt', categoryId: 'cat_chatbot' },
|
||||||
|
{ toolId: 'tool_claude', categoryId: 'cat_chatbot' },
|
||||||
|
{ toolId: 'tool_gemini', categoryId: 'cat_chatbot' },
|
||||||
|
{ toolId: 'tool_perplexity', categoryId: 'cat_chatbot' },
|
||||||
|
{ toolId: 'tool_perplexity', categoryId: 'cat_research' },
|
||||||
|
{ toolId: 'tool_poe', categoryId: 'cat_chatbot' },
|
||||||
|
{ toolId: 'tool_character', categoryId: 'cat_chatbot' },
|
||||||
|
{ toolId: 'tool_coze', categoryId: 'cat_chatbot' },
|
||||||
|
{ toolId: 'tool_pi', categoryId: 'cat_chatbot' },
|
||||||
|
// 写作
|
||||||
|
{ toolId: 'tool_jasper', categoryId: 'cat_writing' },
|
||||||
|
{ toolId: 'tool_jasper', categoryId: 'cat_business' },
|
||||||
|
{ toolId: 'tool_copy_ai', categoryId: 'cat_writing' },
|
||||||
|
{ toolId: 'tool_copy_ai', categoryId: 'cat_business' },
|
||||||
|
{ toolId: 'tool_writesonic', categoryId: 'cat_writing' },
|
||||||
|
{ toolId: 'tool_notion_ai', categoryId: 'cat_writing' },
|
||||||
|
{ toolId: 'tool_notion_ai', categoryId: 'cat_productivity' },
|
||||||
|
{ toolId: 'tool_grammarly', categoryId: 'cat_writing' },
|
||||||
|
{ toolId: 'tool_quillbot', categoryId: 'cat_writing' },
|
||||||
|
{ toolId: 'tool_rytr', categoryId: 'cat_writing' },
|
||||||
|
// 图像生成
|
||||||
|
{ toolId: 'tool_midjourney', categoryId: 'cat_image' },
|
||||||
|
{ toolId: 'tool_dalle', categoryId: 'cat_image' },
|
||||||
|
{ toolId: 'tool_stable_diffusion', categoryId: 'cat_image' },
|
||||||
|
{ toolId: 'tool_leonardo', categoryId: 'cat_image' },
|
||||||
|
{ toolId: 'tool_ideogram', categoryId: 'cat_image' },
|
||||||
|
{ toolId: 'tool_playground', categoryId: 'cat_image' },
|
||||||
|
{ toolId: 'tool_canva_ai', categoryId: 'cat_image' },
|
||||||
|
{ toolId: 'tool_canva_ai', categoryId: 'cat_design' },
|
||||||
|
{ toolId: 'tool_adobe_firefly', categoryId: 'cat_image' },
|
||||||
|
{ toolId: 'tool_adobe_firefly', categoryId: 'cat_design' },
|
||||||
|
// 视频
|
||||||
|
{ toolId: 'tool_runway', categoryId: 'cat_video' },
|
||||||
|
{ toolId: 'tool_pika', categoryId: 'cat_video' },
|
||||||
|
{ toolId: 'tool_heygen', categoryId: 'cat_video' },
|
||||||
|
{ toolId: 'tool_heygen', categoryId: 'cat_business' },
|
||||||
|
{ toolId: 'tool_synthesia', categoryId: 'cat_video' },
|
||||||
|
{ toolId: 'tool_synthesia', categoryId: 'cat_business' },
|
||||||
|
{ toolId: 'tool_luma', categoryId: 'cat_video' },
|
||||||
|
{ toolId: 'tool_kling', categoryId: 'cat_video' },
|
||||||
|
// 音频
|
||||||
|
{ toolId: 'tool_elevenlabs', categoryId: 'cat_audio' },
|
||||||
|
{ toolId: 'tool_suno', categoryId: 'cat_audio' },
|
||||||
|
{ toolId: 'tool_udio', categoryId: 'cat_audio' },
|
||||||
|
{ toolId: 'tool_murf', categoryId: 'cat_audio' },
|
||||||
|
{ toolId: 'tool_descript', categoryId: 'cat_audio' },
|
||||||
|
{ toolId: 'tool_descript', categoryId: 'cat_video' },
|
||||||
|
// 编程
|
||||||
|
{ toolId: 'tool_github_copilot', categoryId: 'cat_coding' },
|
||||||
|
{ toolId: 'tool_cursor', categoryId: 'cat_coding' },
|
||||||
|
{ toolId: 'tool_replit', categoryId: 'cat_coding' },
|
||||||
|
{ toolId: 'tool_codeium', categoryId: 'cat_coding' },
|
||||||
|
{ toolId: 'tool_tabnine', categoryId: 'cat_coding' },
|
||||||
|
{ toolId: 'tool_claude_code', categoryId: 'cat_coding' },
|
||||||
|
// 效率
|
||||||
|
{ toolId: 'tool_otter', categoryId: 'cat_productivity' },
|
||||||
|
{ toolId: 'tool_fireflies', categoryId: 'cat_productivity' },
|
||||||
|
{ toolId: 'tool_mem', categoryId: 'cat_productivity' },
|
||||||
|
{ toolId: 'tool_motion', categoryId: 'cat_productivity' },
|
||||||
|
// 研究
|
||||||
|
{ toolId: 'tool_consensus', categoryId: 'cat_research' },
|
||||||
|
{ toolId: 'tool_elicit', categoryId: 'cat_research' },
|
||||||
|
{ toolId: 'tool_scite', categoryId: 'cat_research' },
|
||||||
|
// 设计
|
||||||
|
{ toolId: 'tool_figma_ai', categoryId: 'cat_design' },
|
||||||
|
{ toolId: 'tool_uizard', categoryId: 'cat_design' },
|
||||||
|
{ toolId: 'tool_magician', categoryId: 'cat_design' },
|
||||||
|
];
|
||||||
|
|
||||||
|
// 工具-标签关联数据
|
||||||
|
const toolTagData = [
|
||||||
|
// ChatGPT
|
||||||
|
{ toolId: 'tool_chatgpt', tagId: 'tag_freemium' },
|
||||||
|
{ toolId: 'tool_chatgpt', tagId: 'tag_gpt' },
|
||||||
|
{ toolId: 'tool_chatgpt', tagId: 'tag_llm' },
|
||||||
|
{ toolId: 'tool_chatgpt', tagId: 'tag_api' },
|
||||||
|
{ toolId: 'tool_chatgpt', tagId: 'tag_popular' },
|
||||||
|
{ toolId: 'tool_chatgpt', tagId: 'tag_mobile' },
|
||||||
|
{ toolId: 'tool_chatgpt', tagId: 'tag_text' },
|
||||||
|
// Claude
|
||||||
|
{ toolId: 'tool_claude', tagId: 'tag_freemium' },
|
||||||
|
{ toolId: 'tool_claude', tagId: 'tag_claude' },
|
||||||
|
{ toolId: 'tool_claude', tagId: 'tag_llm' },
|
||||||
|
{ toolId: 'tool_claude', tagId: 'tag_api' },
|
||||||
|
{ toolId: 'tool_claude', tagId: 'tag_popular' },
|
||||||
|
{ toolId: 'tool_claude', tagId: 'tag_text' },
|
||||||
|
{ toolId: 'tool_claude', tagId: 'tag_code_out' },
|
||||||
|
// Gemini
|
||||||
|
{ toolId: 'tool_gemini', tagId: 'tag_free' },
|
||||||
|
{ toolId: 'tool_gemini', tagId: 'tag_llm' },
|
||||||
|
{ toolId: 'tool_gemini', tagId: 'tag_api' },
|
||||||
|
{ toolId: 'tool_gemini', tagId: 'tag_mobile' },
|
||||||
|
{ toolId: 'tool_gemini', tagId: 'tag_text' },
|
||||||
|
// Perplexity
|
||||||
|
{ toolId: 'tool_perplexity', tagId: 'tag_freemium' },
|
||||||
|
{ toolId: 'tool_perplexity', tagId: 'tag_llm' },
|
||||||
|
{ toolId: 'tool_perplexity', tagId: 'tag_realtime' },
|
||||||
|
{ toolId: 'tool_perplexity', tagId: 'tag_trending' },
|
||||||
|
{ toolId: 'tool_perplexity', tagId: 'tag_text' },
|
||||||
|
// Poe
|
||||||
|
{ toolId: 'tool_poe', tagId: 'tag_freemium' },
|
||||||
|
{ toolId: 'tool_poe', tagId: 'tag_llm' },
|
||||||
|
{ toolId: 'tool_poe', tagId: 'tag_beginner' },
|
||||||
|
{ toolId: 'tool_poe', tagId: 'tag_mobile' },
|
||||||
|
// Character.AI
|
||||||
|
{ toolId: 'tool_character', tagId: 'tag_free' },
|
||||||
|
{ toolId: 'tool_character', tagId: 'tag_llm' },
|
||||||
|
{ toolId: 'tool_character', tagId: 'tag_creator' },
|
||||||
|
{ toolId: 'tool_character', tagId: 'tag_mobile' },
|
||||||
|
// Coze
|
||||||
|
{ toolId: 'tool_coze', tagId: 'tag_free' },
|
||||||
|
{ toolId: 'tool_coze', tagId: 'tag_llm' },
|
||||||
|
{ toolId: 'tool_coze', tagId: 'tag_api' },
|
||||||
|
{ toolId: 'tool_coze', tagId: 'tag_developer' },
|
||||||
|
// Pi
|
||||||
|
{ toolId: 'tool_pi', tagId: 'tag_free' },
|
||||||
|
{ toolId: 'tool_pi', tagId: 'tag_llm' },
|
||||||
|
{ toolId: 'tool_pi', tagId: 'tag_beginner' },
|
||||||
|
{ toolId: 'tool_pi', tagId: 'tag_mobile' },
|
||||||
|
// Jasper
|
||||||
|
{ toolId: 'tool_jasper', tagId: 'tag_paid' },
|
||||||
|
{ toolId: 'tool_jasper', tagId: 'tag_gpt' },
|
||||||
|
{ toolId: 'tool_jasper', tagId: 'tag_enterprise' },
|
||||||
|
{ toolId: 'tool_jasper', tagId: 'tag_marketer' },
|
||||||
|
{ toolId: 'tool_jasper', tagId: 'tag_text' },
|
||||||
|
// Copy.ai
|
||||||
|
{ toolId: 'tool_copy_ai', tagId: 'tag_freemium' },
|
||||||
|
{ toolId: 'tool_copy_ai', tagId: 'tag_gpt' },
|
||||||
|
{ toolId: 'tool_copy_ai', tagId: 'tag_marketer' },
|
||||||
|
{ toolId: 'tool_copy_ai', tagId: 'tag_text' },
|
||||||
|
// Writesonic
|
||||||
|
{ toolId: 'tool_writesonic', tagId: 'tag_freemium' },
|
||||||
|
{ toolId: 'tool_writesonic', tagId: 'tag_gpt' },
|
||||||
|
{ toolId: 'tool_writesonic', tagId: 'tag_marketer' },
|
||||||
|
{ toolId: 'tool_writesonic', tagId: 'tag_text' },
|
||||||
|
// Notion AI
|
||||||
|
{ toolId: 'tool_notion_ai', tagId: 'tag_paid' },
|
||||||
|
{ toolId: 'tool_notion_ai', tagId: 'tag_collaborative' },
|
||||||
|
{ toolId: 'tool_notion_ai', tagId: 'tag_text' },
|
||||||
|
{ toolId: 'tool_notion_ai', tagId: 'tag_popular' },
|
||||||
|
// Grammarly
|
||||||
|
{ toolId: 'tool_grammarly', tagId: 'tag_freemium' },
|
||||||
|
{ toolId: 'tool_grammarly', tagId: 'tag_browser' },
|
||||||
|
{ toolId: 'tool_grammarly', tagId: 'tag_desktop' },
|
||||||
|
{ toolId: 'tool_grammarly', tagId: 'tag_popular' },
|
||||||
|
{ toolId: 'tool_grammarly', tagId: 'tag_text' },
|
||||||
|
// QuillBot
|
||||||
|
{ toolId: 'tool_quillbot', tagId: 'tag_freemium' },
|
||||||
|
{ toolId: 'tool_quillbot', tagId: 'tag_student' },
|
||||||
|
{ toolId: 'tool_quillbot', tagId: 'tag_browser' },
|
||||||
|
{ toolId: 'tool_quillbot', tagId: 'tag_text' },
|
||||||
|
// Rytr
|
||||||
|
{ toolId: 'tool_rytr', tagId: 'tag_freemium' },
|
||||||
|
{ toolId: 'tool_rytr', tagId: 'tag_beginner' },
|
||||||
|
{ toolId: 'tool_rytr', tagId: 'tag_text' },
|
||||||
|
// Midjourney
|
||||||
|
{ toolId: 'tool_midjourney', tagId: 'tag_paid' },
|
||||||
|
{ toolId: 'tool_midjourney', tagId: 'tag_diffusion' },
|
||||||
|
{ toolId: 'tool_midjourney', tagId: 'tag_creator' },
|
||||||
|
{ toolId: 'tool_midjourney', tagId: 'tag_popular' },
|
||||||
|
{ toolId: 'tool_midjourney', tagId: 'tag_image_out' },
|
||||||
|
{ toolId: 'tool_midjourney', tagId: 'tag_editors_choice' },
|
||||||
|
// DALL-E
|
||||||
|
{ toolId: 'tool_dalle', tagId: 'tag_freemium' },
|
||||||
|
{ toolId: 'tool_dalle', tagId: 'tag_diffusion' },
|
||||||
|
{ toolId: 'tool_dalle', tagId: 'tag_api' },
|
||||||
|
{ toolId: 'tool_dalle', tagId: 'tag_image_out' },
|
||||||
|
{ toolId: 'tool_dalle', tagId: 'tag_popular' },
|
||||||
|
// Stable Diffusion
|
||||||
|
{ toolId: 'tool_stable_diffusion', tagId: 'tag_free' },
|
||||||
|
{ toolId: 'tool_stable_diffusion', tagId: 'tag_open_source' },
|
||||||
|
{ toolId: 'tool_stable_diffusion', tagId: 'tag_diffusion' },
|
||||||
|
{ toolId: 'tool_stable_diffusion', tagId: 'tag_developer' },
|
||||||
|
{ toolId: 'tool_stable_diffusion', tagId: 'tag_image_out' },
|
||||||
|
// Leonardo AI
|
||||||
|
{ toolId: 'tool_leonardo', tagId: 'tag_freemium' },
|
||||||
|
{ toolId: 'tool_leonardo', tagId: 'tag_diffusion' },
|
||||||
|
{ toolId: 'tool_leonardo', tagId: 'tag_creator' },
|
||||||
|
{ toolId: 'tool_leonardo', tagId: 'tag_image_out' },
|
||||||
|
// Ideogram
|
||||||
|
{ toolId: 'tool_ideogram', tagId: 'tag_freemium' },
|
||||||
|
{ toolId: 'tool_ideogram', tagId: 'tag_diffusion' },
|
||||||
|
{ toolId: 'tool_ideogram', tagId: 'tag_image_out' },
|
||||||
|
{ toolId: 'tool_ideogram', tagId: 'tag_trending' },
|
||||||
|
// Playground AI
|
||||||
|
{ toolId: 'tool_playground', tagId: 'tag_free' },
|
||||||
|
{ toolId: 'tool_playground', tagId: 'tag_diffusion' },
|
||||||
|
{ toolId: 'tool_playground', tagId: 'tag_beginner' },
|
||||||
|
{ toolId: 'tool_playground', tagId: 'tag_image_out' },
|
||||||
|
// Canva AI
|
||||||
|
{ toolId: 'tool_canva_ai', tagId: 'tag_freemium' },
|
||||||
|
{ toolId: 'tool_canva_ai', tagId: 'tag_beginner' },
|
||||||
|
{ toolId: 'tool_canva_ai', tagId: 'tag_popular' },
|
||||||
|
{ toolId: 'tool_canva_ai', tagId: 'tag_image_out' },
|
||||||
|
// Adobe Firefly
|
||||||
|
{ toolId: 'tool_adobe_firefly', tagId: 'tag_freemium' },
|
||||||
|
{ toolId: 'tool_adobe_firefly', tagId: 'tag_diffusion' },
|
||||||
|
{ toolId: 'tool_adobe_firefly', tagId: 'tag_enterprise' },
|
||||||
|
{ toolId: 'tool_adobe_firefly', tagId: 'tag_image_out' },
|
||||||
|
// Runway
|
||||||
|
{ toolId: 'tool_runway', tagId: 'tag_freemium' },
|
||||||
|
{ toolId: 'tool_runway', tagId: 'tag_diffusion' },
|
||||||
|
{ toolId: 'tool_runway', tagId: 'tag_creator' },
|
||||||
|
{ toolId: 'tool_runway', tagId: 'tag_video_out' },
|
||||||
|
{ toolId: 'tool_runway', tagId: 'tag_editors_choice' },
|
||||||
|
// Pika
|
||||||
|
{ toolId: 'tool_pika', tagId: 'tag_freemium' },
|
||||||
|
{ toolId: 'tool_pika', tagId: 'tag_diffusion' },
|
||||||
|
{ toolId: 'tool_pika', tagId: 'tag_video_out' },
|
||||||
|
{ toolId: 'tool_pika', tagId: 'tag_trending' },
|
||||||
|
// HeyGen
|
||||||
|
{ toolId: 'tool_heygen', tagId: 'tag_paid' },
|
||||||
|
{ toolId: 'tool_heygen', tagId: 'tag_enterprise' },
|
||||||
|
{ toolId: 'tool_heygen', tagId: 'tag_marketer' },
|
||||||
|
{ toolId: 'tool_heygen', tagId: 'tag_video_out' },
|
||||||
|
// Synthesia
|
||||||
|
{ toolId: 'tool_synthesia', tagId: 'tag_paid' },
|
||||||
|
{ toolId: 'tool_synthesia', tagId: 'tag_enterprise' },
|
||||||
|
{ toolId: 'tool_synthesia', tagId: 'tag_video_out' },
|
||||||
|
{ toolId: 'tool_synthesia', tagId: 'tag_popular' },
|
||||||
|
// Luma AI
|
||||||
|
{ toolId: 'tool_luma', tagId: 'tag_freemium' },
|
||||||
|
{ toolId: 'tool_luma', tagId: 'tag_diffusion' },
|
||||||
|
{ toolId: 'tool_luma', tagId: 'tag_video_out' },
|
||||||
|
{ toolId: 'tool_luma', tagId: 'tag_trending' },
|
||||||
|
// Kling AI
|
||||||
|
{ toolId: 'tool_kling', tagId: 'tag_freemium' },
|
||||||
|
{ toolId: 'tool_kling', tagId: 'tag_diffusion' },
|
||||||
|
{ toolId: 'tool_kling', tagId: 'tag_video_out' },
|
||||||
|
{ toolId: 'tool_kling', tagId: 'tag_new' },
|
||||||
|
// ElevenLabs
|
||||||
|
{ toolId: 'tool_elevenlabs', tagId: 'tag_freemium' },
|
||||||
|
{ toolId: 'tool_elevenlabs', tagId: 'tag_api' },
|
||||||
|
{ toolId: 'tool_elevenlabs', tagId: 'tag_creator' },
|
||||||
|
{ toolId: 'tool_elevenlabs', tagId: 'tag_audio_out' },
|
||||||
|
{ toolId: 'tool_elevenlabs', tagId: 'tag_popular' },
|
||||||
|
// Suno
|
||||||
|
{ toolId: 'tool_suno', tagId: 'tag_freemium' },
|
||||||
|
{ toolId: 'tool_suno', tagId: 'tag_creator' },
|
||||||
|
{ toolId: 'tool_suno', tagId: 'tag_audio_out' },
|
||||||
|
{ toolId: 'tool_suno', tagId: 'tag_trending' },
|
||||||
|
{ toolId: 'tool_suno', tagId: 'tag_editors_choice' },
|
||||||
|
// Udio
|
||||||
|
{ toolId: 'tool_udio', tagId: 'tag_freemium' },
|
||||||
|
{ toolId: 'tool_udio', tagId: 'tag_creator' },
|
||||||
|
{ toolId: 'tool_udio', tagId: 'tag_audio_out' },
|
||||||
|
{ toolId: 'tool_udio', tagId: 'tag_new' },
|
||||||
|
// Murf
|
||||||
|
{ toolId: 'tool_murf', tagId: 'tag_freemium' },
|
||||||
|
{ toolId: 'tool_murf', tagId: 'tag_marketer' },
|
||||||
|
{ toolId: 'tool_murf', tagId: 'tag_audio_out' },
|
||||||
|
// Descript
|
||||||
|
{ toolId: 'tool_descript', tagId: 'tag_freemium' },
|
||||||
|
{ toolId: 'tool_descript', tagId: 'tag_creator' },
|
||||||
|
{ toolId: 'tool_descript', tagId: 'tag_audio_out' },
|
||||||
|
{ toolId: 'tool_descript', tagId: 'tag_video_out' },
|
||||||
|
{ toolId: 'tool_descript', tagId: 'tag_desktop' },
|
||||||
|
// GitHub Copilot
|
||||||
|
{ toolId: 'tool_github_copilot', tagId: 'tag_paid' },
|
||||||
|
{ toolId: 'tool_github_copilot', tagId: 'tag_gpt' },
|
||||||
|
{ toolId: 'tool_github_copilot', tagId: 'tag_developer' },
|
||||||
|
{ toolId: 'tool_github_copilot', tagId: 'tag_code_out' },
|
||||||
|
{ toolId: 'tool_github_copilot', tagId: 'tag_popular' },
|
||||||
|
{ toolId: 'tool_github_copilot', tagId: 'tag_editors_choice' },
|
||||||
|
// Cursor
|
||||||
|
{ toolId: 'tool_cursor', tagId: 'tag_freemium' },
|
||||||
|
{ toolId: 'tool_cursor', tagId: 'tag_claude' },
|
||||||
|
{ toolId: 'tool_cursor', tagId: 'tag_developer' },
|
||||||
|
{ toolId: 'tool_cursor', tagId: 'tag_code_out' },
|
||||||
|
{ toolId: 'tool_cursor', tagId: 'tag_trending' },
|
||||||
|
{ toolId: 'tool_cursor', tagId: 'tag_desktop' },
|
||||||
|
// Replit
|
||||||
|
{ toolId: 'tool_replit', tagId: 'tag_freemium' },
|
||||||
|
{ toolId: 'tool_replit', tagId: 'tag_llm' },
|
||||||
|
{ toolId: 'tool_replit', tagId: 'tag_developer' },
|
||||||
|
{ toolId: 'tool_replit', tagId: 'tag_beginner' },
|
||||||
|
{ toolId: 'tool_replit', tagId: 'tag_code_out' },
|
||||||
|
// Codeium
|
||||||
|
{ toolId: 'tool_codeium', tagId: 'tag_free' },
|
||||||
|
{ toolId: 'tool_codeium', tagId: 'tag_llm' },
|
||||||
|
{ toolId: 'tool_codeium', tagId: 'tag_developer' },
|
||||||
|
{ toolId: 'tool_codeium', tagId: 'tag_code_out' },
|
||||||
|
// Tabnine
|
||||||
|
{ toolId: 'tool_tabnine', tagId: 'tag_freemium' },
|
||||||
|
{ toolId: 'tool_tabnine', tagId: 'tag_enterprise' },
|
||||||
|
{ toolId: 'tool_tabnine', tagId: 'tag_developer' },
|
||||||
|
{ toolId: 'tool_tabnine', tagId: 'tag_code_out' },
|
||||||
|
// Claude Code
|
||||||
|
{ toolId: 'tool_claude_code', tagId: 'tag_paid' },
|
||||||
|
{ toolId: 'tool_claude_code', tagId: 'tag_claude' },
|
||||||
|
{ toolId: 'tool_claude_code', tagId: 'tag_developer' },
|
||||||
|
{ toolId: 'tool_claude_code', tagId: 'tag_code_out' },
|
||||||
|
{ toolId: 'tool_claude_code', tagId: 'tag_new' },
|
||||||
|
// Otter.ai
|
||||||
|
{ toolId: 'tool_otter', tagId: 'tag_freemium' },
|
||||||
|
{ toolId: 'tool_otter', tagId: 'tag_collaborative' },
|
||||||
|
{ toolId: 'tool_otter', tagId: 'tag_realtime' },
|
||||||
|
{ toolId: 'tool_otter', tagId: 'tag_text' },
|
||||||
|
{ toolId: 'tool_otter', tagId: 'tag_mobile' },
|
||||||
|
// Fireflies.ai
|
||||||
|
{ toolId: 'tool_fireflies', tagId: 'tag_freemium' },
|
||||||
|
{ toolId: 'tool_fireflies', tagId: 'tag_collaborative' },
|
||||||
|
{ toolId: 'tool_fireflies', tagId: 'tag_enterprise' },
|
||||||
|
{ toolId: 'tool_fireflies', tagId: 'tag_text' },
|
||||||
|
// Mem
|
||||||
|
{ toolId: 'tool_mem', tagId: 'tag_freemium' },
|
||||||
|
{ toolId: 'tool_mem', tagId: 'tag_llm' },
|
||||||
|
{ toolId: 'tool_mem', tagId: 'tag_text' },
|
||||||
|
// Motion
|
||||||
|
{ toolId: 'tool_motion', tagId: 'tag_paid' },
|
||||||
|
{ toolId: 'tool_motion', tagId: 'tag_collaborative' },
|
||||||
|
// Consensus
|
||||||
|
{ toolId: 'tool_consensus', tagId: 'tag_freemium' },
|
||||||
|
{ toolId: 'tool_consensus', tagId: 'tag_llm' },
|
||||||
|
{ toolId: 'tool_consensus', tagId: 'tag_student' },
|
||||||
|
{ toolId: 'tool_consensus', tagId: 'tag_text' },
|
||||||
|
// Elicit
|
||||||
|
{ toolId: 'tool_elicit', tagId: 'tag_freemium' },
|
||||||
|
{ toolId: 'tool_elicit', tagId: 'tag_llm' },
|
||||||
|
{ toolId: 'tool_elicit', tagId: 'tag_student' },
|
||||||
|
{ toolId: 'tool_elicit', tagId: 'tag_text' },
|
||||||
|
// Scite
|
||||||
|
{ toolId: 'tool_scite', tagId: 'tag_paid' },
|
||||||
|
{ toolId: 'tool_scite', tagId: 'tag_student' },
|
||||||
|
{ toolId: 'tool_scite', tagId: 'tag_text' },
|
||||||
|
// Figma AI
|
||||||
|
{ toolId: 'tool_figma_ai', tagId: 'tag_paid' },
|
||||||
|
{ toolId: 'tool_figma_ai', tagId: 'tag_collaborative' },
|
||||||
|
{ toolId: 'tool_figma_ai', tagId: 'tag_popular' },
|
||||||
|
{ toolId: 'tool_figma_ai', tagId: 'tag_image_out' },
|
||||||
|
// Uizard
|
||||||
|
{ toolId: 'tool_uizard', tagId: 'tag_freemium' },
|
||||||
|
{ toolId: 'tool_uizard', tagId: 'tag_beginner' },
|
||||||
|
{ toolId: 'tool_uizard', tagId: 'tag_image_out' },
|
||||||
|
// Magician
|
||||||
|
{ toolId: 'tool_magician', tagId: 'tag_paid' },
|
||||||
|
{ toolId: 'tool_magician', tagId: 'tag_image_out' },
|
||||||
|
{ toolId: 'tool_magician', tagId: 'tag_text' },
|
||||||
|
];
|
||||||
|
|
||||||
|
// ========== 主函数 ==========
|
||||||
|
async function seedTools() {
|
||||||
|
if (!process.env.DATABASE_URL) {
|
||||||
|
console.error("❌ 未找到 DATABASE_URL 环境变量");
|
||||||
|
console.log("请确保 .env.local 文件中设置了 DATABASE_URL");
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
const queryClient = postgres(process.env.DATABASE_URL);
|
||||||
|
const db = drizzle(queryClient);
|
||||||
|
|
||||||
|
try {
|
||||||
|
console.log("🚀 开始导入 AI 工具种子数据...\n");
|
||||||
|
|
||||||
|
// 1. 导入分类
|
||||||
|
console.log("📁 导入分类数据 (10 个)...");
|
||||||
|
for (const cat of categoriesData) {
|
||||||
|
await db.insert(category).values(cat).onConflictDoNothing();
|
||||||
|
}
|
||||||
|
console.log("✅ 分类数据导入完成\n");
|
||||||
|
|
||||||
|
// 2. 导入标签
|
||||||
|
console.log("🏷️ 导入标签数据 (30 个)...");
|
||||||
|
for (const t of tagsData) {
|
||||||
|
await db.insert(tag).values(t).onConflictDoNothing();
|
||||||
|
}
|
||||||
|
console.log("✅ 标签数据导入完成\n");
|
||||||
|
|
||||||
|
// 3. 导入工具
|
||||||
|
console.log("🔧 导入工具数据 (50 个)...");
|
||||||
|
for (const t of toolsData) {
|
||||||
|
await db.insert(tool).values(t).onConflictDoNothing();
|
||||||
|
}
|
||||||
|
console.log("✅ 工具数据导入完成\n");
|
||||||
|
|
||||||
|
// 4. 导入工具-分类关联
|
||||||
|
console.log("🔗 导入工具-分类关联...");
|
||||||
|
for (const tc of toolCategoryData) {
|
||||||
|
await db.insert(toolCategory).values(tc).onConflictDoNothing();
|
||||||
|
}
|
||||||
|
console.log("✅ 工具-分类关联导入完成\n");
|
||||||
|
|
||||||
|
// 5. 导入工具-标签关联
|
||||||
|
console.log("🏷️ 导入工具-标签关联...");
|
||||||
|
for (const tt of toolTagData) {
|
||||||
|
await db.insert(toolTag).values(tt).onConflictDoNothing();
|
||||||
|
}
|
||||||
|
console.log("✅ 工具-标签关联导入完成\n");
|
||||||
|
|
||||||
|
console.log("====================================");
|
||||||
|
console.log("🎉 所有种子数据导入成功!");
|
||||||
|
console.log("====================================");
|
||||||
|
console.log("\n📊 数据统计:");
|
||||||
|
console.log(` - 分类: ${categoriesData.length} 个`);
|
||||||
|
console.log(` - 标签: ${tagsData.length} 个`);
|
||||||
|
console.log(` - 工具: ${toolsData.length} 个`);
|
||||||
|
console.log(` - 工具-分类关联: ${toolCategoryData.length} 条`);
|
||||||
|
console.log(` - 工具-标签关联: ${toolTagData.length} 条`);
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error("❌ 导入失败:", error);
|
||||||
|
await queryClient.end();
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
await queryClient.end();
|
||||||
|
process.exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 运行脚本
|
||||||
|
seedTools();
|
||||||
@@ -0,0 +1,111 @@
|
|||||||
|
#!/usr/bin/env tsx
|
||||||
|
/**
|
||||||
|
* 管理员初始化脚本
|
||||||
|
* 用于设置第一个管理员账号
|
||||||
|
*
|
||||||
|
* 使用方法:
|
||||||
|
* 1. 设置环境变量 ADMIN_EMAIL
|
||||||
|
* 2. 运行: pnpm admin:setup
|
||||||
|
* 或直接运行: ADMIN_EMAIL=admin@example.com pnpm admin:setup
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { drizzle } from "drizzle-orm/postgres-js";
|
||||||
|
import postgres from "postgres";
|
||||||
|
import { eq } from "drizzle-orm";
|
||||||
|
import dotenv from "dotenv";
|
||||||
|
import { resolve } from "path";
|
||||||
|
import { pgTable, text, timestamp, boolean, integer } from "drizzle-orm/pg-core";
|
||||||
|
|
||||||
|
// 加载环境变量
|
||||||
|
dotenv.config({ path: resolve(process.cwd(), ".env.local") });
|
||||||
|
|
||||||
|
// 定义 user 表结构(复制自 schema)
|
||||||
|
const user = pgTable("user", {
|
||||||
|
id: text("id").primaryKey(),
|
||||||
|
name: text("name").notNull(),
|
||||||
|
email: text("email").notNull().unique(),
|
||||||
|
emailVerified: boolean("email_verified").default(false).notNull(),
|
||||||
|
image: text("image"),
|
||||||
|
credits: integer("credits").default(0).notNull(),
|
||||||
|
role: text("role").default("user").notNull(),
|
||||||
|
banned: boolean("banned").default(false).notNull(),
|
||||||
|
banReason: text("ban_reason"),
|
||||||
|
banExpires: timestamp("ban_expires"),
|
||||||
|
createdAt: timestamp("created_at").defaultNow().notNull(),
|
||||||
|
updatedAt: timestamp("updated_at")
|
||||||
|
.defaultNow()
|
||||||
|
.$onUpdate(() => new Date())
|
||||||
|
.notNull(),
|
||||||
|
});
|
||||||
|
|
||||||
|
async function setupAdmin() {
|
||||||
|
const adminEmail = process.env.ADMIN_EMAIL;
|
||||||
|
|
||||||
|
if (!adminEmail) {
|
||||||
|
console.error("❌ 请设置环境变量 ADMIN_EMAIL");
|
||||||
|
console.log("例如: ADMIN_EMAIL=admin@example.com pnpm admin:setup");
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!process.env.DATABASE_URL) {
|
||||||
|
console.error("❌ 未找到 DATABASE_URL 环境变量");
|
||||||
|
console.log("请确保 .env.local 文件中设置了 DATABASE_URL");
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建数据库连接
|
||||||
|
const queryClient = postgres(process.env.DATABASE_URL);
|
||||||
|
const db = drizzle(queryClient);
|
||||||
|
|
||||||
|
try {
|
||||||
|
console.log("🔍 正在查找用户:", adminEmail);
|
||||||
|
|
||||||
|
// 检查用户是否存在
|
||||||
|
const existingUser = await db
|
||||||
|
.select()
|
||||||
|
.from(user)
|
||||||
|
.where(eq(user.email, adminEmail))
|
||||||
|
.limit(1);
|
||||||
|
|
||||||
|
if (existingUser.length === 0) {
|
||||||
|
console.error(`❌ 用户 ${adminEmail} 不存在`);
|
||||||
|
console.log("请先使用此邮箱注册一个账号,然后再运行此脚本");
|
||||||
|
await queryClient.end();
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
const currentUser = existingUser[0];
|
||||||
|
console.log(`✓ 找到用户: ${currentUser.name} (当前角色: ${currentUser.role})`);
|
||||||
|
|
||||||
|
if (currentUser.role === "admin") {
|
||||||
|
console.log(`✅ 用户 ${adminEmail} 已经是管理员`);
|
||||||
|
await queryClient.end();
|
||||||
|
process.exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新用户为管理员
|
||||||
|
console.log("📝 正在更新用户角色...");
|
||||||
|
await db
|
||||||
|
.update(user)
|
||||||
|
.set({
|
||||||
|
role: "admin",
|
||||||
|
updatedAt: new Date()
|
||||||
|
})
|
||||||
|
.where(eq(user.email, adminEmail));
|
||||||
|
|
||||||
|
console.log(`✅ 成功将用户 ${adminEmail} 设置为管理员`);
|
||||||
|
console.log("🚀 现在你可以访问 /admin 路径来进入管理后台");
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error("❌ 设置管理员失败:", error);
|
||||||
|
await queryClient.end();
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 关闭数据库连接
|
||||||
|
await queryClient.end();
|
||||||
|
process.exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 运行脚本
|
||||||
|
setupAdmin();
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
import GoogleAnalytics from "./google-analytics";
|
||||||
|
import ClarityAnalytics from "./clarity-analytics";
|
||||||
|
import { analyticsConfig } from "@/constants/website";
|
||||||
|
|
||||||
|
export type AnalyticsProps = {
|
||||||
|
readonly forceEnableInDevelopment?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function Analytics({ forceEnableInDevelopment = false }: AnalyticsProps = {}) {
|
||||||
|
const { enableInDevelopment } = analyticsConfig;
|
||||||
|
|
||||||
|
if (
|
||||||
|
process.env.NODE_ENV !== "production" &&
|
||||||
|
!enableInDevelopment &&
|
||||||
|
!forceEnableInDevelopment
|
||||||
|
) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<GoogleAnalytics />
|
||||||
|
<ClarityAnalytics />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Analytics;
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
import Script from "next/script";
|
||||||
|
|
||||||
|
export type ClarityAnalyticsProps = {
|
||||||
|
readonly projectId?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function ClarityAnalytics({ projectId }: ClarityAnalyticsProps = {}) {
|
||||||
|
const clarityProjectId = projectId ?? process.env.NEXT_PUBLIC_CLARITY_PROJECT_ID;
|
||||||
|
|
||||||
|
if (!clarityProjectId) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const sanitizedId = clarityProjectId.trim();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Script id="microsoft-clarity" strategy="afterInteractive">
|
||||||
|
{`
|
||||||
|
(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/${sanitizedId}";
|
||||||
|
y=l.getElementsByTagName(r)[0];
|
||||||
|
y.parentNode.insertBefore(t,y);
|
||||||
|
})(window, document, "clarity", "script", "${sanitizedId}");
|
||||||
|
`}
|
||||||
|
</Script>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
import Script from "next/script";
|
||||||
|
|
||||||
|
export type GoogleAnalyticsProps = {
|
||||||
|
readonly trackingId?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function GoogleAnalytics({ trackingId }: GoogleAnalyticsProps = {}) {
|
||||||
|
const gaTrackingId = trackingId ?? process.env.NEXT_PUBLIC_GOOGLE_ANALYTICS_ID;
|
||||||
|
|
||||||
|
if (!gaTrackingId) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const sanitizedId = gaTrackingId.trim();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Script
|
||||||
|
src={`https://www.googletagmanager.com/gtag/js?id=${encodeURIComponent(sanitizedId)}`}
|
||||||
|
strategy="afterInteractive"
|
||||||
|
/>
|
||||||
|
<Script id="google-analytics" strategy="afterInteractive">
|
||||||
|
{`
|
||||||
|
window.dataLayer = window.dataLayer || [];
|
||||||
|
function gtag(){dataLayer.push(arguments);}
|
||||||
|
gtag('js', new Date());
|
||||||
|
gtag('config', '${sanitizedId}', { anonymize_ip: true });
|
||||||
|
`}
|
||||||
|
</Script>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
import { getCategoriesWithCount } from "@/features/tools/actions";
|
||||||
|
import { CategoriesTable } from "@/features/tools/components/categories-table";
|
||||||
|
import { getTranslations } from "next-intl/server";
|
||||||
|
import type { Locale } from "@/i18n.config";
|
||||||
|
|
||||||
|
export default async function AdminCategoriesPage({
|
||||||
|
params,
|
||||||
|
}: {
|
||||||
|
params: Promise<{ locale: Locale }>;
|
||||||
|
}) {
|
||||||
|
const { locale } = await params;
|
||||||
|
const [categories, t] = await Promise.all([
|
||||||
|
getCategoriesWithCount(),
|
||||||
|
getTranslations({ locale, namespace: "Admin.categoriesManagement" }),
|
||||||
|
]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="space-y-6">
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<h1 className="text-2xl font-bold text-foreground">{t("title")}</h1>
|
||||||
|
<div className="flex items-center gap-4">
|
||||||
|
<div className="text-sm text-muted-foreground">
|
||||||
|
{t("toolCountLabel", { count: categories.length })}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<CategoriesTable categories={categories} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
import { db } from "@/lib/db";
|
||||||
|
import { user } from "@/lib/db/schema";
|
||||||
|
import { sql } from "drizzle-orm";
|
||||||
|
import { AdminDashboard } from "@/features/admin/components/admin-dashboard";
|
||||||
|
|
||||||
|
export default async function AdminPage() {
|
||||||
|
// 获取统计数据
|
||||||
|
const [totalUsers, activeUsers] = await Promise.all([
|
||||||
|
// 总用户数
|
||||||
|
db.select({ count: sql<number>`count(*)` }).from(user),
|
||||||
|
|
||||||
|
// 活跃用户数(30天内)
|
||||||
|
db
|
||||||
|
.select({ count: sql<number>`count(*)` })
|
||||||
|
.from(user)
|
||||||
|
.where(sql`${user.updatedAt} > NOW() - INTERVAL '30 days'`),
|
||||||
|
]);
|
||||||
|
|
||||||
|
// 获取最近的用户
|
||||||
|
const recentUsers = await db
|
||||||
|
.select({
|
||||||
|
id: user.id,
|
||||||
|
name: user.name,
|
||||||
|
email: user.email,
|
||||||
|
role: user.role,
|
||||||
|
createdAt: user.createdAt,
|
||||||
|
})
|
||||||
|
.from(user)
|
||||||
|
.orderBy(sql`${user.createdAt} desc`)
|
||||||
|
.limit(5);
|
||||||
|
|
||||||
|
const stats = {
|
||||||
|
totalUsers: totalUsers[0].count,
|
||||||
|
activeUsers: activeUsers[0].count,
|
||||||
|
};
|
||||||
|
|
||||||
|
return <AdminDashboard stats={stats} recentUsers={recentUsers} />;
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
import { getTagsWithCount } from "@/features/tools/actions";
|
||||||
|
import { TagsTable } from "@/features/tools/components/tags-table";
|
||||||
|
import { getTranslations } from "next-intl/server";
|
||||||
|
import type { Locale } from "@/i18n.config";
|
||||||
|
|
||||||
|
export default async function AdminTagsPage({
|
||||||
|
params,
|
||||||
|
}: {
|
||||||
|
params: Promise<{ locale: Locale }>;
|
||||||
|
}) {
|
||||||
|
const { locale } = await params;
|
||||||
|
const [tags, t] = await Promise.all([
|
||||||
|
getTagsWithCount(),
|
||||||
|
getTranslations({ locale, namespace: "Admin.tagsManagement" }),
|
||||||
|
]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="space-y-6">
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<h1 className="text-2xl font-bold text-foreground">{t("title")}</h1>
|
||||||
|
<div className="flex items-center gap-4">
|
||||||
|
<div className="text-sm text-muted-foreground">
|
||||||
|
{t("toolCountLabel", { count: tags.length })}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<TagsTable tags={tags} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
import { getToolsAdmin } from "@/features/tools/actions";
|
||||||
|
import { ToolsTable } from "@/features/tools/components/tools-table";
|
||||||
|
import { getTranslations } from "next-intl/server";
|
||||||
|
import type { Locale } from "@/i18n.config";
|
||||||
|
|
||||||
|
export default async function AdminToolsPage({
|
||||||
|
params,
|
||||||
|
}: {
|
||||||
|
params: Promise<{ locale: Locale }>;
|
||||||
|
}) {
|
||||||
|
const { locale } = await params;
|
||||||
|
const [{ tools, total }, t] = await Promise.all([
|
||||||
|
getToolsAdmin({ pageSize: 100 }),
|
||||||
|
getTranslations({ locale, namespace: "Admin.toolsManagement" }),
|
||||||
|
]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="space-y-6">
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<h1 className="text-2xl font-bold text-foreground">{t("title")}</h1>
|
||||||
|
<div className="flex items-center gap-4">
|
||||||
|
<div className="text-sm text-muted-foreground">{t("totalCount", { count: total })}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<ToolsTable tools={tools} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
import { db } from "@/lib/db";
|
||||||
|
import { user } from "@/lib/db/schema";
|
||||||
|
import { UsersTable } from "@/features/admin/components/users-table";
|
||||||
|
import { sql } from "drizzle-orm";
|
||||||
|
|
||||||
|
export default async function AdminUsersPage() {
|
||||||
|
// 获取所有用户
|
||||||
|
const users = await db
|
||||||
|
.select({
|
||||||
|
id: user.id,
|
||||||
|
name: user.name,
|
||||||
|
email: user.email,
|
||||||
|
emailVerified: user.emailVerified,
|
||||||
|
role: user.role,
|
||||||
|
banned: user.banned,
|
||||||
|
banReason: user.banReason,
|
||||||
|
banExpires: user.banExpires,
|
||||||
|
createdAt: user.createdAt,
|
||||||
|
updatedAt: user.updatedAt,
|
||||||
|
})
|
||||||
|
.from(user)
|
||||||
|
.orderBy(sql`${user.createdAt} desc`);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="space-y-6">
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<h1 className="text-2xl font-bold text-foreground">用户管理</h1>
|
||||||
|
<div className="flex items-center gap-4">
|
||||||
|
<div className="text-sm text-muted-foreground">
|
||||||
|
共 {users.length} 个用户
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<UsersTable users={users} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
import { requireAdmin } from "@/lib/auth/admin";
|
||||||
|
import { AdminSidebar } from "@/features/admin/components/admin-sidebar";
|
||||||
|
import { AdminHeader } from "@/features/admin/components/admin-header";
|
||||||
|
|
||||||
|
export default async function AdminLayout({
|
||||||
|
children,
|
||||||
|
}: {
|
||||||
|
children: React.ReactNode;
|
||||||
|
}) {
|
||||||
|
// 检查管理员权限
|
||||||
|
await requireAdmin();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="min-h-screen bg-muted">
|
||||||
|
<div className="flex">
|
||||||
|
{/* 侧边栏 */}
|
||||||
|
<AdminSidebar />
|
||||||
|
|
||||||
|
{/* 主内容区 */}
|
||||||
|
<div className="flex-1 flex flex-col">
|
||||||
|
<AdminHeader />
|
||||||
|
|
||||||
|
<main className="flex-1 p-6">
|
||||||
|
<div className="mx-auto max-w-7xl">
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,122 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { useState } from "react";
|
||||||
|
import { useTranslations } from "next-intl";
|
||||||
|
import { z } from "zod";
|
||||||
|
import { useForm } from "react-hook-form";
|
||||||
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
|
import { Button } from "@/components/button";
|
||||||
|
import { LocaleLink } from "@/components/locale-link";
|
||||||
|
import { FormShell } from "@/features/forms/components/form-shell";
|
||||||
|
import { AuthMessageCard } from "@/features/forms/components/auth-message-card";
|
||||||
|
import { FormControl, FormField, FormItem, FormLabel, FormMessage } from "@/components/ui/form";
|
||||||
|
import { Input } from "@/features/forms/components/input";
|
||||||
|
import { toast } from "sonner";
|
||||||
|
|
||||||
|
const forgotPasswordSchema = z.object({
|
||||||
|
email: z.string().email("Please enter a valid email address"),
|
||||||
|
});
|
||||||
|
|
||||||
|
type ForgotPasswordInput = z.infer<typeof forgotPasswordSchema>;
|
||||||
|
|
||||||
|
export default function ForgotPasswordPage() {
|
||||||
|
const t = useTranslations();
|
||||||
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
|
const [emailSent, setEmailSent] = useState(false);
|
||||||
|
|
||||||
|
const form = useForm<ForgotPasswordInput>({
|
||||||
|
resolver: zodResolver(forgotPasswordSchema),
|
||||||
|
defaultValues: {
|
||||||
|
email: "",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const onSubmit = async (data: ForgotPasswordInput) => {
|
||||||
|
setIsLoading(true);
|
||||||
|
try {
|
||||||
|
const response = await fetch("/api/auth/forgot-password", {
|
||||||
|
method: "POST",
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
body: JSON.stringify(data),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
setEmailSent(true);
|
||||||
|
toast.success("Password reset email sent!");
|
||||||
|
} else {
|
||||||
|
const error = await response.json();
|
||||||
|
toast.error(error.message || "Failed to send reset email");
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
toast.error("An error occurred. Please try again.");
|
||||||
|
} finally {
|
||||||
|
setIsLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (emailSent) {
|
||||||
|
return (
|
||||||
|
<AuthMessageCard
|
||||||
|
title="Check your email"
|
||||||
|
description="We've sent you a password reset link. Please check your email."
|
||||||
|
>
|
||||||
|
<p className="text-sm text-muted-foreground">
|
||||||
|
Didn't receive an email? Check your spam folder or try again.
|
||||||
|
</p>
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
onClick={() => setEmailSent(false)}
|
||||||
|
className="w-full"
|
||||||
|
>
|
||||||
|
Try Again
|
||||||
|
</Button>
|
||||||
|
<LocaleLink href="/login">
|
||||||
|
<Button variant="simple" className="w-full">
|
||||||
|
Back to Login
|
||||||
|
</Button>
|
||||||
|
</LocaleLink>
|
||||||
|
</AuthMessageCard>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<FormShell<ForgotPasswordInput>
|
||||||
|
form={form}
|
||||||
|
title="Forgot password?"
|
||||||
|
description="Enter your email address and we'll send you a reset link."
|
||||||
|
onSubmit={onSubmit}
|
||||||
|
submitText="Send Reset Link"
|
||||||
|
submitLoadingText="Sending..."
|
||||||
|
isLoading={isLoading}
|
||||||
|
footer={
|
||||||
|
<div className="text-center">
|
||||||
|
<LocaleLink
|
||||||
|
href="/login"
|
||||||
|
className="text-sm text-muted-foreground hover:text-foreground"
|
||||||
|
>
|
||||||
|
Remember your password? Sign in
|
||||||
|
</LocaleLink>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="email"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>Email</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<Input
|
||||||
|
type="email"
|
||||||
|
placeholder="Enter your email"
|
||||||
|
disabled={isLoading}
|
||||||
|
{...field}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</FormShell>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
import { AuthLayout } from "@/layouts/auth-layout";
|
||||||
|
export default function AuthXLayout({
|
||||||
|
children,
|
||||||
|
}: {
|
||||||
|
children: React.ReactNode;
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<AuthLayout>
|
||||||
|
<main className="flex h-full min-h-screen w-full">{children}</main>
|
||||||
|
</AuthLayout>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
import { LoginForm } from "@/features/auth/components/login-form";
|
||||||
|
import { Metadata } from "next";
|
||||||
|
import { getTranslations } from 'next-intl/server';
|
||||||
|
import type { Locale } from "@/i18n.config";
|
||||||
|
|
||||||
|
export async function generateMetadata({
|
||||||
|
params
|
||||||
|
}: {
|
||||||
|
params: Promise<{ locale: Locale }>
|
||||||
|
}): Promise<Metadata> {
|
||||||
|
const { locale } = await params;
|
||||||
|
const t = await getTranslations({ locale, namespace: 'seo' });
|
||||||
|
|
||||||
|
return {
|
||||||
|
title: t('login.title'),
|
||||||
|
description: t('login.description'),
|
||||||
|
openGraph: {
|
||||||
|
images: [t('login.ogImage')],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function LoginPage() {
|
||||||
|
return <LoginForm />;
|
||||||
|
}
|
||||||
@@ -0,0 +1,188 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { useState, useEffect } from "react";
|
||||||
|
import { useRouter, useSearchParams } from "next/navigation";
|
||||||
|
import { useTranslations } from "next-intl";
|
||||||
|
import { z } from "zod";
|
||||||
|
import { useForm } from "react-hook-form";
|
||||||
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
|
import { Button } from "@/components/button";
|
||||||
|
import { LocaleLink } from "@/components/locale-link";
|
||||||
|
import { FormShell } from "@/features/forms/components/form-shell";
|
||||||
|
import { AuthMessageCard } from "@/features/forms/components/auth-message-card";
|
||||||
|
import { FormControl, FormField, FormItem, FormLabel, FormMessage } from "@/components/ui/form";
|
||||||
|
import { Input } from "@/features/forms/components/input";
|
||||||
|
import { toast } from "sonner";
|
||||||
|
|
||||||
|
const resetPasswordSchema = z.object({
|
||||||
|
password: z.string().min(8, "Password must be at least 8 characters"),
|
||||||
|
confirmPassword: z.string(),
|
||||||
|
}).refine((data) => data.password === data.confirmPassword, {
|
||||||
|
message: "Passwords don't match",
|
||||||
|
path: ["confirmPassword"],
|
||||||
|
});
|
||||||
|
|
||||||
|
type ResetPasswordInput = z.infer<typeof resetPasswordSchema>;
|
||||||
|
|
||||||
|
export default function ResetPasswordPage() {
|
||||||
|
const router = useRouter();
|
||||||
|
const searchParams = useSearchParams();
|
||||||
|
const t = useTranslations();
|
||||||
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
|
const [isValidToken, setIsValidToken] = useState<boolean | null>(null);
|
||||||
|
const token = searchParams.get("token");
|
||||||
|
|
||||||
|
const form = useForm<ResetPasswordInput>({
|
||||||
|
resolver: zodResolver(resetPasswordSchema),
|
||||||
|
defaultValues: {
|
||||||
|
password: "",
|
||||||
|
confirmPassword: "",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!token) {
|
||||||
|
setIsValidToken(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify token validity
|
||||||
|
fetch(`/api/auth/verify-reset-token?token=${token}`)
|
||||||
|
.then(res => res.json())
|
||||||
|
.then(data => {
|
||||||
|
setIsValidToken(data.valid);
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
setIsValidToken(false);
|
||||||
|
});
|
||||||
|
}, [token]);
|
||||||
|
|
||||||
|
const onSubmit = async (data: ResetPasswordInput) => {
|
||||||
|
if (!token) {
|
||||||
|
toast.error("Invalid reset link");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setIsLoading(true);
|
||||||
|
try {
|
||||||
|
const response = await fetch("/api/auth/reset-password", {
|
||||||
|
method: "POST",
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
body: JSON.stringify({
|
||||||
|
token,
|
||||||
|
password: data.password,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
toast.success("Password reset successfully!");
|
||||||
|
setTimeout(() => {
|
||||||
|
router.push("/login");
|
||||||
|
}, 2000);
|
||||||
|
} else {
|
||||||
|
const error = await response.json();
|
||||||
|
toast.error(error.message || "Failed to reset password");
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
toast.error("An error occurred. Please try again.");
|
||||||
|
} finally {
|
||||||
|
setIsLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (isValidToken === null) {
|
||||||
|
return (
|
||||||
|
<AuthMessageCard
|
||||||
|
title="Verifying..."
|
||||||
|
description="Please wait while we verify your reset link."
|
||||||
|
>
|
||||||
|
<div className="flex justify-center py-8">
|
||||||
|
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-foreground"></div>
|
||||||
|
</div>
|
||||||
|
</AuthMessageCard>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isValidToken === false) {
|
||||||
|
return (
|
||||||
|
<AuthMessageCard
|
||||||
|
title="Invalid or Expired Link"
|
||||||
|
description="This password reset link is invalid or has expired."
|
||||||
|
>
|
||||||
|
<p className="text-sm text-muted-foreground">
|
||||||
|
Please request a new password reset link.
|
||||||
|
</p>
|
||||||
|
<LocaleLink href="/forgot-password">
|
||||||
|
<Button className="w-full">
|
||||||
|
Request New Link
|
||||||
|
</Button>
|
||||||
|
</LocaleLink>
|
||||||
|
<LocaleLink href="/login">
|
||||||
|
<Button variant="outline" className="w-full">
|
||||||
|
Back to Login
|
||||||
|
</Button>
|
||||||
|
</LocaleLink>
|
||||||
|
</AuthMessageCard>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<FormShell<ResetPasswordInput>
|
||||||
|
form={form}
|
||||||
|
title="Reset your password"
|
||||||
|
description="Enter your new password below."
|
||||||
|
onSubmit={onSubmit}
|
||||||
|
submitText="Reset Password"
|
||||||
|
submitLoadingText="Resetting..."
|
||||||
|
isLoading={isLoading}
|
||||||
|
footer={
|
||||||
|
<div className="text-center">
|
||||||
|
<LocaleLink
|
||||||
|
href="/login"
|
||||||
|
className="text-sm text-muted-foreground hover:text-foreground"
|
||||||
|
>
|
||||||
|
Back to Login
|
||||||
|
</LocaleLink>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="password"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>New Password</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<Input
|
||||||
|
type="password"
|
||||||
|
placeholder="Enter new password"
|
||||||
|
disabled={isLoading}
|
||||||
|
{...field}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="confirmPassword"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>Confirm Password</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<Input
|
||||||
|
type="password"
|
||||||
|
placeholder="Confirm new password"
|
||||||
|
disabled={isLoading}
|
||||||
|
{...field}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</FormShell>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
import { SignupForm } from "@/features/auth/components/signup-form";
|
||||||
|
import { Metadata } from "next";
|
||||||
|
import { getTranslations } from 'next-intl/server';
|
||||||
|
import type { Locale } from "@/i18n.config";
|
||||||
|
|
||||||
|
export async function generateMetadata({
|
||||||
|
params
|
||||||
|
}: {
|
||||||
|
params: Promise<{ locale: Locale }>
|
||||||
|
}): Promise<Metadata> {
|
||||||
|
const { locale } = await params;
|
||||||
|
const t = await getTranslations({ locale, namespace: 'seo' });
|
||||||
|
|
||||||
|
return {
|
||||||
|
title: t('signup.title'),
|
||||||
|
description: t('signup.description'),
|
||||||
|
openGraph: {
|
||||||
|
images: [t('signup.ogImage')],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function SignupPage() {
|
||||||
|
return <SignupForm />;
|
||||||
|
}
|
||||||
@@ -0,0 +1,61 @@
|
|||||||
|
import type { Metadata } from "next";
|
||||||
|
import { notFound } from "next/navigation";
|
||||||
|
|
||||||
|
import { getAllBlogs, getBlogModule } from "@/lib/blog";
|
||||||
|
import { locales, type Locale } from "@/i18n.config";
|
||||||
|
|
||||||
|
interface PageProps {
|
||||||
|
params: Promise<{
|
||||||
|
locale: Locale;
|
||||||
|
slug: string;
|
||||||
|
}>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function generateStaticParams() {
|
||||||
|
const blogs = await getAllBlogs();
|
||||||
|
|
||||||
|
return blogs.flatMap((blog) =>
|
||||||
|
locales.map((locale) => ({
|
||||||
|
slug: blog.slug,
|
||||||
|
locale,
|
||||||
|
}))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function generateMetadata({ params }: PageProps): Promise<Metadata> {
|
||||||
|
const { slug, locale } = await params;
|
||||||
|
|
||||||
|
const blogModule = await getBlogModule(slug, locale);
|
||||||
|
|
||||||
|
if (!blogModule) {
|
||||||
|
notFound();
|
||||||
|
}
|
||||||
|
|
||||||
|
const { blog } = blogModule;
|
||||||
|
const metadata: Metadata = {
|
||||||
|
title: blog.title,
|
||||||
|
description: blog.description,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (blog.image) {
|
||||||
|
metadata.openGraph = {
|
||||||
|
images: [blog.image],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return metadata;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default async function BlogPostPage({ params }: PageProps) {
|
||||||
|
const { slug, locale } = await params;
|
||||||
|
|
||||||
|
const blogModule = await getBlogModule(slug, locale);
|
||||||
|
|
||||||
|
if (!blogModule) {
|
||||||
|
notFound();
|
||||||
|
}
|
||||||
|
|
||||||
|
const MDXContent = blogModule.default;
|
||||||
|
|
||||||
|
return <MDXContent />;
|
||||||
|
}
|
||||||
@@ -0,0 +1,107 @@
|
|||||||
|
import { BlogLayout } from "@/components/blog-layout";
|
||||||
|
|
||||||
|
export const blog = {
|
||||||
|
author: { name: "Toolso.AI", src: "/avatar.jpeg" },
|
||||||
|
date: "2024-12-28",
|
||||||
|
title: "AI Chatbots for Business: ChatGPT, Claude, and Gemini Compared",
|
||||||
|
description:
|
||||||
|
"A detailed comparison of enterprise AI chatbots. Learn which AI assistant is best for your business needs, from customer service to content creation.",
|
||||||
|
image:
|
||||||
|
"https://images.unsplash.com/photo-1531746790731-6c087fecd65a?w=800&auto=format&fit=crop&q=60",
|
||||||
|
};
|
||||||
|
export const metadata = {
|
||||||
|
title: blog.title,
|
||||||
|
description: blog.description,
|
||||||
|
openGraph: {
|
||||||
|
images: [blog.image],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default (props) => <BlogLayout blog={blog} {...props} />;
|
||||||
|
|
||||||
|
## The Business AI Assistant Revolution
|
||||||
|
|
||||||
|
AI chatbots have evolved from simple FAQ bots to sophisticated business partners. Today's enterprise AI assistants can draft emails, analyze data, write code, and even help with strategic decisions.
|
||||||
|
|
||||||
|
## The Big Three: An Overview
|
||||||
|
|
||||||
|
### ChatGPT (OpenAI)
|
||||||
|
|
||||||
|
**Best for:** General-purpose business tasks, content creation, coding assistance
|
||||||
|
|
||||||
|
ChatGPT remains the most widely adopted AI assistant in the business world. With GPT-4 Turbo and the new GPT-4o model, it offers:
|
||||||
|
|
||||||
|
- **128K context window** - Process entire documents at once
|
||||||
|
- **Multimodal capabilities** - Analyze images and charts
|
||||||
|
- **Custom GPTs** - Build specialized assistants for your team
|
||||||
|
- **API access** - Integrate into your existing workflows
|
||||||
|
|
||||||
|
**Pricing:** $20/month (Plus) or $25/user/month (Team)
|
||||||
|
|
||||||
|
### Claude (Anthropic)
|
||||||
|
|
||||||
|
**Best for:** Long document analysis, nuanced writing, safety-critical applications
|
||||||
|
|
||||||
|
Claude has carved out a niche for businesses requiring thoughtful, nuanced AI interactions:
|
||||||
|
|
||||||
|
- **200K context window** - Industry-leading document processing
|
||||||
|
- **Constitutional AI** - Built-in safety and ethics guidelines
|
||||||
|
- **Excellent at following complex instructions**
|
||||||
|
- **Strong coding and analysis capabilities**
|
||||||
|
|
||||||
|
**Pricing:** $20/month (Pro) or custom enterprise pricing
|
||||||
|
|
||||||
|
### Gemini (Google)
|
||||||
|
|
||||||
|
**Best for:** Google Workspace integration, research, real-time information
|
||||||
|
|
||||||
|
Google's Gemini offers unique advantages for businesses in the Google ecosystem:
|
||||||
|
|
||||||
|
- **Deep Google integration** - Works with Docs, Sheets, Gmail
|
||||||
|
- **Real-time information** - Access to current web data
|
||||||
|
- **Multimodal from the ground up** - Native image and video understanding
|
||||||
|
- **Competitive pricing** - Generous free tier
|
||||||
|
|
||||||
|
**Pricing:** $20/month (Advanced) or included with Workspace plans
|
||||||
|
|
||||||
|
## Head-to-Head Comparison
|
||||||
|
|
||||||
|
| Feature | ChatGPT | Claude | Gemini |
|
||||||
|
|---------|---------|--------|--------|
|
||||||
|
| Context Window | 128K | 200K | 1M (Gemini 1.5) |
|
||||||
|
| Coding | Excellent | Very Good | Good |
|
||||||
|
| Writing | Very Good | Excellent | Good |
|
||||||
|
| Analysis | Very Good | Excellent | Very Good |
|
||||||
|
| Integration | API-focused | API-focused | Google-native |
|
||||||
|
| Custom Training | Yes (GPTs) | Limited | Limited |
|
||||||
|
|
||||||
|
## Use Case Recommendations
|
||||||
|
|
||||||
|
### Customer Support
|
||||||
|
**Winner: ChatGPT** - Best ecosystem for building custom support bots
|
||||||
|
|
||||||
|
### Content Marketing
|
||||||
|
**Winner: Claude** - Superior long-form writing quality
|
||||||
|
|
||||||
|
### Data Analysis
|
||||||
|
**Winner: Gemini** - Seamless Google Sheets integration
|
||||||
|
|
||||||
|
### Software Development
|
||||||
|
**Winner: ChatGPT** - Most comprehensive coding assistance
|
||||||
|
|
||||||
|
### Research & Analysis
|
||||||
|
**Winner: Claude** - Best for processing lengthy documents
|
||||||
|
|
||||||
|
## Implementation Tips
|
||||||
|
|
||||||
|
1. **Start with a pilot** - Test with a small team before company-wide rollout
|
||||||
|
2. **Define clear use cases** - Know what problems you're solving
|
||||||
|
3. **Train your team** - Invest in prompt engineering education
|
||||||
|
4. **Monitor usage** - Track ROI and identify optimization opportunities
|
||||||
|
5. **Stay compliant** - Ensure AI use aligns with data privacy regulations
|
||||||
|
|
||||||
|
## Conclusion
|
||||||
|
|
||||||
|
There's no one-size-fits-all AI chatbot for business. Your choice should depend on your specific needs, existing tech stack, and budget. Many businesses find value in using multiple AI assistants for different tasks.
|
||||||
|
|
||||||
|
Explore our complete directory of AI chatbots and assistants on Toolso.AI to find the perfect fit for your organization.
|
||||||
@@ -0,0 +1,107 @@
|
|||||||
|
import { BlogLayout } from "@/components/blog-layout";
|
||||||
|
|
||||||
|
export const blog = {
|
||||||
|
author: { name: "Toolso.AI", src: "/avatar.jpeg" },
|
||||||
|
date: "2024-12-28",
|
||||||
|
title: "企业级AI聊天机器人对比:ChatGPT、Claude和Gemini",
|
||||||
|
description:
|
||||||
|
"深入对比企业级AI聊天机器人。了解哪款AI助手最适合您的业务需求,从客户服务到内容创作。",
|
||||||
|
image:
|
||||||
|
"https://images.unsplash.com/photo-1531746790731-6c087fecd65a?w=800&auto=format&fit=crop&q=60",
|
||||||
|
};
|
||||||
|
export const metadata = {
|
||||||
|
title: blog.title,
|
||||||
|
description: blog.description,
|
||||||
|
openGraph: {
|
||||||
|
images: [blog.image],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default (props) => <BlogLayout blog={blog} {...props} />;
|
||||||
|
|
||||||
|
## 商业AI助手的革命
|
||||||
|
|
||||||
|
AI聊天机器人已从简单的FAQ机器人演变为成熟的商业伙伴。如今的企业级AI助手可以起草邮件、分析数据、编写代码,甚至协助战略决策。
|
||||||
|
|
||||||
|
## 三巨头概览
|
||||||
|
|
||||||
|
### ChatGPT (OpenAI)
|
||||||
|
|
||||||
|
**最适合:** 通用商业任务、内容创作、编程辅助
|
||||||
|
|
||||||
|
ChatGPT仍然是商业领域最广泛采用的AI助手。凭借GPT-4 Turbo和新的GPT-4o模型,它提供:
|
||||||
|
|
||||||
|
- **128K上下文窗口** - 一次处理整个文档
|
||||||
|
- **多模态能力** - 分析图像和图表
|
||||||
|
- **自定义GPTs** - 为团队构建专属助手
|
||||||
|
- **API访问** - 集成到现有工作流程
|
||||||
|
|
||||||
|
**定价:** $20/月(Plus)或 $25/用户/月(Team)
|
||||||
|
|
||||||
|
### Claude (Anthropic)
|
||||||
|
|
||||||
|
**最适合:** 长文档分析、细腻写作、安全关键应用
|
||||||
|
|
||||||
|
Claude为需要深思熟虑、细腻AI交互的企业开辟了一片天地:
|
||||||
|
|
||||||
|
- **200K上下文窗口** - 行业领先的文档处理能力
|
||||||
|
- **宪法AI** - 内置安全和道德准则
|
||||||
|
- **擅长遵循复杂指令**
|
||||||
|
- **强大的编码和分析能力**
|
||||||
|
|
||||||
|
**定价:** $20/月(Pro)或定制企业定价
|
||||||
|
|
||||||
|
### Gemini (Google)
|
||||||
|
|
||||||
|
**最适合:** Google Workspace集成、研究、实时信息
|
||||||
|
|
||||||
|
Google的Gemini为Google生态系统中的企业提供独特优势:
|
||||||
|
|
||||||
|
- **深度Google集成** - 与Docs、Sheets、Gmail协同工作
|
||||||
|
- **实时信息** - 访问当前网络数据
|
||||||
|
- **原生多模态** - 原生图像和视频理解
|
||||||
|
- **有竞争力的定价** - 慷慨的免费层级
|
||||||
|
|
||||||
|
**定价:** $20/月(Advanced)或包含在Workspace计划中
|
||||||
|
|
||||||
|
## 正面对比
|
||||||
|
|
||||||
|
| 特性 | ChatGPT | Claude | Gemini |
|
||||||
|
|------|---------|--------|--------|
|
||||||
|
| 上下文窗口 | 128K | 200K | 1M (Gemini 1.5) |
|
||||||
|
| 编程 | 优秀 | 很好 | 好 |
|
||||||
|
| 写作 | 很好 | 优秀 | 好 |
|
||||||
|
| 分析 | 很好 | 优秀 | 很好 |
|
||||||
|
| 集成 | 以API为中心 | 以API为中心 | Google原生 |
|
||||||
|
| 自定义训练 | 是(GPTs) | 有限 | 有限 |
|
||||||
|
|
||||||
|
## 使用场景推荐
|
||||||
|
|
||||||
|
### 客户支持
|
||||||
|
**推荐:ChatGPT** - 构建自定义支持机器人的最佳生态系统
|
||||||
|
|
||||||
|
### 内容营销
|
||||||
|
**推荐:Claude** - 卓越的长篇写作质量
|
||||||
|
|
||||||
|
### 数据分析
|
||||||
|
**推荐:Gemini** - 与Google Sheets无缝集成
|
||||||
|
|
||||||
|
### 软件开发
|
||||||
|
**推荐:ChatGPT** - 最全面的编码辅助
|
||||||
|
|
||||||
|
### 研究与分析
|
||||||
|
**推荐:Claude** - 处理长篇文档的最佳选择
|
||||||
|
|
||||||
|
## 实施建议
|
||||||
|
|
||||||
|
1. **从试点开始** - 在全公司推广前先用小团队测试
|
||||||
|
2. **明确使用场景** - 清楚您要解决什么问题
|
||||||
|
3. **培训团队** - 投资提示词工程教育
|
||||||
|
4. **监控使用情况** - 跟踪ROI并识别优化机会
|
||||||
|
5. **保持合规** - 确保AI使用符合数据隐私法规
|
||||||
|
|
||||||
|
## 结论
|
||||||
|
|
||||||
|
没有一款AI聊天机器人能适用于所有企业。您的选择应取决于具体需求、现有技术栈和预算。许多企业发现,针对不同任务使用多个AI助手更有价值。
|
||||||
|
|
||||||
|
在 Toolso.AI 浏览我们完整的AI聊天机器人和助手目录,找到最适合您组织的工具。
|
||||||
@@ -0,0 +1,213 @@
|
|||||||
|
import { BlogLayout } from "@/components/blog-layout";
|
||||||
|
|
||||||
|
export const blog = {
|
||||||
|
author: { name: "Toolso.AI", src: "/avatar.jpeg" },
|
||||||
|
date: "2024-12-10",
|
||||||
|
title: "AI Code Assistants: GitHub Copilot vs Cursor vs Codeium",
|
||||||
|
description:
|
||||||
|
"The ultimate guide to AI coding assistants. Compare features, pricing, and real-world performance to find the best AI pair programmer for your development workflow.",
|
||||||
|
image:
|
||||||
|
"https://images.unsplash.com/photo-1555066931-4365d14bab8c?w=800&auto=format&fit=crop&q=60",
|
||||||
|
};
|
||||||
|
export const metadata = {
|
||||||
|
title: blog.title,
|
||||||
|
description: blog.description,
|
||||||
|
openGraph: {
|
||||||
|
images: [blog.image],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default (props) => <BlogLayout blog={blog} {...props} />;
|
||||||
|
|
||||||
|
## The AI Coding Revolution
|
||||||
|
|
||||||
|
AI code assistants have evolved from simple autocomplete to intelligent pair programmers. In 2025, they're not just nice-to-have - they're essential tools for competitive developers.
|
||||||
|
|
||||||
|
## The Big Three Compared
|
||||||
|
|
||||||
|
### GitHub Copilot
|
||||||
|
|
||||||
|
**Developer:** GitHub (Microsoft)
|
||||||
|
|
||||||
|
**Model:** GPT-4 based (Copilot X)
|
||||||
|
|
||||||
|
**Key Features:**
|
||||||
|
- Inline code completion
|
||||||
|
- Chat interface for explanations
|
||||||
|
- Pull request summaries
|
||||||
|
- Documentation generation
|
||||||
|
- CLI assistance
|
||||||
|
|
||||||
|
**Supported IDEs:**
|
||||||
|
- VS Code (best support)
|
||||||
|
- JetBrains IDEs
|
||||||
|
- Neovim
|
||||||
|
- Visual Studio
|
||||||
|
|
||||||
|
**Strengths:**
|
||||||
|
- Largest training dataset (GitHub's entire codebase)
|
||||||
|
- Excellent context understanding
|
||||||
|
- Strong enterprise features
|
||||||
|
- Active development and updates
|
||||||
|
|
||||||
|
**Weaknesses:**
|
||||||
|
- Privacy concerns for enterprise
|
||||||
|
- Subscription required
|
||||||
|
- Can suggest copyrighted code
|
||||||
|
|
||||||
|
**Pricing:**
|
||||||
|
- Individual: $10/month
|
||||||
|
- Business: $19/user/month
|
||||||
|
- Enterprise: Custom pricing
|
||||||
|
|
||||||
|
### Cursor
|
||||||
|
|
||||||
|
**Developer:** Cursor Inc.
|
||||||
|
|
||||||
|
**Model:** GPT-4 + Claude
|
||||||
|
|
||||||
|
**Key Features:**
|
||||||
|
- Full IDE built around AI
|
||||||
|
- Codebase-aware chat
|
||||||
|
- AI-powered refactoring
|
||||||
|
- Multi-file editing
|
||||||
|
- Natural language code generation
|
||||||
|
|
||||||
|
**Supported IDEs:**
|
||||||
|
- Cursor (standalone, VS Code fork)
|
||||||
|
|
||||||
|
**Strengths:**
|
||||||
|
- Best codebase understanding
|
||||||
|
- Powerful chat capabilities
|
||||||
|
- Multi-file context
|
||||||
|
- Fast iteration on features
|
||||||
|
|
||||||
|
**Weaknesses:**
|
||||||
|
- Requires switching IDE
|
||||||
|
- Newer, less battle-tested
|
||||||
|
- Limited to their IDE
|
||||||
|
|
||||||
|
**Pricing:**
|
||||||
|
- Hobby: Free (limited)
|
||||||
|
- Pro: $20/month
|
||||||
|
- Business: $40/user/month
|
||||||
|
|
||||||
|
### Codeium
|
||||||
|
|
||||||
|
**Developer:** Exafunction
|
||||||
|
|
||||||
|
**Model:** Proprietary
|
||||||
|
|
||||||
|
**Key Features:**
|
||||||
|
- Unlimited autocomplete
|
||||||
|
- In-editor chat
|
||||||
|
- Search functionality
|
||||||
|
- Multi-language support
|
||||||
|
- Self-hosted option
|
||||||
|
|
||||||
|
**Supported IDEs:**
|
||||||
|
- VS Code
|
||||||
|
- JetBrains IDEs
|
||||||
|
- Neovim/Vim
|
||||||
|
- Emacs
|
||||||
|
- 40+ editors
|
||||||
|
|
||||||
|
**Strengths:**
|
||||||
|
- Generous free tier
|
||||||
|
- Wide IDE support
|
||||||
|
- Fast completions
|
||||||
|
- Privacy-focused options
|
||||||
|
|
||||||
|
**Weaknesses:**
|
||||||
|
- Less sophisticated than GPT-4 based tools
|
||||||
|
- Chat not as powerful
|
||||||
|
- Smaller community
|
||||||
|
|
||||||
|
**Pricing:**
|
||||||
|
- Individual: Free (unlimited)
|
||||||
|
- Teams: $12/user/month
|
||||||
|
- Enterprise: Custom
|
||||||
|
|
||||||
|
## Feature Comparison
|
||||||
|
|
||||||
|
| Feature | Copilot | Cursor | Codeium |
|
||||||
|
|---------|---------|--------|---------|
|
||||||
|
| Code Completion | ★★★★★ | ★★★★☆ | ★★★★☆ |
|
||||||
|
| Chat Quality | ★★★★☆ | ★★★★★ | ★★★☆☆ |
|
||||||
|
| Codebase Context | ★★★☆☆ | ★★★★★ | ★★★☆☆ |
|
||||||
|
| IDE Support | ★★★★☆ | ★★☆☆☆ | ★★★★★ |
|
||||||
|
| Free Tier | ★☆☆☆☆ | ★★☆☆☆ | ★★★★★ |
|
||||||
|
| Enterprise Ready | ★★★★★ | ★★★☆☆ | ★★★★☆ |
|
||||||
|
|
||||||
|
## Other Notable AI Coding Tools
|
||||||
|
|
||||||
|
### Amazon CodeWhisperer
|
||||||
|
- Free for individual use
|
||||||
|
- Best for AWS development
|
||||||
|
- Security scanning included
|
||||||
|
|
||||||
|
### Tabnine
|
||||||
|
- Privacy-focused
|
||||||
|
- On-premise option
|
||||||
|
- Team learning features
|
||||||
|
|
||||||
|
### Sourcegraph Cody
|
||||||
|
- Excellent code search
|
||||||
|
- Open source friendly
|
||||||
|
- Strong enterprise features
|
||||||
|
|
||||||
|
### Replit AI
|
||||||
|
- Browser-based development
|
||||||
|
- Great for learning
|
||||||
|
- Integrated deployment
|
||||||
|
|
||||||
|
## Choosing the Right Tool
|
||||||
|
|
||||||
|
### Choose Copilot If:
|
||||||
|
- You're in the Microsoft/GitHub ecosystem
|
||||||
|
- Enterprise compliance is critical
|
||||||
|
- You want the most mature solution
|
||||||
|
- VS Code is your primary IDE
|
||||||
|
|
||||||
|
### Choose Cursor If:
|
||||||
|
- You want the best AI-native experience
|
||||||
|
- Large codebase understanding is crucial
|
||||||
|
- You're open to switching IDEs
|
||||||
|
- You value cutting-edge features
|
||||||
|
|
||||||
|
### Choose Codeium If:
|
||||||
|
- Budget is a primary concern
|
||||||
|
- You use multiple IDEs
|
||||||
|
- Privacy is paramount
|
||||||
|
- You want unlimited free usage
|
||||||
|
|
||||||
|
## Productivity Impact
|
||||||
|
|
||||||
|
Real-world studies show:
|
||||||
|
|
||||||
|
- **55% faster** task completion (GitHub research)
|
||||||
|
- **40% less** context switching
|
||||||
|
- **30% fewer** bugs in initial code
|
||||||
|
- **2x faster** onboarding to new codebases
|
||||||
|
|
||||||
|
## Best Practices
|
||||||
|
|
||||||
|
1. **Learn the shortcuts** - Speed comes from muscle memory
|
||||||
|
2. **Review all suggestions** - AI makes mistakes
|
||||||
|
3. **Write good comments** - Better prompts = better code
|
||||||
|
4. **Use chat for explanations** - Faster than documentation
|
||||||
|
5. **Iterate with AI** - Let it refine your code
|
||||||
|
|
||||||
|
## Privacy Considerations
|
||||||
|
|
||||||
|
| Tool | Code Retention | Training on Your Code | Self-Hosted |
|
||||||
|
|------|----------------|----------------------|-------------|
|
||||||
|
| Copilot | Optional | Business: No | No |
|
||||||
|
| Cursor | Minimal | No | No |
|
||||||
|
| Codeium | None | No | Yes |
|
||||||
|
|
||||||
|
## Conclusion
|
||||||
|
|
||||||
|
AI code assistants are the biggest productivity boost for developers since the IDE. Whether you choose Copilot's maturity, Cursor's innovation, or Codeium's accessibility, you'll write better code faster.
|
||||||
|
|
||||||
|
Explore our complete collection of AI development tools on Toolso.AI to find the perfect coding companion.
|
||||||
@@ -0,0 +1,213 @@
|
|||||||
|
import { BlogLayout } from "@/components/blog-layout";
|
||||||
|
|
||||||
|
export const blog = {
|
||||||
|
author: { name: "Toolso.AI", src: "/avatar.jpeg" },
|
||||||
|
date: "2024-12-10",
|
||||||
|
title: "AI编程助手对比:GitHub Copilot vs Cursor vs Codeium",
|
||||||
|
description:
|
||||||
|
"AI编程助手终极指南。比较功能、价格和实际表现,找到最适合你开发工作流的AI结对编程伙伴。",
|
||||||
|
image:
|
||||||
|
"https://images.unsplash.com/photo-1555066931-4365d14bab8c?w=800&auto=format&fit=crop&q=60",
|
||||||
|
};
|
||||||
|
export const metadata = {
|
||||||
|
title: blog.title,
|
||||||
|
description: blog.description,
|
||||||
|
openGraph: {
|
||||||
|
images: [blog.image],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default (props) => <BlogLayout blog={blog} {...props} />;
|
||||||
|
|
||||||
|
## AI编程革命
|
||||||
|
|
||||||
|
AI编程助手已从简单的自动补全演变为智能结对编程伙伴。在2025年,它们不再是可有可无——而是有竞争力的开发者必备的工具。
|
||||||
|
|
||||||
|
## 三大工具对比
|
||||||
|
|
||||||
|
### GitHub Copilot
|
||||||
|
|
||||||
|
**开发者:** GitHub (Microsoft)
|
||||||
|
|
||||||
|
**模型:** 基于GPT-4 (Copilot X)
|
||||||
|
|
||||||
|
**主要功能:**
|
||||||
|
- 行内代码补全
|
||||||
|
- 聊天界面解释代码
|
||||||
|
- Pull Request摘要
|
||||||
|
- 文档生成
|
||||||
|
- CLI辅助
|
||||||
|
|
||||||
|
**支持的IDE:**
|
||||||
|
- VS Code(最佳支持)
|
||||||
|
- JetBrains IDE系列
|
||||||
|
- Neovim
|
||||||
|
- Visual Studio
|
||||||
|
|
||||||
|
**优势:**
|
||||||
|
- 最大的训练数据集(GitHub全部代码库)
|
||||||
|
- 出色的上下文理解
|
||||||
|
- 强大的企业功能
|
||||||
|
- 积极的开发和更新
|
||||||
|
|
||||||
|
**劣势:**
|
||||||
|
- 企业隐私顾虑
|
||||||
|
- 需要订阅
|
||||||
|
- 可能建议受版权保护的代码
|
||||||
|
|
||||||
|
**价格:**
|
||||||
|
- 个人版:$10/月
|
||||||
|
- 商业版:$19/用户/月
|
||||||
|
- 企业版:定制价格
|
||||||
|
|
||||||
|
### Cursor
|
||||||
|
|
||||||
|
**开发者:** Cursor Inc.
|
||||||
|
|
||||||
|
**模型:** GPT-4 + Claude
|
||||||
|
|
||||||
|
**主要功能:**
|
||||||
|
- 围绕AI构建的完整IDE
|
||||||
|
- 代码库感知聊天
|
||||||
|
- AI驱动的重构
|
||||||
|
- 多文件编辑
|
||||||
|
- 自然语言生成代码
|
||||||
|
|
||||||
|
**支持的IDE:**
|
||||||
|
- Cursor(独立版,VS Code分支)
|
||||||
|
|
||||||
|
**优势:**
|
||||||
|
- 最佳代码库理解
|
||||||
|
- 强大的聊天能力
|
||||||
|
- 多文件上下文
|
||||||
|
- 功能快速迭代
|
||||||
|
|
||||||
|
**劣势:**
|
||||||
|
- 需要切换IDE
|
||||||
|
- 较新,经验较少
|
||||||
|
- 仅限于其IDE
|
||||||
|
|
||||||
|
**价格:**
|
||||||
|
- 爱好者版:免费(有限制)
|
||||||
|
- Pro版:$20/月
|
||||||
|
- 商业版:$40/用户/月
|
||||||
|
|
||||||
|
### Codeium
|
||||||
|
|
||||||
|
**开发者:** Exafunction
|
||||||
|
|
||||||
|
**模型:** 专有模型
|
||||||
|
|
||||||
|
**主要功能:**
|
||||||
|
- 无限自动补全
|
||||||
|
- 编辑器内聊天
|
||||||
|
- 搜索功能
|
||||||
|
- 多语言支持
|
||||||
|
- 自托管选项
|
||||||
|
|
||||||
|
**支持的IDE:**
|
||||||
|
- VS Code
|
||||||
|
- JetBrains IDE系列
|
||||||
|
- Neovim/Vim
|
||||||
|
- Emacs
|
||||||
|
- 40+编辑器
|
||||||
|
|
||||||
|
**优势:**
|
||||||
|
- 慷慨的免费层级
|
||||||
|
- 广泛的IDE支持
|
||||||
|
- 快速补全
|
||||||
|
- 注重隐私的选项
|
||||||
|
|
||||||
|
**劣势:**
|
||||||
|
- 不如基于GPT-4的工具复杂
|
||||||
|
- 聊天功能不够强大
|
||||||
|
- 社区较小
|
||||||
|
|
||||||
|
**价格:**
|
||||||
|
- 个人版:免费(无限制)
|
||||||
|
- 团队版:$12/用户/月
|
||||||
|
- 企业版:定制
|
||||||
|
|
||||||
|
## 功能对比
|
||||||
|
|
||||||
|
| 功能 | Copilot | Cursor | Codeium |
|
||||||
|
|------|---------|--------|---------|
|
||||||
|
| 代码补全 | ★★★★★ | ★★★★☆ | ★★★★☆ |
|
||||||
|
| 聊天质量 | ★★★★☆ | ★★★★★ | ★★★☆☆ |
|
||||||
|
| 代码库上下文 | ★★★☆☆ | ★★★★★ | ★★★☆☆ |
|
||||||
|
| IDE支持 | ★★★★☆ | ★★☆☆☆ | ★★★★★ |
|
||||||
|
| 免费层级 | ★☆☆☆☆ | ★★☆☆☆ | ★★★★★ |
|
||||||
|
| 企业就绪 | ★★★★★ | ★★★☆☆ | ★★★★☆ |
|
||||||
|
|
||||||
|
## 其他值得关注的AI编程工具
|
||||||
|
|
||||||
|
### Amazon CodeWhisperer
|
||||||
|
- 个人使用免费
|
||||||
|
- 最适合AWS开发
|
||||||
|
- 包含安全扫描
|
||||||
|
|
||||||
|
### Tabnine
|
||||||
|
- 注重隐私
|
||||||
|
- 本地部署选项
|
||||||
|
- 团队学习功能
|
||||||
|
|
||||||
|
### Sourcegraph Cody
|
||||||
|
- 出色的代码搜索
|
||||||
|
- 开源友好
|
||||||
|
- 强大的企业功能
|
||||||
|
|
||||||
|
### Replit AI
|
||||||
|
- 基于浏览器的开发
|
||||||
|
- 适合学习
|
||||||
|
- 集成部署
|
||||||
|
|
||||||
|
## 选择正确的工具
|
||||||
|
|
||||||
|
### 选择Copilot如果:
|
||||||
|
- 你在Microsoft/GitHub生态系统中
|
||||||
|
- 企业合规至关重要
|
||||||
|
- 你想要最成熟的解决方案
|
||||||
|
- VS Code是你的主要IDE
|
||||||
|
|
||||||
|
### 选择Cursor如果:
|
||||||
|
- 你想要最佳AI原生体验
|
||||||
|
- 大型代码库理解至关重要
|
||||||
|
- 你愿意切换IDE
|
||||||
|
- 你重视前沿功能
|
||||||
|
|
||||||
|
### 选择Codeium如果:
|
||||||
|
- 预算是主要考虑因素
|
||||||
|
- 你使用多个IDE
|
||||||
|
- 隐私至上
|
||||||
|
- 你想要无限免费使用
|
||||||
|
|
||||||
|
## 生产力影响
|
||||||
|
|
||||||
|
实际研究表明:
|
||||||
|
|
||||||
|
- **55%更快**完成任务(GitHub研究)
|
||||||
|
- **40%更少**上下文切换
|
||||||
|
- **30%更少**初始代码bug
|
||||||
|
- **2倍更快**熟悉新代码库
|
||||||
|
|
||||||
|
## 最佳实践
|
||||||
|
|
||||||
|
1. **学习快捷键** - 速度来自肌肉记忆
|
||||||
|
2. **审查所有建议** - AI会犯错
|
||||||
|
3. **写好注释** - 更好的提示 = 更好的代码
|
||||||
|
4. **用聊天获取解释** - 比查文档更快
|
||||||
|
5. **与AI迭代** - 让它优化你的代码
|
||||||
|
|
||||||
|
## 隐私考虑
|
||||||
|
|
||||||
|
| 工具 | 代码保留 | 用你的代码训练 | 自托管 |
|
||||||
|
|------|----------|----------------|--------|
|
||||||
|
| Copilot | 可选 | 商业版:否 | 否 |
|
||||||
|
| Cursor | 最小化 | 否 | 否 |
|
||||||
|
| Codeium | 无 | 否 | 是 |
|
||||||
|
|
||||||
|
## 结论
|
||||||
|
|
||||||
|
AI编程助手是自IDE以来开发者最大的生产力提升。无论你选择Copilot的成熟度、Cursor的创新性还是Codeium的可及性,你都会更快地写出更好的代码。
|
||||||
|
|
||||||
|
在 Toolso.AI 探索我们完整的AI开发工具集合,找到完美的编程伙伴。
|
||||||
@@ -0,0 +1,83 @@
|
|||||||
|
import { BlogLayout } from "@/components/blog-layout";
|
||||||
|
|
||||||
|
export const blog = {
|
||||||
|
author: { name: "Toolso.AI", src: "/avatar.jpeg" },
|
||||||
|
date: "2025-01-02",
|
||||||
|
title: "AI Image Generators Compared: Midjourney vs DALL-E vs Stable Diffusion",
|
||||||
|
description:
|
||||||
|
"A comprehensive comparison of the top AI image generators. Learn the strengths and weaknesses of each platform to choose the best one for your creative needs.",
|
||||||
|
image:
|
||||||
|
"https://images.unsplash.com/photo-1547954575-855750c57bd3?w=800&auto=format&fit=crop&q=60",
|
||||||
|
};
|
||||||
|
export const metadata = {
|
||||||
|
title: blog.title,
|
||||||
|
description: blog.description,
|
||||||
|
openGraph: {
|
||||||
|
images: [blog.image],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default (props) => <BlogLayout blog={blog} {...props} />;
|
||||||
|
|
||||||
|
## The Rise of AI Image Generation
|
||||||
|
|
||||||
|
AI image generation has evolved from a novelty to an essential creative tool. In this guide, we compare the three leading platforms to help you make an informed choice.
|
||||||
|
|
||||||
|
## Midjourney
|
||||||
|
|
||||||
|
### Strengths
|
||||||
|
- **Artistic quality** - Produces stunning, artistic images with minimal effort
|
||||||
|
- **Community** - Active Discord community for inspiration
|
||||||
|
- **Consistency** - Reliable style and quality across generations
|
||||||
|
|
||||||
|
### Weaknesses
|
||||||
|
- Discord-only interface (web app in beta)
|
||||||
|
- Limited fine control over outputs
|
||||||
|
- Subscription required for all usage
|
||||||
|
|
||||||
|
### Best For
|
||||||
|
Artists, designers, and anyone seeking high-quality artistic imagery.
|
||||||
|
|
||||||
|
## DALL-E 3
|
||||||
|
|
||||||
|
### Strengths
|
||||||
|
- **Prompt understanding** - Best-in-class text comprehension
|
||||||
|
- **Integration** - Built into ChatGPT and Microsoft products
|
||||||
|
- **Text rendering** - Excellent at including text in images
|
||||||
|
|
||||||
|
### Weaknesses
|
||||||
|
- More conservative content policies
|
||||||
|
- Less stylistic flexibility
|
||||||
|
- Credit-based pricing
|
||||||
|
|
||||||
|
### Best For
|
||||||
|
Marketers, content creators, and those needing precise prompt adherence.
|
||||||
|
|
||||||
|
## Stable Diffusion
|
||||||
|
|
||||||
|
### Strengths
|
||||||
|
- **Open source** - Run locally, modify freely
|
||||||
|
- **Customization** - Extensive model fine-tuning options
|
||||||
|
- **Cost** - Free to run on your own hardware
|
||||||
|
|
||||||
|
### Weaknesses
|
||||||
|
- Steeper learning curve
|
||||||
|
- Requires technical setup for best results
|
||||||
|
- Quality depends on model and settings
|
||||||
|
|
||||||
|
### Best For
|
||||||
|
Developers, researchers, and power users who want full control.
|
||||||
|
|
||||||
|
## Comparison Table
|
||||||
|
|
||||||
|
| Feature | Midjourney | DALL-E 3 | Stable Diffusion |
|
||||||
|
|---------|------------|----------|------------------|
|
||||||
|
| Image Quality | Excellent | Very Good | Good to Excellent |
|
||||||
|
| Ease of Use | Good | Excellent | Moderate |
|
||||||
|
| Price | $10-60/mo | Pay per use | Free (self-hosted) |
|
||||||
|
| Customization | Limited | Limited | Extensive |
|
||||||
|
| Local Running | No | No | Yes |
|
||||||
|
|
||||||
|
## Conclusion
|
||||||
|
|
||||||
|
Each platform has its place in the AI image generation ecosystem. Consider your specific needs, technical comfort level, and budget when making your choice. Explore more AI image tools on Toolso.AI.
|
||||||
@@ -0,0 +1,83 @@
|
|||||||
|
import { BlogLayout } from "@/components/blog-layout";
|
||||||
|
|
||||||
|
export const blog = {
|
||||||
|
author: { name: "Toolso.AI", src: "/avatar.jpeg" },
|
||||||
|
date: "2025-01-02",
|
||||||
|
title: "AI图像生成器对比:Midjourney vs DALL-E vs Stable Diffusion",
|
||||||
|
description:
|
||||||
|
"全面对比顶级AI图像生成器。了解每个平台的优缺点,选择最适合您创意需求的工具。",
|
||||||
|
image:
|
||||||
|
"https://images.unsplash.com/photo-1547954575-855750c57bd3?w=800&auto=format&fit=crop&q=60",
|
||||||
|
};
|
||||||
|
export const metadata = {
|
||||||
|
title: blog.title,
|
||||||
|
description: blog.description,
|
||||||
|
openGraph: {
|
||||||
|
images: [blog.image],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default (props) => <BlogLayout blog={blog} {...props} />;
|
||||||
|
|
||||||
|
## AI图像生成的崛起
|
||||||
|
|
||||||
|
AI图像生成已经从新奇事物演变为必不可少的创意工具。在本指南中,我们对比三大领先平台,帮助您做出明智的选择。
|
||||||
|
|
||||||
|
## Midjourney
|
||||||
|
|
||||||
|
### 优势
|
||||||
|
- **艺术质量** - 以最小的努力产出令人惊叹的艺术图像
|
||||||
|
- **社区** - 活跃的Discord社区提供灵感
|
||||||
|
- **一致性** - 跨代生成的风格和质量可靠
|
||||||
|
|
||||||
|
### 劣势
|
||||||
|
- 仅支持Discord界面(网页应用处于测试阶段)
|
||||||
|
- 对输出的精细控制有限
|
||||||
|
- 所有使用都需要订阅
|
||||||
|
|
||||||
|
### 最适合
|
||||||
|
艺术家、设计师,以及任何追求高质量艺术图像的人。
|
||||||
|
|
||||||
|
## DALL-E 3
|
||||||
|
|
||||||
|
### 优势
|
||||||
|
- **提示词理解** - 业界最佳的文本理解能力
|
||||||
|
- **集成** - 内置于ChatGPT和Microsoft产品中
|
||||||
|
- **文字渲染** - 在图像中包含文字的效果极佳
|
||||||
|
|
||||||
|
### 劣势
|
||||||
|
- 内容政策更为保守
|
||||||
|
- 风格灵活性较低
|
||||||
|
- 基于积分的定价
|
||||||
|
|
||||||
|
### 最适合
|
||||||
|
营销人员、内容创作者,以及需要精确遵循提示词的用户。
|
||||||
|
|
||||||
|
## Stable Diffusion
|
||||||
|
|
||||||
|
### 优势
|
||||||
|
- **开源** - 可本地运行,自由修改
|
||||||
|
- **定制化** - 广泛的模型微调选项
|
||||||
|
- **成本** - 在自己的硬件上免费运行
|
||||||
|
|
||||||
|
### 劣势
|
||||||
|
- 学习曲线较陡
|
||||||
|
- 需要技术设置才能获得最佳结果
|
||||||
|
- 质量取决于模型和设置
|
||||||
|
|
||||||
|
### 最适合
|
||||||
|
开发者、研究人员,以及想要完全控制的高级用户。
|
||||||
|
|
||||||
|
## 对比表格
|
||||||
|
|
||||||
|
| 特性 | Midjourney | DALL-E 3 | Stable Diffusion |
|
||||||
|
|------|------------|----------|------------------|
|
||||||
|
| 图像质量 | 优秀 | 很好 | 好到优秀 |
|
||||||
|
| 易用性 | 好 | 优秀 | 中等 |
|
||||||
|
| 价格 | $10-60/月 | 按使用付费 | 免费(自托管) |
|
||||||
|
| 定制化 | 有限 | 有限 | 广泛 |
|
||||||
|
| 本地运行 | 否 | 否 | 是 |
|
||||||
|
|
||||||
|
## 结论
|
||||||
|
|
||||||
|
每个平台在AI图像生成生态系统中都有其位置。在做出选择时,请考虑您的具体需求、技术熟练程度和预算。在 Toolso.AI 探索更多AI图像工具。
|
||||||
@@ -0,0 +1,208 @@
|
|||||||
|
import { BlogLayout } from "@/components/blog-layout";
|
||||||
|
|
||||||
|
export const blog = {
|
||||||
|
author: { name: "Toolso.AI", src: "/avatar.jpeg" },
|
||||||
|
date: "2024-12-15",
|
||||||
|
title: "10 AI Productivity Tools That Will Transform Your Workflow in 2025",
|
||||||
|
description:
|
||||||
|
"Supercharge your productivity with these game-changing AI tools. From email management to meeting summaries, discover how AI can save you hours every week.",
|
||||||
|
image:
|
||||||
|
"https://images.unsplash.com/photo-1484480974693-6ca0a78fb36b?w=800&auto=format&fit=crop&q=60",
|
||||||
|
};
|
||||||
|
export const metadata = {
|
||||||
|
title: blog.title,
|
||||||
|
description: blog.description,
|
||||||
|
openGraph: {
|
||||||
|
images: [blog.image],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default (props) => <BlogLayout blog={blog} {...props} />;
|
||||||
|
|
||||||
|
## AI: Your New Productivity Partner
|
||||||
|
|
||||||
|
The average knowledge worker spends 60% of their time on "work about work" - emails, meetings, searching for information. AI tools are changing this dramatically, automating mundane tasks and freeing up time for meaningful work.
|
||||||
|
|
||||||
|
## Top 10 AI Productivity Tools
|
||||||
|
|
||||||
|
### 1. Notion AI
|
||||||
|
|
||||||
|
**What it does:** Integrated AI assistant within your workspace
|
||||||
|
|
||||||
|
**Key features:**
|
||||||
|
- Summarize meeting notes instantly
|
||||||
|
- Generate action items from documents
|
||||||
|
- Write and edit content in context
|
||||||
|
- Q&A over your entire workspace
|
||||||
|
|
||||||
|
**Time saved:** 3-5 hours/week
|
||||||
|
|
||||||
|
**Price:** Included with Notion plans ($10/month add-on for free users)
|
||||||
|
|
||||||
|
### 2. Otter.ai
|
||||||
|
|
||||||
|
**What it does:** AI meeting transcription and summarization
|
||||||
|
|
||||||
|
**Key features:**
|
||||||
|
- Real-time transcription
|
||||||
|
- Automatic meeting summaries
|
||||||
|
- Action item extraction
|
||||||
|
- Speaker identification
|
||||||
|
|
||||||
|
**Time saved:** 2-4 hours/week
|
||||||
|
|
||||||
|
**Price:** Free tier available, Pro at $16.99/month
|
||||||
|
|
||||||
|
### 3. Superhuman
|
||||||
|
|
||||||
|
**What it does:** AI-powered email client
|
||||||
|
|
||||||
|
**Key features:**
|
||||||
|
- AI email writing assistance
|
||||||
|
- Instant reply suggestions
|
||||||
|
- Email prioritization
|
||||||
|
- Scheduled send optimization
|
||||||
|
|
||||||
|
**Time saved:** 4-6 hours/week
|
||||||
|
|
||||||
|
**Price:** $30/month
|
||||||
|
|
||||||
|
### 4. Reclaim.ai
|
||||||
|
|
||||||
|
**What it does:** Intelligent calendar management
|
||||||
|
|
||||||
|
**Key features:**
|
||||||
|
- Auto-schedule tasks and habits
|
||||||
|
- Smart meeting scheduling
|
||||||
|
- Focus time protection
|
||||||
|
- Team coordination
|
||||||
|
|
||||||
|
**Time saved:** 2-3 hours/week
|
||||||
|
|
||||||
|
**Price:** Free tier available, Pro at $8/month
|
||||||
|
|
||||||
|
### 5. Mem
|
||||||
|
|
||||||
|
**What it does:** AI-powered note-taking and knowledge management
|
||||||
|
|
||||||
|
**Key features:**
|
||||||
|
- Auto-organizes notes
|
||||||
|
- Smart search across all content
|
||||||
|
- AI-generated connections
|
||||||
|
- Meeting integration
|
||||||
|
|
||||||
|
**Time saved:** 2-4 hours/week
|
||||||
|
|
||||||
|
**Price:** Free tier available, Pro at $15/month
|
||||||
|
|
||||||
|
### 6. Jasper
|
||||||
|
|
||||||
|
**What it does:** AI content creation platform
|
||||||
|
|
||||||
|
**Key features:**
|
||||||
|
- Brand voice training
|
||||||
|
- Multiple content templates
|
||||||
|
- Team collaboration
|
||||||
|
- Content repurposing
|
||||||
|
|
||||||
|
**Time saved:** 5-10 hours/week (for content creators)
|
||||||
|
|
||||||
|
**Price:** Starting at $49/month
|
||||||
|
|
||||||
|
### 7. Fireflies.ai
|
||||||
|
|
||||||
|
**What it does:** Meeting recording and intelligence
|
||||||
|
|
||||||
|
**Key features:**
|
||||||
|
- Auto-join meetings
|
||||||
|
- Searchable transcripts
|
||||||
|
- Topic tracking
|
||||||
|
- CRM integration
|
||||||
|
|
||||||
|
**Time saved:** 3-5 hours/week
|
||||||
|
|
||||||
|
**Price:** Free tier available, Pro at $18/month
|
||||||
|
|
||||||
|
### 8. Motion
|
||||||
|
|
||||||
|
**What it does:** AI project and task management
|
||||||
|
|
||||||
|
**Key features:**
|
||||||
|
- Intelligent task scheduling
|
||||||
|
- Project timeline optimization
|
||||||
|
- Meeting scheduling
|
||||||
|
- Deadline management
|
||||||
|
|
||||||
|
**Time saved:** 3-5 hours/week
|
||||||
|
|
||||||
|
**Price:** $19/month
|
||||||
|
|
||||||
|
### 9. Grammarly
|
||||||
|
|
||||||
|
**What it does:** AI writing assistant
|
||||||
|
|
||||||
|
**Key features:**
|
||||||
|
- Grammar and spelling check
|
||||||
|
- Tone detection
|
||||||
|
- Clarity suggestions
|
||||||
|
- Plagiarism detection
|
||||||
|
|
||||||
|
**Time saved:** 1-2 hours/week
|
||||||
|
|
||||||
|
**Price:** Free tier available, Premium at $12/month
|
||||||
|
|
||||||
|
### 10. Bardeen
|
||||||
|
|
||||||
|
**What it does:** AI workflow automation
|
||||||
|
|
||||||
|
**Key features:**
|
||||||
|
- No-code automations
|
||||||
|
- Cross-app integrations
|
||||||
|
- AI-powered suggestions
|
||||||
|
- Browser automation
|
||||||
|
|
||||||
|
**Time saved:** 2-5 hours/week
|
||||||
|
|
||||||
|
**Price:** Free tier available, Pro at $10/month
|
||||||
|
|
||||||
|
## Building Your AI Productivity Stack
|
||||||
|
|
||||||
|
### For Individual Contributors
|
||||||
|
1. **Communication:** Superhuman + Otter.ai
|
||||||
|
2. **Organization:** Notion AI + Reclaim.ai
|
||||||
|
3. **Writing:** Grammarly
|
||||||
|
|
||||||
|
### For Managers
|
||||||
|
1. **Meetings:** Otter.ai + Fireflies.ai
|
||||||
|
2. **Scheduling:** Reclaim.ai + Motion
|
||||||
|
3. **Team docs:** Notion AI
|
||||||
|
|
||||||
|
### For Content Creators
|
||||||
|
1. **Writing:** Jasper + Grammarly
|
||||||
|
2. **Research:** Mem
|
||||||
|
3. **Automation:** Bardeen
|
||||||
|
|
||||||
|
## Implementation Tips
|
||||||
|
|
||||||
|
1. **Start with one tool** - Master it before adding more
|
||||||
|
2. **Measure your time** - Track actual time saved
|
||||||
|
3. **Integrate gradually** - Build habits around each tool
|
||||||
|
4. **Review monthly** - Drop tools that don't deliver value
|
||||||
|
5. **Train your team** - Shared tools need shared knowledge
|
||||||
|
|
||||||
|
## The ROI of AI Productivity Tools
|
||||||
|
|
||||||
|
| Tool Category | Avg. Cost/Month | Hours Saved/Week | Value Created |
|
||||||
|
|---------------|-----------------|------------------|---------------|
|
||||||
|
| Email | $30 | 5 | $325/week |
|
||||||
|
| Meetings | $20 | 4 | $260/week |
|
||||||
|
| Writing | $15 | 3 | $195/week |
|
||||||
|
| Scheduling | $15 | 3 | $195/week |
|
||||||
|
|
||||||
|
*Based on $65/hour knowledge worker rate*
|
||||||
|
|
||||||
|
## Conclusion
|
||||||
|
|
||||||
|
AI productivity tools are no longer optional for competitive professionals. The right stack can save 10-20 hours per week - that's an extra month of productive time per year.
|
||||||
|
|
||||||
|
Explore our complete collection of AI productivity tools on Toolso.AI and start reclaiming your time today.
|
||||||
@@ -0,0 +1,208 @@
|
|||||||
|
import { BlogLayout } from "@/components/blog-layout";
|
||||||
|
|
||||||
|
export const blog = {
|
||||||
|
author: { name: "Toolso.AI", src: "/avatar.jpeg" },
|
||||||
|
date: "2024-12-15",
|
||||||
|
title: "2025年将改变你工作流程的10款AI生产力工具",
|
||||||
|
description:
|
||||||
|
"用这些改变游戏规则的AI工具提升你的生产力。从邮件管理到会议总结,发现AI如何每周为你节省数小时。",
|
||||||
|
image:
|
||||||
|
"https://images.unsplash.com/photo-1484480974693-6ca0a78fb36b?w=800&auto=format&fit=crop&q=60",
|
||||||
|
};
|
||||||
|
export const metadata = {
|
||||||
|
title: blog.title,
|
||||||
|
description: blog.description,
|
||||||
|
openGraph: {
|
||||||
|
images: [blog.image],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default (props) => <BlogLayout blog={blog} {...props} />;
|
||||||
|
|
||||||
|
## AI:你的新生产力伙伴
|
||||||
|
|
||||||
|
普通知识工作者将60%的时间花在"关于工作的工作"上——邮件、会议、搜索信息。AI工具正在彻底改变这一状况,自动化处理琐碎任务,释放时间用于有意义的工作。
|
||||||
|
|
||||||
|
## 十大AI生产力工具
|
||||||
|
|
||||||
|
### 1. Notion AI
|
||||||
|
|
||||||
|
**功能:** 工作空间内的集成AI助手
|
||||||
|
|
||||||
|
**主要特性:**
|
||||||
|
- 即时总结会议记录
|
||||||
|
- 从文档生成行动项
|
||||||
|
- 在上下文中写作和编辑
|
||||||
|
- 对整个工作空间进行问答
|
||||||
|
|
||||||
|
**节省时间:** 每周3-5小时
|
||||||
|
|
||||||
|
**价格:** 包含在Notion计划中(免费用户$10/月附加)
|
||||||
|
|
||||||
|
### 2. Otter.ai
|
||||||
|
|
||||||
|
**功能:** AI会议转录和总结
|
||||||
|
|
||||||
|
**主要特性:**
|
||||||
|
- 实时转录
|
||||||
|
- 自动会议摘要
|
||||||
|
- 行动项提取
|
||||||
|
- 说话人识别
|
||||||
|
|
||||||
|
**节省时间:** 每周2-4小时
|
||||||
|
|
||||||
|
**价格:** 提供免费层级,Pro版$16.99/月
|
||||||
|
|
||||||
|
### 3. Superhuman
|
||||||
|
|
||||||
|
**功能:** AI驱动的邮件客户端
|
||||||
|
|
||||||
|
**主要特性:**
|
||||||
|
- AI邮件写作辅助
|
||||||
|
- 即时回复建议
|
||||||
|
- 邮件优先级排序
|
||||||
|
- 定时发送优化
|
||||||
|
|
||||||
|
**节省时间:** 每周4-6小时
|
||||||
|
|
||||||
|
**价格:** $30/月
|
||||||
|
|
||||||
|
### 4. Reclaim.ai
|
||||||
|
|
||||||
|
**功能:** 智能日历管理
|
||||||
|
|
||||||
|
**主要特性:**
|
||||||
|
- 自动安排任务和习惯
|
||||||
|
- 智能会议调度
|
||||||
|
- 专注时间保护
|
||||||
|
- 团队协调
|
||||||
|
|
||||||
|
**节省时间:** 每周2-3小时
|
||||||
|
|
||||||
|
**价格:** 提供免费层级,Pro版$8/月
|
||||||
|
|
||||||
|
### 5. Mem
|
||||||
|
|
||||||
|
**功能:** AI驱动的笔记和知识管理
|
||||||
|
|
||||||
|
**主要特性:**
|
||||||
|
- 自动组织笔记
|
||||||
|
- 跨所有内容的智能搜索
|
||||||
|
- AI生成的关联
|
||||||
|
- 会议集成
|
||||||
|
|
||||||
|
**节省时间:** 每周2-4小时
|
||||||
|
|
||||||
|
**价格:** 提供免费层级,Pro版$15/月
|
||||||
|
|
||||||
|
### 6. Jasper
|
||||||
|
|
||||||
|
**功能:** AI内容创作平台
|
||||||
|
|
||||||
|
**主要特性:**
|
||||||
|
- 品牌语调训练
|
||||||
|
- 多种内容模板
|
||||||
|
- 团队协作
|
||||||
|
- 内容再利用
|
||||||
|
|
||||||
|
**节省时间:** 每周5-10小时(内容创作者)
|
||||||
|
|
||||||
|
**价格:** 起价$49/月
|
||||||
|
|
||||||
|
### 7. Fireflies.ai
|
||||||
|
|
||||||
|
**功能:** 会议录制和智能分析
|
||||||
|
|
||||||
|
**主要特性:**
|
||||||
|
- 自动加入会议
|
||||||
|
- 可搜索的转录文本
|
||||||
|
- 主题追踪
|
||||||
|
- CRM集成
|
||||||
|
|
||||||
|
**节省时间:** 每周3-5小时
|
||||||
|
|
||||||
|
**价格:** 提供免费层级,Pro版$18/月
|
||||||
|
|
||||||
|
### 8. Motion
|
||||||
|
|
||||||
|
**功能:** AI项目和任务管理
|
||||||
|
|
||||||
|
**主要特性:**
|
||||||
|
- 智能任务调度
|
||||||
|
- 项目时间线优化
|
||||||
|
- 会议安排
|
||||||
|
- 截止日期管理
|
||||||
|
|
||||||
|
**节省时间:** 每周3-5小时
|
||||||
|
|
||||||
|
**价格:** $19/月
|
||||||
|
|
||||||
|
### 9. Grammarly
|
||||||
|
|
||||||
|
**功能:** AI写作助手
|
||||||
|
|
||||||
|
**主要特性:**
|
||||||
|
- 语法和拼写检查
|
||||||
|
- 语气检测
|
||||||
|
- 清晰度建议
|
||||||
|
- 抄袭检测
|
||||||
|
|
||||||
|
**节省时间:** 每周1-2小时
|
||||||
|
|
||||||
|
**价格:** 提供免费层级,Premium版$12/月
|
||||||
|
|
||||||
|
### 10. Bardeen
|
||||||
|
|
||||||
|
**功能:** AI工作流自动化
|
||||||
|
|
||||||
|
**主要特性:**
|
||||||
|
- 无代码自动化
|
||||||
|
- 跨应用集成
|
||||||
|
- AI驱动的建议
|
||||||
|
- 浏览器自动化
|
||||||
|
|
||||||
|
**节省时间:** 每周2-5小时
|
||||||
|
|
||||||
|
**价格:** 提供免费层级,Pro版$10/月
|
||||||
|
|
||||||
|
## 构建你的AI生产力工具栈
|
||||||
|
|
||||||
|
### 个人贡献者
|
||||||
|
1. **沟通:** Superhuman + Otter.ai
|
||||||
|
2. **组织:** Notion AI + Reclaim.ai
|
||||||
|
3. **写作:** Grammarly
|
||||||
|
|
||||||
|
### 管理者
|
||||||
|
1. **会议:** Otter.ai + Fireflies.ai
|
||||||
|
2. **日程:** Reclaim.ai + Motion
|
||||||
|
3. **团队文档:** Notion AI
|
||||||
|
|
||||||
|
### 内容创作者
|
||||||
|
1. **写作:** Jasper + Grammarly
|
||||||
|
2. **研究:** Mem
|
||||||
|
3. **自动化:** Bardeen
|
||||||
|
|
||||||
|
## 实施建议
|
||||||
|
|
||||||
|
1. **从一个工具开始** - 掌握后再添加更多
|
||||||
|
2. **衡量你的时间** - 跟踪实际节省的时间
|
||||||
|
3. **逐步整合** - 围绕每个工具建立习惯
|
||||||
|
4. **每月回顾** - 放弃不能带来价值的工具
|
||||||
|
5. **培训团队** - 共享工具需要共享知识
|
||||||
|
|
||||||
|
## AI生产力工具的投资回报
|
||||||
|
|
||||||
|
| 工具类别 | 平均月费 | 每周节省时间 | 创造价值 |
|
||||||
|
|----------|----------|--------------|----------|
|
||||||
|
| 邮件 | $30 | 5小时 | $325/周 |
|
||||||
|
| 会议 | $20 | 4小时 | $260/周 |
|
||||||
|
| 写作 | $15 | 3小时 | $195/周 |
|
||||||
|
| 日程 | $15 | 3小时 | $195/周 |
|
||||||
|
|
||||||
|
*基于$65/小时知识工作者费率*
|
||||||
|
|
||||||
|
## 结论
|
||||||
|
|
||||||
|
对于有竞争力的专业人士来说,AI生产力工具已不再是可选项。正确的工具组合每周可节省10-20小时——相当于每年多出一个月的生产时间。
|
||||||
|
|
||||||
|
在 Toolso.AI 探索我们完整的AI生产力工具集合,今天就开始夺回你的时间。
|
||||||
@@ -0,0 +1,164 @@
|
|||||||
|
import { BlogLayout } from "@/components/blog-layout";
|
||||||
|
|
||||||
|
export const blog = {
|
||||||
|
author: { name: "Toolso.AI", src: "/avatar.jpeg" },
|
||||||
|
date: "2024-12-20",
|
||||||
|
title: "AI Video Generation Tools: The Future of Content Creation",
|
||||||
|
description:
|
||||||
|
"Explore the revolutionary world of AI video generation. From text-to-video to AI avatars, discover tools that are transforming how we create video content.",
|
||||||
|
image:
|
||||||
|
"https://images.unsplash.com/photo-1492619375914-88005aa9e8fb?w=800&auto=format&fit=crop&q=60",
|
||||||
|
};
|
||||||
|
export const metadata = {
|
||||||
|
title: blog.title,
|
||||||
|
description: blog.description,
|
||||||
|
openGraph: {
|
||||||
|
images: [blog.image],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default (props) => <BlogLayout blog={blog} {...props} />;
|
||||||
|
|
||||||
|
## The AI Video Revolution
|
||||||
|
|
||||||
|
Video content has always been resource-intensive to produce. AI is changing that dramatically, making professional-quality video creation accessible to everyone.
|
||||||
|
|
||||||
|
## Categories of AI Video Tools
|
||||||
|
|
||||||
|
### Text-to-Video Generation
|
||||||
|
|
||||||
|
These tools create videos from text descriptions alone.
|
||||||
|
|
||||||
|
**Runway Gen-2**
|
||||||
|
- Industry-leading quality
|
||||||
|
- Motion brush for precise control
|
||||||
|
- Up to 4 seconds per generation
|
||||||
|
- **Price:** Starting at $15/month
|
||||||
|
|
||||||
|
**Pika Labs**
|
||||||
|
- Excellent motion quality
|
||||||
|
- Discord and web interface
|
||||||
|
- Free tier available
|
||||||
|
- **Price:** Free tier + paid plans
|
||||||
|
|
||||||
|
**Sora (OpenAI)**
|
||||||
|
- Groundbreaking quality (limited access)
|
||||||
|
- Up to 60 seconds
|
||||||
|
- Realistic physics simulation
|
||||||
|
- **Price:** Not yet publicly available
|
||||||
|
|
||||||
|
### AI Avatar Videos
|
||||||
|
|
||||||
|
Create videos with AI-generated presenters.
|
||||||
|
|
||||||
|
**HeyGen**
|
||||||
|
- 100+ AI avatars
|
||||||
|
- Voice cloning
|
||||||
|
- Multi-language support
|
||||||
|
- **Best for:** Corporate training, marketing
|
||||||
|
|
||||||
|
**Synthesia**
|
||||||
|
- Enterprise-focused
|
||||||
|
- Custom avatar creation
|
||||||
|
- SOC 2 compliant
|
||||||
|
- **Best for:** Large organizations
|
||||||
|
|
||||||
|
**D-ID**
|
||||||
|
- Photo-to-video animation
|
||||||
|
- API available
|
||||||
|
- Quick turnaround
|
||||||
|
- **Best for:** Personalized videos
|
||||||
|
|
||||||
|
### Video Editing & Enhancement
|
||||||
|
|
||||||
|
AI-powered tools for improving existing footage.
|
||||||
|
|
||||||
|
**Runway**
|
||||||
|
- Remove backgrounds
|
||||||
|
- Object removal
|
||||||
|
- Color grading
|
||||||
|
- **Best for:** Professional editors
|
||||||
|
|
||||||
|
**Descript**
|
||||||
|
- Text-based video editing
|
||||||
|
- Filler word removal
|
||||||
|
- Overdub voice cloning
|
||||||
|
- **Best for:** Podcasters and educators
|
||||||
|
|
||||||
|
**CapCut**
|
||||||
|
- Auto-captions
|
||||||
|
- Background removal
|
||||||
|
- Free to use
|
||||||
|
- **Best for:** Social media creators
|
||||||
|
|
||||||
|
### AI Video Upscaling
|
||||||
|
|
||||||
|
Enhance video quality with AI.
|
||||||
|
|
||||||
|
**Topaz Video AI**
|
||||||
|
- Up to 8K upscaling
|
||||||
|
- Frame rate conversion
|
||||||
|
- Noise reduction
|
||||||
|
- **Best for:** Restoring old footage
|
||||||
|
|
||||||
|
**Neural Love**
|
||||||
|
- Online upscaling
|
||||||
|
- Multiple enhancement options
|
||||||
|
- Batch processing
|
||||||
|
- **Best for:** Quick online upscaling
|
||||||
|
|
||||||
|
## Use Cases and Recommendations
|
||||||
|
|
||||||
|
### Marketing & Advertising
|
||||||
|
**Recommended:** HeyGen + Runway
|
||||||
|
- Create spokesperson videos without actors
|
||||||
|
- Generate B-roll footage from descriptions
|
||||||
|
|
||||||
|
### Social Media Content
|
||||||
|
**Recommended:** CapCut + Pika Labs
|
||||||
|
- Quick editing with auto-captions
|
||||||
|
- Eye-catching AI-generated clips
|
||||||
|
|
||||||
|
### Corporate Training
|
||||||
|
**Recommended:** Synthesia + Descript
|
||||||
|
- Consistent AI presenters
|
||||||
|
- Easy script updates
|
||||||
|
|
||||||
|
### Film & Entertainment
|
||||||
|
**Recommended:** Runway + Topaz
|
||||||
|
- Professional-grade generation
|
||||||
|
- High-quality upscaling
|
||||||
|
|
||||||
|
## Current Limitations
|
||||||
|
|
||||||
|
Be aware of these limitations:
|
||||||
|
|
||||||
|
1. **Length constraints** - Most tools limited to seconds, not minutes
|
||||||
|
2. **Consistency issues** - Characters may change between shots
|
||||||
|
3. **Text rendering** - Text in videos often appears garbled
|
||||||
|
4. **Hands and faces** - Still challenging for AI
|
||||||
|
5. **Computing costs** - High-quality generation is expensive
|
||||||
|
|
||||||
|
## The Future of AI Video
|
||||||
|
|
||||||
|
What's coming:
|
||||||
|
|
||||||
|
- **Longer generations** - Minutes instead of seconds
|
||||||
|
- **Better consistency** - Maintain characters across scenes
|
||||||
|
- **Real-time generation** - Interactive AI video
|
||||||
|
- **Improved audio sync** - Natural lip movements
|
||||||
|
- **Accessible pricing** - More affordable for individuals
|
||||||
|
|
||||||
|
## Getting Started Tips
|
||||||
|
|
||||||
|
1. **Start with free tiers** - Test before committing
|
||||||
|
2. **Learn prompting** - Better prompts = better results
|
||||||
|
3. **Combine tools** - Use multiple tools for best results
|
||||||
|
4. **Plan your workflow** - AI is a tool, not a replacement
|
||||||
|
5. **Stay ethical** - Be transparent about AI use
|
||||||
|
|
||||||
|
## Conclusion
|
||||||
|
|
||||||
|
AI video generation is still in its early stages but evolving rapidly. The tools available today can already save significant time and resources for content creators. As the technology matures, we'll see even more impressive capabilities.
|
||||||
|
|
||||||
|
Explore our curated collection of AI video tools on Toolso.AI to find the perfect solution for your video creation needs.
|
||||||
@@ -0,0 +1,164 @@
|
|||||||
|
import { BlogLayout } from "@/components/blog-layout";
|
||||||
|
|
||||||
|
export const blog = {
|
||||||
|
author: { name: "Toolso.AI", src: "/avatar.jpeg" },
|
||||||
|
date: "2024-12-20",
|
||||||
|
title: "AI视频生成工具:内容创作的未来",
|
||||||
|
description:
|
||||||
|
"探索AI视频生成的革命性世界。从文字转视频到AI虚拟人,发现正在改变我们创作视频内容方式的工具。",
|
||||||
|
image:
|
||||||
|
"https://images.unsplash.com/photo-1492619375914-88005aa9e8fb?w=800&auto=format&fit=crop&q=60",
|
||||||
|
};
|
||||||
|
export const metadata = {
|
||||||
|
title: blog.title,
|
||||||
|
description: blog.description,
|
||||||
|
openGraph: {
|
||||||
|
images: [blog.image],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default (props) => <BlogLayout blog={blog} {...props} />;
|
||||||
|
|
||||||
|
## AI视频革命
|
||||||
|
|
||||||
|
视频内容的制作一直是资源密集型的。AI正在彻底改变这一点,让专业品质的视频创作变得人人可及。
|
||||||
|
|
||||||
|
## AI视频工具分类
|
||||||
|
|
||||||
|
### 文字转视频生成
|
||||||
|
|
||||||
|
这些工具仅从文字描述就能创建视频。
|
||||||
|
|
||||||
|
**Runway Gen-2**
|
||||||
|
- 行业领先的质量
|
||||||
|
- 运动笔刷精确控制
|
||||||
|
- 每次生成最多4秒
|
||||||
|
- **价格:** 起价$15/月
|
||||||
|
|
||||||
|
**Pika Labs**
|
||||||
|
- 出色的运动质量
|
||||||
|
- Discord和网页界面
|
||||||
|
- 提供免费层级
|
||||||
|
- **价格:** 免费层级 + 付费计划
|
||||||
|
|
||||||
|
**Sora (OpenAI)**
|
||||||
|
- 突破性的质量(限制访问)
|
||||||
|
- 最长60秒
|
||||||
|
- 逼真的物理模拟
|
||||||
|
- **价格:** 尚未公开发布
|
||||||
|
|
||||||
|
### AI虚拟人视频
|
||||||
|
|
||||||
|
使用AI生成的主持人创建视频。
|
||||||
|
|
||||||
|
**HeyGen**
|
||||||
|
- 100+AI虚拟人形象
|
||||||
|
- 语音克隆
|
||||||
|
- 多语言支持
|
||||||
|
- **最适合:** 企业培训、营销
|
||||||
|
|
||||||
|
**Synthesia**
|
||||||
|
- 专注企业
|
||||||
|
- 自定义虚拟人创建
|
||||||
|
- SOC 2合规
|
||||||
|
- **最适合:** 大型组织
|
||||||
|
|
||||||
|
**D-ID**
|
||||||
|
- 照片转视频动画
|
||||||
|
- 提供API
|
||||||
|
- 快速周转
|
||||||
|
- **最适合:** 个性化视频
|
||||||
|
|
||||||
|
### 视频编辑与增强
|
||||||
|
|
||||||
|
用于改进现有素材的AI驱动工具。
|
||||||
|
|
||||||
|
**Runway**
|
||||||
|
- 移除背景
|
||||||
|
- 物体移除
|
||||||
|
- 色彩调整
|
||||||
|
- **最适合:** 专业剪辑师
|
||||||
|
|
||||||
|
**Descript**
|
||||||
|
- 基于文本的视频编辑
|
||||||
|
- 移除填充词
|
||||||
|
- Overdub语音克隆
|
||||||
|
- **最适合:** 播客主和教育者
|
||||||
|
|
||||||
|
**CapCut**
|
||||||
|
- 自动字幕
|
||||||
|
- 背景移除
|
||||||
|
- 免费使用
|
||||||
|
- **最适合:** 社交媒体创作者
|
||||||
|
|
||||||
|
### AI视频升级
|
||||||
|
|
||||||
|
用AI增强视频质量。
|
||||||
|
|
||||||
|
**Topaz Video AI**
|
||||||
|
- 最高8K升级
|
||||||
|
- 帧率转换
|
||||||
|
- 降噪
|
||||||
|
- **最适合:** 修复老旧素材
|
||||||
|
|
||||||
|
**Neural Love**
|
||||||
|
- 在线升级
|
||||||
|
- 多种增强选项
|
||||||
|
- 批量处理
|
||||||
|
- **最适合:** 快速在线升级
|
||||||
|
|
||||||
|
## 使用场景和推荐
|
||||||
|
|
||||||
|
### 营销与广告
|
||||||
|
**推荐:** HeyGen + Runway
|
||||||
|
- 无需演员即可创建代言人视频
|
||||||
|
- 从描述生成B-roll素材
|
||||||
|
|
||||||
|
### 社交媒体内容
|
||||||
|
**推荐:** CapCut + Pika Labs
|
||||||
|
- 带自动字幕的快速编辑
|
||||||
|
- 引人注目的AI生成短片
|
||||||
|
|
||||||
|
### 企业培训
|
||||||
|
**推荐:** Synthesia + Descript
|
||||||
|
- 一致的AI主持人
|
||||||
|
- 轻松更新脚本
|
||||||
|
|
||||||
|
### 影视娱乐
|
||||||
|
**推荐:** Runway + Topaz
|
||||||
|
- 专业级生成
|
||||||
|
- 高质量升级
|
||||||
|
|
||||||
|
## 当前局限性
|
||||||
|
|
||||||
|
请注意以下局限:
|
||||||
|
|
||||||
|
1. **时长限制** - 大多数工具限于几秒,而非几分钟
|
||||||
|
2. **一致性问题** - 角色可能在不同镜头间变化
|
||||||
|
3. **文字渲染** - 视频中的文字经常显示乱码
|
||||||
|
4. **手部和面部** - 对AI来说仍具挑战性
|
||||||
|
5. **计算成本** - 高质量生成很昂贵
|
||||||
|
|
||||||
|
## AI视频的未来
|
||||||
|
|
||||||
|
即将到来的发展:
|
||||||
|
|
||||||
|
- **更长的生成** - 分钟级而非秒级
|
||||||
|
- **更好的一致性** - 跨场景保持角色
|
||||||
|
- **实时生成** - 交互式AI视频
|
||||||
|
- **改进的音频同步** - 自然的唇部动作
|
||||||
|
- **亲民的定价** - 个人用户更能负担
|
||||||
|
|
||||||
|
## 入门建议
|
||||||
|
|
||||||
|
1. **从免费层级开始** - 承诺前先测试
|
||||||
|
2. **学习提示词技巧** - 更好的提示词 = 更好的结果
|
||||||
|
3. **组合工具** - 使用多个工具获得最佳效果
|
||||||
|
4. **规划工作流程** - AI是工具,不是替代品
|
||||||
|
5. **保持道德** - 对AI使用保持透明
|
||||||
|
|
||||||
|
## 结论
|
||||||
|
|
||||||
|
AI视频生成仍处于早期阶段,但发展迅速。目前可用的工具已经能为内容创作者节省大量时间和资源。随着技术成熟,我们将看到更令人印象深刻的能力。
|
||||||
|
|
||||||
|
在 Toolso.AI 探索我们精选的AI视频工具集合,找到满足您视频创作需求的完美解决方案。
|
||||||
@@ -0,0 +1,71 @@
|
|||||||
|
import { BlogLayout } from "@/components/blog-layout";
|
||||||
|
|
||||||
|
export const blog = {
|
||||||
|
author: { name: "Toolso.AI", src: "/avatar.jpeg" },
|
||||||
|
date: "2025-01-03",
|
||||||
|
title: "Best AI Tools in 2025: A Complete Guide",
|
||||||
|
description:
|
||||||
|
"Discover the most powerful AI tools of 2025. From ChatGPT to Midjourney, we cover the best AI applications for productivity, creativity, and business.",
|
||||||
|
image:
|
||||||
|
"https://images.unsplash.com/photo-1677442136019-21780ecad995?w=800&auto=format&fit=crop&q=60",
|
||||||
|
};
|
||||||
|
export const metadata = {
|
||||||
|
title: blog.title,
|
||||||
|
description: blog.description,
|
||||||
|
openGraph: {
|
||||||
|
images: [blog.image],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default (props) => <BlogLayout blog={blog} {...props} />;
|
||||||
|
|
||||||
|
## The AI Revolution in 2025
|
||||||
|
|
||||||
|
Artificial Intelligence has transformed the way we work, create, and communicate. In 2025, AI tools have become more accessible, powerful, and diverse than ever before. Whether you're a developer, designer, writer, or business professional, there's an AI tool designed to enhance your workflow.
|
||||||
|
|
||||||
|
## Top AI Categories
|
||||||
|
|
||||||
|
### 1. AI Chatbots & Assistants
|
||||||
|
|
||||||
|
The conversational AI space has seen remarkable growth:
|
||||||
|
|
||||||
|
- **ChatGPT** - OpenAI's flagship product continues to lead with GPT-4 and beyond
|
||||||
|
- **Claude** - Anthropic's AI assistant known for nuanced understanding
|
||||||
|
- **Gemini** - Google's multimodal AI powerhouse
|
||||||
|
|
||||||
|
### 2. AI Image Generation
|
||||||
|
|
||||||
|
Visual AI tools have revolutionized creative work:
|
||||||
|
|
||||||
|
- **Midjourney** - Industry leader for artistic image generation
|
||||||
|
- **DALL-E 3** - OpenAI's image generator with exceptional prompt understanding
|
||||||
|
- **Stable Diffusion** - Open-source flexibility for developers
|
||||||
|
|
||||||
|
### 3. AI Writing Tools
|
||||||
|
|
||||||
|
Content creation has been transformed:
|
||||||
|
|
||||||
|
- **Jasper** - Enterprise-focused AI writing platform
|
||||||
|
- **Copy.ai** - Marketing-focused content generation
|
||||||
|
- **Notion AI** - Integrated writing assistance in your workspace
|
||||||
|
|
||||||
|
### 4. AI Video Tools
|
||||||
|
|
||||||
|
Video creation is now accessible to everyone:
|
||||||
|
|
||||||
|
- **Runway** - Professional video generation and editing
|
||||||
|
- **Pika** - Text-to-video with stunning results
|
||||||
|
- **HeyGen** - AI avatar video creation
|
||||||
|
|
||||||
|
## How to Choose the Right AI Tool
|
||||||
|
|
||||||
|
When selecting an AI tool, consider:
|
||||||
|
|
||||||
|
1. **Your specific use case** - What problem are you solving?
|
||||||
|
2. **Integration needs** - Does it work with your existing tools?
|
||||||
|
3. **Pricing** - Free tier vs. paid features
|
||||||
|
4. **Learning curve** - How quickly can you get started?
|
||||||
|
|
||||||
|
## Conclusion
|
||||||
|
|
||||||
|
The AI landscape in 2025 offers unprecedented opportunities for productivity and creativity. Explore our curated collection of AI tools on Toolso.AI to find the perfect solutions for your needs.
|
||||||
@@ -0,0 +1,71 @@
|
|||||||
|
import { BlogLayout } from "@/components/blog-layout";
|
||||||
|
|
||||||
|
export const blog = {
|
||||||
|
author: { name: "Toolso.AI", src: "/avatar.jpeg" },
|
||||||
|
date: "2025-01-03",
|
||||||
|
title: "2025年最佳AI工具完全指南",
|
||||||
|
description:
|
||||||
|
"探索2025年最强大的AI工具。从ChatGPT到Midjourney,我们为您介绍生产力、创意和商业领域最佳的AI应用。",
|
||||||
|
image:
|
||||||
|
"https://images.unsplash.com/photo-1677442136019-21780ecad995?w=800&auto=format&fit=crop&q=60",
|
||||||
|
};
|
||||||
|
export const metadata = {
|
||||||
|
title: blog.title,
|
||||||
|
description: blog.description,
|
||||||
|
openGraph: {
|
||||||
|
images: [blog.image],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default (props) => <BlogLayout blog={blog} {...props} />;
|
||||||
|
|
||||||
|
## 2025年的AI革命
|
||||||
|
|
||||||
|
人工智能已经彻底改变了我们工作、创作和沟通的方式。在2025年,AI工具变得比以往任何时候都更加易用、强大和多样化。无论您是开发者、设计师、作家还是商业专业人士,都有专门为增强您工作流程而设计的AI工具。
|
||||||
|
|
||||||
|
## 顶级AI工具分类
|
||||||
|
|
||||||
|
### 1. AI聊天机器人与助手
|
||||||
|
|
||||||
|
对话式AI领域取得了显著增长:
|
||||||
|
|
||||||
|
- **ChatGPT** - OpenAI的旗舰产品,凭借GPT-4及更高版本继续领先
|
||||||
|
- **Claude** - Anthropic的AI助手,以细腻的理解能力著称
|
||||||
|
- **Gemini** - Google的多模态AI强者
|
||||||
|
|
||||||
|
### 2. AI图像生成
|
||||||
|
|
||||||
|
视觉AI工具彻底改变了创意工作:
|
||||||
|
|
||||||
|
- **Midjourney** - 艺术图像生成的行业领导者
|
||||||
|
- **DALL-E 3** - OpenAI的图像生成器,具有卓越的提示词理解能力
|
||||||
|
- **Stable Diffusion** - 为开发者提供开源灵活性
|
||||||
|
|
||||||
|
### 3. AI写作工具
|
||||||
|
|
||||||
|
内容创作已被彻底变革:
|
||||||
|
|
||||||
|
- **Jasper** - 专注企业的AI写作平台
|
||||||
|
- **Copy.ai** - 专注营销的内容生成
|
||||||
|
- **Notion AI** - 工作空间中的集成写作助手
|
||||||
|
|
||||||
|
### 4. AI视频工具
|
||||||
|
|
||||||
|
视频创作现在人人可及:
|
||||||
|
|
||||||
|
- **Runway** - 专业视频生成和编辑
|
||||||
|
- **Pika** - 效果惊艳的文字转视频
|
||||||
|
- **HeyGen** - AI虚拟人视频创作
|
||||||
|
|
||||||
|
## 如何选择合适的AI工具
|
||||||
|
|
||||||
|
选择AI工具时,请考虑:
|
||||||
|
|
||||||
|
1. **您的具体用例** - 您要解决什么问题?
|
||||||
|
2. **集成需求** - 它能与您现有的工具配合使用吗?
|
||||||
|
3. **定价** - 免费版与付费功能的对比
|
||||||
|
4. **学习曲线** - 您能多快上手?
|
||||||
|
|
||||||
|
## 结论
|
||||||
|
|
||||||
|
2025年的AI领域为生产力和创意提供了前所未有的机遇。浏览 Toolso.AI 精选的AI工具集合,找到最适合您需求的解决方案。
|
||||||
@@ -0,0 +1,170 @@
|
|||||||
|
import { BlogLayout } from "@/components/blog-layout";
|
||||||
|
|
||||||
|
export const blog = {
|
||||||
|
author: { name: "Toolso.AI", src: "/avatar.jpeg" },
|
||||||
|
date: "2024-12-25",
|
||||||
|
title: "Best Free AI Tools in 2025: No Credit Card Required",
|
||||||
|
description:
|
||||||
|
"Discover powerful AI tools that won't cost you a penny. From image generation to writing assistants, explore the best free AI resources available today.",
|
||||||
|
image:
|
||||||
|
"https://images.unsplash.com/photo-1633356122544-f134324a6cee?w=800&auto=format&fit=crop&q=60",
|
||||||
|
};
|
||||||
|
export const metadata = {
|
||||||
|
title: blog.title,
|
||||||
|
description: blog.description,
|
||||||
|
openGraph: {
|
||||||
|
images: [blog.image],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default (props) => <BlogLayout blog={blog} {...props} />;
|
||||||
|
|
||||||
|
## Free AI Tools That Actually Deliver
|
||||||
|
|
||||||
|
You don't need to spend a fortune to harness the power of AI. Many cutting-edge tools offer generous free tiers that can handle most personal and small business needs.
|
||||||
|
|
||||||
|
## Free AI Chatbots
|
||||||
|
|
||||||
|
### ChatGPT Free
|
||||||
|
- Access to GPT-3.5
|
||||||
|
- Unlimited conversations
|
||||||
|
- Basic coding assistance
|
||||||
|
- **Limitation:** No GPT-4 access, slower during peak times
|
||||||
|
|
||||||
|
### Claude.ai Free
|
||||||
|
- Access to Claude 3 Sonnet
|
||||||
|
- Generous daily message limit
|
||||||
|
- Long document analysis
|
||||||
|
- **Limitation:** Lower priority during high demand
|
||||||
|
|
||||||
|
### Google Gemini Free
|
||||||
|
- Access to Gemini Pro
|
||||||
|
- Real-time web access
|
||||||
|
- Google integration
|
||||||
|
- **Limitation:** Limited advanced features
|
||||||
|
|
||||||
|
### Microsoft Copilot Free
|
||||||
|
- GPT-4 powered
|
||||||
|
- Image generation included
|
||||||
|
- No sign-up required
|
||||||
|
- **Limitation:** Daily conversation limits
|
||||||
|
|
||||||
|
## Free AI Image Generators
|
||||||
|
|
||||||
|
### Bing Image Creator
|
||||||
|
- DALL-E 3 powered
|
||||||
|
- High-quality outputs
|
||||||
|
- 15 free boosts daily
|
||||||
|
- **Best for:** Quick, quality image generation
|
||||||
|
|
||||||
|
### Leonardo.ai Free
|
||||||
|
- 150 daily tokens
|
||||||
|
- Multiple model options
|
||||||
|
- Community models access
|
||||||
|
- **Best for:** Artistic and game asset creation
|
||||||
|
|
||||||
|
### Playground AI
|
||||||
|
- 500 free images daily
|
||||||
|
- Multiple AI models
|
||||||
|
- Image editing tools
|
||||||
|
- **Best for:** Volume image generation
|
||||||
|
|
||||||
|
### Canva AI (Free Tier)
|
||||||
|
- Limited AI image generation
|
||||||
|
- Design template integration
|
||||||
|
- Easy editing tools
|
||||||
|
- **Best for:** Marketing materials
|
||||||
|
|
||||||
|
## Free AI Writing Tools
|
||||||
|
|
||||||
|
### Notion AI (Limited)
|
||||||
|
- Free trial available
|
||||||
|
- Integrated with Notion workspace
|
||||||
|
- Summarization and writing help
|
||||||
|
- **Best for:** Note-taking and organization
|
||||||
|
|
||||||
|
### Grammarly Free
|
||||||
|
- Basic grammar checking
|
||||||
|
- Tone detection
|
||||||
|
- Browser extension
|
||||||
|
- **Best for:** Error-free writing
|
||||||
|
|
||||||
|
### QuillBot Free
|
||||||
|
- Paraphrasing tool
|
||||||
|
- Grammar checker
|
||||||
|
- Summarizer (limited)
|
||||||
|
- **Best for:** Rewriting and paraphrasing
|
||||||
|
|
||||||
|
### Copy.ai Free
|
||||||
|
- 2,000 words per month
|
||||||
|
- 90+ copywriting tools
|
||||||
|
- Multiple languages
|
||||||
|
- **Best for:** Marketing copy snippets
|
||||||
|
|
||||||
|
## Free AI Coding Assistants
|
||||||
|
|
||||||
|
### GitHub Copilot (Students/Open Source)
|
||||||
|
- Free for students and maintainers
|
||||||
|
- VS Code integration
|
||||||
|
- Code completion
|
||||||
|
- **Best for:** Students and open source contributors
|
||||||
|
|
||||||
|
### Codeium Free
|
||||||
|
- Unlimited code completion
|
||||||
|
- Multiple IDE support
|
||||||
|
- Chat assistance
|
||||||
|
- **Best for:** Professional developers on a budget
|
||||||
|
|
||||||
|
### Amazon CodeWhisperer Free
|
||||||
|
- Individual tier is free
|
||||||
|
- AWS integration
|
||||||
|
- Security scanning
|
||||||
|
- **Best for:** AWS developers
|
||||||
|
|
||||||
|
### Replit AI (Free Tier)
|
||||||
|
- Basic code assistance
|
||||||
|
- Browser-based IDE
|
||||||
|
- Deployment included
|
||||||
|
- **Best for:** Learning and prototyping
|
||||||
|
|
||||||
|
## Free AI Audio & Video Tools
|
||||||
|
|
||||||
|
### ElevenLabs Free
|
||||||
|
- 10,000 characters/month
|
||||||
|
- High-quality voice synthesis
|
||||||
|
- Multiple voices
|
||||||
|
- **Best for:** Short voiceover projects
|
||||||
|
|
||||||
|
### Kapwing Free
|
||||||
|
- Basic video editing
|
||||||
|
- AI-powered features
|
||||||
|
- Watermark on exports
|
||||||
|
- **Best for:** Social media clips
|
||||||
|
|
||||||
|
### Descript Free
|
||||||
|
- 1 hour of transcription
|
||||||
|
- Basic editing
|
||||||
|
- Screen recording
|
||||||
|
- **Best for:** Podcast editing
|
||||||
|
|
||||||
|
## Tips for Maximizing Free AI Tools
|
||||||
|
|
||||||
|
1. **Combine multiple free tools** - Use different tools for different tasks
|
||||||
|
2. **Create multiple accounts** - Where terms allow, for different projects
|
||||||
|
3. **Time your usage** - Use free tiers during off-peak hours
|
||||||
|
4. **Export and save** - Don't lose your generated content
|
||||||
|
5. **Stay updated** - Free tiers often change
|
||||||
|
|
||||||
|
## When to Upgrade
|
||||||
|
|
||||||
|
Consider paid plans when:
|
||||||
|
- You hit free limits regularly
|
||||||
|
- Quality becomes critical
|
||||||
|
- Time savings justify the cost
|
||||||
|
- You need advanced features
|
||||||
|
|
||||||
|
## Conclusion
|
||||||
|
|
||||||
|
The free AI tool landscape is remarkably capable. For many users, free tiers provide everything needed to boost productivity and creativity. Start with free options and upgrade only when necessary.
|
||||||
|
|
||||||
|
Browse our complete collection of free AI tools on Toolso.AI - we've tagged all tools with free tiers for easy discovery.
|
||||||
@@ -0,0 +1,170 @@
|
|||||||
|
import { BlogLayout } from "@/components/blog-layout";
|
||||||
|
|
||||||
|
export const blog = {
|
||||||
|
author: { name: "Toolso.AI", src: "/avatar.jpeg" },
|
||||||
|
date: "2024-12-25",
|
||||||
|
title: "2025年最佳免费AI工具:无需信用卡",
|
||||||
|
description:
|
||||||
|
"发现不花一分钱就能使用的强大AI工具。从图像生成到写作助手,探索当今最佳的免费AI资源。",
|
||||||
|
image:
|
||||||
|
"https://images.unsplash.com/photo-1633356122544-f134324a6cee?w=800&auto=format&fit=crop&q=60",
|
||||||
|
};
|
||||||
|
export const metadata = {
|
||||||
|
title: blog.title,
|
||||||
|
description: blog.description,
|
||||||
|
openGraph: {
|
||||||
|
images: [blog.image],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default (props) => <BlogLayout blog={blog} {...props} />;
|
||||||
|
|
||||||
|
## 真正好用的免费AI工具
|
||||||
|
|
||||||
|
利用AI的力量不需要花费大量金钱。许多尖端工具提供慷慨的免费层级,可以满足大多数个人和小型企业的需求。
|
||||||
|
|
||||||
|
## 免费AI聊天机器人
|
||||||
|
|
||||||
|
### ChatGPT 免费版
|
||||||
|
- 访问GPT-3.5
|
||||||
|
- 无限对话
|
||||||
|
- 基础编程辅助
|
||||||
|
- **限制:** 无法访问GPT-4,高峰期较慢
|
||||||
|
|
||||||
|
### Claude.ai 免费版
|
||||||
|
- 访问Claude 3 Sonnet
|
||||||
|
- 慷慨的每日消息限制
|
||||||
|
- 长文档分析
|
||||||
|
- **限制:** 高需求期间优先级较低
|
||||||
|
|
||||||
|
### Google Gemini 免费版
|
||||||
|
- 访问Gemini Pro
|
||||||
|
- 实时网络访问
|
||||||
|
- Google集成
|
||||||
|
- **限制:** 高级功能受限
|
||||||
|
|
||||||
|
### Microsoft Copilot 免费版
|
||||||
|
- GPT-4驱动
|
||||||
|
- 包含图像生成
|
||||||
|
- 无需注册
|
||||||
|
- **限制:** 每日对话限制
|
||||||
|
|
||||||
|
## 免费AI图像生成器
|
||||||
|
|
||||||
|
### Bing Image Creator
|
||||||
|
- DALL-E 3驱动
|
||||||
|
- 高质量输出
|
||||||
|
- 每日15次免费加速
|
||||||
|
- **最适合:** 快速、高质量图像生成
|
||||||
|
|
||||||
|
### Leonardo.ai 免费版
|
||||||
|
- 每日150个代币
|
||||||
|
- 多种模型选项
|
||||||
|
- 社区模型访问
|
||||||
|
- **最适合:** 艺术和游戏资产创作
|
||||||
|
|
||||||
|
### Playground AI
|
||||||
|
- 每日500张免费图片
|
||||||
|
- 多种AI模型
|
||||||
|
- 图像编辑工具
|
||||||
|
- **最适合:** 批量图像生成
|
||||||
|
|
||||||
|
### Canva AI(免费层级)
|
||||||
|
- 有限的AI图像生成
|
||||||
|
- 设计模板集成
|
||||||
|
- 简单编辑工具
|
||||||
|
- **最适合:** 营销材料
|
||||||
|
|
||||||
|
## 免费AI写作工具
|
||||||
|
|
||||||
|
### Notion AI(有限)
|
||||||
|
- 提供免费试用
|
||||||
|
- 与Notion工作空间集成
|
||||||
|
- 摘要和写作帮助
|
||||||
|
- **最适合:** 笔记和组织
|
||||||
|
|
||||||
|
### Grammarly 免费版
|
||||||
|
- 基础语法检查
|
||||||
|
- 语气检测
|
||||||
|
- 浏览器扩展
|
||||||
|
- **最适合:** 无错误写作
|
||||||
|
|
||||||
|
### QuillBot 免费版
|
||||||
|
- 改写工具
|
||||||
|
- 语法检查器
|
||||||
|
- 摘要器(有限)
|
||||||
|
- **最适合:** 改写和释义
|
||||||
|
|
||||||
|
### Copy.ai 免费版
|
||||||
|
- 每月2,000字
|
||||||
|
- 90+文案工具
|
||||||
|
- 多语言支持
|
||||||
|
- **最适合:** 营销文案片段
|
||||||
|
|
||||||
|
## 免费AI编程助手
|
||||||
|
|
||||||
|
### GitHub Copilot(学生/开源)
|
||||||
|
- 对学生和维护者免费
|
||||||
|
- VS Code集成
|
||||||
|
- 代码补全
|
||||||
|
- **最适合:** 学生和开源贡献者
|
||||||
|
|
||||||
|
### Codeium 免费版
|
||||||
|
- 无限代码补全
|
||||||
|
- 多IDE支持
|
||||||
|
- 聊天辅助
|
||||||
|
- **最适合:** 预算有限的专业开发者
|
||||||
|
|
||||||
|
### Amazon CodeWhisperer 免费版
|
||||||
|
- 个人层级免费
|
||||||
|
- AWS集成
|
||||||
|
- 安全扫描
|
||||||
|
- **最适合:** AWS开发者
|
||||||
|
|
||||||
|
### Replit AI(免费层级)
|
||||||
|
- 基础代码辅助
|
||||||
|
- 基于浏览器的IDE
|
||||||
|
- 包含部署
|
||||||
|
- **最适合:** 学习和原型开发
|
||||||
|
|
||||||
|
## 免费AI音视频工具
|
||||||
|
|
||||||
|
### ElevenLabs 免费版
|
||||||
|
- 每月10,000字符
|
||||||
|
- 高质量语音合成
|
||||||
|
- 多种声音
|
||||||
|
- **最适合:** 短配音项目
|
||||||
|
|
||||||
|
### Kapwing 免费版
|
||||||
|
- 基础视频编辑
|
||||||
|
- AI驱动功能
|
||||||
|
- 导出带水印
|
||||||
|
- **最适合:** 社交媒体短片
|
||||||
|
|
||||||
|
### Descript 免费版
|
||||||
|
- 1小时转录
|
||||||
|
- 基础编辑
|
||||||
|
- 屏幕录制
|
||||||
|
- **最适合:** 播客编辑
|
||||||
|
|
||||||
|
## 最大化利用免费AI工具的技巧
|
||||||
|
|
||||||
|
1. **组合多个免费工具** - 不同任务使用不同工具
|
||||||
|
2. **创建多个账户** - 在条款允许的情况下,用于不同项目
|
||||||
|
3. **合理安排使用时间** - 在非高峰时段使用免费层级
|
||||||
|
4. **导出并保存** - 不要丢失生成的内容
|
||||||
|
5. **保持更新** - 免费层级经常变化
|
||||||
|
|
||||||
|
## 何时升级
|
||||||
|
|
||||||
|
在以下情况考虑付费计划:
|
||||||
|
- 经常达到免费限制
|
||||||
|
- 质量变得至关重要
|
||||||
|
- 节省的时间值得付费
|
||||||
|
- 需要高级功能
|
||||||
|
|
||||||
|
## 结论
|
||||||
|
|
||||||
|
免费AI工具的能力非常出色。对于许多用户来说,免费层级提供了提升生产力和创造力所需的一切。从免费选项开始,只在必要时升级。
|
||||||
|
|
||||||
|
在 Toolso.AI 浏览我们完整的免费AI工具集合 - 我们已为所有带免费层级的工具添加标签,方便发现。
|
||||||
@@ -0,0 +1,130 @@
|
|||||||
|
import { BlogLayout } from "@/components/blog-layout";
|
||||||
|
|
||||||
|
export const blog = {
|
||||||
|
author: { name: "Toolso.AI", src: "/avatar.jpeg" },
|
||||||
|
date: "2025-01-01",
|
||||||
|
title: "How to Choose the Right AI Writing Tool for Your Needs",
|
||||||
|
description:
|
||||||
|
"Navigate the crowded AI writing tool market with our comprehensive guide. From blogging to marketing copy, find the perfect AI assistant for your content creation.",
|
||||||
|
image:
|
||||||
|
"https://images.unsplash.com/photo-1455390582262-044cdead277a?w=800&auto=format&fit=crop&q=60",
|
||||||
|
};
|
||||||
|
export const metadata = {
|
||||||
|
title: blog.title,
|
||||||
|
description: blog.description,
|
||||||
|
openGraph: {
|
||||||
|
images: [blog.image],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default (props) => <BlogLayout blog={blog} {...props} />;
|
||||||
|
|
||||||
|
## The AI Writing Tool Landscape
|
||||||
|
|
||||||
|
With dozens of AI writing tools flooding the market, choosing the right one can feel overwhelming. This guide will help you cut through the noise and find your perfect match.
|
||||||
|
|
||||||
|
## Understanding Your Needs
|
||||||
|
|
||||||
|
Before diving into tools, ask yourself:
|
||||||
|
|
||||||
|
1. **What type of content do you create?**
|
||||||
|
- Blog posts and articles
|
||||||
|
- Marketing copy
|
||||||
|
- Technical documentation
|
||||||
|
- Social media content
|
||||||
|
- Email campaigns
|
||||||
|
|
||||||
|
2. **What's your workflow like?**
|
||||||
|
- Do you need standalone tools or integrations?
|
||||||
|
- How important is collaboration?
|
||||||
|
- What's your budget?
|
||||||
|
|
||||||
|
## Top AI Writing Tools by Use Case
|
||||||
|
|
||||||
|
### For Long-Form Content
|
||||||
|
|
||||||
|
**ChatGPT / Claude**
|
||||||
|
- Best for brainstorming and drafting
|
||||||
|
- Excellent for research assistance
|
||||||
|
- Flexible prompting for any content type
|
||||||
|
|
||||||
|
**Jasper**
|
||||||
|
- Purpose-built for marketing content
|
||||||
|
- Templates for various content types
|
||||||
|
- Brand voice consistency features
|
||||||
|
|
||||||
|
### For Marketing Copy
|
||||||
|
|
||||||
|
**Copy.ai**
|
||||||
|
- Quick generation of marketing variants
|
||||||
|
- A/B testing suggestions
|
||||||
|
- Social media post generation
|
||||||
|
|
||||||
|
**Writesonic**
|
||||||
|
- Landing page copy
|
||||||
|
- Product descriptions
|
||||||
|
- Ad copy variations
|
||||||
|
|
||||||
|
### For SEO Content
|
||||||
|
|
||||||
|
**Surfer SEO + AI**
|
||||||
|
- SEO-optimized content suggestions
|
||||||
|
- Keyword integration guidance
|
||||||
|
- Content score tracking
|
||||||
|
|
||||||
|
**Clearscope**
|
||||||
|
- Comprehensive content optimization
|
||||||
|
- Competitor analysis
|
||||||
|
- Readability scoring
|
||||||
|
|
||||||
|
### For Technical Writing
|
||||||
|
|
||||||
|
**GitHub Copilot**
|
||||||
|
- Code documentation
|
||||||
|
- Technical explanations
|
||||||
|
- API documentation
|
||||||
|
|
||||||
|
**Notion AI**
|
||||||
|
- Integrated workspace writing
|
||||||
|
- Meeting notes summarization
|
||||||
|
- Technical specifications
|
||||||
|
|
||||||
|
## Key Features to Consider
|
||||||
|
|
||||||
|
### 1. Output Quality
|
||||||
|
Not all AI writing is equal. Test each tool with your specific use cases.
|
||||||
|
|
||||||
|
### 2. Customization
|
||||||
|
Can you train the tool on your brand voice? Does it learn from your feedback?
|
||||||
|
|
||||||
|
### 3. Integration
|
||||||
|
Does it work with your existing tools? Consider:
|
||||||
|
- Google Docs / Microsoft Word
|
||||||
|
- Content management systems
|
||||||
|
- SEO tools
|
||||||
|
|
||||||
|
### 4. Pricing Model
|
||||||
|
- Per-word pricing
|
||||||
|
- Monthly subscriptions
|
||||||
|
- Usage-based credits
|
||||||
|
|
||||||
|
## Our Recommendations
|
||||||
|
|
||||||
|
| Use Case | Recommended Tool | Why |
|
||||||
|
|----------|------------------|-----|
|
||||||
|
| General Writing | ChatGPT Plus | Versatility and quality |
|
||||||
|
| Marketing | Jasper | Purpose-built features |
|
||||||
|
| SEO Content | Surfer SEO | Optimization focus |
|
||||||
|
| Technical | GitHub Copilot | Code-aware |
|
||||||
|
| Quick Copy | Copy.ai | Speed and templates |
|
||||||
|
|
||||||
|
## Getting Started
|
||||||
|
|
||||||
|
1. **Start with free trials** - Most tools offer them
|
||||||
|
2. **Test with real projects** - Use your actual content needs
|
||||||
|
3. **Compare outputs** - Try the same prompt across tools
|
||||||
|
4. **Consider the learning curve** - Factor in time to proficiency
|
||||||
|
|
||||||
|
## Conclusion
|
||||||
|
|
||||||
|
The best AI writing tool is the one that fits your specific workflow and content needs. Don't just follow the hype - test thoroughly and choose based on your requirements. Browse our complete collection of AI writing tools on Toolso.AI.
|
||||||
@@ -0,0 +1,130 @@
|
|||||||
|
import { BlogLayout } from "@/components/blog-layout";
|
||||||
|
|
||||||
|
export const blog = {
|
||||||
|
author: { name: "Toolso.AI", src: "/avatar.jpeg" },
|
||||||
|
date: "2025-01-01",
|
||||||
|
title: "如何选择适合您需求的AI写作工具",
|
||||||
|
description:
|
||||||
|
"通过我们的全面指南,帮您在众多AI写作工具中做出选择。从博客写作到营销文案,找到最适合您内容创作的AI助手。",
|
||||||
|
image:
|
||||||
|
"https://images.unsplash.com/photo-1455390582262-044cdead277a?w=800&auto=format&fit=crop&q=60",
|
||||||
|
};
|
||||||
|
export const metadata = {
|
||||||
|
title: blog.title,
|
||||||
|
description: blog.description,
|
||||||
|
openGraph: {
|
||||||
|
images: [blog.image],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default (props) => <BlogLayout blog={blog} {...props} />;
|
||||||
|
|
||||||
|
## AI写作工具市场概况
|
||||||
|
|
||||||
|
随着数十款AI写作工具涌入市场,选择合适的工具可能让人感到不知所措。本指南将帮助您拨开迷雾,找到最适合您的工具。
|
||||||
|
|
||||||
|
## 了解您的需求
|
||||||
|
|
||||||
|
在深入了解工具之前,先问问自己:
|
||||||
|
|
||||||
|
1. **您创作什么类型的内容?**
|
||||||
|
- 博客文章
|
||||||
|
- 营销文案
|
||||||
|
- 技术文档
|
||||||
|
- 社交媒体内容
|
||||||
|
- 邮件营销
|
||||||
|
|
||||||
|
2. **您的工作流程是怎样的?**
|
||||||
|
- 您需要独立工具还是集成方案?
|
||||||
|
- 协作有多重要?
|
||||||
|
- 您的预算是多少?
|
||||||
|
|
||||||
|
## 按使用场景推荐的顶级AI写作工具
|
||||||
|
|
||||||
|
### 长篇内容写作
|
||||||
|
|
||||||
|
**ChatGPT / Claude**
|
||||||
|
- 最适合头脑风暴和起草
|
||||||
|
- 出色的研究辅助能力
|
||||||
|
- 灵活的提示词适用于任何内容类型
|
||||||
|
|
||||||
|
**Jasper**
|
||||||
|
- 专为营销内容打造
|
||||||
|
- 各种内容类型的模板
|
||||||
|
- 品牌语调一致性功能
|
||||||
|
|
||||||
|
### 营销文案
|
||||||
|
|
||||||
|
**Copy.ai**
|
||||||
|
- 快速生成营销变体
|
||||||
|
- A/B测试建议
|
||||||
|
- 社交媒体帖子生成
|
||||||
|
|
||||||
|
**Writesonic**
|
||||||
|
- 落地页文案
|
||||||
|
- 产品描述
|
||||||
|
- 广告文案变体
|
||||||
|
|
||||||
|
### SEO内容
|
||||||
|
|
||||||
|
**Surfer SEO + AI**
|
||||||
|
- SEO优化内容建议
|
||||||
|
- 关键词整合指导
|
||||||
|
- 内容评分追踪
|
||||||
|
|
||||||
|
**Clearscope**
|
||||||
|
- 全面的内容优化
|
||||||
|
- 竞争对手分析
|
||||||
|
- 可读性评分
|
||||||
|
|
||||||
|
### 技术写作
|
||||||
|
|
||||||
|
**GitHub Copilot**
|
||||||
|
- 代码文档
|
||||||
|
- 技术说明
|
||||||
|
- API文档
|
||||||
|
|
||||||
|
**Notion AI**
|
||||||
|
- 集成工作空间写作
|
||||||
|
- 会议记录总结
|
||||||
|
- 技术规格说明
|
||||||
|
|
||||||
|
## 需要考虑的关键特性
|
||||||
|
|
||||||
|
### 1. 输出质量
|
||||||
|
并非所有AI写作质量相同。用您的具体用例测试每个工具。
|
||||||
|
|
||||||
|
### 2. 定制化
|
||||||
|
您能用品牌语调训练工具吗?它会从您的反馈中学习吗?
|
||||||
|
|
||||||
|
### 3. 集成性
|
||||||
|
它能与您现有的工具配合使用吗?考虑:
|
||||||
|
- Google Docs / Microsoft Word
|
||||||
|
- 内容管理系统
|
||||||
|
- SEO工具
|
||||||
|
|
||||||
|
### 4. 定价模式
|
||||||
|
- 按字数定价
|
||||||
|
- 月度订阅
|
||||||
|
- 基于使用量的积分
|
||||||
|
|
||||||
|
## 我们的推荐
|
||||||
|
|
||||||
|
| 使用场景 | 推荐工具 | 原因 |
|
||||||
|
|----------|----------|------|
|
||||||
|
| 通用写作 | ChatGPT Plus | 多功能性和质量 |
|
||||||
|
| 营销 | Jasper | 专门构建的功能 |
|
||||||
|
| SEO内容 | Surfer SEO | 专注优化 |
|
||||||
|
| 技术写作 | GitHub Copilot | 代码感知 |
|
||||||
|
| 快速文案 | Copy.ai | 速度和模板 |
|
||||||
|
|
||||||
|
## 入门建议
|
||||||
|
|
||||||
|
1. **从免费试用开始** - 大多数工具都提供试用
|
||||||
|
2. **用真实项目测试** - 使用您实际的内容需求
|
||||||
|
3. **比较输出结果** - 在不同工具中尝试相同的提示词
|
||||||
|
4. **考虑学习曲线** - 将熟练掌握的时间计算在内
|
||||||
|
|
||||||
|
## 结论
|
||||||
|
|
||||||
|
最好的AI写作工具是适合您特定工作流程和内容需求的工具。不要只跟风 - 彻底测试并根据您的要求做出选择。在 Toolso.AI 浏览我们完整的AI写作工具集合。
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
import { type Metadata } from "next";
|
||||||
|
import { getAllBlogs } from "@/lib/blog";
|
||||||
|
import { Background } from "@/components/background";
|
||||||
|
import { Container } from "@/components/container";
|
||||||
|
import { Heading } from "@/components/heading";
|
||||||
|
import { Subheading } from "@/components/subheading";
|
||||||
|
import { BlogCard } from "@/components/blog-card";
|
||||||
|
import { getTranslations } from 'next-intl/server';
|
||||||
|
import { type Locale } from '@/i18n.config';
|
||||||
|
import { generatePageMetadata } from "@/lib/metadata";
|
||||||
|
|
||||||
|
export async function generateMetadata({
|
||||||
|
params
|
||||||
|
}: {
|
||||||
|
params: Promise<{ locale: Locale }>
|
||||||
|
}): Promise<Metadata> {
|
||||||
|
const { locale } = await params;
|
||||||
|
const t = await getTranslations({ locale, namespace: 'blog' });
|
||||||
|
|
||||||
|
return generatePageMetadata({
|
||||||
|
locale,
|
||||||
|
path: '/blog',
|
||||||
|
title: t('title'),
|
||||||
|
description: t('subtitle'),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
interface PageProps {
|
||||||
|
params: Promise<{
|
||||||
|
locale: Locale;
|
||||||
|
}>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default async function ArticlesIndex({ params }: PageProps) {
|
||||||
|
const { locale } = await params;
|
||||||
|
const t = await getTranslations({ locale, namespace: 'blog' });
|
||||||
|
let blogs = await getAllBlogs(locale);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="relative overflow-hidden py-20 md:py-0">
|
||||||
|
<Background />
|
||||||
|
<Container className="flex flex-col items-center justify-between pb-20">
|
||||||
|
<div className="relative z-20 py-10 md:pt-40">
|
||||||
|
<Heading as="h1">{t('title')}</Heading>
|
||||||
|
<Subheading className="text-center">
|
||||||
|
{t('subtitle')}
|
||||||
|
</Subheading>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-10 relative z-20 w-full mb-10">
|
||||||
|
{blogs.slice(0, 2).map((blog, index) => (
|
||||||
|
<BlogCard blog={blog} key={blog.title + index} />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-3 gap-10 w-full relative z-20">
|
||||||
|
{blogs.slice(2).map((blog, index) => (
|
||||||
|
<BlogCard blog={blog} key={blog.title + index} />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</Container>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
import { notFound, redirect } from "next/navigation";
|
||||||
|
import { getCategoryBySlug } from "@/features/tools/actions";
|
||||||
|
import { getLocale } from "next-intl/server";
|
||||||
|
|
||||||
|
interface CategoryPageProps {
|
||||||
|
params: Promise<{
|
||||||
|
slug: string;
|
||||||
|
}>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function generateMetadata({ params }: CategoryPageProps) {
|
||||||
|
const { slug } = await params;
|
||||||
|
const category = await getCategoryBySlug(slug);
|
||||||
|
|
||||||
|
if (!category) {
|
||||||
|
return {
|
||||||
|
title: "Category Not Found",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
title: `${category.nameEn} - AI Tools`,
|
||||||
|
description: category.descriptionEn || `Explore ${category.nameEn} AI tools`,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export default async function CategoryPage({ params }: CategoryPageProps) {
|
||||||
|
const { slug } = await params;
|
||||||
|
const locale = await getLocale();
|
||||||
|
const category = await getCategoryBySlug(slug);
|
||||||
|
|
||||||
|
if (!category) {
|
||||||
|
notFound();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 重定向到工具列表页并带上分类参数
|
||||||
|
redirect(`/${locale}/tools?category=${slug}`);
|
||||||
|
}
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
import { Background } from "@/components/background";
|
||||||
|
import { Metadata } from "next";
|
||||||
|
import { FeaturedTestimonials } from "@/components/featured-testimonials";
|
||||||
|
import { cn } from "@/lib/utils";
|
||||||
|
import { HorizontalGradient } from "@/components/horizontal-gradient";
|
||||||
|
import { ContactForm } from "@/features/marketing/components/contact-form";
|
||||||
|
import { getTranslations } from 'next-intl/server';
|
||||||
|
import type { Locale } from "@/i18n.config";
|
||||||
|
import { generatePageMetadata } from "@/lib/metadata";
|
||||||
|
|
||||||
|
export async function generateMetadata({
|
||||||
|
params
|
||||||
|
}: {
|
||||||
|
params: Promise<{ locale: Locale }>
|
||||||
|
}): Promise<Metadata> {
|
||||||
|
const { locale } = await params;
|
||||||
|
const t = await getTranslations({ locale, namespace: 'contact' });
|
||||||
|
|
||||||
|
return generatePageMetadata({
|
||||||
|
locale,
|
||||||
|
path: '/contact',
|
||||||
|
title: t('title'),
|
||||||
|
description: t('subtitle'),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export default async function ContactPage({
|
||||||
|
params
|
||||||
|
}: {
|
||||||
|
params: Promise<{ locale: Locale }>;
|
||||||
|
}) {
|
||||||
|
const { locale } = await params;
|
||||||
|
const t = await getTranslations({ locale, namespace: 'contact' });
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="relative overflow-hidden py-20 md:py-0 px-4 md:px-20 bg-background">
|
||||||
|
<div className="w-full min-h-screen grid grid-cols-1 md:grid-cols-2 relative overflow-hidden">
|
||||||
|
<Background />
|
||||||
|
<ContactForm />
|
||||||
|
<div className="relative w-full z-20 hidden md:flex border-l border-border overflow-hidden bg-background items-center justify-center">
|
||||||
|
<div className="max-w-sm mx-auto">
|
||||||
|
<FeaturedTestimonials />
|
||||||
|
<p
|
||||||
|
className={cn(
|
||||||
|
"font-semibold text-xl text-center text-muted-foreground"
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{t('testimonial.title')}
|
||||||
|
</p>
|
||||||
|
<p
|
||||||
|
className={cn(
|
||||||
|
"font-normal text-base text-center text-foreground mt-8"
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{t('testimonial.description')}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<HorizontalGradient className="top-20" />
|
||||||
|
<HorizontalGradient className="bottom-20" />
|
||||||
|
<HorizontalGradient className="-right-80 transform rotate-90 inset-y-0 h-full scale-x-150" />
|
||||||
|
<HorizontalGradient className="-left-80 transform rotate-90 inset-y-0 h-full scale-x-150" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,254 @@
|
|||||||
|
/* eslint-disable react/no-unescaped-entities */
|
||||||
|
import { Metadata } from "next";
|
||||||
|
import { getTranslations } from 'next-intl/server';
|
||||||
|
import type { Locale } from "@/i18n.config";
|
||||||
|
|
||||||
|
export async function generateMetadata({
|
||||||
|
params
|
||||||
|
}: {
|
||||||
|
params: Promise<{ locale: Locale }>
|
||||||
|
}): Promise<Metadata> {
|
||||||
|
const { locale } = await params;
|
||||||
|
const t = await getTranslations({ locale, namespace: 'seo' });
|
||||||
|
|
||||||
|
return {
|
||||||
|
title: t('cookies.title'),
|
||||||
|
description: t('cookies.description'),
|
||||||
|
openGraph: {
|
||||||
|
images: [t('cookies.ogImage')],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function CookiesPage() {
|
||||||
|
return (
|
||||||
|
<div className="container mx-auto max-w-4xl px-4 py-16">
|
||||||
|
<div className="prose prose-gray dark:prose-invert max-w-none">
|
||||||
|
<h1 className="text-4xl font-bold mb-8">Cookie Policy</h1>
|
||||||
|
|
||||||
|
<p className="text-muted-foreground mb-8">
|
||||||
|
Effective Date: [Date]
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<section className="mb-8">
|
||||||
|
<h2 className="text-2xl font-semibold mb-4">1. Introduction</h2>
|
||||||
|
<p>
|
||||||
|
[Your Company Name] ("[Company]", "we", "our", or "us") uses cookies and similar tracking technologies on our website and services. This Cookie Policy explains what cookies are, how we use them, and your choices regarding their use.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
By continuing to use our website, you consent to our use of cookies as described in this policy. If you do not agree to our use of cookies, please adjust your browser settings or discontinue use of our website.
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section className="mb-8">
|
||||||
|
<h2 className="text-2xl font-semibold mb-4">2. What Are Cookies?</h2>
|
||||||
|
<p>
|
||||||
|
Cookies are small text files that are placed on your device (computer, smartphone, or tablet) when you visit a website. They are widely used to make websites work more efficiently and provide information to website owners.
|
||||||
|
</p>
|
||||||
|
<p className="mt-4">
|
||||||
|
Similar technologies include web beacons, pixels, local storage, and other tracking technologies. Throughout this policy, we refer to all of these technologies collectively as "cookies."
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section className="mb-8">
|
||||||
|
<h2 className="text-2xl font-semibold mb-4">3. Types of Cookies We Use</h2>
|
||||||
|
|
||||||
|
<h3 className="text-xl font-semibold mb-3">Essential Cookies</h3>
|
||||||
|
<p>
|
||||||
|
These cookies are necessary for the website to function properly. They enable core functionality such as security, authentication, and accessibility. You cannot opt-out of these cookies.
|
||||||
|
</p>
|
||||||
|
<ul className="list-disc pl-6 space-y-2">
|
||||||
|
<li><strong>Session cookies:</strong> Maintain your session state</li>
|
||||||
|
<li><strong>Authentication cookies:</strong> Keep you signed in</li>
|
||||||
|
<li><strong>Security cookies:</strong> Protect against CSRF attacks</li>
|
||||||
|
<li><strong>Load balancing cookies:</strong> Distribute traffic across servers</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h3 className="text-xl font-semibold mb-3 mt-4">Functional Cookies</h3>
|
||||||
|
<p>
|
||||||
|
These cookies enable enhanced functionality and personalization. They may be set by us or third-party providers whose services we use.
|
||||||
|
</p>
|
||||||
|
<ul className="list-disc pl-6 space-y-2">
|
||||||
|
<li><strong>Preference cookies:</strong> Remember your settings (language, theme)</li>
|
||||||
|
<li><strong>Feature cookies:</strong> Enable specific features you've requested</li>
|
||||||
|
<li><strong>Personalization cookies:</strong> Customize your experience</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h3 className="text-xl font-semibold mb-3 mt-4">Analytics and Performance Cookies</h3>
|
||||||
|
<p>
|
||||||
|
These cookies help us understand how visitors interact with our website by collecting and reporting information anonymously.
|
||||||
|
</p>
|
||||||
|
<ul className="list-disc pl-6 space-y-2">
|
||||||
|
<li><strong>Google Analytics:</strong> Track user behavior and site performance</li>
|
||||||
|
<li><strong>Performance monitoring:</strong> Identify site speed issues</li>
|
||||||
|
<li><strong>Error tracking:</strong> Log JavaScript errors and issues</li>
|
||||||
|
<li><strong>A/B testing:</strong> Test different versions of pages</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h3 className="text-xl font-semibold mb-3 mt-4">Marketing and Advertising Cookies</h3>
|
||||||
|
<p>
|
||||||
|
These cookies may be set through our site by advertising partners to build a profile of your interests and show relevant ads on other sites.
|
||||||
|
</p>
|
||||||
|
<ul className="list-disc pl-6 space-y-2">
|
||||||
|
<li><strong>Remarketing cookies:</strong> Show relevant ads on other platforms</li>
|
||||||
|
<li><strong>Social media cookies:</strong> Enable social sharing features</li>
|
||||||
|
<li><strong>Conversion tracking:</strong> Measure ad effectiveness</li>
|
||||||
|
</ul>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section className="mb-8">
|
||||||
|
<h2 className="text-2xl font-semibold mb-4">4. Specific Cookies We Use</h2>
|
||||||
|
<p>Here are the main cookies we use and their purposes:</p>
|
||||||
|
|
||||||
|
<div className="overflow-x-auto mt-4">
|
||||||
|
<table className="min-w-full border-collapse border border-border">
|
||||||
|
<thead>
|
||||||
|
<tr className="bg-muted">
|
||||||
|
<th className="border border-border px-4 py-2 text-left">Cookie Name</th>
|
||||||
|
<th className="border border-border px-4 py-2 text-left">Purpose</th>
|
||||||
|
<th className="border border-border px-4 py-2 text-left">Duration</th>
|
||||||
|
<th className="border border-border px-4 py-2 text-left">Type</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td className="border border-border px-4 py-2">session-token</td>
|
||||||
|
<td className="border border-border px-4 py-2">Authentication</td>
|
||||||
|
<td className="border border-border px-4 py-2">30 days</td>
|
||||||
|
<td className="border border-border px-4 py-2">Essential</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td className="border border-border px-4 py-2">csrf-token</td>
|
||||||
|
<td className="border border-border px-4 py-2">Security</td>
|
||||||
|
<td className="border border-border px-4 py-2">Session</td>
|
||||||
|
<td className="border border-border px-4 py-2">Essential</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td className="border border-border px-4 py-2">theme</td>
|
||||||
|
<td className="border border-border px-4 py-2">UI preferences</td>
|
||||||
|
<td className="border border-border px-4 py-2">1 year</td>
|
||||||
|
<td className="border border-border px-4 py-2">Functional</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td className="border border-border px-4 py-2">locale</td>
|
||||||
|
<td className="border border-border px-4 py-2">Language preference</td>
|
||||||
|
<td className="border border-border px-4 py-2">1 year</td>
|
||||||
|
<td className="border border-border px-4 py-2">Functional</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td className="border border-border px-4 py-2">_ga, _gid</td>
|
||||||
|
<td className="border border-border px-4 py-2">Google Analytics</td>
|
||||||
|
<td className="border border-border px-4 py-2">2 years / 24 hours</td>
|
||||||
|
<td className="border border-border px-4 py-2">Analytics</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section className="mb-8">
|
||||||
|
<h2 className="text-2xl font-semibold mb-4">5. Third-Party Cookies</h2>
|
||||||
|
<p>
|
||||||
|
We may use third-party services that set their own cookies, including:
|
||||||
|
</p>
|
||||||
|
<ul className="list-disc pl-6 space-y-2">
|
||||||
|
<li><strong>Authentication providers:</strong> Google, GitHub OAuth services</li>
|
||||||
|
<li><strong>Payment processors:</strong> Creem payment services</li>
|
||||||
|
<li><strong>Analytics providers:</strong> Google Analytics, monitoring services</li>
|
||||||
|
<li><strong>Content delivery networks:</strong> Cloudflare, CDN services</li>
|
||||||
|
<li><strong>Customer support:</strong> Support widget and chat services</li>
|
||||||
|
</ul>
|
||||||
|
<p className="mt-4">
|
||||||
|
These third parties have their own privacy policies addressing how they use such information.
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section className="mb-8">
|
||||||
|
<h2 className="text-2xl font-semibold mb-4">6. Your Cookie Choices</h2>
|
||||||
|
|
||||||
|
<h3 className="text-xl font-semibold mb-3">Browser Settings</h3>
|
||||||
|
<p>
|
||||||
|
Most web browsers allow you to control cookies through their settings. You can:
|
||||||
|
</p>
|
||||||
|
<ul className="list-disc pl-6 space-y-2">
|
||||||
|
<li>View what cookies are stored on your device</li>
|
||||||
|
<li>Delete cookies individually or entirely</li>
|
||||||
|
<li>Block third-party cookies</li>
|
||||||
|
<li>Block all cookies from specific websites</li>
|
||||||
|
<li>Block all cookies entirely</li>
|
||||||
|
<li>Set preferences for specific websites</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h3 className="text-xl font-semibold mb-3 mt-4">Cookie Management Instructions</h3>
|
||||||
|
<p>Here are links to cookie management instructions for popular browsers:</p>
|
||||||
|
<ul className="list-disc pl-6 space-y-2">
|
||||||
|
<li><strong>Chrome:</strong> Settings → Privacy and security → Cookies</li>
|
||||||
|
<li><strong>Firefox:</strong> Settings → Privacy & Security → Cookies</li>
|
||||||
|
<li><strong>Safari:</strong> Preferences → Privacy → Cookies</li>
|
||||||
|
<li><strong>Edge:</strong> Settings → Privacy, search, and services → Cookies</li>
|
||||||
|
<li><strong>Opera:</strong> Settings → Advanced → Privacy & security → Cookies</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h3 className="text-xl font-semibold mb-3 mt-4">Opt-Out Options</h3>
|
||||||
|
<p>
|
||||||
|
You can opt-out of specific types of cookies:
|
||||||
|
</p>
|
||||||
|
<ul className="list-disc pl-6 space-y-2">
|
||||||
|
<li><strong>Google Analytics:</strong> Install the Google Analytics Opt-out Browser Add-on</li>
|
||||||
|
<li><strong>Advertising cookies:</strong> Visit <a href="https://optout.aboutads.info" className="text-blue-600 hover:underline">optout.aboutads.info</a></li>
|
||||||
|
<li><strong>European users:</strong> Visit <a href="https://www.youronlinechoices.eu" className="text-blue-600 hover:underline">www.youronlinechoices.eu</a></li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h3 className="text-xl font-semibold mb-3 mt-4">Do Not Track</h3>
|
||||||
|
<p>
|
||||||
|
Some browsers offer a "Do Not Track" (DNT) setting. We currently do not respond to DNT signals, but we respect your cookie choices as described above.
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section className="mb-8">
|
||||||
|
<h2 className="text-2xl font-semibold mb-4">7. Impact of Disabling Cookies</h2>
|
||||||
|
<p>
|
||||||
|
Please note that if you choose to block or delete cookies:
|
||||||
|
</p>
|
||||||
|
<ul className="list-disc pl-6 space-y-2">
|
||||||
|
<li>Some parts of our website may not function properly</li>
|
||||||
|
<li>You may not be able to access certain features</li>
|
||||||
|
<li>You may need to re-enter information more frequently</li>
|
||||||
|
<li>Your preferences may not be saved</li>
|
||||||
|
<li>You may need to manually adjust settings each visit</li>
|
||||||
|
</ul>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section className="mb-8">
|
||||||
|
<h2 className="text-2xl font-semibold mb-4">8. Cookie Policy for Mobile Apps</h2>
|
||||||
|
<p>
|
||||||
|
If we offer mobile applications, they may use similar tracking technologies. Mobile platforms provide device-level settings to control these technologies. Please refer to your device manufacturer's instructions.
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section className="mb-8">
|
||||||
|
<h2 className="text-2xl font-semibold mb-4">9. Updates to This Cookie Policy</h2>
|
||||||
|
<p>
|
||||||
|
We may update this Cookie Policy from time to time to reflect changes in our practices or for legal, operational, or regulatory reasons. We will post any changes on this page with an updated effective date.
|
||||||
|
</p>
|
||||||
|
<p className="mt-4">
|
||||||
|
We encourage you to review this Cookie Policy periodically to stay informed about our use of cookies.
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section className="mb-8">
|
||||||
|
<h2 className="text-2xl font-semibold mb-4">10. Contact Us</h2>
|
||||||
|
<p>
|
||||||
|
If you have any questions about our use of cookies or this Cookie Policy, please contact us at:
|
||||||
|
</p>
|
||||||
|
<ul className="list-none space-y-2 mt-4">
|
||||||
|
<li>Email: [Privacy Email]</li>
|
||||||
|
<li>Support: [Support Email]</li>
|
||||||
|
<li>Website: [Your Website]</li>
|
||||||
|
<li>Address: [Your Address]</li>
|
||||||
|
</ul>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
import type { Metadata } from "next";
|
||||||
|
import { NavBar } from "@/features/navigation/components/navbar";
|
||||||
|
import { Footer } from "@/components/footer";
|
||||||
|
import { getTranslations } from 'next-intl/server';
|
||||||
|
import type { Locale } from "@/i18n.config";
|
||||||
|
|
||||||
|
export async function generateMetadata({
|
||||||
|
params
|
||||||
|
}: {
|
||||||
|
params: Promise<{ locale: Locale }>
|
||||||
|
}): Promise<Metadata> {
|
||||||
|
const { locale } = await params;
|
||||||
|
const t = await getTranslations({ locale, namespace: 'seo' });
|
||||||
|
|
||||||
|
return {
|
||||||
|
title: t('home.title'),
|
||||||
|
description: t('home.description'),
|
||||||
|
openGraph: {
|
||||||
|
images: [t('home.ogImage')],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function RootLayout({
|
||||||
|
children,
|
||||||
|
}: Readonly<{
|
||||||
|
children: React.ReactNode;
|
||||||
|
}>) {
|
||||||
|
return (
|
||||||
|
<main>
|
||||||
|
<NavBar />
|
||||||
|
{children}
|
||||||
|
<Footer />
|
||||||
|
</main>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,85 @@
|
|||||||
|
import { Container } from "@/components/container";
|
||||||
|
import { Hero } from "@/components/hero";
|
||||||
|
import { Background } from "@/components/background";
|
||||||
|
import { CTA } from "@/components/cta";
|
||||||
|
import { FeaturedTools } from "@/components/featured-tools";
|
||||||
|
import { WebSiteJsonLd, OrganizationJsonLd } from "@/components/json-ld";
|
||||||
|
import { Metadata } from "next";
|
||||||
|
import { getTranslations, getLocale } from 'next-intl/server';
|
||||||
|
import type { Locale } from "@/i18n.config";
|
||||||
|
import { generatePageMetadata } from "@/lib/metadata";
|
||||||
|
import { getPublishedTools, getCategoriesWithCount } from "@/features/tools/actions";
|
||||||
|
|
||||||
|
export async function generateMetadata({
|
||||||
|
params
|
||||||
|
}: {
|
||||||
|
params: Promise<{ locale: Locale }>;
|
||||||
|
}): Promise<Metadata> {
|
||||||
|
const { locale } = await params;
|
||||||
|
const t = await getTranslations({ locale, namespace: 'hero' });
|
||||||
|
|
||||||
|
return generatePageMetadata({
|
||||||
|
locale,
|
||||||
|
path: '',
|
||||||
|
title: t('title'),
|
||||||
|
description: t('description'),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export default async function Home() {
|
||||||
|
const locale = await getLocale();
|
||||||
|
const baseUrl = process.env.NEXT_PUBLIC_APP_URL || 'https://toolso.ai';
|
||||||
|
|
||||||
|
// 获取精选工具和分类数据
|
||||||
|
const [{ tools }, categories] = await Promise.all([
|
||||||
|
getPublishedTools({ pageSize: 8 }),
|
||||||
|
getCategoriesWithCount(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="relative">
|
||||||
|
{/* Structured Data for SEO */}
|
||||||
|
<WebSiteJsonLd
|
||||||
|
name="Toolso.AI"
|
||||||
|
description="Discover the best AI tools to boost your productivity"
|
||||||
|
url={`${baseUrl}/${locale}`}
|
||||||
|
locale={locale}
|
||||||
|
/>
|
||||||
|
<OrganizationJsonLd
|
||||||
|
name="Toolso.AI"
|
||||||
|
url={baseUrl}
|
||||||
|
logo={`${baseUrl}/banner.png`}
|
||||||
|
sameAs={[
|
||||||
|
'https://coolchange.ai',
|
||||||
|
'https://chatppt.app',
|
||||||
|
'https://aihealthguide.co',
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* Background 放在最外层,覆盖整个页面 */}
|
||||||
|
<div className="absolute inset-0 h-full w-full overflow-hidden">
|
||||||
|
<Background />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Hero 部分 */}
|
||||||
|
<Container className="flex flex-col items-center justify-center pt-32 pb-20 md:pt-40 md:pb-32">
|
||||||
|
<Hero />
|
||||||
|
</Container>
|
||||||
|
|
||||||
|
{/* 精选工具部分 */}
|
||||||
|
<div className="relative bg-background">
|
||||||
|
<Container>
|
||||||
|
<FeaturedTools tools={tools} categories={categories} />
|
||||||
|
</Container>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* CTA 部分 */}
|
||||||
|
<div className="relative">
|
||||||
|
<div className="absolute inset-0 h-full w-full overflow-hidden">
|
||||||
|
<Background />
|
||||||
|
</div>
|
||||||
|
<CTA />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,256 @@
|
|||||||
|
/* eslint-disable react/no-unescaped-entities */
|
||||||
|
import { Metadata } from "next";
|
||||||
|
import { getTranslations } from 'next-intl/server';
|
||||||
|
import type { Locale } from "@/i18n.config";
|
||||||
|
|
||||||
|
export async function generateMetadata({
|
||||||
|
params
|
||||||
|
}: {
|
||||||
|
params: Promise<{ locale: Locale }>
|
||||||
|
}): Promise<Metadata> {
|
||||||
|
const { locale } = await params;
|
||||||
|
const t = await getTranslations({ locale, namespace: 'seo' });
|
||||||
|
|
||||||
|
return {
|
||||||
|
title: t('privacy.title'),
|
||||||
|
description: t('privacy.description'),
|
||||||
|
openGraph: {
|
||||||
|
images: [t('privacy.ogImage')],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function PrivacyPage() {
|
||||||
|
return (
|
||||||
|
<div className="container mx-auto max-w-4xl px-4 py-16">
|
||||||
|
<div className="prose prose-gray dark:prose-invert max-w-none">
|
||||||
|
<h1 className="text-4xl font-bold mb-8">Privacy Policy</h1>
|
||||||
|
|
||||||
|
<p className="text-muted-foreground mb-8">
|
||||||
|
Effective Date: [Date]
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<section className="mb-8">
|
||||||
|
<h2 className="text-2xl font-semibold mb-4">1. Introduction</h2>
|
||||||
|
<p>
|
||||||
|
[Your Company Name] ("[Company]", "we", "our", or "us") is committed to protecting your privacy. This Privacy Policy explains how we collect, use, disclose, and safeguard your information when you visit our website and use our services.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Please read this privacy policy carefully. If you do not agree with the terms of this privacy policy, please do not access the site.
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section className="mb-8">
|
||||||
|
<h2 className="text-2xl font-semibold mb-4">2. Information We Collect</h2>
|
||||||
|
|
||||||
|
<h3 className="text-xl font-semibold mb-3">Personal Information</h3>
|
||||||
|
<p>We may collect personal information that you provide directly to us, such as:</p>
|
||||||
|
<ul className="list-disc pl-6 space-y-2">
|
||||||
|
<li>Name and contact information (email address, phone number)</li>
|
||||||
|
<li>Account credentials (username, password)</li>
|
||||||
|
<li>Payment information (processed through secure third-party providers)</li>
|
||||||
|
<li>Profile information (avatar, preferences)</li>
|
||||||
|
<li>Communications with us (support tickets, emails)</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h3 className="text-xl font-semibold mb-3 mt-4">Automatically Collected Information</h3>
|
||||||
|
<p>When you visit our Service, we automatically collect certain information, including:</p>
|
||||||
|
<ul className="list-disc pl-6 space-y-2">
|
||||||
|
<li>Device information (IP address, browser type, operating system)</li>
|
||||||
|
<li>Usage data (pages visited, time spent, clicks)</li>
|
||||||
|
<li>Cookies and similar tracking technologies</li>
|
||||||
|
<li>Log data (access times, referring URLs)</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h3 className="text-xl font-semibold mb-3 mt-4">Information from Third-Party Services</h3>
|
||||||
|
<p>If you authenticate using OAuth providers (Google, GitHub, etc.), we may receive:</p>
|
||||||
|
<ul className="list-disc pl-6 space-y-2">
|
||||||
|
<li>Basic profile information (name, email, profile picture)</li>
|
||||||
|
<li>Authentication tokens</li>
|
||||||
|
<li>Any additional information you authorize</li>
|
||||||
|
</ul>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section className="mb-8">
|
||||||
|
<h2 className="text-2xl font-semibold mb-4">3. How We Use Your Information</h2>
|
||||||
|
<p>We use the information we collect to:</p>
|
||||||
|
<ul className="list-disc pl-6 space-y-2">
|
||||||
|
<li>Provide, operate, and maintain our Service</li>
|
||||||
|
<li>Process transactions and manage your account</li>
|
||||||
|
<li>Send administrative information and updates</li>
|
||||||
|
<li>Respond to your comments, questions, and support requests</li>
|
||||||
|
<li>Improve and personalize your experience</li>
|
||||||
|
<li>Monitor and analyze usage patterns and trends</li>
|
||||||
|
<li>Develop new products, services, and features</li>
|
||||||
|
<li>Prevent fraud and enhance security</li>
|
||||||
|
<li>Comply with legal obligations</li>
|
||||||
|
<li>Send marketing communications (with your consent)</li>
|
||||||
|
</ul>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section className="mb-8">
|
||||||
|
<h2 className="text-2xl font-semibold mb-4">4. How We Share Your Information</h2>
|
||||||
|
<p>We may share your information in the following situations:</p>
|
||||||
|
|
||||||
|
<h3 className="text-xl font-semibold mb-3">With Service Providers</h3>
|
||||||
|
<p>We share information with third-party vendors who perform services on our behalf, such as:</p>
|
||||||
|
<ul className="list-disc pl-6 space-y-2">
|
||||||
|
<li>Payment processors (Creem, PayPal)</li>
|
||||||
|
<li>Cloud hosting providers</li>
|
||||||
|
<li>Analytics services</li>
|
||||||
|
<li>Customer support tools</li>
|
||||||
|
<li>Email service providers</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h3 className="text-xl font-semibold mb-3 mt-4">For Legal Purposes</h3>
|
||||||
|
<p>We may disclose your information when required by law or to:</p>
|
||||||
|
<ul className="list-disc pl-6 space-y-2">
|
||||||
|
<li>Comply with legal obligations</li>
|
||||||
|
<li>Respond to lawful requests from public authorities</li>
|
||||||
|
<li>Protect our rights, privacy, safety, or property</li>
|
||||||
|
<li>Enforce our terms and agreements</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h3 className="text-xl font-semibold mb-3 mt-4">Business Transfers</h3>
|
||||||
|
<p>
|
||||||
|
In the event of a merger, acquisition, reorganization, or sale of assets, your information may be transferred as part of that transaction.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h3 className="text-xl font-semibold mb-3 mt-4">With Your Consent</h3>
|
||||||
|
<p>
|
||||||
|
We may share your information for any other purpose with your explicit consent.
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section className="mb-8">
|
||||||
|
<h2 className="text-2xl font-semibold mb-4">5. Data Security</h2>
|
||||||
|
<p>
|
||||||
|
We implement appropriate technical and organizational security measures to protect your personal information, including:
|
||||||
|
</p>
|
||||||
|
<ul className="list-disc pl-6 space-y-2">
|
||||||
|
<li>Encryption of data in transit (SSL/TLS)</li>
|
||||||
|
<li>Encryption of sensitive data at rest</li>
|
||||||
|
<li>Regular security audits and assessments</li>
|
||||||
|
<li>Access controls and authentication requirements</li>
|
||||||
|
<li>Employee training on data protection</li>
|
||||||
|
<li>Incident response procedures</li>
|
||||||
|
</ul>
|
||||||
|
<p className="mt-4">
|
||||||
|
However, no method of transmission over the Internet or electronic storage is 100% secure. While we strive to protect your personal information, we cannot guarantee absolute security.
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section className="mb-8">
|
||||||
|
<h2 className="text-2xl font-semibold mb-4">6. Data Retention</h2>
|
||||||
|
<p>
|
||||||
|
We retain your personal information for as long as necessary to:
|
||||||
|
</p>
|
||||||
|
<ul className="list-disc pl-6 space-y-2">
|
||||||
|
<li>Provide our services to you</li>
|
||||||
|
<li>Comply with legal obligations</li>
|
||||||
|
<li>Resolve disputes and enforce agreements</li>
|
||||||
|
<li>Maintain business records</li>
|
||||||
|
</ul>
|
||||||
|
<p className="mt-4">
|
||||||
|
When we no longer need your information, we will securely delete or anonymize it in accordance with our data retention policies.
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section className="mb-8">
|
||||||
|
<h2 className="text-2xl font-semibold mb-4">7. Your Rights and Choices</h2>
|
||||||
|
<p>Depending on your location, you may have the following rights regarding your personal information:</p>
|
||||||
|
|
||||||
|
<h3 className="text-xl font-semibold mb-3">Access and Portability</h3>
|
||||||
|
<p>You can request access to your personal information and receive a copy in a structured, machine-readable format.</p>
|
||||||
|
|
||||||
|
<h3 className="text-xl font-semibold mb-3 mt-4">Correction and Update</h3>
|
||||||
|
<p>You can request that we correct or update inaccurate or incomplete personal information.</p>
|
||||||
|
|
||||||
|
<h3 className="text-xl font-semibold mb-3 mt-4">Deletion</h3>
|
||||||
|
<p>You can request deletion of your personal information, subject to certain exceptions.</p>
|
||||||
|
|
||||||
|
<h3 className="text-xl font-semibold mb-3 mt-4">Objection and Restriction</h3>
|
||||||
|
<p>You can object to or request that we restrict processing of your personal information.</p>
|
||||||
|
|
||||||
|
<h3 className="text-xl font-semibold mb-3 mt-4">Marketing Communications</h3>
|
||||||
|
<p>You can opt-out of marketing emails by clicking the unsubscribe link or contacting us directly.</p>
|
||||||
|
|
||||||
|
<h3 className="text-xl font-semibold mb-3 mt-4">Cookies</h3>
|
||||||
|
<p>You can manage cookie preferences through your browser settings. See our Cookie Policy for more information.</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section className="mb-8">
|
||||||
|
<h2 className="text-2xl font-semibold mb-4">8. International Data Transfers</h2>
|
||||||
|
<p>
|
||||||
|
Your information may be transferred to and processed in countries other than your country of residence. These countries may have different data protection laws than your country.
|
||||||
|
</p>
|
||||||
|
<p className="mt-4">
|
||||||
|
We take appropriate safeguards to ensure that your personal information remains protected in accordance with this Privacy Policy.
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section className="mb-8">
|
||||||
|
<h2 className="text-2xl font-semibold mb-4">9. California Privacy Rights (CCPA)</h2>
|
||||||
|
<p>
|
||||||
|
If you are a California resident, you have specific rights under the California Consumer Privacy Act (CCPA), including:
|
||||||
|
</p>
|
||||||
|
<ul className="list-disc pl-6 space-y-2">
|
||||||
|
<li>The right to know what personal information we collect, use, and disclose</li>
|
||||||
|
<li>The right to request deletion of your personal information</li>
|
||||||
|
<li>The right to opt-out of the sale of personal information (we do not sell personal information)</li>
|
||||||
|
<li>The right to non-discrimination for exercising your privacy rights</li>
|
||||||
|
</ul>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section className="mb-8">
|
||||||
|
<h2 className="text-2xl font-semibold mb-4">10. European Privacy Rights (GDPR)</h2>
|
||||||
|
<p>
|
||||||
|
If you are located in the European Economic Area (EEA) or United Kingdom, you have additional rights under the General Data Protection Regulation (GDPR), including:
|
||||||
|
</p>
|
||||||
|
<ul className="list-disc pl-6 space-y-2">
|
||||||
|
<li>The right to be informed about data processing</li>
|
||||||
|
<li>The right to rectification of inaccurate data</li>
|
||||||
|
<li>The right to erasure ("right to be forgotten")</li>
|
||||||
|
<li>The right to data portability</li>
|
||||||
|
<li>The right to object to processing</li>
|
||||||
|
<li>Rights related to automated decision-making</li>
|
||||||
|
<li>The right to lodge a complaint with supervisory authorities</li>
|
||||||
|
</ul>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section className="mb-8">
|
||||||
|
<h2 className="text-2xl font-semibold mb-4">11. Children's Privacy</h2>
|
||||||
|
<p>
|
||||||
|
Our Service is not intended for children under 18 years of age. We do not knowingly collect personal information from children under 18. If you become aware that a child has provided us with personal information, please contact us immediately.
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section className="mb-8">
|
||||||
|
<h2 className="text-2xl font-semibold mb-4">12. Changes to This Privacy Policy</h2>
|
||||||
|
<p>
|
||||||
|
We may update our Privacy Policy from time to time. We will notify you of any changes by posting the new Privacy Policy on this page and updating the "Effective Date" at the top.
|
||||||
|
</p>
|
||||||
|
<p className="mt-4">
|
||||||
|
For significant changes, we will provide additional notice through email or a prominent notice on our Service.
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section className="mb-8">
|
||||||
|
<h2 className="text-2xl font-semibold mb-4">13. Contact Us</h2>
|
||||||
|
<p>
|
||||||
|
If you have any questions or concerns about this Privacy Policy or our data practices, please contact us at:
|
||||||
|
</p>
|
||||||
|
<ul className="list-none space-y-2 mt-4">
|
||||||
|
<li>Email: [Privacy Email]</li>
|
||||||
|
<li>Support: [Support Email]</li>
|
||||||
|
<li>Website: [Your Website]</li>
|
||||||
|
<li>Address: [Your Address]</li>
|
||||||
|
</ul>
|
||||||
|
<p className="mt-4">
|
||||||
|
For data protection inquiries, you may also contact our Data Protection Officer at: [DPO Email]
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,175 @@
|
|||||||
|
/* eslint-disable react/no-unescaped-entities */
|
||||||
|
import { Metadata } from "next";
|
||||||
|
import { getTranslations } from 'next-intl/server';
|
||||||
|
import type { Locale } from "@/i18n.config";
|
||||||
|
|
||||||
|
export async function generateMetadata({
|
||||||
|
params
|
||||||
|
}: {
|
||||||
|
params: Promise<{ locale: Locale }>
|
||||||
|
}): Promise<Metadata> {
|
||||||
|
const { locale } = await params;
|
||||||
|
const t = await getTranslations({ locale, namespace: 'seo' });
|
||||||
|
|
||||||
|
return {
|
||||||
|
title: t('terms.title'),
|
||||||
|
description: t('terms.description'),
|
||||||
|
openGraph: {
|
||||||
|
images: [t('terms.ogImage')],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function TermsPage() {
|
||||||
|
return (
|
||||||
|
<div className="container mx-auto max-w-4xl px-4 py-16">
|
||||||
|
<div className="prose prose-gray dark:prose-invert max-w-none">
|
||||||
|
<h1 className="text-4xl font-bold mb-8">Terms of Service</h1>
|
||||||
|
|
||||||
|
<p className="text-muted-foreground mb-8">
|
||||||
|
Effective Date: [Date]
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<section className="mb-8">
|
||||||
|
<h2 className="text-2xl font-semibold mb-4">1. Introduction</h2>
|
||||||
|
<p>
|
||||||
|
Welcome to [Your Company Name] ("[Company]", "we", "our", or "us"). These Terms of Service ("Terms") govern your use of our website and services (collectively, the "Service") located at [Your Domain].
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
By accessing or using our Service, you agree to be bound by these Terms. If you disagree with any part of these terms, then you may not access the Service.
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section className="mb-8">
|
||||||
|
<h2 className="text-2xl font-semibold mb-4">2. Service Description</h2>
|
||||||
|
<p>
|
||||||
|
[Your Company Name] provides [describe your service/product]. Our Service allows you to:
|
||||||
|
</p>
|
||||||
|
<ul className="list-disc pl-6 space-y-2">
|
||||||
|
<li>[Feature/capability 1]</li>
|
||||||
|
<li>[Feature/capability 2]</li>
|
||||||
|
<li>[Feature/capability 3]</li>
|
||||||
|
<li>[Additional features as needed]</li>
|
||||||
|
</ul>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section className="mb-8">
|
||||||
|
<h2 className="text-2xl font-semibold mb-4">3. User Accounts</h2>
|
||||||
|
<p>
|
||||||
|
To access certain features of our Service, you may be required to create an account. When creating an account, you must:
|
||||||
|
</p>
|
||||||
|
<ul className="list-disc pl-6 space-y-2">
|
||||||
|
<li>Provide accurate, current, and complete information</li>
|
||||||
|
<li>Maintain the security of your password and account</li>
|
||||||
|
<li>Promptly update your account information to keep it accurate</li>
|
||||||
|
<li>Accept all risks of unauthorized access to your account</li>
|
||||||
|
<li>Be at least 18 years old or the age of legal consent in your jurisdiction</li>
|
||||||
|
</ul>
|
||||||
|
<p className="mt-4">
|
||||||
|
You are responsible for all activities that occur under your account. We reserve the right to refuse service, terminate accounts, or remove content at our sole discretion.
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section className="mb-8">
|
||||||
|
<h2 className="text-2xl font-semibold mb-4">4. Acceptable Use</h2>
|
||||||
|
<p>
|
||||||
|
You agree not to use the Service to:
|
||||||
|
</p>
|
||||||
|
<ul className="list-disc pl-6 space-y-2">
|
||||||
|
<li>Violate any laws or regulations</li>
|
||||||
|
<li>Infringe upon the rights of others</li>
|
||||||
|
<li>Upload or transmit viruses or malicious code</li>
|
||||||
|
<li>Engage in any activity that disrupts or interferes with the Service</li>
|
||||||
|
<li>Attempt to gain unauthorized access to any portion of the Service</li>
|
||||||
|
<li>Harass, abuse, or harm another person</li>
|
||||||
|
<li>Use the Service for any illegal or unauthorized purpose</li>
|
||||||
|
<li>Violate any applicable laws in your jurisdiction</li>
|
||||||
|
</ul>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section className="mb-8">
|
||||||
|
<h2 className="text-2xl font-semibold mb-4">5. Intellectual Property Rights</h2>
|
||||||
|
<p>
|
||||||
|
The Service and its original content, features, and functionality are and will remain the exclusive property of [Your Company Name] and its licensors. The Service is protected by copyright, trademark, and other laws. Our trademarks and trade dress may not be used in connection with any product or service without our prior written consent.
|
||||||
|
</p>
|
||||||
|
<p className="mt-4">
|
||||||
|
You retain ownership of any content you submit to the Service. By submitting content, you grant us a worldwide, non-exclusive, royalty-free license to use, reproduce, modify, and distribute your content in connection with operating and providing the Service.
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section className="mb-8">
|
||||||
|
<h2 className="text-2xl font-semibold mb-4">6. Service Usage</h2>
|
||||||
|
<p>
|
||||||
|
When using our Service, you agree to:
|
||||||
|
</p>
|
||||||
|
<ul className="list-disc pl-6 space-y-2">
|
||||||
|
<li>Use the Service in accordance with these Terms</li>
|
||||||
|
<li>Not attempt to circumvent any security measures</li>
|
||||||
|
<li>Not use automated systems to access the Service without permission</li>
|
||||||
|
<li>Report any security vulnerabilities you discover</li>
|
||||||
|
</ul>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section className="mb-8">
|
||||||
|
<h2 className="text-2xl font-semibold mb-4">7. Disclaimers and Limitations of Liability</h2>
|
||||||
|
<p>
|
||||||
|
THE SERVICE IS PROVIDED ON AN "AS IS" AND "AS AVAILABLE" BASIS. WE DISCLAIM ALL WARRANTIES, WHETHER EXPRESS OR IMPLIED, INCLUDING THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT.
|
||||||
|
</p>
|
||||||
|
<p className="mt-4">
|
||||||
|
IN NO EVENT SHALL [YOUR COMPANY NAME], ITS DIRECTORS, EMPLOYEES, PARTNERS, AGENTS, OR AFFILIATES BE LIABLE FOR ANY INDIRECT, INCIDENTAL, SPECIAL, CONSEQUENTIAL, OR PUNITIVE DAMAGES, INCLUDING WITHOUT LIMITATION, LOSS OF PROFITS, DATA, USE, GOODWILL, OR OTHER INTANGIBLE LOSSES.
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section className="mb-8">
|
||||||
|
<h2 className="text-2xl font-semibold mb-4">8. Indemnification</h2>
|
||||||
|
<p>
|
||||||
|
You agree to defend, indemnify, and hold harmless [Your Company Name] and its affiliates from and against any claims, liabilities, damages, judgments, awards, losses, costs, expenses, or fees arising out of or relating to your violation of these Terms or your use of the Service.
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section className="mb-8">
|
||||||
|
<h2 className="text-2xl font-semibold mb-4">9. Termination</h2>
|
||||||
|
<p>
|
||||||
|
We may terminate or suspend your account and access to the Service immediately, without prior notice or liability, for any reason whatsoever, including without limitation if you breach the Terms.
|
||||||
|
</p>
|
||||||
|
<p className="mt-4">
|
||||||
|
Upon termination, your right to use the Service will immediately cease. All provisions of the Terms which by their nature should survive termination shall survive termination.
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section className="mb-8">
|
||||||
|
<h2 className="text-2xl font-semibold mb-4">10. Privacy Policy</h2>
|
||||||
|
<p>
|
||||||
|
Your use of the Service is also governed by our Privacy Policy. Please review our Privacy Policy, which also governs the Site and informs users of our data collection practices.
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section className="mb-8">
|
||||||
|
<h2 className="text-2xl font-semibold mb-4">11. Changes to Terms</h2>
|
||||||
|
<p>
|
||||||
|
We reserve the right to modify or replace these Terms at any time. If a revision is material, we will provide at least 30 days notice prior to any new terms taking effect.
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section className="mb-8">
|
||||||
|
<h2 className="text-2xl font-semibold mb-4">12. Governing Law</h2>
|
||||||
|
<p>
|
||||||
|
These Terms shall be governed and construed in accordance with the laws of [Your Jurisdiction], without regard to its conflict of law provisions. Any legal action or proceeding arising under these Terms will be brought exclusively in courts located in [Your Jurisdiction].
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section className="mb-8">
|
||||||
|
<h2 className="text-2xl font-semibold mb-4">13. Contact Information</h2>
|
||||||
|
<p>
|
||||||
|
If you have any questions about these Terms, please contact us at:
|
||||||
|
</p>
|
||||||
|
<ul className="list-none space-y-2 mt-4">
|
||||||
|
<li>Email: [Your Email]</li>
|
||||||
|
<li>Website: [Your Website]</li>
|
||||||
|
<li>Address: [Your Address]</li>
|
||||||
|
</ul>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,229 @@
|
|||||||
|
import { notFound } from "next/navigation";
|
||||||
|
import Link from "next/link";
|
||||||
|
import { getToolBySlug } from "@/features/tools/actions";
|
||||||
|
import { getLocale, getTranslations } from "next-intl/server";
|
||||||
|
import { ExternalLink, ArrowLeft, Tag } from "lucide-react";
|
||||||
|
import { Button } from "@/components/button";
|
||||||
|
import { SoftwareApplicationJsonLd } from "@/components/json-ld";
|
||||||
|
|
||||||
|
interface ToolDetailPageProps {
|
||||||
|
params: Promise<{
|
||||||
|
slug: string;
|
||||||
|
}>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function generateMetadata({ params }: ToolDetailPageProps) {
|
||||||
|
const { slug } = await params;
|
||||||
|
const locale = await getLocale();
|
||||||
|
const t = await getTranslations("tools");
|
||||||
|
const tool = await getToolBySlug(slug);
|
||||||
|
|
||||||
|
if (!tool) {
|
||||||
|
return {
|
||||||
|
title: t("notFound"),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const name = locale === "zh" && tool.nameZh ? tool.nameZh : tool.nameEn;
|
||||||
|
const description =
|
||||||
|
locale === "zh" && tool.descriptionZh
|
||||||
|
? tool.descriptionZh
|
||||||
|
: tool.descriptionEn;
|
||||||
|
|
||||||
|
return {
|
||||||
|
title: `${name} - ${t("title")}`,
|
||||||
|
description: description || `${t("subtitle")} - ${name}`,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export default async function ToolDetailPage({ params }: ToolDetailPageProps) {
|
||||||
|
const { slug } = await params;
|
||||||
|
const locale = await getLocale();
|
||||||
|
const t = await getTranslations("tools");
|
||||||
|
const tool = await getToolBySlug(slug);
|
||||||
|
|
||||||
|
if (!tool) {
|
||||||
|
notFound();
|
||||||
|
}
|
||||||
|
|
||||||
|
const name = locale === "zh" && tool.nameZh ? tool.nameZh : tool.nameEn;
|
||||||
|
const description =
|
||||||
|
locale === "zh" && tool.descriptionZh
|
||||||
|
? tool.descriptionZh
|
||||||
|
: tool.descriptionEn;
|
||||||
|
|
||||||
|
const getCategoryName = (category: { nameEn: string; nameZh: string | null }) =>
|
||||||
|
locale === "zh" && category.nameZh ? category.nameZh : category.nameEn;
|
||||||
|
|
||||||
|
const getTagName = (tag: { nameEn: string; nameZh: string | null }) =>
|
||||||
|
locale === "zh" && tag.nameZh ? tag.nameZh : tag.nameEn;
|
||||||
|
|
||||||
|
const baseUrl = process.env.NEXT_PUBLIC_APP_URL || 'https://toolso.ai';
|
||||||
|
const primaryCategory = tool.categories?.[0]?.nameEn;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="container mx-auto px-4 pt-24 pb-12">
|
||||||
|
{/* Structured Data for AI Tool */}
|
||||||
|
<SoftwareApplicationJsonLd
|
||||||
|
name={name}
|
||||||
|
description={description || ''}
|
||||||
|
url={tool.websiteUrl || `${baseUrl}/${locale}/tools/${slug}`}
|
||||||
|
image={tool.logoUrl || tool.coverImageUrl || undefined}
|
||||||
|
category={primaryCategory}
|
||||||
|
applicationCategory="AI Tool"
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* 返回按钮 */}
|
||||||
|
<Link
|
||||||
|
href={`/${locale}/tools`}
|
||||||
|
className="inline-flex items-center gap-2 text-muted-foreground hover:text-foreground mb-8"
|
||||||
|
>
|
||||||
|
<ArrowLeft className="h-4 w-4" />
|
||||||
|
{t("backToTools")}
|
||||||
|
</Link>
|
||||||
|
|
||||||
|
<div className="grid grid-cols-1 lg:grid-cols-3 gap-12">
|
||||||
|
{/* 主内容区 */}
|
||||||
|
<div className="lg:col-span-2 space-y-8">
|
||||||
|
{/* 工具头部 */}
|
||||||
|
<div className="flex items-start gap-6">
|
||||||
|
{tool.logoUrl ? (
|
||||||
|
<img
|
||||||
|
src={tool.logoUrl}
|
||||||
|
alt={name}
|
||||||
|
className="h-20 w-20 rounded-2xl object-cover"
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<div className="h-20 w-20 rounded-2xl bg-secondary flex items-center justify-center">
|
||||||
|
<span className="text-3xl font-bold text-muted-foreground">
|
||||||
|
{name.charAt(0).toUpperCase()}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<div className="flex-1">
|
||||||
|
<h1 className="text-3xl font-bold text-foreground mb-2">
|
||||||
|
{name}
|
||||||
|
</h1>
|
||||||
|
{tool.domain && (
|
||||||
|
<p className="text-muted-foreground">{tool.domain}</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 封面图 */}
|
||||||
|
{tool.coverImageUrl && (
|
||||||
|
<div className="rounded-xl overflow-hidden border border-border">
|
||||||
|
<img
|
||||||
|
src={tool.coverImageUrl}
|
||||||
|
alt={name}
|
||||||
|
className="w-full h-auto"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* 描述 */}
|
||||||
|
{description && (
|
||||||
|
<div className="prose prose-neutral dark:prose-invert max-w-none">
|
||||||
|
<h2 className="text-xl font-semibold text-foreground mb-4">
|
||||||
|
{t("about")}
|
||||||
|
</h2>
|
||||||
|
<p className="text-muted-foreground leading-relaxed whitespace-pre-wrap">
|
||||||
|
{description}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* 分类 */}
|
||||||
|
{tool.categories && tool.categories.length > 0 && (
|
||||||
|
<div>
|
||||||
|
<h2 className="text-xl font-semibold text-foreground mb-4">
|
||||||
|
{t("categories")}
|
||||||
|
</h2>
|
||||||
|
<div className="flex flex-wrap gap-2">
|
||||||
|
{tool.categories.map((category) => (
|
||||||
|
<Link
|
||||||
|
key={category.id}
|
||||||
|
href={`/${locale}/tools?category=${category.slug}`}
|
||||||
|
className="px-4 py-2 rounded-lg bg-secondary text-foreground hover:bg-hover transition-colors"
|
||||||
|
>
|
||||||
|
{category.icon && (
|
||||||
|
<span className="mr-2">{category.icon}</span>
|
||||||
|
)}
|
||||||
|
{getCategoryName(category)}
|
||||||
|
</Link>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* 标签 */}
|
||||||
|
{tool.tags && tool.tags.length > 0 && (
|
||||||
|
<div>
|
||||||
|
<h2 className="text-xl font-semibold text-foreground mb-4">
|
||||||
|
{t("tags")}
|
||||||
|
</h2>
|
||||||
|
<div className="flex flex-wrap gap-2">
|
||||||
|
{tool.tags.map((tag) => (
|
||||||
|
<span
|
||||||
|
key={tag.id}
|
||||||
|
className="inline-flex items-center gap-1 px-3 py-1 rounded-full bg-secondary text-sm text-muted-foreground"
|
||||||
|
>
|
||||||
|
<Tag className="h-3 w-3" />
|
||||||
|
{getTagName(tag)}
|
||||||
|
</span>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 侧边栏 */}
|
||||||
|
<div className="lg:col-span-1">
|
||||||
|
<div className="sticky top-24 space-y-6">
|
||||||
|
{/* 访问按钮 */}
|
||||||
|
{tool.websiteUrl && (
|
||||||
|
<a
|
||||||
|
href={tool.websiteUrl}
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
className="w-full"
|
||||||
|
>
|
||||||
|
<Button className="w-full">
|
||||||
|
<ExternalLink className="h-4 w-4 mr-2" />
|
||||||
|
{t("visitButton")}
|
||||||
|
</Button>
|
||||||
|
</a>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* 工具信息卡片 */}
|
||||||
|
<div className="bg-secondary rounded-xl p-6 space-y-4">
|
||||||
|
<h3 className="font-semibold text-foreground">
|
||||||
|
{t("toolInfo")}
|
||||||
|
</h3>
|
||||||
|
|
||||||
|
{tool.domain && (
|
||||||
|
<div>
|
||||||
|
<p className="text-sm text-muted-foreground">
|
||||||
|
{t("domain")}
|
||||||
|
</p>
|
||||||
|
<p className="text-foreground">{tool.domain}</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<p className="text-sm text-muted-foreground">
|
||||||
|
{t("added")}
|
||||||
|
</p>
|
||||||
|
<p className="text-foreground">
|
||||||
|
{new Date(tool.createdAt).toLocaleDateString(
|
||||||
|
locale === "zh" ? "zh-CN" : "en-US"
|
||||||
|
)}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,95 @@
|
|||||||
|
import { getPublishedTools, getCategoriesWithCount } from "@/features/tools/actions";
|
||||||
|
import { ToolsList } from "@/features/tools/components/tools-list";
|
||||||
|
import { CollectionPageJsonLd } from "@/components/json-ld";
|
||||||
|
import { getLocale, getTranslations } from "next-intl/server";
|
||||||
|
|
||||||
|
export async function generateMetadata() {
|
||||||
|
const t = await getTranslations("seo.tools");
|
||||||
|
return {
|
||||||
|
title: t("title"),
|
||||||
|
description: t("description"),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ToolsPageProps {
|
||||||
|
searchParams: Promise<{
|
||||||
|
page?: string;
|
||||||
|
category?: string;
|
||||||
|
search?: string;
|
||||||
|
}>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default async function ToolsPage({ searchParams }: ToolsPageProps) {
|
||||||
|
const params = await searchParams;
|
||||||
|
const locale = await getLocale();
|
||||||
|
const t = await getTranslations("tools");
|
||||||
|
const page = parseInt(params.page || "1", 10);
|
||||||
|
const categorySlug = params.category;
|
||||||
|
const search = params.search;
|
||||||
|
|
||||||
|
// 获取分类列表用于筛选
|
||||||
|
const categories = await getCategoriesWithCount();
|
||||||
|
|
||||||
|
// 找到对应的 categoryId
|
||||||
|
let categoryId: string | undefined;
|
||||||
|
if (categorySlug) {
|
||||||
|
const category = categories.find((c) => c.slug === categorySlug);
|
||||||
|
categoryId = category?.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取工具列表
|
||||||
|
const { tools, total, pageSize } = await getPublishedTools({
|
||||||
|
page,
|
||||||
|
pageSize: 12,
|
||||||
|
categoryId,
|
||||||
|
search,
|
||||||
|
});
|
||||||
|
|
||||||
|
const totalPages = Math.ceil(total / pageSize);
|
||||||
|
|
||||||
|
// 准备翻译文本传递给客户端组件
|
||||||
|
const translations = {
|
||||||
|
title: t("title"),
|
||||||
|
subtitle: t("subtitle"),
|
||||||
|
searchPlaceholder: t("searchPlaceholder"),
|
||||||
|
all: t("all"),
|
||||||
|
foundTools: t("foundTools", { count: total }),
|
||||||
|
visitWebsite: t("visitWebsite"),
|
||||||
|
noTools: t("noTools"),
|
||||||
|
};
|
||||||
|
|
||||||
|
const baseUrl = process.env.NEXT_PUBLIC_APP_URL || 'https://toolso.ai';
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="container mx-auto px-4 py-12">
|
||||||
|
{/* Structured Data for AI Tools Directory */}
|
||||||
|
<CollectionPageJsonLd
|
||||||
|
name={translations.title}
|
||||||
|
description={translations.subtitle}
|
||||||
|
url={`${baseUrl}/${locale}/tools`}
|
||||||
|
itemCount={total}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div className="text-center mb-12">
|
||||||
|
<h1 className="text-4xl font-bold text-foreground mb-4">
|
||||||
|
{translations.title}
|
||||||
|
</h1>
|
||||||
|
<p className="text-lg text-muted-foreground max-w-2xl mx-auto">
|
||||||
|
{translations.subtitle}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<ToolsList
|
||||||
|
tools={tools}
|
||||||
|
categories={categories}
|
||||||
|
currentCategory={categorySlug}
|
||||||
|
currentSearch={search}
|
||||||
|
currentPage={page}
|
||||||
|
totalPages={totalPages}
|
||||||
|
total={total}
|
||||||
|
locale={locale}
|
||||||
|
translations={translations}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,140 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { useSession, signOut } from "@/lib/auth-client";
|
||||||
|
import { useRouter } from "next/navigation";
|
||||||
|
import { useLocale, useTranslations } from "next-intl";
|
||||||
|
import { Button } from "@/components/button";
|
||||||
|
import { Container } from "@/components/container";
|
||||||
|
import { Background } from "@/components/background";
|
||||||
|
import { motion } from "framer-motion";
|
||||||
|
|
||||||
|
export default function DashboardPage() {
|
||||||
|
const router = useRouter();
|
||||||
|
const session = useSession();
|
||||||
|
const locale = useLocale();
|
||||||
|
const t = useTranslations("dashboard");
|
||||||
|
const tCommon = useTranslations("common");
|
||||||
|
|
||||||
|
// Authentication is already handled in the layout
|
||||||
|
const user = session.data?.user;
|
||||||
|
|
||||||
|
if (!user) {
|
||||||
|
return (
|
||||||
|
<div className="relative min-h-screen">
|
||||||
|
<Background />
|
||||||
|
<Container className="relative z-10 py-20">
|
||||||
|
<div className="flex justify-center items-center h-64">
|
||||||
|
<p className="text-muted-foreground">Loading...</p>
|
||||||
|
</div>
|
||||||
|
</Container>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="relative min-h-screen">
|
||||||
|
<Background />
|
||||||
|
<Container className="relative z-10 py-20">
|
||||||
|
<motion.div
|
||||||
|
initial={{ y: 20, opacity: 0 }}
|
||||||
|
animate={{ y: 0, opacity: 1 }}
|
||||||
|
transition={{ ease: "easeOut", duration: 0.5 }}
|
||||||
|
>
|
||||||
|
<h1 className="text-4xl md:text-6xl font-bold text-foreground mb-4">
|
||||||
|
{t("title")}
|
||||||
|
</h1>
|
||||||
|
<p className="text-xl text-muted-foreground mb-12">
|
||||||
|
{t("welcome")}, {user?.name || user?.email}
|
||||||
|
</p>
|
||||||
|
</motion.div>
|
||||||
|
|
||||||
|
<motion.div
|
||||||
|
initial={{ y: 40, opacity: 0 }}
|
||||||
|
animate={{ y: 0, opacity: 1 }}
|
||||||
|
transition={{ ease: "easeOut", duration: 0.5, delay: 0.1 }}
|
||||||
|
className="grid grid-cols-1 md:grid-cols-2 gap-6"
|
||||||
|
>
|
||||||
|
{/* Personal Info Card */}
|
||||||
|
<div className="bg-card/50 backdrop-blur-md rounded-2xl p-6 border border-border">
|
||||||
|
<h3 className="text-xl font-semibold text-card-foreground mb-4">
|
||||||
|
{t("cards.personalInfo.title")}
|
||||||
|
</h3>
|
||||||
|
<div className="space-y-3">
|
||||||
|
<div className="flex flex-col">
|
||||||
|
<span className="text-sm text-muted-foreground">
|
||||||
|
{t("cards.personalInfo.labels.name")}
|
||||||
|
</span>
|
||||||
|
<span className="text-base font-medium text-card-foreground">
|
||||||
|
{user?.name || t("cards.personalInfo.labels.notSet")}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col">
|
||||||
|
<span className="text-sm text-muted-foreground">
|
||||||
|
{t("cards.personalInfo.labels.email")}
|
||||||
|
</span>
|
||||||
|
<span className="text-base font-medium text-card-foreground">
|
||||||
|
{user?.email}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col">
|
||||||
|
<span className="text-sm text-muted-foreground">
|
||||||
|
{t("cards.personalInfo.labels.status")}
|
||||||
|
</span>
|
||||||
|
<span className="text-base font-medium text-green-500">
|
||||||
|
{tCommon("status.verified")}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Quick Actions Card */}
|
||||||
|
<div className="bg-card/50 backdrop-blur-md rounded-2xl p-6 border border-border">
|
||||||
|
<h3 className="text-xl font-semibold text-card-foreground mb-4">
|
||||||
|
{t("cards.quickActions.title")}
|
||||||
|
</h3>
|
||||||
|
<div className="space-y-3">
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
className="w-full justify-start transition-colors"
|
||||||
|
onClick={() => router.push(`/${locale}/profile`)}
|
||||||
|
>
|
||||||
|
{t("cards.quickActions.editProfile")}
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
className="w-full justify-start transition-colors"
|
||||||
|
>
|
||||||
|
{t("cards.quickActions.accountSettings")}
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
className="w-full justify-start transition-colors"
|
||||||
|
>
|
||||||
|
{t("cards.quickActions.securitySettings")}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</motion.div>
|
||||||
|
|
||||||
|
<motion.div
|
||||||
|
initial={{ y: 40, opacity: 0 }}
|
||||||
|
animate={{ y: 0, opacity: 1 }}
|
||||||
|
transition={{ ease: "easeOut", duration: 0.5, delay: 0.3 }}
|
||||||
|
className="mt-12 flex justify-center"
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
onClick={async () => {
|
||||||
|
await signOut();
|
||||||
|
router.push("/");
|
||||||
|
router.refresh();
|
||||||
|
}}
|
||||||
|
variant="simple"
|
||||||
|
className="px-8"
|
||||||
|
>
|
||||||
|
Sign Out
|
||||||
|
</Button>
|
||||||
|
</motion.div>
|
||||||
|
</Container>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
import { EmailVerifiedGuard } from "@/features/auth/components/email-verified-guard";
|
||||||
|
import { NavBar } from "@/features/navigation/components/navbar";
|
||||||
|
|
||||||
|
export default function ProtectedLayout({
|
||||||
|
children,
|
||||||
|
}: {
|
||||||
|
children: React.ReactNode;
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<EmailVerifiedGuard requireEmailVerification={true}>
|
||||||
|
<main className="min-h-screen">
|
||||||
|
<NavBar />
|
||||||
|
{children}
|
||||||
|
</main>
|
||||||
|
</EmailVerifiedGuard>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,176 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { useSession } from "@/lib/auth-client";
|
||||||
|
import Image from "next/image";
|
||||||
|
import { useTranslations, useLocale } from "next-intl";
|
||||||
|
import { Container } from "@/components/container";
|
||||||
|
import { Background } from "@/components/background";
|
||||||
|
import { motion } from "framer-motion";
|
||||||
|
|
||||||
|
export default function ProfilePage() {
|
||||||
|
const session = useSession();
|
||||||
|
const locale = useLocale();
|
||||||
|
const t = useTranslations("profile");
|
||||||
|
const tCommon = useTranslations("common");
|
||||||
|
|
||||||
|
// Authentication is already handled in the layout
|
||||||
|
const user = session.data?.user;
|
||||||
|
const initial = user?.name
|
||||||
|
? user.name.charAt(0).toUpperCase()
|
||||||
|
: user?.email?.charAt(0).toUpperCase() || "U";
|
||||||
|
|
||||||
|
// Format registration date
|
||||||
|
const formatMemberSince = () => {
|
||||||
|
if (!user?.createdAt) return "-";
|
||||||
|
const date = new Date(user.createdAt);
|
||||||
|
return date.toLocaleDateString(locale === "zh" ? "zh-CN" : "en-US", {
|
||||||
|
year: "numeric",
|
||||||
|
month: "long",
|
||||||
|
day: "numeric",
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!user) {
|
||||||
|
return (
|
||||||
|
<div className="relative min-h-screen">
|
||||||
|
<Background />
|
||||||
|
<Container className="relative z-10 py-20">
|
||||||
|
<div className="flex justify-center items-center h-64">
|
||||||
|
<p className="text-muted-foreground">Loading...</p>
|
||||||
|
</div>
|
||||||
|
</Container>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="relative min-h-screen">
|
||||||
|
<Background />
|
||||||
|
<Container className="relative z-10 py-20">
|
||||||
|
<motion.div
|
||||||
|
initial={{ y: 20, opacity: 0 }}
|
||||||
|
animate={{ y: 0, opacity: 1 }}
|
||||||
|
transition={{ ease: "easeOut", duration: 0.5 }}
|
||||||
|
className="max-w-4xl mx-auto"
|
||||||
|
>
|
||||||
|
{/* Header Section */}
|
||||||
|
<div className="text-center mb-12">
|
||||||
|
<h1 className="text-4xl md:text-6xl font-bold text-foreground mb-4">
|
||||||
|
{t("title")}
|
||||||
|
</h1>
|
||||||
|
<p className="text-xl text-muted-foreground">{t("subtitle")}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Profile Card */}
|
||||||
|
<motion.div
|
||||||
|
initial={{ y: 40, opacity: 0 }}
|
||||||
|
animate={{ y: 0, opacity: 1 }}
|
||||||
|
transition={{ ease: "easeOut", duration: 0.5, delay: 0.1 }}
|
||||||
|
className="bg-card/50 backdrop-blur-md rounded-3xl p-8 border border-border"
|
||||||
|
>
|
||||||
|
{/* Avatar Section */}
|
||||||
|
<div className="flex flex-col items-center mb-10">
|
||||||
|
<motion.div
|
||||||
|
initial={{ scale: 0.8, opacity: 0 }}
|
||||||
|
animate={{ scale: 1, opacity: 1 }}
|
||||||
|
transition={{ ease: "easeOut", duration: 0.5, delay: 0.2 }}
|
||||||
|
className="w-32 h-32 rounded-full bg-gradient-to-br from-blue-500 to-purple-600 flex items-center justify-center text-white font-bold text-4xl mb-6 ring-4 ring-border/50"
|
||||||
|
>
|
||||||
|
{user?.image ? (
|
||||||
|
<Image
|
||||||
|
src={user.image}
|
||||||
|
alt={user?.name || "User"}
|
||||||
|
width={128}
|
||||||
|
height={128}
|
||||||
|
className="w-full h-full rounded-full object-cover"
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
initial
|
||||||
|
)}
|
||||||
|
</motion.div>
|
||||||
|
<h2 className="text-3xl font-bold text-card-foreground mb-2">
|
||||||
|
{user?.name || t("sections.basicInfo.nameNotSet")}
|
||||||
|
</h2>
|
||||||
|
<p className="text-lg text-muted-foreground">{user?.email}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Information Grid */}
|
||||||
|
<div className="grid md:grid-cols-2 gap-8">
|
||||||
|
{/* Basic Information */}
|
||||||
|
<motion.div
|
||||||
|
initial={{ x: -20, opacity: 0 }}
|
||||||
|
animate={{ x: 0, opacity: 1 }}
|
||||||
|
transition={{ ease: "easeOut", duration: 0.5, delay: 0.3 }}
|
||||||
|
className="space-y-6"
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
<h3 className="text-lg font-semibold text-card-foreground mb-4">
|
||||||
|
{t("sections.basicInfo.title")}
|
||||||
|
</h3>
|
||||||
|
<div className="space-y-4">
|
||||||
|
<div className="bg-muted/50 rounded-xl p-4">
|
||||||
|
<span className="text-sm text-muted-foreground block mb-1">
|
||||||
|
{t("sections.basicInfo.fullName")}
|
||||||
|
</span>
|
||||||
|
<span className="text-lg font-medium text-card-foreground">
|
||||||
|
{user?.name || t("sections.basicInfo.notSet")}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div className="bg-muted/50 rounded-xl p-4">
|
||||||
|
<span className="text-sm text-muted-foreground block mb-1">
|
||||||
|
{t("sections.basicInfo.email")}
|
||||||
|
</span>
|
||||||
|
<span className="text-lg font-medium text-card-foreground">
|
||||||
|
{user?.email}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div className="bg-muted/50 rounded-xl p-4">
|
||||||
|
<span className="text-sm text-muted-foreground block mb-1">
|
||||||
|
{t("sections.basicInfo.verificationStatus")}
|
||||||
|
</span>
|
||||||
|
<span className="inline-flex items-center px-3 py-1 rounded-full text-sm font-medium bg-green-500/10 text-green-600">
|
||||||
|
{tCommon("status.verified")}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</motion.div>
|
||||||
|
|
||||||
|
{/* Account Information */}
|
||||||
|
<motion.div
|
||||||
|
initial={{ x: 20, opacity: 0 }}
|
||||||
|
animate={{ x: 0, opacity: 1 }}
|
||||||
|
transition={{ ease: "easeOut", duration: 0.5, delay: 0.3 }}
|
||||||
|
className="space-y-6"
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
<h3 className="text-lg font-semibold text-card-foreground mb-4">
|
||||||
|
{t("sections.accountSettings.title")}
|
||||||
|
</h3>
|
||||||
|
<div className="space-y-4">
|
||||||
|
<div className="bg-muted/50 rounded-xl p-4">
|
||||||
|
<span className="text-sm text-muted-foreground block mb-1">
|
||||||
|
{t("sections.accountSettings.memberSince")}
|
||||||
|
</span>
|
||||||
|
<span className="text-lg font-medium text-card-foreground">
|
||||||
|
{formatMemberSince()}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div className="bg-muted/50 rounded-xl p-4">
|
||||||
|
<span className="text-sm text-muted-foreground block mb-1">
|
||||||
|
{t("sections.basicInfo.accountId")}
|
||||||
|
</span>
|
||||||
|
<span className="text-lg font-medium text-card-foreground font-mono">
|
||||||
|
{user?.id || "N/A"}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</motion.div>
|
||||||
|
</div>
|
||||||
|
</motion.div>
|
||||||
|
</motion.div>
|
||||||
|
</Container>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,130 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import { Mail, ArrowRight, RefreshCcw } from 'lucide-react';
|
||||||
|
import Link from 'next/link';
|
||||||
|
import { useState } from 'react';
|
||||||
|
import { useRouter } from 'next/navigation';
|
||||||
|
import { useLocale, useTranslations } from 'next-intl';
|
||||||
|
|
||||||
|
export default function CheckEmailPage() {
|
||||||
|
const router = useRouter();
|
||||||
|
const locale = useLocale();
|
||||||
|
const t = useTranslations('auth.checkEmail');
|
||||||
|
const [isResending, setIsResending] = useState(false);
|
||||||
|
const [resendMessage, setResendMessage] = useState('');
|
||||||
|
|
||||||
|
const handleResendEmail = async () => {
|
||||||
|
try {
|
||||||
|
setIsResending(true);
|
||||||
|
setResendMessage('');
|
||||||
|
|
||||||
|
const response = await fetch('/api/auth/resend-verification', {
|
||||||
|
method: 'POST',
|
||||||
|
credentials: 'include',
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
setResendMessage(t('resendSuccess'));
|
||||||
|
} else {
|
||||||
|
const data = await response.json();
|
||||||
|
setResendMessage(data.error || t('resendError'));
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
setResendMessage(t('resendError'));
|
||||||
|
} finally {
|
||||||
|
setIsResending(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="min-h-screen flex items-center justify-center bg-muted">
|
||||||
|
<div className="max-w-md w-full mx-auto p-6">
|
||||||
|
<div className="bg-card rounded-2xl shadow-lg border border-border p-8">
|
||||||
|
<div className="text-center">
|
||||||
|
{/* Email Icon with Animation */}
|
||||||
|
<div className="relative inline-block">
|
||||||
|
<div className="absolute inset-0 bg-muted-foreground/20 rounded-full opacity-20 animate-ping"></div>
|
||||||
|
<Mail className="w-20 h-20 mx-auto mb-6 text-card-foreground relative" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Title */}
|
||||||
|
<h1 className="text-3xl font-bold text-card-foreground mb-3">
|
||||||
|
{t('title')}
|
||||||
|
</h1>
|
||||||
|
|
||||||
|
{/* Description */}
|
||||||
|
<p className="text-muted-foreground mb-8 leading-relaxed">
|
||||||
|
{t('description')}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
{/* Email Illustration */}
|
||||||
|
<div className="bg-muted rounded-lg p-4 mb-6">
|
||||||
|
<p className="text-sm text-muted-foreground mb-2">
|
||||||
|
{t('notReceived')}
|
||||||
|
</p>
|
||||||
|
<ul className="text-left text-sm text-card-foreground space-y-2">
|
||||||
|
<li className="flex items-start">
|
||||||
|
<ArrowRight className="w-4 h-4 mr-2 mt-0.5 text-muted-foreground flex-shrink-0" />
|
||||||
|
<span>{t('checkSpam')}</span>
|
||||||
|
</li>
|
||||||
|
<li className="flex items-start">
|
||||||
|
<ArrowRight className="w-4 h-4 mr-2 mt-0.5 text-muted-foreground flex-shrink-0" />
|
||||||
|
<span>{t('checkEmailCorrect')}</span>
|
||||||
|
</li>
|
||||||
|
<li className="flex items-start">
|
||||||
|
<ArrowRight className="w-4 h-4 mr-2 mt-0.5 text-muted-foreground flex-shrink-0" />
|
||||||
|
<span>{t('waitAndCheck')}</span>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Resend Button */}
|
||||||
|
<button
|
||||||
|
onClick={handleResendEmail}
|
||||||
|
disabled={isResending}
|
||||||
|
className="w-full mb-4 px-6 py-3 bg-foreground hover:bg-foreground/90 disabled:bg-muted-foreground text-background font-medium rounded-lg transition-colors flex items-center justify-center"
|
||||||
|
>
|
||||||
|
{isResending ? (
|
||||||
|
<>
|
||||||
|
<RefreshCcw className="w-5 h-5 mr-2 animate-spin" />
|
||||||
|
{t('resending')}
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<RefreshCcw className="w-5 h-5 mr-2" />
|
||||||
|
{t('resendButton')}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
|
|
||||||
|
{/* Resend Message */}
|
||||||
|
{resendMessage && (
|
||||||
|
<p className={`text-sm mb-4 ${
|
||||||
|
resendMessage.includes('successfully')
|
||||||
|
? 'text-green-600'
|
||||||
|
: 'text-red-600'
|
||||||
|
}`}>
|
||||||
|
{resendMessage}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Back to Login */}
|
||||||
|
<Link
|
||||||
|
href={`/${locale}/login`}
|
||||||
|
className="text-sm text-muted-foreground hover:text-foreground transition-colors"
|
||||||
|
>
|
||||||
|
{t('backToLogin')}
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Security Note */}
|
||||||
|
<div className="mt-8 pt-6 border-t border-border">
|
||||||
|
<p className="text-xs text-center text-muted-foreground">
|
||||||
|
{t('securityNote')}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
import { NextIntlClientProvider } from "next-intl";
|
||||||
|
import { getMessages, setRequestLocale } from "next-intl/server";
|
||||||
|
import { notFound } from "next/navigation";
|
||||||
|
import { locales } from "@/i18n.config";
|
||||||
|
import { GeistSans } from "geist/font/sans";
|
||||||
|
import { cn } from "@/lib/utils";
|
||||||
|
import { ViewTransitions } from "next-view-transitions";
|
||||||
|
import { ThemeProvider } from "@/context/theme-provider";
|
||||||
|
import { Toaster } from "sonner";
|
||||||
|
import Analytics from "@/analytics/analytics";
|
||||||
|
import "../globals.css";
|
||||||
|
|
||||||
|
export default async function LocaleLayout({
|
||||||
|
children,
|
||||||
|
params
|
||||||
|
}: {
|
||||||
|
children: React.ReactNode;
|
||||||
|
params: Promise<{ locale: string }>;
|
||||||
|
}) {
|
||||||
|
const { locale } = await params;
|
||||||
|
|
||||||
|
if (!locales.includes(locale as any)) {
|
||||||
|
notFound();
|
||||||
|
}
|
||||||
|
|
||||||
|
setRequestLocale(locale);
|
||||||
|
|
||||||
|
const messages = await getMessages({ locale });
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ViewTransitions>
|
||||||
|
<html lang={locale} suppressHydrationWarning>
|
||||||
|
<body
|
||||||
|
className={cn(
|
||||||
|
GeistSans.className,
|
||||||
|
"bg-background text-foreground antialiased min-h-screen w-full"
|
||||||
|
)}
|
||||||
|
suppressHydrationWarning
|
||||||
|
>
|
||||||
|
<ThemeProvider
|
||||||
|
attribute="class"
|
||||||
|
enableSystem
|
||||||
|
disableTransitionOnChange
|
||||||
|
defaultTheme="light"
|
||||||
|
>
|
||||||
|
<NextIntlClientProvider locale={locale} messages={messages}>
|
||||||
|
{children}
|
||||||
|
<Toaster position="top-right" richColors />
|
||||||
|
<Analytics />
|
||||||
|
</NextIntlClientProvider>
|
||||||
|
</ThemeProvider>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
</ViewTransitions>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,143 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
import { useSearchParams, useRouter } from 'next/navigation';
|
||||||
|
import { CheckCircle2, XCircle, Loader2, Mail } from 'lucide-react';
|
||||||
|
import Link from 'next/link';
|
||||||
|
import { useLocale, useTranslations } from 'next-intl';
|
||||||
|
|
||||||
|
export default function VerifyEmailPage() {
|
||||||
|
const searchParams = useSearchParams();
|
||||||
|
const router = useRouter();
|
||||||
|
const locale = useLocale();
|
||||||
|
const t = useTranslations('auth.verifyEmail');
|
||||||
|
const [status, setStatus] = useState<'loading' | 'success' | 'error'>('loading');
|
||||||
|
const [message, setMessage] = useState('');
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const verifyEmail = async () => {
|
||||||
|
const token = searchParams.get('token');
|
||||||
|
|
||||||
|
if (!token) {
|
||||||
|
setStatus('error');
|
||||||
|
setMessage('Invalid verification link. Please check your email for the correct link.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(`/api/auth/verify-email?token=${token}`);
|
||||||
|
const data = await response.json();
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
setStatus('success');
|
||||||
|
setMessage('Your email has been successfully verified! Redirecting to login...');
|
||||||
|
|
||||||
|
// Redirect to login after 3 seconds
|
||||||
|
setTimeout(() => {
|
||||||
|
router.push(`/${locale}/login`);
|
||||||
|
}, 3000);
|
||||||
|
} else {
|
||||||
|
setStatus('error');
|
||||||
|
setMessage(data.error || 'Verification failed. The link may have expired.');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
setStatus('error');
|
||||||
|
setMessage('An error occurred while verifying your email. Please try again.');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
verifyEmail();
|
||||||
|
}, [searchParams, router, locale]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="min-h-screen flex items-center justify-center bg-gradient-to-br from-muted to-background">
|
||||||
|
<div className="max-w-md w-full mx-auto p-6">
|
||||||
|
<div className="bg-card rounded-2xl shadow-xl p-8 border border-border">
|
||||||
|
{status === 'loading' && (
|
||||||
|
<div className="text-center">
|
||||||
|
<Loader2 className="w-16 h-16 mx-auto mb-4 text-blue-500 animate-spin" />
|
||||||
|
<h1 className="text-2xl font-bold text-card-foreground mb-2">
|
||||||
|
{t('verifying')}
|
||||||
|
</h1>
|
||||||
|
<p className="text-muted-foreground">
|
||||||
|
{t('verifyingDescription')}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{status === 'success' && (
|
||||||
|
<div className="text-center">
|
||||||
|
<CheckCircle2 className="w-16 h-16 mx-auto mb-4 text-green-500" />
|
||||||
|
<h1 className="text-2xl font-bold text-card-foreground mb-2">
|
||||||
|
{t('success')}
|
||||||
|
</h1>
|
||||||
|
<p className="text-muted-foreground mb-6">
|
||||||
|
{message}
|
||||||
|
</p>
|
||||||
|
<Link
|
||||||
|
href={`/${locale}/login`}
|
||||||
|
className="inline-block px-6 py-3 bg-primary hover:bg-primary/90 text-primary-foreground font-medium rounded-lg transition-colors"
|
||||||
|
>
|
||||||
|
{t('goToLogin')}
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{status === 'error' && (
|
||||||
|
<div className="text-center">
|
||||||
|
{message.includes('already been used') || message.includes('already verified') ? (
|
||||||
|
<>
|
||||||
|
<CheckCircle2 className="w-16 h-16 mx-auto mb-4 text-blue-500" />
|
||||||
|
<h1 className="text-2xl font-bold text-card-foreground mb-2">
|
||||||
|
{t('alreadyVerified')}
|
||||||
|
</h1>
|
||||||
|
<p className="text-muted-foreground mb-6">
|
||||||
|
{message}
|
||||||
|
</p>
|
||||||
|
<Link
|
||||||
|
href={`/${locale}/login`}
|
||||||
|
className="block px-6 py-3 bg-primary hover:bg-primary/90 text-primary-foreground font-medium rounded-lg transition-colors"
|
||||||
|
>
|
||||||
|
{t('goToLogin')}
|
||||||
|
</Link>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<XCircle className="w-16 h-16 mx-auto mb-4 text-red-500" />
|
||||||
|
<h1 className="text-2xl font-bold text-card-foreground mb-2">
|
||||||
|
{t('failed')}
|
||||||
|
</h1>
|
||||||
|
<p className="text-muted-foreground mb-6">
|
||||||
|
{message}
|
||||||
|
</p>
|
||||||
|
<div className="space-y-3">
|
||||||
|
<Link
|
||||||
|
href={`/${locale}/login`}
|
||||||
|
className="block px-6 py-3 bg-primary hover:bg-primary/90 text-primary-foreground font-medium rounded-lg transition-colors"
|
||||||
|
>
|
||||||
|
{t('goToLogin')}
|
||||||
|
</Link>
|
||||||
|
<Link
|
||||||
|
href={`/${locale}/check-email`}
|
||||||
|
className="block px-6 py-3 border border-border hover:bg-muted text-card-foreground font-medium rounded-lg transition-colors"
|
||||||
|
>
|
||||||
|
{t('requestNew')}
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Additional Info */}
|
||||||
|
<div className="mt-8 pt-6 border-t border-border">
|
||||||
|
<div className="flex items-center justify-center text-sm text-muted-foreground">
|
||||||
|
<Mail className="w-4 h-4 mr-2" />
|
||||||
|
<span>{t('checkSpamNote')}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
import { auth } from "@/lib/auth";
|
||||||
|
import { toNextJsHandler } from "better-auth/next-js";
|
||||||
|
|
||||||
|
export const { POST, GET } = toNextJsHandler(auth);
|
||||||
@@ -0,0 +1,107 @@
|
|||||||
|
import { NextRequest, NextResponse } from "next/server";
|
||||||
|
import { db } from "@/lib/db";
|
||||||
|
import { user, verification, account } from "@/lib/db/schema";
|
||||||
|
import { eq, and } from "drizzle-orm";
|
||||||
|
import { sendPasswordResetEmail } from "@/lib/email";
|
||||||
|
import crypto from "crypto";
|
||||||
|
|
||||||
|
const VERIFICATION_TYPE = "password_reset";
|
||||||
|
|
||||||
|
export async function POST(request: NextRequest) {
|
||||||
|
try {
|
||||||
|
const { email } = await request.json();
|
||||||
|
|
||||||
|
if (!email) {
|
||||||
|
return NextResponse.json(
|
||||||
|
{ message: "Email is required" },
|
||||||
|
{ status: 400 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find user by email
|
||||||
|
const existingUser = await db
|
||||||
|
.select()
|
||||||
|
.from(user)
|
||||||
|
.where(eq(user.email, email))
|
||||||
|
.limit(1);
|
||||||
|
|
||||||
|
if (!existingUser || existingUser.length === 0) {
|
||||||
|
// Don't reveal if email exists or not
|
||||||
|
return NextResponse.json(
|
||||||
|
{ message: "If the email exists, a reset link has been sent." },
|
||||||
|
{ status: 200 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const foundUser = existingUser[0];
|
||||||
|
|
||||||
|
// Ensure user has a credential account before sending email
|
||||||
|
const credentialAccount = await db
|
||||||
|
.select({ id: account.id })
|
||||||
|
.from(account)
|
||||||
|
.where(
|
||||||
|
and(
|
||||||
|
eq(account.userId, foundUser.id),
|
||||||
|
eq(account.providerId, "credential")
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.limit(1);
|
||||||
|
|
||||||
|
if (credentialAccount.length === 0) {
|
||||||
|
// Return generic response to avoid leaking account type
|
||||||
|
return NextResponse.json(
|
||||||
|
{ message: "If the email exists, a reset link has been sent." },
|
||||||
|
{ status: 200 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate reset token
|
||||||
|
const resetToken = crypto.randomBytes(32).toString("hex");
|
||||||
|
const hashedToken = crypto
|
||||||
|
.createHash("sha256")
|
||||||
|
.update(resetToken)
|
||||||
|
.digest("hex");
|
||||||
|
|
||||||
|
// Set token expiry to 1 hour from now
|
||||||
|
const expiresAt = new Date(Date.now() + 60 * 60 * 1000);
|
||||||
|
|
||||||
|
// Remove any existing password reset tokens for the user
|
||||||
|
await db
|
||||||
|
.delete(verification)
|
||||||
|
.where(
|
||||||
|
and(
|
||||||
|
eq(verification.identifier, `${VERIFICATION_TYPE}:${foundUser.id}`),
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
// Store token in verification table
|
||||||
|
await db.insert(verification).values({
|
||||||
|
id: crypto.randomUUID(),
|
||||||
|
identifier: `${VERIFICATION_TYPE}:${foundUser.id}`,
|
||||||
|
value: hashedToken,
|
||||||
|
expiresAt,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Send reset email
|
||||||
|
const result = await sendPasswordResetEmail(email, resetToken);
|
||||||
|
|
||||||
|
if (!result.success) {
|
||||||
|
console.error("Failed to send password reset email:", result.error);
|
||||||
|
return NextResponse.json(
|
||||||
|
{ message: "Failed to send reset email. Please try again." },
|
||||||
|
{ status: 500 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return NextResponse.json(
|
||||||
|
{ message: "If the email exists, a reset link has been sent." },
|
||||||
|
{ status: 200 }
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Password reset error:", error);
|
||||||
|
return NextResponse.json(
|
||||||
|
{ message: "An error occurred. Please try again." },
|
||||||
|
{ status: 500 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,86 @@
|
|||||||
|
import { NextRequest, NextResponse } from "next/server";
|
||||||
|
import { db } from "@/lib/db";
|
||||||
|
import { user, verification } from "@/lib/db/schema";
|
||||||
|
import { eq } from "drizzle-orm";
|
||||||
|
import { sendVerificationEmail } from "@/lib/email";
|
||||||
|
import crypto from "crypto";
|
||||||
|
import { auth } from "@/lib/auth";
|
||||||
|
|
||||||
|
export async function POST(request: NextRequest) {
|
||||||
|
try {
|
||||||
|
// Get current session
|
||||||
|
const session = await auth.api.getSession({
|
||||||
|
headers: request.headers,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!session || !session.user) {
|
||||||
|
return NextResponse.json(
|
||||||
|
{ message: "Unauthorized" },
|
||||||
|
{ status: 401 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const userId = session.user.id;
|
||||||
|
|
||||||
|
// Check if user is already verified
|
||||||
|
const existingUser = await db
|
||||||
|
.select()
|
||||||
|
.from(user)
|
||||||
|
.where(eq(user.id, userId))
|
||||||
|
.limit(1);
|
||||||
|
|
||||||
|
if (!existingUser || existingUser.length === 0) {
|
||||||
|
return NextResponse.json(
|
||||||
|
{ message: "User not found" },
|
||||||
|
{ status: 404 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const currentUser = existingUser[0];
|
||||||
|
|
||||||
|
if (currentUser.emailVerified) {
|
||||||
|
return NextResponse.json(
|
||||||
|
{ message: "Email is already verified" },
|
||||||
|
{ status: 400 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate new verification token
|
||||||
|
const verificationToken = crypto.randomBytes(32).toString('hex');
|
||||||
|
const expiresAt = new Date(Date.now() + 24 * 60 * 60 * 1000); // 24 hours
|
||||||
|
|
||||||
|
// Remove existing verification token before inserting a new one
|
||||||
|
await db
|
||||||
|
.delete(verification)
|
||||||
|
.where(eq(verification.id, `email-verification:${userId}`));
|
||||||
|
|
||||||
|
// Store verification token
|
||||||
|
await db.insert(verification).values({
|
||||||
|
id: `email-verification:${userId}`,
|
||||||
|
identifier: "email-verification",
|
||||||
|
value: verificationToken,
|
||||||
|
expiresAt,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Send verification email
|
||||||
|
const result = await sendVerificationEmail(currentUser.email, verificationToken);
|
||||||
|
|
||||||
|
if (!result.success) {
|
||||||
|
return NextResponse.json(
|
||||||
|
{ message: "Failed to send verification email" },
|
||||||
|
{ status: 500 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return NextResponse.json(
|
||||||
|
{ message: "Verification email sent successfully" },
|
||||||
|
{ status: 200 }
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Resend verification error:", error);
|
||||||
|
return NextResponse.json(
|
||||||
|
{ message: "An error occurred. Please try again." },
|
||||||
|
{ status: 500 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,118 @@
|
|||||||
|
import { NextRequest, NextResponse } from "next/server";
|
||||||
|
import { db } from "@/lib/db";
|
||||||
|
import { verification, account } from "@/lib/db/schema";
|
||||||
|
import { eq, and, gt, like } from "drizzle-orm";
|
||||||
|
import crypto from "crypto";
|
||||||
|
import { hashPassword } from "@/lib/auth";
|
||||||
|
|
||||||
|
const VERIFICATION_TYPE = "password_reset";
|
||||||
|
|
||||||
|
export async function POST(request: NextRequest) {
|
||||||
|
try {
|
||||||
|
const { token, password } = await request.json();
|
||||||
|
|
||||||
|
if (!token || !password) {
|
||||||
|
return NextResponse.json(
|
||||||
|
{ message: "Token and password are required" },
|
||||||
|
{ status: 400 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (password.length < 8) {
|
||||||
|
return NextResponse.json(
|
||||||
|
{ message: "Password must be at least 8 characters" },
|
||||||
|
{ status: 400 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hash the token to match what's in database
|
||||||
|
const hashedToken = crypto
|
||||||
|
.createHash('sha256')
|
||||||
|
.update(token)
|
||||||
|
.digest('hex');
|
||||||
|
|
||||||
|
// Find valid token in verification table
|
||||||
|
const resetToken = await db
|
||||||
|
.select()
|
||||||
|
.from(verification)
|
||||||
|
.where(
|
||||||
|
and(
|
||||||
|
eq(verification.value, hashedToken),
|
||||||
|
like(verification.identifier, `${VERIFICATION_TYPE}:%`),
|
||||||
|
gt(verification.expiresAt, new Date())
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.limit(1);
|
||||||
|
|
||||||
|
if (!resetToken || resetToken.length === 0) {
|
||||||
|
return NextResponse.json(
|
||||||
|
{ message: "Invalid or expired token" },
|
||||||
|
{ status: 400 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const tokenData = resetToken[0];
|
||||||
|
// Extract userId from identifier (format: "password_reset:userId")
|
||||||
|
const userId = tokenData.identifier.replace(`${VERIFICATION_TYPE}:`, "");
|
||||||
|
|
||||||
|
// Ensure user has a credential account
|
||||||
|
const credentialAccount = await db
|
||||||
|
.select({ id: account.id })
|
||||||
|
.from(account)
|
||||||
|
.where(
|
||||||
|
and(
|
||||||
|
eq(account.userId, userId),
|
||||||
|
eq(account.providerId, "credential")
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.limit(1);
|
||||||
|
|
||||||
|
if (credentialAccount.length === 0) {
|
||||||
|
return NextResponse.json(
|
||||||
|
{ message: "Password reset is not available for this account" },
|
||||||
|
{ status: 400 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hash new password
|
||||||
|
const hashedPassword = await hashPassword(password);
|
||||||
|
|
||||||
|
// Update user's password in the account table and ensure the update succeeds
|
||||||
|
const updatedAccounts = await db
|
||||||
|
.update(account)
|
||||||
|
.set({
|
||||||
|
password: hashedPassword,
|
||||||
|
updatedAt: new Date(),
|
||||||
|
})
|
||||||
|
.where(
|
||||||
|
and(
|
||||||
|
eq(account.userId, userId),
|
||||||
|
eq(account.providerId, "credential")
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.returning({ id: account.id });
|
||||||
|
|
||||||
|
if (updatedAccounts.length === 0) {
|
||||||
|
return NextResponse.json(
|
||||||
|
{ message: "Failed to update password" },
|
||||||
|
{ status: 500 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete the used token
|
||||||
|
await db
|
||||||
|
.delete(verification)
|
||||||
|
.where(eq(verification.identifier, tokenData.identifier));
|
||||||
|
|
||||||
|
return NextResponse.json(
|
||||||
|
{ message: "Password reset successfully" },
|
||||||
|
{ status: 200 }
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Password reset error:", error);
|
||||||
|
return NextResponse.json(
|
||||||
|
{ message: "An error occurred. Please try again." },
|
||||||
|
{ status: 500 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,100 @@
|
|||||||
|
import { NextRequest, NextResponse } from "next/server";
|
||||||
|
import { db } from "@/lib/db";
|
||||||
|
import { user, verification } from "@/lib/db/schema";
|
||||||
|
import { eq, and, gt } from "drizzle-orm";
|
||||||
|
|
||||||
|
export async function GET(request: NextRequest) {
|
||||||
|
try {
|
||||||
|
const { searchParams } = new URL(request.url);
|
||||||
|
const token = searchParams.get("token");
|
||||||
|
|
||||||
|
if (!token) {
|
||||||
|
return NextResponse.json(
|
||||||
|
{ success: false, error: "Verification token is required" },
|
||||||
|
{ status: 400 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Log for debugging
|
||||||
|
console.log("Verifying token:", token);
|
||||||
|
|
||||||
|
// First, try to find the verification token
|
||||||
|
const verificationRecord = await db
|
||||||
|
.select()
|
||||||
|
.from(verification)
|
||||||
|
.where(
|
||||||
|
eq(verification.value, token)
|
||||||
|
)
|
||||||
|
.limit(1);
|
||||||
|
|
||||||
|
console.log("Verification record found:", verificationRecord);
|
||||||
|
|
||||||
|
// If no record found, check if it's because the user is already verified
|
||||||
|
if (!verificationRecord || verificationRecord.length === 0) {
|
||||||
|
// Token not found - could be already used or invalid
|
||||||
|
// Let's be more user-friendly with the error message
|
||||||
|
return NextResponse.json(
|
||||||
|
{
|
||||||
|
success: false,
|
||||||
|
error: "This verification link has already been used or is invalid. If your email is already verified, you can proceed to login."
|
||||||
|
},
|
||||||
|
{ status: 400 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if token is expired
|
||||||
|
const record = verificationRecord[0];
|
||||||
|
if (record.expiresAt < new Date()) {
|
||||||
|
return NextResponse.json(
|
||||||
|
{ success: false, error: "This verification link has expired. Please request a new one." },
|
||||||
|
{ status: 400 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if it's an email verification token
|
||||||
|
if (record.identifier !== "email-verification") {
|
||||||
|
return NextResponse.json(
|
||||||
|
{ success: false, error: "Invalid verification token type" },
|
||||||
|
{ status: 400 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract user ID from verification record
|
||||||
|
// The ID format should be "email-verification:userId"
|
||||||
|
const parts = record.id.split(":");
|
||||||
|
const userId = parts.length > 1 ? parts[1] : parts[0];
|
||||||
|
|
||||||
|
if (!userId) {
|
||||||
|
return NextResponse.json(
|
||||||
|
{ success: false, error: "Invalid verification token format" },
|
||||||
|
{ status: 400 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update user's email verification status
|
||||||
|
const updateResult = await db
|
||||||
|
.update(user)
|
||||||
|
.set({
|
||||||
|
emailVerified: true,
|
||||||
|
updatedAt: new Date()
|
||||||
|
})
|
||||||
|
.where(eq(user.id, userId));
|
||||||
|
|
||||||
|
// Delete the used verification token
|
||||||
|
await db
|
||||||
|
.delete(verification)
|
||||||
|
.where(eq(verification.id, record.id));
|
||||||
|
|
||||||
|
// Return success response (not redirect, as this is called via fetch)
|
||||||
|
return NextResponse.json(
|
||||||
|
{ success: true, message: "Email verified successfully" },
|
||||||
|
{ status: 200 }
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Email verification error:", error);
|
||||||
|
return NextResponse.json(
|
||||||
|
{ success: false, error: "An error occurred during verification" },
|
||||||
|
{ status: 500 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
import { NextRequest, NextResponse } from "next/server";
|
||||||
|
import { db } from "@/lib/db";
|
||||||
|
import { verification } from "@/lib/db/schema";
|
||||||
|
import { eq, and, gt, like } from "drizzle-orm";
|
||||||
|
import crypto from "crypto";
|
||||||
|
|
||||||
|
const VERIFICATION_TYPE = "password_reset";
|
||||||
|
|
||||||
|
export async function GET(request: NextRequest) {
|
||||||
|
try {
|
||||||
|
const { searchParams } = new URL(request.url);
|
||||||
|
const token = searchParams.get("token");
|
||||||
|
|
||||||
|
if (!token) {
|
||||||
|
return NextResponse.json(
|
||||||
|
{ valid: false, message: "Token is required" },
|
||||||
|
{ status: 400 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hash the token to match what's in database
|
||||||
|
const hashedToken = crypto
|
||||||
|
.createHash('sha256')
|
||||||
|
.update(token)
|
||||||
|
.digest('hex');
|
||||||
|
|
||||||
|
// Find valid token in verification table
|
||||||
|
const validToken = await db
|
||||||
|
.select()
|
||||||
|
.from(verification)
|
||||||
|
.where(
|
||||||
|
and(
|
||||||
|
eq(verification.value, hashedToken),
|
||||||
|
like(verification.identifier, `${VERIFICATION_TYPE}:%`),
|
||||||
|
gt(verification.expiresAt, new Date())
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.limit(1);
|
||||||
|
|
||||||
|
if (!validToken || validToken.length === 0) {
|
||||||
|
return NextResponse.json(
|
||||||
|
{ valid: false, message: "Invalid or expired token" },
|
||||||
|
{ status: 200 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return NextResponse.json(
|
||||||
|
{ valid: true },
|
||||||
|
{ status: 200 }
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Token verification error:", error);
|
||||||
|
return NextResponse.json(
|
||||||
|
{ valid: false, message: "An error occurred" },
|
||||||
|
{ status: 500 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,171 @@
|
|||||||
|
import { NextRequest, NextResponse } from "next/server";
|
||||||
|
import { auth } from "@/lib/auth";
|
||||||
|
import { uploadImageFromUrl } from "@/lib/r2-storage";
|
||||||
|
|
||||||
|
// Simple image dimension detection for common formats
|
||||||
|
async function getImageDimensions(buffer: Buffer, mimeType: string): Promise<{ width: number; height: number } | null> {
|
||||||
|
try {
|
||||||
|
// For JPEG
|
||||||
|
if (mimeType === 'image/jpeg' || mimeType === 'image/jpg') {
|
||||||
|
// JPEG starts with FF D8 FF
|
||||||
|
if (buffer[0] === 0xFF && buffer[1] === 0xD8) {
|
||||||
|
let offset = 2;
|
||||||
|
while (offset < buffer.length) {
|
||||||
|
if (buffer[offset] !== 0xFF) break;
|
||||||
|
|
||||||
|
const marker = buffer[offset + 1];
|
||||||
|
if (marker === 0xC0 || marker === 0xC2) { // SOF0 or SOF2
|
||||||
|
const height = buffer.readUInt16BE(offset + 5);
|
||||||
|
const width = buffer.readUInt16BE(offset + 7);
|
||||||
|
return { width, height };
|
||||||
|
}
|
||||||
|
|
||||||
|
offset += 2 + buffer.readUInt16BE(offset + 2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// For PNG
|
||||||
|
if (mimeType === 'image/png') {
|
||||||
|
// PNG starts with 89 50 4E 47
|
||||||
|
if (buffer[0] === 0x89 && buffer[1] === 0x50) {
|
||||||
|
const width = buffer.readUInt32BE(16);
|
||||||
|
const height = buffer.readUInt32BE(20);
|
||||||
|
return { width, height };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// For WebP
|
||||||
|
if (mimeType === 'image/webp') {
|
||||||
|
// WebP starts with RIFF
|
||||||
|
if (buffer.toString('utf8', 0, 4) === 'RIFF') {
|
||||||
|
const width = buffer.readUInt16LE(26) + 1;
|
||||||
|
const height = buffer.readUInt16LE(28) + 1;
|
||||||
|
return { width, height };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error detecting image dimensions:", error);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function POST(req: NextRequest) {
|
||||||
|
try {
|
||||||
|
// Authenticate user
|
||||||
|
const session = await auth.api.getSession({
|
||||||
|
headers: req.headers,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!session?.session?.userId) {
|
||||||
|
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
|
||||||
|
}
|
||||||
|
|
||||||
|
const userId = session.session.userId;
|
||||||
|
|
||||||
|
// Parse form data
|
||||||
|
const formData = await req.formData();
|
||||||
|
const file = formData.get('file') as File;
|
||||||
|
|
||||||
|
if (!file) {
|
||||||
|
return NextResponse.json({ error: "No file provided" }, { status: 400 });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check file type
|
||||||
|
if (!file.type.startsWith('image/')) {
|
||||||
|
return NextResponse.json({ error: "File must be an image" }, { status: 400 });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check file size (max 10MB)
|
||||||
|
if (file.size > 10 * 1024 * 1024) {
|
||||||
|
return NextResponse.json({ error: "File size must be less than 10MB" }, { status: 400 });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert File to buffer
|
||||||
|
const bytes = await file.arrayBuffer();
|
||||||
|
const buffer = Buffer.from(bytes);
|
||||||
|
|
||||||
|
// For video generation, check minimum image dimensions
|
||||||
|
// Volcano Engine requires minimum 300px height
|
||||||
|
try {
|
||||||
|
// Simple dimension check for common formats
|
||||||
|
const dimensions = await getImageDimensions(buffer, file.type);
|
||||||
|
if (dimensions && (dimensions.width < 300 || dimensions.height < 300)) {
|
||||||
|
return NextResponse.json({
|
||||||
|
error: "Image must be at least 300x300 pixels for video generation"
|
||||||
|
}, { status: 400 });
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.warn("Could not validate image dimensions:", err);
|
||||||
|
// Continue anyway if we can't determine dimensions
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate unique filename
|
||||||
|
const timestamp = Date.now();
|
||||||
|
const random = Math.random().toString(36).substring(7);
|
||||||
|
const extension = file.type.split('/')[1] || 'png';
|
||||||
|
const filename = `uploads/${userId}/${timestamp}_${random}.${extension}`;
|
||||||
|
|
||||||
|
// For now, save to a temporary location
|
||||||
|
// In production, you would upload to R2 or other storage
|
||||||
|
// Since we already have R2 configured, let's use it
|
||||||
|
const { S3Client, PutObjectCommand } = await import("@aws-sdk/client-s3");
|
||||||
|
|
||||||
|
const STORAGE_ACCESS_KEY_ID = process.env.STORAGE_ACCESS_KEY_ID || '';
|
||||||
|
const STORAGE_SECRET_ACCESS_KEY = process.env.STORAGE_SECRET_ACCESS_KEY || '';
|
||||||
|
const STORAGE_ENDPOINT = process.env.STORAGE_ENDPOINT || '';
|
||||||
|
const STORAGE_BUCKET_NAME = process.env.STORAGE_BUCKET_NAME || 'toolso-uploads';
|
||||||
|
const STORAGE_PUBLIC_URL = process.env.STORAGE_PUBLIC_URL || '';
|
||||||
|
|
||||||
|
if (!STORAGE_ACCESS_KEY_ID || !STORAGE_SECRET_ACCESS_KEY) {
|
||||||
|
// If R2 is not configured, return a data URL for testing
|
||||||
|
const base64 = buffer.toString('base64');
|
||||||
|
const dataUrl = `data:${file.type};base64,${base64}`;
|
||||||
|
return NextResponse.json({ url: dataUrl });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract endpoint URL from the full endpoint if needed
|
||||||
|
const getEndpointUrl = () => {
|
||||||
|
if (STORAGE_ENDPOINT.includes('.r2.cloudflarestorage.com')) {
|
||||||
|
const parts = STORAGE_ENDPOINT.split('/');
|
||||||
|
return parts[0] + '//' + parts[2];
|
||||||
|
}
|
||||||
|
return STORAGE_ENDPOINT;
|
||||||
|
};
|
||||||
|
|
||||||
|
const r2Client = new S3Client({
|
||||||
|
region: "auto",
|
||||||
|
endpoint: getEndpointUrl(),
|
||||||
|
credentials: {
|
||||||
|
accessKeyId: STORAGE_ACCESS_KEY_ID,
|
||||||
|
secretAccessKey: STORAGE_SECRET_ACCESS_KEY,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const command = new PutObjectCommand({
|
||||||
|
Bucket: STORAGE_BUCKET_NAME,
|
||||||
|
Key: filename,
|
||||||
|
Body: buffer,
|
||||||
|
ContentType: file.type,
|
||||||
|
});
|
||||||
|
|
||||||
|
await r2Client.send(command);
|
||||||
|
|
||||||
|
const publicUrl = `${STORAGE_PUBLIC_URL}/${filename}`;
|
||||||
|
|
||||||
|
return NextResponse.json({
|
||||||
|
url: publicUrl,
|
||||||
|
filename: file.name,
|
||||||
|
size: file.size,
|
||||||
|
type: file.type
|
||||||
|
});
|
||||||
|
|
||||||
|
} catch (error: any) {
|
||||||
|
console.error("Upload error:", error);
|
||||||
|
return NextResponse.json({
|
||||||
|
error: error.message || "Failed to upload file"
|
||||||
|
}, { status: 500 });
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
import { NextRequest, NextResponse } from "next/server";
|
||||||
|
import { auth } from "@/lib/auth";
|
||||||
|
|
||||||
|
export async function POST(req: NextRequest) {
|
||||||
|
try {
|
||||||
|
// Authenticate user
|
||||||
|
const session = await auth.api.getSession({
|
||||||
|
headers: req.headers,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!session?.session?.userId) {
|
||||||
|
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
|
||||||
|
}
|
||||||
|
|
||||||
|
const formData = await req.formData();
|
||||||
|
const file = formData.get('file') as File;
|
||||||
|
|
||||||
|
if (!file) {
|
||||||
|
return NextResponse.json({ error: "No file provided" }, { status: 400 });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check file type
|
||||||
|
if (!file.type.startsWith('image/')) {
|
||||||
|
return NextResponse.json({ error: "File must be an image" }, { status: 400 });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check file size (max 5MB)
|
||||||
|
if (file.size > 5 * 1024 * 1024) {
|
||||||
|
return NextResponse.json({ error: "File size must be less than 5MB" }, { status: 400 });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert to base64 data URL for temporary use
|
||||||
|
// This is a simple solution that works for small images
|
||||||
|
const bytes = await file.arrayBuffer();
|
||||||
|
const buffer = Buffer.from(bytes);
|
||||||
|
const base64 = buffer.toString('base64');
|
||||||
|
const dataUrl = `data:${file.type};base64,${base64}`;
|
||||||
|
|
||||||
|
// In production, you would upload to a proper storage service
|
||||||
|
// For now, we'll use a public image service or return the data URL
|
||||||
|
|
||||||
|
// Option 1: Use a free image hosting service (like imgur)
|
||||||
|
// Option 2: Return data URL (works for small images)
|
||||||
|
// Option 3: Use an existing public image for testing
|
||||||
|
|
||||||
|
// For testing, let's use a sample public image URL
|
||||||
|
// You can replace this with actual upload logic later
|
||||||
|
const testImageUrl = "https://ark-project.tos-cn-beijing.volces.com/doc_image/seepro_i2v.png";
|
||||||
|
|
||||||
|
console.log('Image uploaded (using test URL for now)');
|
||||||
|
|
||||||
|
return NextResponse.json({
|
||||||
|
url: testImageUrl, // Use test URL for now
|
||||||
|
originalName: file.name,
|
||||||
|
size: file.size,
|
||||||
|
type: file.type,
|
||||||
|
message: "Using test image URL for demo. In production, this would upload to real storage."
|
||||||
|
});
|
||||||
|
|
||||||
|
} catch (error: any) {
|
||||||
|
console.error("Upload error:", error);
|
||||||
|
return NextResponse.json({
|
||||||
|
error: error.message || "Failed to upload file"
|
||||||
|
}, { status: 500 });
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
import { auth } from "@/lib/auth";
|
||||||
|
import { db } from "@/lib/db";
|
||||||
|
import { user } from "@/lib/db/schema";
|
||||||
|
import { eq } from "drizzle-orm";
|
||||||
|
import { NextResponse } from "next/server";
|
||||||
|
import { headers } from "next/headers";
|
||||||
|
|
||||||
|
export async function GET() {
|
||||||
|
try {
|
||||||
|
const session = await auth.api.getSession({
|
||||||
|
headers: await headers(),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!session || !session.user) {
|
||||||
|
return NextResponse.json({ isAdmin: false });
|
||||||
|
}
|
||||||
|
|
||||||
|
const dbUser = await db
|
||||||
|
.select({ role: user.role })
|
||||||
|
.from(user)
|
||||||
|
.where(eq(user.id, session.user.id))
|
||||||
|
.limit(1);
|
||||||
|
|
||||||
|
const isAdmin = dbUser[0]?.role === "admin";
|
||||||
|
|
||||||
|
return NextResponse.json({ isAdmin });
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error checking admin status:", error);
|
||||||
|
return NextResponse.json({ isAdmin: false });
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
import { NextRequest, NextResponse } from "next/server";
|
||||||
|
import { auth } from "@/lib/auth";
|
||||||
|
import { db } from "@/lib/db";
|
||||||
|
import { user as userTable } from "@/lib/db/schema";
|
||||||
|
import { eq } from "drizzle-orm";
|
||||||
|
|
||||||
|
export async function GET(req: NextRequest) {
|
||||||
|
try {
|
||||||
|
// Get session from Better Auth
|
||||||
|
const session = await auth.api.getSession({
|
||||||
|
headers: req.headers,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!session?.session?.userId) {
|
||||||
|
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
|
||||||
|
}
|
||||||
|
|
||||||
|
const userId = session.session.userId;
|
||||||
|
|
||||||
|
// Get user profile
|
||||||
|
const users = await db
|
||||||
|
.select({
|
||||||
|
id: userTable.id,
|
||||||
|
name: userTable.name,
|
||||||
|
email: userTable.email,
|
||||||
|
emailVerified: userTable.emailVerified,
|
||||||
|
image: userTable.image,
|
||||||
|
createdAt: userTable.createdAt,
|
||||||
|
})
|
||||||
|
.from(userTable)
|
||||||
|
.where(eq(userTable.id, userId));
|
||||||
|
|
||||||
|
if (users.length === 0) {
|
||||||
|
return NextResponse.json({ error: "User not found" }, { status: 404 });
|
||||||
|
}
|
||||||
|
|
||||||
|
const user = users[0];
|
||||||
|
|
||||||
|
return NextResponse.json({ user });
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error fetching user profile:", error);
|
||||||
|
return NextResponse.json({ error: "Server error" }, { status: 500 });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
After Width: | Height: | Size: 2.9 KiB |
|
After Width: | Height: | Size: 4.2 KiB |
@@ -0,0 +1,106 @@
|
|||||||
|
@import "./prism.css";
|
||||||
|
|
||||||
|
@tailwind base;
|
||||||
|
@tailwind components;
|
||||||
|
@tailwind utilities;
|
||||||
|
|
||||||
|
@layer base {
|
||||||
|
:root {
|
||||||
|
/* 主要背景和前景色 */
|
||||||
|
--background: 0 0% 100%;
|
||||||
|
--foreground: 0 0% 3.9%;
|
||||||
|
|
||||||
|
/* 卡片容器 */
|
||||||
|
--card: 0 0% 100%;
|
||||||
|
--card-foreground: 0 0% 3.9%;
|
||||||
|
|
||||||
|
/* 弹出层 */
|
||||||
|
--popover: 0 0% 100%;
|
||||||
|
--popover-foreground: 0 0% 3.9%;
|
||||||
|
|
||||||
|
/* 主色调 (用于主要按钮、链接等) */
|
||||||
|
--primary: 0 0% 9%;
|
||||||
|
--primary-foreground: 0 0% 98%;
|
||||||
|
|
||||||
|
/* 次要色调 */
|
||||||
|
--secondary: 0 0% 96.1%;
|
||||||
|
--secondary-foreground: 0 0% 9%;
|
||||||
|
|
||||||
|
/* 柔和/禁用状态 */
|
||||||
|
--muted: 0 0% 96.1%;
|
||||||
|
--muted-foreground: 0 0% 35%;
|
||||||
|
|
||||||
|
/* 强调色 */
|
||||||
|
--accent: 0 0% 96.1%;
|
||||||
|
--accent-foreground: 0 0% 9%;
|
||||||
|
|
||||||
|
/* 边框、输入框、焦点环 */
|
||||||
|
--border: 0 0% 89.8%;
|
||||||
|
--input: 0 0% 89.8%;
|
||||||
|
--ring: 0 0% 3.9%;
|
||||||
|
|
||||||
|
/* 渐变色支持 */
|
||||||
|
--gradient-from: 0 0% 96.1%;
|
||||||
|
--gradient-to: 0 0% 89.8%;
|
||||||
|
|
||||||
|
/* 导航栏特定颜色 */
|
||||||
|
--navbar-bg: 0 0% 98%;
|
||||||
|
--navbar-bg-scrolled: 0 0% 96.1%;
|
||||||
|
|
||||||
|
/* 阴影颜色 (用于导航栏、卡片等) */
|
||||||
|
--shadow-color: 0 0% 89.8%;
|
||||||
|
|
||||||
|
/* 悬停状态 */
|
||||||
|
--hover-bg: 0 0% 96.1%;
|
||||||
|
--hover-foreground: 0 0% 0%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark {
|
||||||
|
--background: 0 0% 3.9%;
|
||||||
|
--foreground: 0 0% 98%;
|
||||||
|
|
||||||
|
--card: 0 0% 3.9%;
|
||||||
|
--card-foreground: 0 0% 98%;
|
||||||
|
|
||||||
|
--popover: 0 0% 3.9%;
|
||||||
|
--popover-foreground: 0 0% 98%;
|
||||||
|
|
||||||
|
--primary: 0 0% 98%;
|
||||||
|
--primary-foreground: 0 0% 9%;
|
||||||
|
|
||||||
|
--secondary: 0 0% 14.9%;
|
||||||
|
--secondary-foreground: 0 0% 98%;
|
||||||
|
|
||||||
|
--muted: 0 0% 14.9%;
|
||||||
|
--muted-foreground: 0 0% 70%;
|
||||||
|
|
||||||
|
--accent: 0 0% 14.9%;
|
||||||
|
--accent-foreground: 0 0% 98%;
|
||||||
|
|
||||||
|
--border: 0 0% 14.9%;
|
||||||
|
--input: 0 0% 14.9%;
|
||||||
|
--ring: 0 0% 83.1%;
|
||||||
|
|
||||||
|
--gradient-from: 0 0% 3.9%;
|
||||||
|
--gradient-to: 0 0% 14.9%;
|
||||||
|
|
||||||
|
--navbar-bg: 0 0% 11.8%;
|
||||||
|
--navbar-bg-scrolled: 0 0% 14.9%;
|
||||||
|
|
||||||
|
--shadow-color: 0 0% 14.9%;
|
||||||
|
|
||||||
|
--hover-bg: 0 0% 14.9%;
|
||||||
|
--hover-foreground: 0 0% 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
html {
|
||||||
|
scroll-padding-top: 80px;
|
||||||
|
scroll-behavior: smooth;
|
||||||
|
}
|
||||||
|
|
||||||
|
@layer utilities {
|
||||||
|
.text-balance {
|
||||||
|
text-wrap: balance;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<circle cx="16" cy="16" r="14" fill="#000"/>
|
||||||
|
<path d="M10 10h12M16 10v12" stroke="#FFF" stroke-width="3.5" stroke-linecap="round"/>
|
||||||
|
<circle cx="23" cy="9" r="2" fill="#FFF"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 284 B |
@@ -0,0 +1,9 @@
|
|||||||
|
import "./globals.css";
|
||||||
|
|
||||||
|
export default function RootLayout({
|
||||||
|
children,
|
||||||
|
}: {
|
||||||
|
children: React.ReactNode;
|
||||||
|
}) {
|
||||||
|
return children;
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
import type { MetadataRoute } from 'next'
|
||||||
|
|
||||||
|
export default function manifest(): MetadataRoute.Manifest {
|
||||||
|
return {
|
||||||
|
name: 'Toolso.AI - AI Tools Directory',
|
||||||
|
short_name: 'Toolso.AI',
|
||||||
|
description: 'Discover the best AI tools to boost your productivity. Browse our curated directory of AI tools across various categories.',
|
||||||
|
start_url: '/',
|
||||||
|
display: 'standalone',
|
||||||
|
background_color: '#ffffff',
|
||||||
|
theme_color: '#000000',
|
||||||
|
categories: ['productivity', 'utilities', 'business', 'education'],
|
||||||
|
icons: [
|
||||||
|
{
|
||||||
|
src: '/icon-192.png',
|
||||||
|
sizes: '192x192',
|
||||||
|
type: 'image/png',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
src: '/icon-512.png',
|
||||||
|
sizes: '512x512',
|
||||||
|
type: 'image/png',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
src: '/icon-512.png',
|
||||||
|
sizes: '512x512',
|
||||||
|
type: 'image/png',
|
||||||
|
purpose: 'maskable',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
import Link from 'next/link';
|
||||||
|
import { defaultLocale } from '@/i18n.config';
|
||||||
|
|
||||||
|
export default function NotFound() {
|
||||||
|
return (
|
||||||
|
<html>
|
||||||
|
<body>
|
||||||
|
<div style={{ textAlign: 'center', padding: '100px' }}>
|
||||||
|
<h1>404 - Page Not Found</h1>
|
||||||
|
<p>The page you are looking for doesn't exist.</p>
|
||||||
|
<Link href={`/${defaultLocale}`}>
|
||||||
|
Go to Homepage
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
pre[class*="language-"] {
|
||||||
|
color: theme("colors.zinc.100");
|
||||||
|
}
|
||||||
|
|
||||||
|
.token.tag,
|
||||||
|
.token.class-name,
|
||||||
|
.token.selector,
|
||||||
|
.token.selector .class,
|
||||||
|
.token.selector.class,
|
||||||
|
.token.function {
|
||||||
|
color: theme("colors.blue.400");
|
||||||
|
}
|
||||||
|
|
||||||
|
.token.attr-name,
|
||||||
|
.token.keyword,
|
||||||
|
.token.rule,
|
||||||
|
.token.pseudo-class,
|
||||||
|
.token.important {
|
||||||
|
color: theme("colors.slate.300");
|
||||||
|
}
|
||||||
|
|
||||||
|
.token.module {
|
||||||
|
color: theme("colors.emerald.400");
|
||||||
|
}
|
||||||
|
|
||||||
|
.token.attr-value,
|
||||||
|
.token.class,
|
||||||
|
.token.string,
|
||||||
|
.token.property {
|
||||||
|
color: theme("colors.teal.300");
|
||||||
|
}
|
||||||
|
|
||||||
|
.token.punctuation,
|
||||||
|
.token.attr-equals {
|
||||||
|
color: theme("colors.zinc.500");
|
||||||
|
}
|
||||||
|
|
||||||
|
.token.unit,
|
||||||
|
.language-css .token.function {
|
||||||
|
color: theme("colors.sky.200");
|
||||||
|
}
|
||||||
|
|
||||||
|
.token.comment,
|
||||||
|
.token.operator,
|
||||||
|
.token.combinator {
|
||||||
|
color: theme("colors.zinc.400");
|
||||||
|
}
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
import { MetadataRoute } from 'next'
|
||||||
|
|
||||||
|
export default function sitemap(): MetadataRoute.Sitemap {
|
||||||
|
const baseUrl = process.env.NEXT_PUBLIC_APP_URL || 'https://toolso.ai'
|
||||||
|
|
||||||
|
// Static routes with SEO priority for AI tools directory
|
||||||
|
const staticRoutes = [
|
||||||
|
{ path: '', changeFreq: 'daily' as const, priority: 1.0 },
|
||||||
|
{ path: '/tools', changeFreq: 'daily' as const, priority: 0.95 }, // AI tools directory - high priority
|
||||||
|
{ path: '/blog', changeFreq: 'weekly' as const, priority: 0.8 },
|
||||||
|
{ path: '/contact', changeFreq: 'monthly' as const, priority: 0.5 },
|
||||||
|
{ path: '/privacy', changeFreq: 'monthly' as const, priority: 0.3 },
|
||||||
|
{ path: '/terms', changeFreq: 'monthly' as const, priority: 0.3 },
|
||||||
|
{ path: '/cookies', changeFreq: 'monthly' as const, priority: 0.3 },
|
||||||
|
]
|
||||||
|
|
||||||
|
// Generate sitemap entries for both languages
|
||||||
|
const locales = ['zh', 'en']
|
||||||
|
const sitemapEntries: MetadataRoute.Sitemap = []
|
||||||
|
|
||||||
|
locales.forEach((locale) => {
|
||||||
|
staticRoutes.forEach((route) => {
|
||||||
|
sitemapEntries.push({
|
||||||
|
url: `${baseUrl}/${locale}${route.path}`,
|
||||||
|
lastModified: new Date(),
|
||||||
|
changeFrequency: route.changeFreq,
|
||||||
|
priority: route.priority,
|
||||||
|
alternates: {
|
||||||
|
languages: {
|
||||||
|
zh: `${baseUrl}/zh${route.path}`,
|
||||||
|
en: `${baseUrl}/en${route.path}`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
return sitemapEntries
|
||||||
|
}
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
"use client";
|
||||||
|
import Link from "next/link";
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
export const LogoIcon = () => {
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
width="24"
|
||||||
|
height="24"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
fill="none"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
className="h-6 w-6"
|
||||||
|
>
|
||||||
|
{/* 圆形背景 */}
|
||||||
|
<circle cx="12" cy="12" r="10" fill="currentColor" />
|
||||||
|
{/* 字母 T - 代表 Toolso */}
|
||||||
|
<path
|
||||||
|
d="M8 8h8M12 8v8"
|
||||||
|
stroke="hsl(var(--background))"
|
||||||
|
strokeWidth="2.5"
|
||||||
|
strokeLinecap="round"
|
||||||
|
/>
|
||||||
|
{/* AI 小星芒装饰 */}
|
||||||
|
<circle cx="17" cy="7" r="1.5" fill="hsl(var(--background))" />
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Logo = () => {
|
||||||
|
return (
|
||||||
|
<Link
|
||||||
|
href="/"
|
||||||
|
className="font-normal flex space-x-2 items-center text-sm mr-4 text-foreground px-2 py-1 relative z-20"
|
||||||
|
>
|
||||||
|
<LogoIcon />
|
||||||
|
<span className="font-medium text-foreground">Toolso.AI</span>
|
||||||
|
</Link>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -0,0 +1,130 @@
|
|||||||
|
"use client";
|
||||||
|
import { cn } from "@/lib/utils";
|
||||||
|
import { motion } from "framer-motion";
|
||||||
|
import { useTheme } from "next-themes";
|
||||||
|
import React, { useId } from "react";
|
||||||
|
|
||||||
|
export const Background = () => {
|
||||||
|
return (
|
||||||
|
<div className="absolute inset-0 h-full w-full overflow-hidden pointer-events-none z-0">
|
||||||
|
<div className="absolute inset-0 h-full w-full bg-background pointer-events-none [mask-image:radial-gradient(ellipse_at_center,transparent,white)]" />
|
||||||
|
{Array.from({ length: 6 }).map((_, index) => (
|
||||||
|
<div className="flex" key={"grid-column" + index}>
|
||||||
|
{Array.from({ length: 10 }).map((_, index) => (
|
||||||
|
<GridBlock key={`grid-row` + index} />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const GridBlock = () => {
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col items-start justify-center w-60">
|
||||||
|
<div className="flex items-center justify-center">
|
||||||
|
<Dot />
|
||||||
|
<SVG />
|
||||||
|
{/* <Dot /> */}
|
||||||
|
</div>
|
||||||
|
<SVGVertical className="ml-3" />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const Dot = () => {
|
||||||
|
return (
|
||||||
|
<div className="h-6 w-6 bg-background flex items-center justify-center rounded-full">
|
||||||
|
<div className="h-2 w-2 bg-muted rounded-full" />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const SVGVertical = ({ className }: { className?: string }) => {
|
||||||
|
const width = 1;
|
||||||
|
const height = 140;
|
||||||
|
|
||||||
|
const id = useId();
|
||||||
|
return (
|
||||||
|
<motion.svg
|
||||||
|
width={width}
|
||||||
|
height={height}
|
||||||
|
viewBox={`0 0 ${width} ${height}`}
|
||||||
|
fill="none"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
className={cn("text-border", className)}
|
||||||
|
>
|
||||||
|
<path d="M0.5 0.5V479" stroke="currentColor" strokeWidth={2} />
|
||||||
|
<motion.path
|
||||||
|
d="M0.5 0.5V479"
|
||||||
|
stroke={`url(#gradient-${id})`}
|
||||||
|
strokeWidth={2}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<defs>
|
||||||
|
<motion.linearGradient
|
||||||
|
id={`gradient-${id}`}
|
||||||
|
initial={{ x1: 2, y1: -200, x2: 2, y2: -100 }}
|
||||||
|
animate={{ x1: 2, y1: 400, x2: 2, y2: 600 }}
|
||||||
|
transition={{
|
||||||
|
repeat: Infinity,
|
||||||
|
duration: Math.random() * 2 + 1,
|
||||||
|
delay: Math.floor(Math.random() * 6) + 5,
|
||||||
|
}}
|
||||||
|
gradientUnits="userSpaceOnUse"
|
||||||
|
>
|
||||||
|
<motion.stop offset="0%" stopColor="transparent" />
|
||||||
|
<motion.stop offset="50%" stopColor="var(--neutral-200)" />
|
||||||
|
<motion.stop offset="100%" stopColor="transparent" />
|
||||||
|
</motion.linearGradient>
|
||||||
|
</defs>
|
||||||
|
</motion.svg>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const SVG = ({ className }: { className?: string }) => {
|
||||||
|
const width = 300;
|
||||||
|
const height = 1;
|
||||||
|
|
||||||
|
const id = useId();
|
||||||
|
return (
|
||||||
|
<motion.svg
|
||||||
|
width={width}
|
||||||
|
height={height}
|
||||||
|
viewBox={`0 0 ${width} ${height}`}
|
||||||
|
fill="none"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
className={cn("text-border", className)}
|
||||||
|
>
|
||||||
|
<path d="M0.5 0.5H479" stroke="currentColor" />
|
||||||
|
<motion.path
|
||||||
|
d="M0.5 0.5H479"
|
||||||
|
stroke={`url(#gradient-${id})`}
|
||||||
|
strokeWidth={1}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<defs>
|
||||||
|
<motion.linearGradient
|
||||||
|
id={`gradient-${id}`}
|
||||||
|
initial={{ x1: -200, y1: 0, x2: -100, y2: 0 }}
|
||||||
|
animate={{ x1: 400, y1: 0, x2: 600, y2: 0 }}
|
||||||
|
transition={{
|
||||||
|
repeat: Infinity,
|
||||||
|
duration: Math.random() * 2 + 1,
|
||||||
|
delay: Math.floor(Math.random() * 6) + 5,
|
||||||
|
}}
|
||||||
|
gradientUnits="userSpaceOnUse"
|
||||||
|
>
|
||||||
|
<motion.stop offset="0%" stopColor="transparent" />
|
||||||
|
<motion.stop offset="50%" stopColor="var(--neutral-200)" />
|
||||||
|
<motion.stop offset="100%" stopColor="transparent" />
|
||||||
|
</motion.linearGradient>
|
||||||
|
</defs>
|
||||||
|
</motion.svg>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Use the below rect to debug linear gradient
|
||||||
|
{
|
||||||
|
/* <motion.rect width={width} height={width} fill={`url(#gradient-${id})`} /> */
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
import React from "react";
|
||||||
|
|
||||||
|
export const Badge: React.FC<
|
||||||
|
{ children: React.ReactNode } & React.ComponentPropsWithoutRef<"button">
|
||||||
|
> = ({ children, ...props }) => {
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
{...props}
|
||||||
|
className="bg-secondary no-underline group cursor-pointer relative md:shadow-2xl rounded-full p-px text-[10px] sm:text-xs font-semibold leading-6 text-muted-foreground inline-block w-fit mx-auto"
|
||||||
|
>
|
||||||
|
<span className="absolute inset-0 overflow-hidden rounded-full">
|
||||||
|
<span className="absolute inset-0 rounded-full opacity-0 transition-opacity duration-500 group-hover:opacity-100" />
|
||||||
|
</span>
|
||||||
|
<div className="relative flex space-x-2 items-center z-10 rounded-full bg-muted py-1.5 px-4 ring-1 ring-border">
|
||||||
|
<span>{children}</span>
|
||||||
|
<svg
|
||||||
|
fill="none"
|
||||||
|
height="16"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
width="16"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M10.75 8.75L14.25 12L10.75 15.25"
|
||||||
|
stroke="currentColor"
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
strokeWidth="1.5"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<span className="absolute -bottom-0 left-[1.125rem] h-px w-[calc(100%-2.25rem)] bg-gradient-to-r from-muted-foreground/0 via-muted-foreground/90 to-muted-foreground/0 transition-opacity duration-500 group-hover:opacity-40" />
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
import { Link } from "next-view-transitions";
|
||||||
|
import React from "react";
|
||||||
|
import { BlurImage } from "./blur-image";
|
||||||
|
import { LogoIcon } from "./Logo";
|
||||||
|
import Image from "next/image";
|
||||||
|
import Balancer from "react-wrap-balancer";
|
||||||
|
import { BlogWithSlug } from "@/lib/blog";
|
||||||
|
|
||||||
|
export const BlogCard = ({ blog }: { blog: BlogWithSlug }) => {
|
||||||
|
const truncate = (text: string, length: number) => {
|
||||||
|
return text.length > length ? text.slice(0, length) + "..." : text;
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<Link
|
||||||
|
className="shadow-derek rounded-3xl border border-border w-full bg-card text-card-foreground overflow-hidden hover:scale-[1.02] transition duration-200"
|
||||||
|
href={`/blog/${blog.slug}`}
|
||||||
|
>
|
||||||
|
{blog.image ? (
|
||||||
|
<BlurImage
|
||||||
|
src={blog.image || ""}
|
||||||
|
alt={blog.title}
|
||||||
|
height="800"
|
||||||
|
width="800"
|
||||||
|
className="h-52 object-cover object-top w-full"
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<div className="h-52 flex items-center justify-center bg-card">
|
||||||
|
<LogoIcon />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<div className="p-4 md:p-8 bg-card">
|
||||||
|
<div className="flex space-x-2 items-center mb-2">
|
||||||
|
<Image
|
||||||
|
src={blog.author.src}
|
||||||
|
alt={blog.author.name}
|
||||||
|
width={20}
|
||||||
|
height={20}
|
||||||
|
className="rounded-full h-5 w-5"
|
||||||
|
unoptimized={blog.author.src.startsWith('http')}
|
||||||
|
/>
|
||||||
|
<p className="text-sm font-normal text-muted-foreground">{blog.author.name}</p>
|
||||||
|
</div>
|
||||||
|
<p className="text-lg font-bold mb-4 text-foreground">
|
||||||
|
<Balancer>{blog.title}</Balancer>
|
||||||
|
</p>
|
||||||
|
<p className="text-left text-sm mt-2 text-muted-foreground">
|
||||||
|
{truncate(blog.description, 100)}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</Link>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -0,0 +1,78 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { BlogWithSlug } from "@/lib/blog";
|
||||||
|
import { IconArrowLeft } from "@tabler/icons-react";
|
||||||
|
import { Container } from "./container";
|
||||||
|
import Image from "next/image";
|
||||||
|
import { Logo } from "./Logo";
|
||||||
|
import Link from "next/link";
|
||||||
|
import { format } from "date-fns";
|
||||||
|
export function BlogLayout({
|
||||||
|
blog,
|
||||||
|
children,
|
||||||
|
}: {
|
||||||
|
blog: BlogWithSlug;
|
||||||
|
children: React.ReactNode;
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<Container className="mt-16 lg:mt-32">
|
||||||
|
<div className="flex justify-between items-center px-2 py-8">
|
||||||
|
<Link href="/blog" className="flex space-x-2 items-center">
|
||||||
|
<IconArrowLeft className="w-4 h-4 text-muted-foreground" />
|
||||||
|
<span className="text-sm text-muted-foreground">Back</span>
|
||||||
|
</Link>
|
||||||
|
<div className="flex space-x-2 items-center">
|
||||||
|
<div className="flex space-x-2 items-center ">
|
||||||
|
<Image
|
||||||
|
src={blog.author.src}
|
||||||
|
alt={blog.author.name}
|
||||||
|
width={20}
|
||||||
|
height={20}
|
||||||
|
className="rounded-full h-5 w-5"
|
||||||
|
/>
|
||||||
|
<p className="text-sm font-normal text-muted-foreground">
|
||||||
|
{blog.author.name}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div className="h-5 rounded-lg w-0.5 bg-border" />
|
||||||
|
<time dateTime={blog.date} className="flex items-center text-base ">
|
||||||
|
<span className="text-muted-foreground text-sm">
|
||||||
|
{format(new Date(blog.date), "MMMM dd, yyyy")}
|
||||||
|
</span>
|
||||||
|
</time>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="max-w-4xl mx-auto">
|
||||||
|
{blog.image ? (
|
||||||
|
// eslint-disable-next-line @next/next/no-img-element
|
||||||
|
<img
|
||||||
|
src={blog.image}
|
||||||
|
className="h-40 md:h-96 w-full aspect-square object-cover rounded-3xl"
|
||||||
|
alt={blog.title}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<div className="h-40 md:h-96 w-full aspect-square rounded-3xl shadow-derek bg-card flex items-center justify-center">
|
||||||
|
<Logo />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div className="xl:relative">
|
||||||
|
<div className="mx-auto max-w-2xl">
|
||||||
|
<article className="pb-8">
|
||||||
|
<header className="flex flex-col">
|
||||||
|
<h1 className="mt-8 text-4xl font-bold tracking-tight text-foreground sm:text-5xl ">
|
||||||
|
{blog.title}
|
||||||
|
</h1>
|
||||||
|
</header>
|
||||||
|
<div
|
||||||
|
className="mt-8 prose prose-sm dark:prose-invert"
|
||||||
|
data-mdx-content
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Container>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import clsx from "clsx";
|
||||||
|
import Image from "next/image";
|
||||||
|
import React, { useState } from "react";
|
||||||
|
|
||||||
|
interface IBlurImage {
|
||||||
|
height?: any;
|
||||||
|
width?: any;
|
||||||
|
src?: string | any;
|
||||||
|
objectFit?: any;
|
||||||
|
className?: string | any;
|
||||||
|
alt?: string | undefined;
|
||||||
|
[x: string]: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const BlurImage = ({
|
||||||
|
height,
|
||||||
|
width,
|
||||||
|
src,
|
||||||
|
className,
|
||||||
|
objectFit,
|
||||||
|
alt,
|
||||||
|
...rest
|
||||||
|
}: IBlurImage) => {
|
||||||
|
const [isLoading, setLoading] = useState(true);
|
||||||
|
// 判断是否为远程图片
|
||||||
|
const isRemoteImage = typeof src === 'string' && src.startsWith('http');
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Image
|
||||||
|
className={clsx(
|
||||||
|
"transition duration-300 transform",
|
||||||
|
isLoading ? "blur-sm scale-105" : "blur-0 scale-100",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
onLoad={() => setLoading(false)}
|
||||||
|
src={src}
|
||||||
|
width={width}
|
||||||
|
height={height}
|
||||||
|
loading="lazy"
|
||||||
|
decoding="async"
|
||||||
|
blurDataURL={isRemoteImage ? undefined : src}
|
||||||
|
alt={alt ? alt : "Avatar"}
|
||||||
|
unoptimized={isRemoteImage}
|
||||||
|
{...rest}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
import { cn } from "@/lib/utils";
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
export const Button: React.FC<{
|
||||||
|
children?: React.ReactNode;
|
||||||
|
className?: string;
|
||||||
|
variant?: "simple" | "outline" | "primary";
|
||||||
|
size?: "sm" | "md" | "lg";
|
||||||
|
as?: React.ElementType;
|
||||||
|
[x: string]: any;
|
||||||
|
}> = ({
|
||||||
|
children,
|
||||||
|
className,
|
||||||
|
variant = "primary",
|
||||||
|
size = "md",
|
||||||
|
as: Tag = "button",
|
||||||
|
...props
|
||||||
|
}) => {
|
||||||
|
const sizeClass =
|
||||||
|
size === "sm"
|
||||||
|
? "text-xs px-3 py-1.5"
|
||||||
|
: size === "lg"
|
||||||
|
? "text-base px-6 py-3"
|
||||||
|
: "text-sm md:text-sm px-4 py-2";
|
||||||
|
|
||||||
|
const variantClass =
|
||||||
|
variant === "simple"
|
||||||
|
? "bg-transparent hover:bg-hover border border-transparent text-foreground hover:text-hover-foreground transition font-medium duration-200 rounded-full flex items-center justify-center"
|
||||||
|
: variant === "outline"
|
||||||
|
? "bg-background hover:bg-primary hover:shadow-xl text-foreground border border-border hover:text-primary-foreground transition font-medium duration-200 rounded-full flex items-center justify-center"
|
||||||
|
: variant === "primary"
|
||||||
|
? "bg-primary hover:bg-primary/90 border border-transparent text-primary-foreground transition font-medium duration-200 rounded-full flex items-center justify-center shadow-[0px_-1px_0px_0px_rgba(255,255,255,0.25)_inset,_0px_1px_0px_0px_rgba(255,255,255,0.25)_inset]"
|
||||||
|
: "";
|
||||||
|
return (
|
||||||
|
<Tag
|
||||||
|
className={cn(
|
||||||
|
"relative z-10",
|
||||||
|
variantClass,
|
||||||
|
sizeClass,
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
{children ?? `Get Started`}
|
||||||
|
</Tag>
|
||||||
|
);
|
||||||
|
};
|
||||||