Compare commits

..

12 Commits

Author SHA1 Message Date
7d1e54cf3f fix(desktop): 🐛 修复设置菜单的翻译问题 2026-01-25 18:11:14 +08:00
455dd1f4cd feat(desktop): 实现一些功能
1. 实现任务暂停功能

2. 实现页面的国际化功能

3.优化项目的结构以及BUG

4. 优化系统架构

5. 实现一大堆的功能
2026-01-25 03:30:23 +08:00
3f7347427e feat(desktop): 实现MD文件模板导出方式 2026-01-11 22:54:46 +08:00
cb7a1ba6d8 docs(desktop): 📝 完善项目文档 2026-01-11 15:42:13 +08:00
8fafd7d8ec feat(desktop): 实现阅读墙展示阅读记录 2026-01-11 15:13:33 +08:00
48fb287aa7 feat(desktop): 实现一些功能
1. 实现了用户阅读画像

2. 实现了全局检索功能
2026-01-11 14:40:31 +08:00
75cc9dc06d feat(desktop): 新增更多功能菜单,但是没有实现功能
1. 实现了用户画像页面设计
2026-01-11 02:42:46 +08:00
311aa59482 feat(desktop): 实现海报导出功能 2026-01-11 01:04:25 +08:00
36cf521851 feat(desktop): 优化一些逻辑
1. 优化通知配置

2. 优化命名规范

3. 优化代码逻辑
2026-01-10 23:37:28 +08:00
bce411af7e ci(desktop): ⚙️ 优化git提交流程 2026-01-10 21:28:19 +08:00
ca15400d3d fix:添加代码的git提交规范 2026-01-10 21:23:50 +08:00
a69a19ca49 fix:修改命名规范 2026-01-10 21:15:09 +08:00
129 changed files with 17987 additions and 1089 deletions

3
.husky/commit-msg Normal file
View File

@@ -0,0 +1,3 @@
#!/usr/bin/env sh
npx --no-install commitlint --edit "$1"

3
.husky/pre-commit Normal file
View File

@@ -0,0 +1,3 @@
#!/usr/bin/env sh
npx lint-staged

View File

@@ -0,0 +1,175 @@
# i18n集成和语言配置功能实现计划
## 1. 依赖安装
| 任务ID | 任务名称 | 技术栈 | 详细描述 |
|--------|----------|--------|----------|
| I18N-001 | 安装Vue I18n依赖 | Vue I18n | 使用pnpm安装Vue I18n库 |
## 2. 配置服务扩展
| 任务ID | 任务名称 | 技术栈 | 详细描述 |
|--------|----------|--------|----------|
| I18N-002 | 更新配置类型定义 | TypeScript | 在IConfig.ts中添加语言配置类型 |
| I18N-003 | 扩展配置服务 | TypeScript | 在ConfigService中添加语言配置的读写方法 |
## 3. i18n配置实现
| 任务ID | 任务名称 | 技术栈 | 详细描述 |
|--------|----------|--------|----------|
| I18N-004 | 创建i18n配置文件 | Vue I18n | 创建i18n配置文件和初始化逻辑 |
| I18N-005 | 创建语言包 | JSON | 创建中英文语言包文件 |
| I18N-006 | 集成i18n到Vue应用 | Vue 3 | 在main.ts中注册i18n插件 |
## 4. 语言设置页面实现
| 任务ID | 任务名称 | 技术栈 | 详细描述 |
|--------|----------|--------|----------|
| I18N-007 | 添加语言设置组件 | Vue 3 | 在setting页面添加语言选择组件 |
| I18N-008 | 实现语言切换逻辑 | Vue 3 | 实现语言切换功能和配置保存 |
## 5. 前端页面i18n集成
| 任务ID | 任务名称 | 技术栈 | 详细描述 |
|--------|----------|--------|----------|
| I18N-009 | 翻译任务管理页面 | Vue I18n | 将任务管理页面的文本替换为i18n翻译 |
| I18N-010 | 翻译阅读心得页面 | Vue I18n | 将阅读心得页面的文本替换为i18n翻译 |
| I18N-011 | 翻译设置页面 | Vue I18n | 将设置页面的文本替换为i18n翻译 |
| I18N-012 | 翻译其他页面 | Vue I18n | 将其他页面的文本替换为i18n翻译 |
## 6. 测试和优化
| 任务ID | 任务名称 | 技术栈 | 详细描述 |
|--------|----------|--------|----------|
| I18N-013 | 测试语言切换功能 | Vue I18n | 测试语言切换是否正常工作 |
| I18N-014 | 测试配置持久化 | Electron Store | 测试语言配置是否持久化保存 |
| I18N-015 | 优化i18n性能 | Vue I18n | 优化i18n加载性能和使用体验 |
## 技术实现细节
### 1. 配置服务扩展
1. **更新IConfig.ts**
- 添加语言配置类型定义
- 添加语言配置Schema验证
2. **扩展ConfigService**
- 添加LANGUAGE_KEY常量
- 添加getLanguageConfig方法
- 添加saveLanguageConfig方法
- 在schema中添加语言配置定义
### 2. i18n配置实现
1. **创建i18n配置文件**
```typescript
// src/renderer/plugins/i18n.ts
import { createI18n } from 'vue-i18n'
import zh from '@renderer/locales/zh.json'
import en from '@renderer/locales/en.json'
const i18n = createI18n({
legacy: false,
locale: 'zh',
messages: {
zh,
en
}
})
export default i18n
```
2. **创建语言包**
- src/renderer/locales/zh.json中文语言包
- src/renderer/locales/en.json英文语言包
3. **集成到Vue应用**
```typescript
// src/renderer/main.ts
import i18n from '@renderer/plugins/i18n'
app.use(i18n)
```
### 3. 语言设置页面实现
1. **添加语言设置组件**
```vue
<!-- src/renderer/src/pages/setting/components/LanguageSetting.vue -->
<template>
<div class="language-setting">
<h3>语言设置</h3>
<a-radio-group v-model="selectedLanguage" @change="handleLanguageChange">
<a-radio value="zh">中文</a-radio>
<a-radio value="en">English</a-radio>
</a-radio-group>
</div>
</template>
```
2. **实现语言切换逻辑**
```typescript
// src/renderer/src/pages/setting/components/LanguageSetting.vue
<script setup lang="ts">
import { ref, onMounted } from 'vue'
import { trpc } from '@renderer/lib/trpc'
import { useI18n } from 'vue-i18n'
const { locale } = useI18n()
const selectedLanguage = ref('zh')
onMounted(async () => {
// 从配置中获取当前语言
const languageConfig = await trpc.config.getLanguageConfig.query()
selectedLanguage.value = languageConfig?.language || 'zh'
locale.value = selectedLanguage.value
})
const handleLanguageChange = async () => {
// 更新i18n语言
locale.value = selectedLanguage.value
// 保存到配置
await trpc.config.saveLanguageConfig.mutate({ language: selectedLanguage.value })
}
</script>
```
### 4. 前端页面i18n集成
1. **在组件中使用i18n**
```vue
<template>
<div>{{ t('common.title') }}</div>
</template>
<script setup lang="ts">
import { useI18n } from 'vue-i18n'
const { t } = useI18n()
</script>
```
2. **批量替换文本**
- 逐个页面替换硬编码文本为i18n翻译
- 确保所有动态文本都能正确翻译
- 添加必要的语言包条目
## 预期效果
1. **语言切换功能**:用户可以在设置页面切换中文和英文
2. **配置持久化**语言设置会保存到electron-store中应用重启后保持不变
3. **全页面翻译**:所有前端页面的文本都会根据当前语言显示对应的翻译
4. **平滑切换**:语言切换时页面会实时更新,无需刷新
## 技术栈
- Vue 3
- Vue I18n
- TypeScript
- Electron Store
- tRPC
## 实现时间
计划在1-2小时内完成所有任务包括依赖安装、配置扩展、i18n集成和页面翻译。

View File

@@ -0,0 +1,183 @@
# 任务暂停功能实现计划
## 1. 实现顺序
按照Task.md中的优先级顺序我将按以下步骤实现任务暂停功能
### 1.1 数据库模型扩展 (PAUSE-001)
- 检查ReadingReflectionTaskBatch实体当前定义
- 添加isPaused和pausedAt字段
- 运行数据库迁移
### 1.2 实现暂停/恢复API (PAUSE-002)
- 在task.router.ts中添加pauseBatch和resumeBatch方法
- 实现暂停和恢复任务的业务逻辑
### 1.3 任务执行器改进 (PAUSE-003)
- 在TaskExecutor中添加任务状态检查
- 实现任务执行的中断和恢复机制
- 确保任务执行过程中能响应暂停命令
### 1.4 前端暂停按钮功能 (PAUSE-004)
- 为暂停按钮添加点击事件处理
- 实现暂停/恢复状态的切换逻辑
- 调用后端API实现暂停和恢复操作
### 1.5 暂停状态视觉反馈 (PAUSE-005)
- 添加暂停状态的视觉提示
- 实现按钮样式和文字的动态变化
- 显示暂停原因和预计恢复时间
### 1.6 任务队列管理 (PAUSE-006)
- 实现任务队列管理系统
- 支持暂停/恢复整个队列
- 处理队列中任务的状态管理
### 1.7 持久化暂停状态 (PAUSE-007)
- 确保暂停状态能持久化保存
- 应用重启后恢复暂停状态
- 处理断电等异常情况
## 2. 技术实现细节
### 2.1 数据库模型扩展
```typescript
// src/main/db/entities/ReadingReflectionTaskBatch.ts
@Entity('reading_reflection_task_batches')
export class ReadingReflectionTaskBatch {
// 现有字段...
@Column({ type: 'boolean', default: false })
isPaused!: boolean
@CreateDateColumn({ type: 'datetime', nullable: true })
pausedAt!: Date | null
}
```
### 2.2 实现暂停/恢复API
```typescript
// src/rpc/router/task.router.ts
export const taskRouter = router({
// 现有方法...
/**
* 暂停任务批次
*/
pauseBatch: publicProcedure
.input(z.object({ batchId: z.string() }))
.mutation(async ({ input }) => {
// 暂停任务逻辑
}),
/**
* 恢复任务批次
*/
resumeBatch: publicProcedure
.input(z.object({ batchId: z.string() }))
.mutation(async ({ input }) => {
// 恢复任务逻辑
})
})
```
### 2.3 任务执行器改进
```typescript
// src/main/manager/taskExecutor.ts
export class TaskExecutor {
// 现有方法...
private async checkPauseStatus(taskId: string): Promise<boolean> {
// 检查任务是否被暂停
}
private async executeSubTask(taskId: string, subTaskId: string, task: any, index: number, total: number): Promise<void> {
// 执行子任务,定期检查暂停状态
}
}
```
### 2.4 前端暂停按钮功能
```vue
<!-- src/renderer/src/pages/task/index.vue -->
<template>
<a-button
size="mini"
type="primary"
@click="handlePauseResume"
>
<template #icon>
<pause v-if="!isPaused" theme="outline" size="12" />
<play v-else theme="outline" size="12" />
</template>
{{ isPaused ? '恢复队列' : '暂停队列' }}
</a-button>
</template>
```
## 3. 预期效果
1. **暂停功能**:用户可以通过暂停按钮暂停正在执行的任务
2. **恢复功能**:用户可以恢复被暂停的任务
3. **状态反馈**:界面会显示当前任务的暂停状态
4. **持久化**:任务暂停状态会被持久化保存
5. **队列管理**:支持暂停/恢复整个任务队列
## 4. 测试计划
1. **功能测试**
- 验证暂停按钮能正常触发暂停操作
- 验证恢复按钮能正常恢复任务
- 验证暂停状态能正确显示
2. **边界测试**
- 测试同时暂停多个任务
- 测试应用重启后暂停状态是否保持
- 测试暂停状态下的任务进度更新
3. **性能测试**
- 测试大量任务时的暂停/恢复响应时间
- 验证暂停状态下的系统资源占用
## 5. 风险评估
1. **任务中断风险**:任务执行过程中中断可能导致数据不一致
2. **性能影响**:频繁检查暂停状态可能影响性能
3. **并发冲突**:多个任务同时暂停/恢复可能导致冲突
4. **持久化失败**:断电等异常情况可能导致暂停状态丢失
## 6. 解决方案
1. **任务中断处理**:实现事务机制,确保任务中断时数据一致性
2. **性能优化**:合理设置暂停状态检查频率,避免频繁查询数据库
3. **并发控制**:使用锁机制确保任务状态更新的原子性
4. **持久化保障**:使用事务确保暂停状态能可靠保存
## 7. 代码规范
- 遵循项目现有的代码风格和命名规范
- 确保类型安全使用TypeScript严格模式
- 添加必要的注释和文档
- 编写单元测试确保功能正确性
## 8. 交付标准
1. 所有暂停功能按优先级实现完成
2. 代码通过TypeScript类型检查
3. 没有语法错误和运行时错误
4. 功能测试通过
5. 代码符合项目的质量标准
## 9. 后续优化
1. 添加任务暂停的原因记录
2. 实现自动恢复功能
3. 支持基于时间的暂停计划
4. 添加暂停状态的日志记录
5. 优化暂停状态的查询性能
现在我将按照这个计划开始实现任务暂停功能,首先检查数据库模型。

View File

@@ -0,0 +1,380 @@
# 优化任务列表和技术实现文档
## 1. 任务列表优化
### 1.1 国际化支持优化
| 任务ID | 任务名称 | 优先级 | 技术栈 | 详细描述 |
|--------|----------|--------|--------|----------|
| I18N-001 | 选择并集成i18n库 | P1 | Vue I18n | 选择合适的i18n库并集成到项目中配置基本的国际化环境 |
| I18N-002 | 提取前端文本资源 | P1 | Vue I18n | 将所有前端组件中的硬编码文本提取到国际化资源文件中 |
| I18N-003 | 实现语言切换功能 | P2 | Vue I18n | 添加语言切换组件,支持中英文切换 |
| I18N-004 | 优化i18n性能 | P3 | Vue I18n | 实现按需加载语言包,优化国际化性能 |
### 1.2 暂停功能优化
| 任务ID | 任务名称 | 优先级 | 技术栈 | 详细描述 |
|--------|----------|--------|--------|----------|
| PAUSE-001 | 数据库模型扩展 | P0 | TypeORM | 为ReadingReflectionTaskBatch添加isPaused和pausedAt字段 |
| PAUSE-002 | 实现暂停/恢复API | P0 | tRPC | 添加暂停和恢复任务的RPC方法 |
| PAUSE-003 | 任务执行器改进 | P0 | TypeScript | 在TaskExecutor中实现任务状态检查和暂停逻辑 |
| PAUSE-004 | 前端暂停按钮功能 | P0 | Vue 3 | 为暂停按钮添加点击事件和状态切换逻辑 |
| PAUSE-005 | 暂停状态视觉反馈 | P1 | Vue 3 | 添加暂停状态的视觉提示,包括按钮样式变化和状态文字 |
| PAUSE-006 | 任务队列管理 | P1 | TypeScript | 实现任务队列管理,支持暂停/恢复整个队列 |
| PAUSE-007 | 持久化暂停状态 | P2 | TypeORM | 确保任务暂停状态在应用重启后保持 |
## 2. 技术实现文档
### 2.1 国际化支持实现指南
#### 2.1.1 集成Vue I18n
1. **安装依赖**
```bash
npm install vue-i18n@next
```
2. **创建i18n配置文件**
```typescript
// src/renderer/plugins/i18n.ts
import { createI18n } from 'vue-i18n'
import zh from '@renderer/locales/zh.json'
import en from '@renderer/locales/en.json'
const i18n = createI18n({
legacy: false,
locale: 'zh',
messages: {
zh,
en
}
})
export default i18n
```
3. **在main.ts中注册**
```typescript
// src/renderer/main.ts
import i18n from '@renderer/plugins/i18n'
app.use(i18n)
```
#### 2.1.2 提取文本资源
1. **创建语言包文件**
```json
// src/renderer/locales/zh.json
{
"task": {
"title": "任务管理",
"status": {
"pending": "任务排队中",
"writing": "运行中",
"completed": "任务完成",
"failed": "任务失败"
}
}
}
```
2. **在组件中使用**
```vue
<template>
<div>{{ t('task.title') }}</div>
<div>{{ t(`task.status.${task.status}`) }}</div>
</template>
<script setup lang="ts">
import { useI18n } from 'vue-i18n'
const { t } = useI18n()
</script>
```
### 2.2 暂停功能实现指南
#### 2.2.1 数据库模型扩展
1. **修改实体类**
```typescript
// src/main/db/entities/ReadingReflectionTaskBatch.ts
import { Column, CreateDateColumn, Entity, OneToMany, PrimaryColumn } from 'typeorm'
@Entity('reading_reflection_task_batches')
export class ReadingReflectionTaskBatch {
// 现有字段...
@Column({ type: 'boolean', default: false })
isPaused!: boolean
@CreateDateColumn({ type: 'datetime', nullable: true })
pausedAt!: Date | null
}
```
2. **运行数据库迁移**
```bash
npm run typeorm migration:generate -- -n AddPauseFields
npm run typeorm migration:run
```
#### 2.2.2 实现暂停/恢复API
1. **添加RPC方法**
```typescript
// src/rpc/router/task.router.ts
export const taskRouter = router({
// 现有方法...
/**
* 暂停任务批次
*/
pauseBatch: publicProcedure
.input(z.object({ batchId: z.string() }))
.mutation(async ({ input }) => {
const batchRepo = AppDataSource.getRepository(ReadingReflectionTaskBatch)
await batchRepo.update(input.batchId, {
isPaused: true,
pausedAt: new Date()
})
return { success: true }
}),
/**
* 恢复任务批次
*/
resumeBatch: publicProcedure
.input(z.object({ batchId: z.string() }))
.mutation(async ({ input }) => {
const batchRepo = AppDataSource.getRepository(ReadingReflectionTaskBatch)
await batchRepo.update(input.batchId, {
isPaused: false,
pausedAt: null
})
return { success: true }
})
})
```
#### 2.2.3 任务执行器改进
1. **添加暂停检查逻辑**
```typescript
// src/main/manager/taskExecutor.ts
export class TaskExecutor {
// 现有方法...
private async checkPauseStatus(taskId: string): Promise<boolean> {
const batch = await this.batchRepo.findOne({ where: { id: taskId } })
return batch?.isPaused || false
}
private async executeSubTask(taskId: string, subTaskId: string, task: any, index: number, total: number): Promise<void> {
// 现有代码...
for await (const chunk of stream) {
// 检查暂停状态
if (await this.checkPauseStatus(taskId)) {
await this.itemRepo.update(subTaskId, {
status: 'PAUSED'
})
await this.taskStatusManager.updateBatchStatus(taskId)
return // 退出执行
}
// 现有处理逻辑...
}
}
}
```
#### 2.2.4 前端暂停按钮功能
1. **添加按钮点击事件**
```vue
<!-- src/renderer/src/pages/task/index.vue -->
<template>
<a-button
size="mini"
type="primary"
class="bg-[#7816ff] border-none shadow-sm shadow-purple-200"
@click="handlePauseResume"
>
<template #icon>
<pause v-if="!isPaused" theme="outline" size="12" />
<play v-else theme="outline" size="12" />
</template>
{{ isPaused ? '恢复队列' : '暂停队列' }}
</a-button>
</template>
<script setup lang="ts">
import { Pause, Play } from '@icon-park/vue-next'
import { ref, computed } from 'vue'
import { trpc } from '@renderer/lib/trpc'
const activeTaskId = computed(() => getQuery('id') as string)
const isPaused = ref(false)
// 监听任务状态
const batchSub = trpc.task.onBatchProgressUpdate.subscribe(undefined, {
onData(data) {
if (data.batchId === activeTaskId.value) {
// 更新暂停状态
isPaused.value = data.isPaused || false
}
}
})
const handlePauseResume = async () => {
try {
if (isPaused.value) {
await trpc.task.resumeBatch.mutate({ batchId: activeTaskId.value })
} else {
await trpc.task.pauseBatch.mutate({ batchId: activeTaskId.value })
}
} catch (err) {
console.error('暂停/恢复失败:', err)
}
}
</script>
```
## 3. 学习资源
### 3.1 国际化支持学习
1. **Vue I18n官方文档**
- 地址https://vue-i18n.intlify.dev/
- 内容详细的API文档和使用示例
2. **国际化最佳实践**
- 地址https://www.smashingmagazine.com/2018/09/internationalization-vue-apps/)
- 内容:国际化的设计原则和最佳实践
### 3.2 暂停功能学习
1. **TypeORM官方文档**
- 地址https://typeorm.io/
- 内容:数据库模型设计和迁移
2. **tRPC官方文档**
- 地址https://trpc.io/
- 内容API设计和实现
3. **任务队列管理**
- 地址https://www.alexdebrie.com/posts/nodejs-task-queues/)
- 内容:任务队列的设计和实现
## 4. 测试指南
### 4.1 国际化支持测试
1. **功能测试**
- 验证所有文本都能正确显示
- 验证语言切换功能正常
- 验证动态文本(如任务状态)能正确翻译
2. **性能测试**
- 检查语言切换时的页面响应时间
- 验证语言包加载性能
### 4.2 暂停功能测试
1. **功能测试**
- 验证暂停按钮能正常触发暂停操作
- 验证恢复按钮能正常恢复任务
- 验证暂停状态能正确持久化
- 验证任务执行过程中能被暂停
2. **边界测试**
- 测试同时暂停多个任务
- 测试应用重启后暂停状态是否保持
- 测试暂停状态下的任务进度更新
3. **性能测试**
- 测试大量任务时的暂停/恢复响应时间
- 验证暂停状态下的系统资源占用
## 5. 部署指南
### 5.1 国际化支持部署
1. **构建生产版本**
```bash
npm run build:renderer
```
2. **验证构建产物**
- 检查语言包是否正确包含在构建产物中
- 验证国际化功能在生产环境中正常工作
### 5.2 暂停功能部署
1. **数据库迁移**
- 在生产环境运行数据库迁移
- 验证数据库字段添加成功
2. **部署应用**
```bash
npm run build:main
npm run build:renderer
npm run package
```
3. **验证部署**
- 验证暂停/恢复功能在生产环境中正常工作
- 监控系统性能和稳定性
## 6. 维护指南
### 6.1 国际化支持维护
1. **添加新语言**
- 复制现有语言包文件
- 翻译所有文本
- 在i18n配置中添加新语言
2. **更新文本**
- 修改对应语言包中的文本
- 重新构建应用
### 6.2 暂停功能维护
1. **监控暂停状态**
- 添加暂停状态的日志记录
- 监控长时间暂停的任务
2. **优化暂停逻辑**
- 根据实际使用情况调整暂停检查频率
- 优化暂停状态的数据库查询性能
3. **添加新功能**
- 支持批量暂停/恢复
- 添加暂停原因记录
- 实现自动恢复功能
## 7. 总结
通过本技术文档,您可以学习到:
1. **国际化支持的完整实现流程**
- 从库的选择到最终部署
- 最佳实践和性能优化
2. **暂停功能的设计和实现**
- 数据库模型扩展
- API设计和实现
- 前端交互逻辑
- 任务执行器改进
3. **完整的技术文档编写规范**
- 任务列表优化
- 实现指南
- 学习资源
- 测试指南
- 部署和维护指南
希望本技术文档能够帮助您学习和掌握相关技术,顺利完成项目优化任务。

View File

@@ -0,0 +1,98 @@
# 项目优化计划
根据 `d:\working\tools\read_book\Task.md` 文件中的优化建议,我将按照以下计划逐步优化项目:
## 1. 代码结构与组织优化
### 1.1 目录结构优化(进行中)
- ✅ 创建了 `src/main/services/ai/core``src/main/services/ai/utils` 目录
- ✅ 将 `llmService.ts` 移动到了 `core` 目录中
- ✅ 更新了相关文件的导入路径
### 1.2 模块拆分
- **目标**:将 `readingReflectionsTaskManager.ts` 拆分为多个小类
- **实施步骤**
- 创建 `NotificationService` 类,负责处理通知逻辑
- 创建 `TaskStatusManager` 类,负责管理任务状态
- 创建 `TaskExecutor` 类,负责执行任务
- 重构 `TaskManager` 类,使其成为协调器
## 2. 错误处理改进
### 2.1 实现完善的错误处理机制
- **目标**:改进当前简单的错误处理方式
- **实施步骤**
- 创建自定义错误类,如 `DatabaseError``AIError`
- 实现统一的错误处理中间件
- 添加详细的日志记录
- 为关键操作添加重试机制
## 3. 数据库操作优化
### 3.1 使用批量更新和批量查询
- **目标**:减少数据库交互次数,提高性能
- **实施步骤**
- 优化 `updateBatchStatus` 方法,使用聚合查询替代多次查询
- 实现批量更新任务状态的功能
- 优化 `startBatchTask` 方法中的数据库操作
### 3.2 添加数据库索引
- **目标**:提高查询性能
- **实施步骤**
-`ReadingReflectionTaskItem``batchId` 字段添加索引
- 为常用查询字段添加索引
## 4. 异步操作优化
### 4.1 改进并发控制策略
- **目标**:提高异步操作的效率和可靠性
- **实施步骤**
- 实现动态调整并发数的机制
- 改进 `startBatchTask` 方法中的并发处理
- 优化异步流处理逻辑
## 5. 安全性增强
### 5.1 改进数据加密方式
- **目标**:提高数据安全性
- **实施步骤**
- 改进 `electron-store` 的加密方式
- 实现更安全的密钥管理策略
### 5.2 实现严格的输入验证
- **目标**:防止恶意输入和安全漏洞
- **实施步骤**
- 使用 Zod 库实现严格的输入验证
- 为所有外部输入添加验证逻辑
- 实现 API 调用的验证机制
## 6. 功能改进
### 6.1 实现配置管理系统
- **目标**:提高配置的灵活性和可维护性
- **实施步骤**
- 实现配置管理服务
- 支持不同环境的配置
- 提供配置界面
## 7. 技术栈升级
### 7.1 更新依赖版本
- **目标**:保持技术的先进性,获取最新的功能和安全修复
- **实施步骤**
- 更新 `p-limit` 等依赖到最新版本
- 测试更新后的依赖兼容性
## 实施顺序
按照优先级和实施难度,我将按照以下顺序实施优化:
1. **代码结构优化**:重新组织目录结构和拆分大型模块
2. **错误处理改进**:实现完善的错误处理机制
3. **数据库操作优化**:使用批量更新和批量查询,添加数据库索引
4. **异步操作优化**:改进并发控制策略
5. **安全性增强**:改进数据加密方式,实现严格的输入验证
6. **功能改进**:实现配置管理系统
7. **技术栈升级**:更新依赖版本
现在,我将开始实施第一个优化任务:拆分 `readingReflectionsTaskManager.ts` 模块。

View File

@@ -0,0 +1,50 @@
# debugging
Comprehensive debugging specialist for errors, test failures, log analysis, and system problems. Use when encountering issues, analyzing error logs, investigating system anomalies, debugging production issues, analyzing stack traces, or identifying root causes. Combines general debugging workflows with error pattern detection and log analysis.
---
## 📦 Downloaded from [Skillstore.io](https://skillstore.io)
This skill was downloaded from **AI Skillstore** — the official marketplace for Claude Code, Codex, and Claude skills.
🔗 **Skill Page**: [skillstore.io/skills/x-89jobrien-debugging](https://skillstore.io/skills/x-89jobrien-debugging)
## 🚀 Installation
### Via Claude Code Plugin System
```
/plugin marketplace add aiskillstore/marketplace
/plugin install x-89jobrien-debugging@aiskillstore
```
### Manual Installation
Copy the contents of this folder to your project's `.claude/skills/` directory.
## 📋 Skill Info
| Property | Value |
|----------|-------|
| **Name** | debugging |
| **Version** | 1.0.1 |
| **Author** | Joseph OBrien |
### Supported Tools
- claude
- codex
- claude-code
## 🌐 Discover More Skills
Browse thousands of AI skills at **[skillstore.io](https://skillstore.io)**:
- 🔍 Search by category, tool, or keyword
- ⭐ Find verified, security-audited skills
- 📤 Submit your own skills to share with the community
---
*From [skillstore.io](https://skillstore.io) — AI Skills Marketplace*

View File

@@ -0,0 +1,553 @@
---
name: debugging
description: Comprehensive debugging specialist for errors, test failures, log analysis,
---
# Debugging
This skill provides comprehensive debugging capabilities for identifying and fixing errors, test failures, unexpected behavior, and production issues. It combines general debugging workflows with specialized error analysis, log parsing, and pattern recognition.
## When to Use This Skill
- When encountering errors or exceptions in code
- When tests are failing and you need to understand why
- When investigating unexpected behavior or bugs
- When analyzing stack traces and error messages
- When debugging production issues
- When fixing issues reported by users or QA
- When analyzing error logs and stack traces
- When investigating performance issues or anomalies
- When correlating errors across multiple services
- When identifying recurring error patterns
- When setting up error monitoring and alerting
- When conducting post-mortem analysis of incidents
## What This Skill Does
1. **Error Analysis**: Captures and analyzes error messages and stack traces
2. **Log Parsing**: Extracts errors from logs using regex patterns and structured parsing
3. **Stack Trace Analysis**: Analyzes stack traces across multiple programming languages
4. **Error Correlation**: Identifies relationships between errors across distributed systems
5. **Pattern Recognition**: Detects common error patterns and anti-patterns
6. **Reproduction**: Identifies steps to reproduce the issue
7. **Isolation**: Locates the exact failure point in code
8. **Root Cause Analysis**: Works backward from symptoms to identify underlying causes
9. **Minimal Fix**: Implements the smallest change that resolves the issue
10. **Verification**: Confirms the solution works and doesn't introduce new issues
11. **Monitoring Setup**: Creates queries and alerts for error detection
## Helper Scripts
This skill includes Python helper scripts in `scripts/`:
- **`parse_logs.py`**: Parses log files and extracts errors, exceptions, and stack traces. Outputs JSON with error analysis and pattern detection.
```bash
python scripts/parse_logs.py /var/log/app.log
```
## How to Use
### Debug an Error
```
Debug this error: TypeError: Cannot read property 'x' of undefined
```
```
Investigate why the test is failing in test_user_service.js
```
### Analyze Error Logs
```
Analyze the error logs in /var/log/app.log and identify the root cause
```
```
Investigate why the API is returning 500 errors
```
### Pattern Detection
```
Find patterns in these error logs from the past 24 hours
```
```
Correlate errors between the API service and database
```
## Debugging Process
### 1. Capture Error Information
**Error Message:**
- Read the full error message
- Note the error type (TypeError, ReferenceError, etc.)
- Identify the error location (file and line number)
**Stack Trace:**
- Analyze the call stack
- Identify the sequence of function calls
- Find where the error originated
**Context:**
- Check recent code changes
- Review related code files
- Understand the execution flow
### 2. Error Extraction (Log Analysis)
**Using Helper Script:**
The skill includes a Python helper script for parsing logs:
```bash
# Parse log file and extract errors
python scripts/parse_logs.py /var/log/app.log
```
**Manual Log Parsing Patterns:**
```bash
# Extract errors from logs
grep -i "error\|exception\|fatal\|critical" /var/log/app.log
# Extract stack traces
grep -A 20 "Exception\|Error\|Traceback" /var/log/app.log
# Extract specific error types
grep "TypeError\|ReferenceError\|SyntaxError" /var/log/app.log
```
**Structured Log Parsing:**
```javascript
// Parse JSON logs
const errors = logs
.filter(log => log.level === 'error' || log.level === 'critical')
.map(log => ({
timestamp: log.timestamp,
message: log.message,
stack: log.stack,
context: log.context
}));
```
### 3. Stack Trace Analysis
**Common Patterns:**
**JavaScript/Node.js:**
```
Error: Cannot read property 'x' of undefined
at FunctionName (file.js:123:45)
at AnotherFunction (file.js:456:78)
```
**Python:**
```
Traceback (most recent call last):
File "app.py", line 123, in function_name
result = process(data)
File "utils.py", line 45, in process
return data['key']
KeyError: 'key'
```
**Java:**
```
java.lang.NullPointerException
at com.example.Class.method(Class.java:123)
at com.example.AnotherClass.call(AnotherClass.java:456)
```
### 4. Error Correlation
**Timeline Analysis:**
- Group errors by timestamp
- Identify error spikes and patterns
- Correlate with deployments or changes
- Check for cascading failures
**Service Correlation:**
- Map errors across service boundaries
- Identify upstream/downstream relationships
- Track error propagation paths
- Find common failure points
### 5. Pattern Recognition
**Common Error Patterns:**
**N+1 Query Problem:**
```
Multiple database queries in loop
Pattern: SELECT * FROM users; SELECT * FROM posts WHERE user_id = ?
```
**Memory Leaks:**
```
Gradually increasing memory usage
Pattern: Memory growth over time without release
```
**Race Conditions:**
```
Intermittent failures under load
Pattern: Errors only occur with concurrent requests
```
**Timeout Issues:**
```
Requests timing out
Pattern: Errors after specific duration (e.g., 30s)
```
### 6. Reproduce the Issue
**Reproduction Steps:**
1. Identify the exact conditions that trigger the error
2. Create a minimal test case that reproduces the issue
3. Verify the issue is consistent and reproducible
4. Document the steps clearly
**Example:**
```markdown
## Reproduction Steps
1. Navigate to `/users/123`
2. Click "Edit Profile"
3. Submit form without filling required fields
4. Error occurs: "Cannot read property 'validate' of undefined"
```
### 7. Isolate the Failure Location
**Code Analysis:**
- Read the code around the error location
- Trace the execution path
- Identify where the assumption breaks
- Check variable states and values
**Debugging Techniques:**
- Add strategic logging to track execution
- Use debugger breakpoints
- Inspect variable states
- Check function return values
- Verify data structures
### 8. Form and Test Hypotheses
**Hypothesis Formation:**
- What could cause this error?
- What assumptions might be wrong?
- What edge cases weren't considered?
- What dependencies might be missing?
**Testing Hypotheses:**
- Add logging to verify assumptions
- Test edge cases
- Check input validation
- Verify dependencies are available
- Test with different data
### 9. Root Cause Analysis
**Investigation Steps:**
1. **Start with Symptoms**: What error is occurring?
2. **Work Backward**: What changed before the error?
3. **Check Patterns**: Is this recurring or isolated?
4. **Correlate Events**: What else happened at the same time?
5. **Identify Cause**: What is the underlying issue?
**Analysis Framework:**
```markdown
## Error Analysis
**Error**: [Description]
**Frequency**: [How often]
**Timeline**: [When it started]
**Affected Services**: [Which services]
**User Impact**: [How many users affected]
**Root Cause Hypothesis**:
- [Primary hypothesis with evidence]
- [Alternative hypotheses]
**Evidence**:
- [Log entries supporting hypothesis]
- [Error patterns observed]
- [Correlation with other events]
**Recommended Actions**:
- [Immediate fix]
- [Long-term prevention]
```
### 10. Implement Minimal Fix
**Fix Principles:**
- Fix the root cause, not just symptoms
- Make the smallest change possible
- Preserve existing functionality
- Don't introduce new complexity
- Add appropriate error handling
**Fix Verification:**
- Test the fix with the reproduction case
- Verify no regressions
- Check edge cases
- Ensure error handling is appropriate
- Confirm the fix is complete
### 11. Document the Solution
**Documentation Should Include:**
- Root cause explanation
- Evidence supporting the diagnosis
- Specific code fix
- Testing approach
- Prevention recommendations
## Examples
### Example 1: Null Reference Error
**Input**: Error: `TypeError: Cannot read property 'name' of undefined`
**Output**:
```markdown
## Debugging Analysis
### Error Information
- **Type**: TypeError
- **Location**: `src/services/user.js:45`
- **Message**: Cannot read property 'name' of undefined
### Root Cause
The `getUser()` function returns `undefined` when a user is not found, but the code assumes it always returns a user object.
### Code Location
```javascript
// src/services/user.js:45
const user = getUser(userId);
console.log(user.name); // ❌ Error: user is undefined
```
### Fix
```javascript
// Add null check
const user = getUser(userId);
if (!user) {
throw new Error(`User ${userId} not found`);
}
console.log(user.name); // ✅ Safe
```
### Prevention
- Always validate return values from functions that might return null/undefined
- Use optional chaining: `user?.name`
- Add TypeScript types to catch these issues at compile time
```
### Example 2: Database Connection Errors (Log Analysis)
**Input**: Error logs showing database connection failures
**Output**:
```markdown
## Error Analysis: Database Connection Failures
### Pattern Detected
**Error**: `ECONNREFUSED` errors from PostgreSQL
**Frequency**: 15 errors/minute, started 2 hours ago
**Timeline**: Errors began after deployment at 14:30
### Root Cause Analysis
**Hypothesis**: Connection pool exhaustion
**Evidence**:
- Errors correlate with high traffic period (14:30-16:30)
- Connection pool size: 10, concurrent requests: 50+
- No connection cleanup in error handlers
- Errors spike during peak usage
**Code Location**: `src/db/connection.js:45`
**Fix**:
```javascript
// Add connection cleanup
try {
const result = await query(sql);
return result;
} catch (error) {
// Ensure connection is released
await releaseConnection();
throw error;
}
```
**Monitoring Query**:
```sql
SELECT count(*) FROM pg_stat_activity WHERE state = 'active';
```
```
## Reference Files
For detailed debugging workflows, error patterns, and techniques, load reference files as needed:
- **`references/debugging_workflows.md`** - Common debugging workflows by issue type, language-specific debugging, debugging techniques, debugging checklists, and common error patterns (database errors, memory leaks, race conditions, timeouts, authentication errors, network errors, application errors, performance errors)
- **`references/INCIDENT_POSTMORTEM.template.md`** - Incident postmortem template with timeline, root cause analysis, and action items
When debugging specific types of issues or analyzing error patterns, load `references/debugging_workflows.md` and refer to the relevant section.
## Best Practices
### Debugging Approach
1. **Start with Symptoms**: Understand what's wrong before jumping to solutions
2. **Work Backward**: Trace from error to cause
3. **Test Hypotheses**: Don't assume, verify
4. **Minimal Changes**: Fix only what's necessary
5. **Verify Fixes**: Always test that the fix works
### Log Analysis Techniques
1. **Use Structured Logging**: JSON logs are easier to parse and analyze
2. **Include Context**: Add request IDs, user IDs, timestamps to all logs
3. **Log Levels**: Use appropriate levels (error, warn, info, debug)
4. **Correlation IDs**: Use request IDs to trace errors across services
5. **Error Grouping**: Group similar errors to identify patterns
### Error Pattern Recognition
**Time-Based Patterns:**
- Errors at specific times (deployment windows, peak hours)
- Errors after specific duration (timeouts, memory leaks)
- Errors during specific events (database migrations, cache clears)
**Frequency Patterns:**
- Sudden spikes (deployment issues, traffic spikes)
- Gradual increases (memory leaks, resource exhaustion)
- Intermittent (race conditions, timing issues)
**Correlation Patterns:**
- Errors in multiple services simultaneously (infrastructure issues)
- Errors after specific user actions (application bugs)
- Errors correlated with external services (dependency issues)
### Common Debugging Patterns
**Null/Undefined Checks:**
```javascript
// Always check for null/undefined
if (!value) {
// Handle missing value
}
```
**Error Handling:**
```javascript
try {
// Risky operation
} catch (error) {
// Log error with context
console.error('Operation failed:', error);
// Handle gracefully
}
```
**Logging:**
```javascript
// Strategic logging
console.log('Before operation:', { userId, data });
const result = await operation();
console.log('After operation:', { result });
```
**Type Checking:**
```javascript
// Verify types
if (typeof value !== 'string') {
throw new TypeError('Expected string');
}
```
### Monitoring Setup
**Error Rate Monitoring:**
```javascript
// Track error rate over time
const errorRate = errors.length / totalRequests;
if (errorRate > 0.01) { // 1% error rate threshold
alert('High error rate detected');
}
```
**Error Alerting:**
- Alert on error rate spikes (> 5% increase)
- Alert on new error types
- Alert on critical error patterns
- Alert on error correlation across services
### Prevention Strategies
1. **Input Validation**: Validate all inputs at boundaries
2. **Type Safety**: Use TypeScript or type checking
3. **Error Boundaries**: Catch errors at appropriate levels
4. **Testing**: Write tests for edge cases
5. **Code Review**: Review code for common pitfalls
## Related Use Cases
- Fixing production bugs
- Debugging test failures
- Investigating user-reported issues
- Analyzing error logs
- Root cause analysis
- Performance debugging
- Production incident investigation
- System reliability analysis
- Error monitoring setup
- Post-mortem analysis
- Debugging distributed systems

View File

@@ -0,0 +1,617 @@
---
author: Joseph OBrien
status: unpublished
updated: '2025-12-23'
version: 1.0.1
tag: skill
type: reference
parent: debugging
---
# Debugging Workflows
Reference guide for common debugging workflows and techniques across different scenarios.
## Debugging Workflows by Issue Type
### Production Errors
**Workflow:**
1. Capture error message and stack trace
2. Check error logs for context
3. Identify when error started (deployment, traffic spike, etc.)
4. Reproduce in staging environment
5. Add logging to trace execution path
6. Identify root cause
7. Implement fix with tests
8. Deploy and monitor
**Tools:**
- Error tracking (Sentry, Rollbar)
- Log aggregation (ELK, Datadog)
- APM tools (New Relic, AppDynamics)
### Test Failures
**Workflow:**
1. Read test failure message
2. Understand what the test expects
3. Run test in isolation
4. Check test data and setup
5. Trace through code execution
6. Identify why test fails
7. Fix code or test as appropriate
8. Verify test passes
**Tools:**
- Test runner debug mode
- IDE debugger
- Test coverage tools
### Performance Issues
**Workflow:**
1. Measure current performance
2. Identify slow operations
3. Profile to find bottlenecks
4. Analyze profiling data
5. Optimize identified bottlenecks
6. Measure improvement
7. Verify no regressions
**Tools:**
- Profilers (Chrome DevTools, py-spy)
- APM tools
- Performance monitoring
## Language-Specific Debugging
### JavaScript/Node.js
**Debugging Tools:**
- Chrome DevTools
- Node.js debugger
- console.log (strategic logging)
- debugger statement
**Common Issues:**
- Undefined variables
- Async/await errors
- Promise rejections
- Scope issues
- Type coercion
**Techniques:**
- Use debugger breakpoints
- Log variable states
- Check call stack
- Inspect closures
- Monitor event loop
### Python
**Debugging Tools:**
- pdb (Python debugger)
- ipdb (enhanced debugger)
- print() statements
- logging module
**Common Issues:**
- AttributeError
- TypeError
- IndentationError
- Import errors
- NameError
**Techniques:**
- Use pdb.set_trace()
- Check variable types
- Verify imports
- Check indentation
- Use type hints
### Java
**Debugging Tools:**
- IntelliJ debugger
- Eclipse debugger
- jdb (command line)
- Logging frameworks
**Common Issues:**
- NullPointerException
- ClassCastException
- OutOfMemoryError
- StackOverflowError
**Techniques:**
- Set breakpoints
- Inspect variables
- Check exception stack traces
- Monitor memory usage
- Use profilers
## Debugging Techniques
### Binary Search
**When to Use:**
- Large codebase
- Unclear where issue is
- Many potential causes
**Process:**
1. Divide code in half
2. Test which half has issue
3. Repeat on problematic half
4. Narrow down to specific location
### Rubber Duck Debugging
**Process:**
1. Explain code to "rubber duck" (or yourself)
2. Walk through execution step by step
3. Identify assumptions
4. Find where logic breaks
### Logging Strategy
**What to Log:**
- Function entry/exit
- Variable values at key points
- Decision points (if/else branches)
- Error conditions
- Performance metrics
**Log Levels:**
- DEBUG: Detailed diagnostic info
- INFO: General informational messages
- WARN: Warning messages
- ERROR: Error conditions
- CRITICAL: Critical failures
### Reproducing Issues
**Steps:**
1. Identify exact conditions that trigger issue
2. Document steps to reproduce
3. Create minimal test case
4. Verify issue reproduces consistently
5. Isolate variables
**Common Challenges:**
- Intermittent issues
- Race conditions
- Environment-specific
- Data-dependent
## Debugging Checklist
### Before Starting
- [ ] Understand what should happen
- [ ] Understand what's actually happening
- [ ] Have reproduction steps
- [ ] Have access to logs/debugger
### During Debugging
- [ ] Form hypotheses
- [ ] Test hypotheses systematically
- [ ] Document findings
- [ ] Check assumptions
- [ ] Look for patterns
### After Finding Root Cause
- [ ] Verify root cause is correct
- [ ] Understand why it happened
- [ ] Implement fix
- [ ] Test fix thoroughly
- [ ] Check for similar issues
- [ ] Document solution
## Common Error Patterns
Reference guide for identifying and resolving common error patterns across different systems and languages.
### Database Errors
#### Connection Pool Exhaustion
**Symptoms:**
- `ECONNREFUSED` errors
- Errors spike during high traffic
- Connection pool size is smaller than concurrent requests
**Pattern:**
```
Error: ECONNREFUSED
Connection pool exhausted
Too many connections
```
**Root Causes:**
- Connection pool size too small
- Connections not being released
- Long-running transactions holding connections
- Missing connection cleanup in error handlers
**Solutions:**
- Increase connection pool size
- Ensure connections are released in finally blocks
- Add connection timeout
- Implement connection retry logic
#### N+1 Query Problem
**Symptoms:**
- Slow response times
- Many database queries for single operation
- Queries increase linearly with data size
**Pattern:**
```
SELECT * FROM users;
SELECT * FROM posts WHERE user_id = 1;
SELECT * FROM posts WHERE user_id = 2;
SELECT * FROM posts WHERE user_id = 3;
...
```
**Solutions:**
- Use eager loading (JOINs)
- Batch queries
- Use data loaders
- Implement query result caching
### Memory Leaks
#### Event Listener Leaks
**Symptoms:**
- Memory usage grows over time
- No decrease after component/page unload
- Correlates with user interactions
**Pattern:**
```javascript
// Problem: Listeners registered but never removed
window.addEventListener('resize', handler);
// Missing: window.removeEventListener('resize', handler);
```
**Solutions:**
- Always remove event listeners
- Use cleanup functions in React useEffect
- Use WeakMap for automatic cleanup
- Monitor listener count
#### Closure Leaks
**Symptoms:**
- Memory growth in long-running applications
- Large objects retained in closures
- Circular references
**Pattern:**
```javascript
// Problem: Large object retained in closure
function createHandler(largeData) {
return function() {
// largeData retained even if not used
};
}
```
**Solutions:**
- Avoid retaining large objects in closures
- Use WeakMap/WeakSet when possible
- Clear references when done
- Use memory profilers to identify leaks
### Race Conditions
#### Concurrent Modification
**Symptoms:**
- Intermittent failures
- Data inconsistency
- Errors only under load
**Pattern:**
```
Thread 1: Read value (100)
Thread 2: Read value (100)
Thread 1: Write value (101)
Thread 2: Write value (101) // Lost update
```
**Solutions:**
- Use locks/mutexes
- Implement optimistic locking
- Use atomic operations
- Add request queuing
#### Async Race Conditions
**Symptoms:**
- Results arrive out of order
- Stale data displayed
- Race between multiple async operations
**Pattern:**
```javascript
// Problem: Race between requests
fetch('/api/users/1').then(setUser1);
fetch('/api/users/2').then(setUser2);
// Results may arrive in wrong order
```
**Solutions:**
- Use Promise.all for parallel operations
- Cancel previous requests
- Use request IDs to match responses
- Implement request deduplication
### Timeout Issues
#### Request Timeouts
**Symptoms:**
- Requests fail after specific duration
- Timeout errors in logs
- Slow external dependencies
**Pattern:**
```
Error: Request timeout after 30000ms
ETIMEDOUT
```
**Solutions:**
- Increase timeout for slow operations
- Implement retry with exponential backoff
- Add timeout configuration
- Optimize slow operations
#### Database Query Timeouts
**Symptoms:**
- Queries fail after timeout period
- Slow query logs show long-running queries
- Timeouts during peak load
**Solutions:**
- Optimize slow queries
- Add appropriate indexes
- Increase query timeout
- Implement query cancellation
### Authentication Errors
#### Token Expiration
**Symptoms:**
- 401 Unauthorized errors
- Errors after specific time period
- Token refresh needed
**Pattern:**
```
401 Unauthorized
Token expired
Invalid token
```
**Solutions:**
- Implement token refresh logic
- Handle token expiration gracefully
- Add token expiration checks
- Use refresh tokens
#### Session Expiration
**Symptoms:**
- Users logged out unexpectedly
- Session errors after inactivity
- Cookie expiration issues
**Solutions:**
- Extend session on activity
- Implement session refresh
- Handle expiration gracefully
- Clear expired sessions
### Network Errors
#### Connection Refused
**Symptoms:**
- Service unavailable errors
- Connection refused errors
- Service not running
**Pattern:**
```
ECONNREFUSED
Connection refused
Service unavailable
```
**Solutions:**
- Check if service is running
- Verify port configuration
- Check firewall rules
- Implement health checks
#### DNS Resolution Failures
**Symptoms:**
- Cannot resolve hostname
- DNS lookup failures
- Network configuration issues
**Pattern:**
```
ENOTFOUND
DNS resolution failed
getaddrinfo failed
```
**Solutions:**
- Verify DNS configuration
- Check hostname spelling
- Use IP addresses as fallback
- Implement DNS caching
### Application Errors
#### Null Reference Errors
**Symptoms:**
- NullPointerException (Java)
- TypeError: Cannot read property (JavaScript)
- AttributeError (Python)
**Pattern:**
```
TypeError: Cannot read property 'x' of undefined
NullPointerException
AttributeError: 'NoneType' object has no attribute
```
**Solutions:**
- Add null checks
- Use optional chaining
- Provide default values
- Validate inputs
#### Type Errors
**Symptoms:**
- Type mismatch errors
- Invalid type errors
- Casting failures
**Pattern:**
```
TypeError: expected string, got number
InvalidCastException
Type mismatch
```
**Solutions:**
- Add type validation
- Use type guards
- Implement proper type checking
- Handle type conversions
### Performance Errors
#### Out of Memory
**Symptoms:**
- Application crashes
- Memory limit exceeded
- Heap out of memory
**Pattern:**
```
OutOfMemoryError
Heap out of memory
Memory limit exceeded
```
**Solutions:**
- Increase memory limits
- Optimize memory usage
- Implement pagination
- Use streaming for large data
#### CPU Exhaustion
**Symptoms:**
- Slow response times
- High CPU usage
- Application freezing
**Pattern:**
- High CPU utilization (90%+)
- Slow processing
- Event loop blocking
**Solutions:**
- Optimize algorithms
- Use worker threads
- Implement caching
- Break up long-running tasks

View File

@@ -0,0 +1,206 @@
---
author: Joseph OBrien
status: unpublished
updated: '2025-12-23'
version: 1.0.1
tag: skill
type: reference
parent: debugging
---
# Incident Postmortem: {{INCIDENT_TITLE}}
**Incident ID:** {{INC-XXXX}}
**Date:** {{YYYY-MM-DD}}
**Duration:** {{START_TIME}} - {{END_TIME}} ({{DURATION}})
**Severity:** {{SEV1|SEV2|SEV3|SEV4}}
**Status:** {{RESOLVED|MONITORING}}
---
## Summary
{{ONE_PARAGRAPH_SUMMARY}}
### Impact
| Metric | Value |
|--------|-------|
| Users Affected | {{N}} |
| Revenue Impact | ${{N}} |
| Requests Failed | {{N}} |
| Error Rate | {{N}}% |
| Downtime | {{DURATION}} |
---
## Timeline
| Time (UTC) | Event |
|------------|-------|
| {{HH:MM}} | {{TRIGGER_EVENT}} |
| {{HH:MM}} | Alert fired: {{ALERT_NAME}} |
| {{HH:MM}} | On-call paged |
| {{HH:MM}} | Investigation started |
| {{HH:MM}} | Root cause identified |
| {{HH:MM}} | Mitigation applied |
| {{HH:MM}} | Service recovered |
| {{HH:MM}} | Incident closed |
---
## Root Cause
{{DETAILED_ROOT_CAUSE_ANALYSIS}}
### Contributing Factors
1. {{FACTOR_1}}
2. {{FACTOR_2}}
3. {{FACTOR_3}}
### What Failed
- **Detection:** {{HOW_WAS_IT_DETECTED}}
- **Prevention:** {{WHY_WASNT_IT_PREVENTED}}
- **Response:** {{RESPONSE_GAPS}}
---
## Resolution
### Immediate Actions
1. {{ACTION_1}}
2. {{ACTION_2}}
### Mitigation Steps
```bash
{{COMMANDS_OR_STEPS_TAKEN}}
```
### Verification
- [ ] Service health restored
- [ ] Error rates normalized
- [ ] No recurring alerts
---
## Lessons Learned
### What Went Well
- {{POSITIVE_1}}
- {{POSITIVE_2}}
### What Went Wrong
- {{NEGATIVE_1}}
- {{NEGATIVE_2}}
### Where We Got Lucky
- {{LUCKY_1}}
---
## Action Items
| ID | Action | Owner | Priority | Due Date | Status |
|----|--------|-------|----------|----------|--------|
| 1 | {{ACTION}} | {{OWNER}} | {{P1-4}} | {{DATE}} | {{STATUS}} |
| 2 | {{ACTION}} | {{OWNER}} | {{P1-4}} | {{DATE}} | {{STATUS}} |
| 3 | {{ACTION}} | {{OWNER}} | {{P1-4}} | {{DATE}} | {{STATUS}} |
### Prevention
- [ ] {{PREVENTIVE_MEASURE_1}}
- [ ] {{PREVENTIVE_MEASURE_2}}
### Detection
- [ ] {{DETECTION_IMPROVEMENT_1}}
- [ ] {{DETECTION_IMPROVEMENT_2}}
### Response
- [ ] {{RESPONSE_IMPROVEMENT_1}}
- [ ] {{RESPONSE_IMPROVEMENT_2}}
---
## Technical Details
### Affected Systems
| System | Impact | Recovery |
|--------|--------|----------|
| {{SYSTEM}} | {{DESCRIPTION}} | {{TIME}} |
### Metrics During Incident
| Metric | Normal | During Incident | Peak |
|--------|--------|-----------------|------|
| Latency (p99) | {{MS}} | {{MS}} | {{MS}} |
| Error Rate | {{N}}% | {{N}}% | {{N}}% |
| CPU Usage | {{N}}% | {{N}}% | {{N}}% |
| Memory | {{N}}GB | {{N}}GB | {{N}}GB |
### Logs
```
{{RELEVANT_LOG_SNIPPETS}}
```
---
## Communication
### Internal
| Time | Channel | Message |
|------|---------|---------|
| {{TIME}} | {{SLACK/EMAIL}} | {{SUMMARY}} |
### External
| Time | Channel | Audience | Message |
|------|---------|----------|---------|
| {{TIME}} | Status Page | Customers | {{MESSAGE}} |
---
## Related Incidents
| ID | Date | Similarity |
|----|------|------------|
| {{INC-XXXX}} | {{DATE}} | {{DESCRIPTION}} |
---
## Appendix
### A. Alert Configuration
```yaml
{{ALERT_CONFIG}}
```
### B. Runbook Updates Needed
- {{RUNBOOK_UPDATE_1}}
- {{RUNBOOK_UPDATE_2}}
---
## Quality Checklist
- [ ] Timeline is complete and accurate
- [ ] Root cause clearly identified
- [ ] Impact quantified
- [ ] Action items have owners and due dates
- [ ] Lessons learned documented
- [ ] Prevention measures identified
- [ ] Related incidents linked

View File

@@ -0,0 +1,124 @@
#!/usr/bin/env python3
"""Error log parser for debugging skill.
Extracts errors, exceptions, and stack traces from log files.
"""
import json
import re
import sys
from pathlib import Path
def extract_errors(log_content: str) -> list[dict]:
"""Extract errors and exceptions from log content."""
errors = []
patterns = {
"error": re.compile(r"(?i)(error|ERROR):\s*(.+?)(?:\n|$)"),
"exception": re.compile(r"(?i)(exception|Exception|EXCEPTION):\s*(.+?)(?:\n|$)"),
"fatal": re.compile(r"(?i)(fatal|FATAL):\s*(.+?)(?:\n|$)"),
"critical": re.compile(r"(?i)(critical|CRITICAL):\s*(.+?)(?:\n|$)"),
"traceback": re.compile(r"Traceback \(most recent call last\):(.+?)(?=\n\w|\Z)", re.DOTALL),
}
lines = log_content.split("\n")
for i, line in enumerate(lines):
for error_type, pattern in patterns.items():
match = pattern.search(line)
if match:
error = {
"type": error_type,
"message": (match.group(2) if len(match.groups()) > 1 else match.group(0)),
"line_number": i + 1,
"line_content": line,
"timestamp": extract_timestamp(line),
}
if error_type == "traceback":
error["stack_trace"] = match.group(1).strip()
errors.append(error)
return errors
def extract_timestamp(line: str) -> str | None:
"""Extract timestamp from log line if present."""
timestamp_patterns = [
r"(\d{4}-\d{2}-\d{2}[\sT]\d{2}:\d{2}:\d{2})",
r"(\d{2}/\d{2}/\d{4}\s+\d{2}:\d{2}:\d{2})",
r"\[(\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}:\d{2})\]",
]
for pattern in timestamp_patterns:
match = re.search(pattern, line)
if match:
return match.group(1)
return None
def group_errors_by_type(errors: list[dict]) -> dict[str, list[dict]]:
"""Group errors by type."""
grouped: dict[str, list[dict]] = {}
for error in errors:
error_type = error["type"]
if error_type not in grouped:
grouped[error_type] = []
grouped[error_type].append(error)
return grouped
def analyze_error_patterns(errors: list[dict]) -> dict:
"""Analyze error patterns and provide insights."""
if not errors:
return {}
type_counts: dict[str, int] = {}
for error in errors:
error_type = error["type"]
type_counts[error_type] = type_counts.get(error_type, 0) + 1
message_counts: dict[str, int] = {}
for error in errors:
message = error.get("message", "")[:100]
message_counts[message] = message_counts.get(message, 0) + 1
most_common = sorted(message_counts.items(), key=lambda x: x[1], reverse=True)[:5]
return {
"total_errors": len(errors),
"by_type": type_counts,
"most_common_errors": [{"message": msg, "count": count} for msg, count in most_common],
}
def main():
"""Main entry point."""
if len(sys.argv) < 2:
print("Usage: parse_logs.py <log_file>")
sys.exit(1)
log_file = Path(sys.argv[1])
if not log_file.exists():
print(f"Error: File not found: {log_file}")
sys.exit(1)
log_content = log_file.read_text()
errors = extract_errors(log_content)
analysis = analyze_error_patterns(errors)
output = {
"file": str(log_file),
"errors": errors,
"analysis": analysis,
"grouped_by_type": group_errors_by_type(errors),
}
print(json.dumps(output, indent=2))
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,50 @@
# frontend-design
Creates unique, production-grade frontend interfaces with exceptional design quality. Use when user asks to build web components, pages, materials, posters, or applications (e.g., websites, landing pages, dashboards, React components, HTML/CSS layouts, or styling/beautifying any web UI). Generates creative, polished code and UI designs that avoid mediocre AI aesthetics.
---
## 📦 Downloaded from [Skillstore.io](https://skillstore.io)
This skill was downloaded from **AI Skillstore** — the official marketplace for Claude Code, Codex, and Claude skills.
🔗 **Skill Page**: [skillstore.io/skills/yyh211-frontend-design](https://skillstore.io/skills/yyh211-frontend-design)
## 🚀 Installation
### Via Claude Code Plugin System
```
/plugin marketplace add aiskillstore/marketplace
/plugin install yyh211-frontend-design@aiskillstore
```
### Manual Installation
Copy the contents of this folder to your project's `.claude/skills/` directory.
## 📋 Skill Info
| Property | Value |
|----------|-------|
| **Name** | frontend-design |
| **Version** | 1.0.0 |
| **Author** | YYH211 |
### Supported Tools
- claude
- codex
- claude-code
## 🌐 Discover More Skills
Browse thousands of AI skills at **[skillstore.io](https://skillstore.io)**:
- 🔍 Search by category, tool, or keyword
- ⭐ Find verified, security-audited skills
- 📤 Submit your own skills to share with the community
---
*From [skillstore.io](https://skillstore.io) — AI Skills Marketplace*

View File

@@ -0,0 +1,491 @@
---
name: frontend-design
description: Creates unique, production-grade frontend interfaces with exceptional design quality. Use when user asks to build web components, pages, materials, posters, or applications (e.g., websites, landing pages, dashboards, React components, HTML/CSS layouts, or styling/beautifying any web UI). Generates creative, polished code and UI designs that avoid mediocre AI aesthetics.
---
# Frontend Design Skill
此技能指导创建独特的生产级前端界面,避免平庸的"AI 粗糙"美学。实现真正可用的代码,并高度关注美学细节和创意选择。
## When to Use This Skill
使用此技能当用户请求:
- 构建 Web 组件、页面或完整应用程序
- 创建着陆页、仪表盘或营销页面
- 设计 React、Vue 或原生 HTML/CSS 界面
- 美化或重新设计现有的 Web UI
- 创建海报、素材或视觉设计元素(用于 Web
- 需要高设计品质和独特美学的任何前端项目
**关键触发词**: Web 组件、页面、应用、网站、着陆页、仪表盘、React 组件、HTML/CSS、UI 设计、美化、前端
## 核心原则
在编写代码之前,必须进行深入的设计思考。每个界面都应该是独特的、有意图的、令人难忘的。
### 设计思维流程
在实现任何代码之前,回答以下问题:
#### 1. 目的 (Purpose)
- **问题**: 此界面解决什么问题?
- **用户**: 谁使用它?在什么情境下使用?
- **目标**: 用户需要完成什么任务?
#### 2. 风格方向 (Style Direction)
选择一个**明确且大胆**的美学方向。不要选择"现代简约"这样的通用描述,而是选择极致的风格:
**风格选项**(但不限于这些):
- **极简主义**: 极度克制,大量留白,精准排版
- **极致混乱**: 密集布局,重叠元素,视觉冲击
- **复古未来主义**: 80年代霓虹色网格合成波风格
- **有机/自然**: 流动形状,自然色调,柔和曲线
- **奢华/精致**: 优雅字体,金色点缀,精细细节
- **俏皮/玩具感**: 明亮色彩,圆角,趣味动画
- **编辑/杂志风格**: 大胆排版,网格系统,黑白为主
- **粗犷/原始**: 单色,硬边,实用主义
- **装饰艺术/几何**: 对称图案,几何形状,高对比度
- **柔和/粉彩**: 温和色彩,渐变,梦幻感
- **工业/实用**: 系统字体,单色,功能优先
- **新拟态**: 柔和阴影,浮雕效果,微妙深度
- **玻璃态**: 模糊背景,透明度,光感
**关键**: 选择清晰的概念方向并精准执行。大胆的极致主义和精致的极简主义都有效——关键在于**意图**,而不是强度。
#### 3. 技术限制 (Constraints)
- 使用什么框架React, Vue, 原生 HTML/CSS
- 性能要求?(动画复杂度,文件大小)
- 可访问性要求ARIA 标签,键盘导航,色彩对比度)
- 浏览器兼容性?
#### 4. 差异化 (Differentiation)
- **记忆点**: 是什么让它令人难忘?
- **独特性**: 用户会记住哪一个细节?
- **惊喜**: 哪里会让用户眼前一亮?
## 前端美学指南
### 1. 排版 (Typography)
**原则**: 字体选择是设计的灵魂。
**Do**:
- ✅ 选择**独特且有个性**的字体
- ✅ 标题使用引人注目的字体,正文使用易读字体
- ✅ 尝试意想不到的字体配对
- ✅ 使用字体变体font-weight, font-style创造层次
- ✅ 精确控制字间距letter-spacing和行高line-height
**Don't**:
- ❌ 使用通用字体Arial, Helvetica, Inter, Roboto, 系统字体
- ❌ 所有文本使用相同的字体和大小
- ❌ 忽略字体加载性能(使用 font-display: swap
**推荐字体来源**:
- Google Fonts (选择小众、独特的字体)
- 自定义字体(如果项目允许)
**示例字体组合**:
```css
/* 极简编辑风格 */
--font-heading: 'Playfair Display', serif;
--font-body: 'Source Sans Pro', sans-serif;
/* 现代科技风格 */
--font-heading: 'Space Mono', monospace;
--font-body: 'DM Sans', sans-serif;
/* 优雅奢华风格 */
--font-heading: 'Cormorant Garamond', serif;
--font-body: 'Lato', sans-serif;
```
### 2. 颜色与主题 (Color & Theme)
**原则**: 颜色定义情绪和品牌。
**Do**:
- ✅ 使用 CSS 变量保持一致性
- ✅ 主色调 + 鲜明点缀色的组合
- ✅ 考虑色彩心理学(蓝色=信任,红色=紧迫,绿色=成功)
- ✅ 使用渐变营造深度(但要有品味)
- ✅ 保持色彩对比度WCAG AA 标准:至少 4.5:1
**Don't**:
- ❌ 俗套配色:白色背景 + 紫色渐变
- ❌ 过多颜色3-5 个主色已足够)
- ❌ 忽略可访问性
**示例主题**:
```css
:root {
/* 极简黑白 */
--color-primary: #000000;
--color-secondary: #ffffff;
--color-accent: #ff3366;
/* 复古未来 */
--color-primary: #1a1a2e;
--color-secondary: #16213e;
--color-accent: #00fff5;
--color-highlight: #ff006e;
/* 自然有机 */
--color-primary: #2d6a4f;
--color-secondary: #52b788;
--color-accent: #ffc857;
}
```
### 3. 动效 (Animation & Motion)
**原则**: 动画应该增强体验,而不是分散注意力。
**Do**:
- ✅ 优先使用 CSS 动画(性能更好)
- ✅ 设计页面加载动画(首次印象)
- ✅ 使用 `animation-delay` 实现元素逐个显示
- ✅ 悬停状态添加微妙过渡
- ✅ 滚动触发动画Intersection Observer
- ✅ 对于 React使用 Framer Motion 或 React Spring
**Don't**:
- ❌ 过度使用动画(每个元素都在动)
- ❌ 动画持续时间过长(> 500ms 会让人不耐烦)
- ❌ 忽略 `prefers-reduced-motion` 媒体查询
**示例动画**:
```css
/* 页面加载 - 元素逐个淡入 */
.fade-in-up {
animation: fadeInUp 0.6s ease-out forwards;
opacity: 0;
}
.fade-in-up:nth-child(1) { animation-delay: 0.1s; }
.fade-in-up:nth-child(2) { animation-delay: 0.2s; }
.fade-in-up:nth-child(3) { animation-delay: 0.3s; }
@keyframes fadeInUp {
from {
opacity: 0;
transform: translateY(30px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
/* 悬停效果 */
.card {
transition: transform 0.3s ease, box-shadow 0.3s ease;
}
.card:hover {
transform: translateY(-8px);
box-shadow: 0 20px 40px rgba(0,0,0,0.15);
}
```
### 4. 空间构成 (Spatial Composition)
**原则**: 布局应该引导视线,创造视觉节奏。
**Do**:
- ✅ 尝试不对称布局
- ✅ 使用重叠元素创造深度
- ✅ 对角线流程引导视线
- ✅ 打破网格的元素(但有意图)
- ✅ 宽敞的留白或精心控制的密度
- ✅ 使用 Grid 和 Flexbox 创造复杂布局
**Don't**:
- ❌ 所有元素居中对齐
- ❌ 均匀分布的网格(无聊)
- ❌ 忽略响应式设计
**示例布局技巧**:
```css
/* 不对称网格 */
.grid-asymmetric {
display: grid;
grid-template-columns: 2fr 1fr;
gap: 40px;
}
/* 重叠效果 */
.overlap-container {
position: relative;
}
.overlap-item {
position: absolute;
z-index: 2;
transform: translate(-20%, -20%);
}
/* 对角线流程 */
.diagonal-section {
transform: skewY(-3deg);
padding: 100px 0;
}
.diagonal-section > * {
transform: skewY(3deg);
}
```
### 5. 背景和视觉细节 (Background & Visual Details)
**原则**: 背景营造氛围和深度。
**Do**:
- ✅ 渐变网格
- ✅ 噪点纹理
- ✅ 几何图案
- ✅ 分层透明度
- ✅ 戏剧性阴影
- ✅ 装饰性边框
- ✅ 自定义光标(如果适合风格)
- ✅ 颗粒叠加效果
**Don't**:
- ❌ 纯色背景(除非极简风格)
- ❌ 低质量或不相关的库存图片
- ❌ 过度使用阴影box-shadow 污染)
**示例背景效果**:
```css
/* 渐变网格背景 */
.gradient-grid {
background:
linear-gradient(90deg, rgba(255,255,255,0.05) 1px, transparent 1px),
linear-gradient(rgba(255,255,255,0.05) 1px, transparent 1px),
linear-gradient(135deg, #667eea 0%, #764ba2 100%);
background-size: 50px 50px, 50px 50px, 100% 100%;
}
/* 噪点纹理 */
.noise-texture {
position: relative;
}
.noise-texture::before {
content: '';
position: absolute;
inset: 0;
background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 200 200' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='noise'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='4' /%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23noise)' opacity='0.05'/%3E%3C/svg%3E");
pointer-events: none;
}
/* 玻璃态效果 */
.glass-card {
background: rgba(255, 255, 255, 0.1);
backdrop-filter: blur(10px);
border: 1px solid rgba(255, 255, 255, 0.2);
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
}
```
## 避免通用 AI 美学
**绝对禁止的元素**:
- ❌ 过度使用的字体Inter, Roboto, Arial, 系统字体
- ❌ 俗套配色:白色背景 + 紫色渐变
- ❌ 可预测的布局模式(居中卡片网格)
- ❌ 缺乏特定上下文特征的千篇一律设计
**如何避免**:
- ✅ 为每个项目选择**不同**的字体
- ✅ 在浅色/深色主题之间变化
- ✅ 尝试不同的布局结构
- ✅ 添加独特的品牌元素和个性
## 实现复杂性与美学匹配
**关键原则**: 代码复杂度应与设计愿景相匹配。
### 极繁主义设计 → 复杂代码
- 大量动画和过渡效果
- 多层叠加元素
- 复杂的交互状态
- 详细的视觉效果(粒子、渐变、纹理)
```jsx
// 示例:复杂的动画卡片
<motion.div
initial={{ opacity: 0, scale: 0.8, rotateX: -15 }}
animate={{ opacity: 1, scale: 1, rotateX: 0 }}
whileHover={{
scale: 1.05,
rotateY: 5,
boxShadow: "0 25px 50px rgba(0,0,0,0.2)"
}}
transition={{
type: "spring",
stiffness: 300,
damping: 20
}}
>
{/* 复杂内容 */}
</motion.div>
```
### 极简主义设计 → 精准代码
- 克制的动画(仅在关键时刻)
- 精确的间距和排版
- 细微的过渡效果
- 关注细节而非数量
```css
/* 示例:精致的极简主义 */
.minimal-card {
padding: 60px;
background: #ffffff;
border: 1px solid rgba(0,0,0,0.08);
transition: border-color 0.3s ease;
}
.minimal-card:hover {
border-color: rgba(0,0,0,0.2);
}
.minimal-card h2 {
font-family: 'Cormorant Garamond', serif;
font-size: 2.5rem;
font-weight: 300;
letter-spacing: -0.02em;
line-height: 1.2;
margin: 0 0 20px 0;
}
```
## 工作流程
### 第 1 步:理解需求
- 阅读用户请求,提取关键信息
- 确定项目类型(组件、页面、完整应用)
- 识别技术栈React、Vue、原生 HTML/CSS
### 第 2 步:设计思考
- 回答设计思维流程中的 4 个问题
- 选择明确的美学方向
- 在心中可视化最终效果
### 第 3 步:技术决策
- 选择框架和工具
- 决定动画库Framer Motion、CSS、React Spring
- 确定字体来源
### 第 4 步:实现
- 编写语义化 HTML 结构
- 实现 CSS 样式(使用 CSS 变量)
- 添加交互和动画
- 确保响应式设计
### 第 5 步:精细化
- 调整间距和排版
- 优化动画时间
- 测试不同屏幕尺寸
- 确保可访问性ARIA、键盘导航
## 示例场景
### 场景 1: 创建着陆页
**用户请求**: "帮我创建一个 SaaS 产品的着陆页"
**设计思考**:
- 目的:展示产品价值,吸引用户注册
- 风格:现代科技 + 编辑风格,使用 Space Grotesk 字体,黑白 + 蓝色点缀
- 布局:不对称,英雄区域占据 70% 屏幕,对角线流程
- 差异化:独特的字体配对,大胆的排版层次
**实现重点**:
- Hero section 使用大字号标题4-6rem
- 滚动触发的淡入动画
- 玻璃态 CTA 按钮
- 响应式网格展示功能
### 场景 2: 设计仪表盘
**用户请求**: "创建一个数据分析仪表盘"
**设计思考**:
- 目的:清晰展示数据,支持快速决策
- 风格:实用主义 + 精致,使用 IBM Plex Sans深色主题
- 布局:网格系统,卡片式布局,数据可视化优先
- 差异化:微妙的动画过渡,悬停显示详细信息
**实现重点**:
- 深色背景减少眼睛疲劳
- 卡片使用柔和阴影和边框
- 图表使用鲜明的点缀色
- 加载状态使用骨架屏
### 场景 3: React 组件库
**用户请求**: "创建一套自定义按钮组件"
**设计思考**:
- 目的:可复用、可定制的按钮系统
- 风格:灵活,支持多种变体
- 技术:使用 styled-components 或 CSS modules
- 差异化:独特的悬停效果和加载状态
**实现重点**:
- 主按钮、次要按钮、文本按钮变体
- 大小变体small, medium, large
- 加载和禁用状态
- 平滑的过渡动画
## 代码质量标准
### 必须遵守:
- ✅ 语义化 HTML`<header>`, `<nav>`, `<main>`, `<article>`
- ✅ BEM 命名规范或 CSS Modules
- ✅ CSS 变量用于颜色和间距
- ✅ 移动优先的响应式设计
- ✅ 可访问性ARIA 标签,键盘导航)
- ✅ 性能优化(图片懒加载,字体优化)
### 禁止:
- ❌ 内联样式(除非动态值)
- ❌ 不必要的 `!important`
- ❌ 硬编码的颜色值(使用 CSS 变量)
- ❌ 未优化的图片
- ❌ 无意义的类名(`.box1`, `.container2`
## 技术栈参考
### 推荐工具:
- **字体**: Google Fonts, Font Squirrel
- **颜色**: Coolors.co, Adobe Color
- **动画**: Framer Motion (React), Anime.js, GreenSock
- **图标**: Heroicons, Lucide, Phosphor Icons
- **CSS 框架**: Tailwind CSS (自定义配置), styled-components
### 避免:
- ❌ Bootstrap, Material-UI容易产生通用外观
- ❌ 默认 Tailwind 配置(需要自定义)
## 检查清单
在完成实现后,验证以下内容:
- [ ] 选择了独特的字体组合(不是 Inter/Roboto/Arial
- [ ] 颜色方案有明确的美学方向
- [ ] 至少有 1-2 处精心设计的动画
- [ ] 布局不是简单的居中网格
- [ ] 有独特的视觉细节(背景、纹理、阴影)
- [ ] 响应式设计在手机和桌面都好看
- [ ] 可访问性标准达标对比度、ARIA
- [ ] 代码清晰、可维护
- [ ] 性能良好(无卡顿动画,快速加载)
## 最后提醒
> **创造性诠释是关键**。不要问用户"你想要什么颜色?",而是基于上下文做出大胆的设计决策。每个设计都应该是独一无二的。在不同项目之间变化浅色/深色主题、字体和美学风格。
> **追求卓越,而非完美**。一个有强烈个性的设计胜过一个"安全"但平庸的设计。敢于尝试,为用户带来惊喜。

View File

@@ -0,0 +1,202 @@
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 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
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
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.

View File

@@ -0,0 +1,50 @@
# mcp-builder
Guide for creating high-quality MCP (Model Context Protocol) servers that enable LLMs to interact with external services through well-designed tools. Use when building MCP servers to integrate external APIs or services, whether in Python (FastMCP) or Node/TypeScript (MCP SDK).
---
## 📦 Downloaded from [Skillstore.io](https://skillstore.io)
This skill was downloaded from **AI Skillstore** — the official marketplace for Claude Code, Codex, and Claude skills.
🔗 **Skill Page**: [skillstore.io/skills/yyh211-mcp-builder](https://skillstore.io/skills/yyh211-mcp-builder)
## 🚀 Installation
### Via Claude Code Plugin System
```
/plugin marketplace add aiskillstore/marketplace
/plugin install yyh211-mcp-builder@aiskillstore
```
### Manual Installation
Copy the contents of this folder to your project's `.claude/skills/` directory.
## 📋 Skill Info
| Property | Value |
|----------|-------|
| **Name** | mcp-builder |
| **Version** | 1.0.0 |
| **Author** | YYH211 |
### Supported Tools
- claude
- codex
- claude-code
## 🌐 Discover More Skills
Browse thousands of AI skills at **[skillstore.io](https://skillstore.io)**:
- 🔍 Search by category, tool, or keyword
- ⭐ Find verified, security-audited skills
- 📤 Submit your own skills to share with the community
---
*From [skillstore.io](https://skillstore.io) — AI Skills Marketplace*

View File

@@ -0,0 +1,235 @@
---
name: mcp-builder
description: Guide for creating high-quality MCP (Model Context Protocol) servers that enable LLMs to interact with external services through well-designed tools. Use when building MCP servers to integrate external APIs or services, whether in Python (FastMCP) or Node/TypeScript (MCP SDK).
---
# MCP Server Development Guide
## Overview
Create MCP (Model Context Protocol) servers that enable LLMs to interact with external services through well-designed tools. The quality of an MCP server is measured by how well it enables LLMs to accomplish real-world tasks.
---
# Process
## 🚀 High-Level Workflow
Creating a high-quality MCP server involves four main phases:
### Phase 1: Deep Research and Planning
#### 1.1 Understand Modern MCP Design
**API Coverage vs. Workflow Tools:**
Balance comprehensive API endpoint coverage with specialized workflow tools. Workflow tools can be more convenient for specific tasks, while comprehensive coverage gives agents flexibility to compose operations. Performance varies by client—some clients benefit from code execution that combines basic tools, while others work better with higher-level workflows. When uncertain, prioritize comprehensive API coverage.
**Tool Naming and Discoverability:**
Clear, descriptive tool names help agents find the right tools quickly. Use consistent prefixes (e.g., `github_create_issue`, `github_list_repos`) and action-oriented naming.
**Context Management:**
Agents benefit from concise tool descriptions and the ability to filter/paginate results. Design tools that return focused, relevant data. Some clients support code execution which can help agents filter and process data efficiently.
**Actionable Error Messages:**
Error messages should guide agents toward solutions with specific suggestions and next steps.
#### 1.2 Study MCP Protocol Documentation
**Navigate the MCP specification:**
Start with the sitemap to find relevant pages: `https://modelcontextprotocol.io/sitemap.xml`
Then fetch specific pages with `.md` suffix for markdown format (e.g., `https://modelcontextprotocol.io/specification/draft.md`).
Key pages to review:
- Specification overview and architecture
- Transport mechanisms (streamable HTTP, stdio)
- Tool, resource, and prompt definitions
#### 1.3 Study Framework Documentation
**Recommended stack:**
- **Language**: TypeScript (high-quality SDK support and good compatibility in many execution environments e.g. MCPB. Plus AI models are good at generating TypeScript code, benefiting from its broad usage, static typing and good linting tools)
- **Transport**: Streamable HTTP for remote servers, using stateless JSON (simpler to scale and maintain, as opposed to stateful sessions and streaming responses). stdio for local servers.
**Load framework documentation:**
- **MCP Best Practices**: [📋 View Best Practices](./reference/mcp_best_practices.md) - Core guidelines
**For TypeScript (recommended):**
- **TypeScript SDK**: Use WebFetch to load `https://raw.githubusercontent.com/modelcontextprotocol/typescript-sdk/main/README.md`
- [⚡ TypeScript Guide](./reference/node_mcp_server.md) - TypeScript patterns and examples
**For Python:**
- **Python SDK**: Use WebFetch to load `https://raw.githubusercontent.com/modelcontextprotocol/python-sdk/main/README.md`
- [🐍 Python Guide](./reference/python_mcp_server.md) - Python patterns and examples
#### 1.4 Plan Your Implementation
**Understand the API:**
Review the service's API documentation to identify key endpoints, authentication requirements, and data models. Use web search and WebFetch as needed.
**Tool Selection:**
Prioritize comprehensive API coverage. List endpoints to implement, starting with the most common operations.
---
### Phase 2: Implementation
#### 2.1 Set Up Project Structure
See language-specific guides for project setup:
- [⚡ TypeScript Guide](./reference/node_mcp_server.md) - Project structure, package.json, tsconfig.json
- [🐍 Python Guide](./reference/python_mcp_server.md) - Module organization, dependencies
#### 2.2 Implement Core Infrastructure
Create shared utilities:
- API client with authentication
- Error handling helpers
- Response formatting (JSON/Markdown)
- Pagination support
#### 2.3 Implement Tools
For each tool:
**Input Schema:**
- Use Zod (TypeScript) or Pydantic (Python)
- Include constraints and clear descriptions
- Add examples in field descriptions
**Output Schema:**
- Define `outputSchema` where possible for structured data
- Use `structuredContent` in tool responses (TypeScript SDK feature)
- Helps clients understand and process tool outputs
**Tool Description:**
- Concise summary of functionality
- Parameter descriptions
- Return type schema
**Implementation:**
- Async/await for I/O operations
- Proper error handling with actionable messages
- Support pagination where applicable
- Return both text content and structured data when using modern SDKs
**Annotations:**
- `readOnlyHint`: true/false
- `destructiveHint`: true/false
- `idempotentHint`: true/false
- `openWorldHint`: true/false
---
### Phase 3: Review and Test
#### 3.1 Code Quality
Review for:
- No duplicated code (DRY principle)
- Consistent error handling
- Full type coverage
- Clear tool descriptions
#### 3.2 Build and Test
**TypeScript:**
- Run `npm run build` to verify compilation
- Test with MCP Inspector: `npx @modelcontextprotocol/inspector`
**Python:**
- Verify syntax: `python -m py_compile your_server.py`
- Test with MCP Inspector
See language-specific guides for detailed testing approaches and quality checklists.
---
### Phase 4: Create Evaluations
After implementing your MCP server, create comprehensive evaluations to test its effectiveness.
**Load [✅ Evaluation Guide](./reference/evaluation.md) for complete evaluation guidelines.**
#### 4.1 Understand Evaluation Purpose
Use evaluations to test whether LLMs can effectively use your MCP server to answer realistic, complex questions.
#### 4.2 Create 10 Evaluation Questions
To create effective evaluations, follow the process outlined in the evaluation guide:
1. **Tool Inspection**: List available tools and understand their capabilities
2. **Content Exploration**: Use READ-ONLY operations to explore available data
3. **Question Generation**: Create 10 complex, realistic questions
4. **Answer Verification**: Solve each question yourself to verify answers
#### 4.3 Evaluation Requirements
Ensure each question is:
- **Independent**: Not dependent on other questions
- **Read-only**: Only non-destructive operations required
- **Complex**: Requiring multiple tool calls and deep exploration
- **Realistic**: Based on real use cases humans would care about
- **Verifiable**: Single, clear answer that can be verified by string comparison
- **Stable**: Answer won't change over time
#### 4.4 Output Format
Create an XML file with this structure:
```xml
<evaluation>
<qa_pair>
<question>Find discussions about AI model launches with animal codenames. One model needed a specific safety designation that uses the format ASL-X. What number X was being determined for the model named after a spotted wild cat?</question>
<answer>3</answer>
</qa_pair>
<!-- More qa_pairs... -->
</evaluation>
```
---
# Reference Files
## 📚 Documentation Library
Load these resources as needed during development:
### Core MCP Documentation (Load First)
- **MCP Protocol**: Start with sitemap at `https://modelcontextprotocol.io/sitemap.xml`, then fetch specific pages with `.md` suffix
- [📋 MCP Best Practices](./reference/mcp_best_practices.md) - Universal MCP guidelines including:
- Server and tool naming conventions
- Response format guidelines (JSON vs Markdown)
- Pagination best practices
- Transport selection (streamable HTTP vs stdio)
- Security and error handling standards
### SDK Documentation (Load During Phase 1/2)
- **Python SDK**: Fetch from `https://raw.githubusercontent.com/modelcontextprotocol/python-sdk/main/README.md`
- **TypeScript SDK**: Fetch from `https://raw.githubusercontent.com/modelcontextprotocol/typescript-sdk/main/README.md`
### Language-Specific Implementation Guides (Load During Phase 2)
- [🐍 Python Implementation Guide](./reference/python_mcp_server.md) - Complete Python/FastMCP guide with:
- Server initialization patterns
- Pydantic model examples
- Tool registration with `@mcp.tool`
- Complete working examples
- Quality checklist
- [⚡ TypeScript Implementation Guide](./reference/node_mcp_server.md) - Complete TypeScript guide with:
- Project structure
- Zod schema patterns
- Tool registration with `server.registerTool`
- Complete working examples
- Quality checklist
### Evaluation Guide (Load During Phase 4)
- [✅ Evaluation Guide](./reference/evaluation.md) - Complete evaluation creation guide with:
- Question creation guidelines
- Answer verification strategies
- XML format specifications
- Example questions and answers
- Running an evaluation with the provided scripts

View File

@@ -0,0 +1,602 @@
# MCP Server Evaluation Guide
## Overview
This document provides guidance on creating comprehensive evaluations for MCP servers. Evaluations test whether LLMs can effectively use your MCP server to answer realistic, complex questions using only the tools provided.
---
## Quick Reference
### Evaluation Requirements
- Create 10 human-readable questions
- Questions must be READ-ONLY, INDEPENDENT, NON-DESTRUCTIVE
- Each question requires multiple tool calls (potentially dozens)
- Answers must be single, verifiable values
- Answers must be STABLE (won't change over time)
### Output Format
```xml
<evaluation>
<qa_pair>
<question>Your question here</question>
<answer>Single verifiable answer</answer>
</qa_pair>
</evaluation>
```
---
## Purpose of Evaluations
The measure of quality of an MCP server is NOT how well or comprehensively the server implements tools, but how well these implementations (input/output schemas, docstrings/descriptions, functionality) enable LLMs with no other context and access ONLY to the MCP servers to answer realistic and difficult questions.
## Evaluation Overview
Create 10 human-readable questions requiring ONLY READ-ONLY, INDEPENDENT, NON-DESTRUCTIVE, and IDEMPOTENT operations to answer. Each question should be:
- Realistic
- Clear and concise
- Unambiguous
- Complex, requiring potentially dozens of tool calls or steps
- Answerable with a single, verifiable value that you identify in advance
## Question Guidelines
### Core Requirements
1. **Questions MUST be independent**
- Each question should NOT depend on the answer to any other question
- Should not assume prior write operations from processing another question
2. **Questions MUST require ONLY NON-DESTRUCTIVE AND IDEMPOTENT tool use**
- Should not instruct or require modifying state to arrive at the correct answer
3. **Questions must be REALISTIC, CLEAR, CONCISE, and COMPLEX**
- Must require another LLM to use multiple (potentially dozens of) tools or steps to answer
### Complexity and Depth
4. **Questions must require deep exploration**
- Consider multi-hop questions requiring multiple sub-questions and sequential tool calls
- Each step should benefit from information found in previous questions
5. **Questions may require extensive paging**
- May need paging through multiple pages of results
- May require querying old data (1-2 years out-of-date) to find niche information
- The questions must be DIFFICULT
6. **Questions must require deep understanding**
- Rather than surface-level knowledge
- May pose complex ideas as True/False questions requiring evidence
- May use multiple-choice format where LLM must search different hypotheses
7. **Questions must not be solvable with straightforward keyword search**
- Do not include specific keywords from the target content
- Use synonyms, related concepts, or paraphrases
- Require multiple searches, analyzing multiple related items, extracting context, then deriving the answer
### Tool Testing
8. **Questions should stress-test tool return values**
- May elicit tools returning large JSON objects or lists, overwhelming the LLM
- Should require understanding multiple modalities of data:
- IDs and names
- Timestamps and datetimes (months, days, years, seconds)
- File IDs, names, extensions, and mimetypes
- URLs, GIDs, etc.
- Should probe the tool's ability to return all useful forms of data
9. **Questions should MOSTLY reflect real human use cases**
- The kinds of information retrieval tasks that HUMANS assisted by an LLM would care about
10. **Questions may require dozens of tool calls**
- This challenges LLMs with limited context
- Encourages MCP server tools to reduce information returned
11. **Include ambiguous questions**
- May be ambiguous OR require difficult decisions on which tools to call
- Force the LLM to potentially make mistakes or misinterpret
- Ensure that despite AMBIGUITY, there is STILL A SINGLE VERIFIABLE ANSWER
### Stability
12. **Questions must be designed so the answer DOES NOT CHANGE**
- Do not ask questions that rely on "current state" which is dynamic
- For example, do not count:
- Number of reactions to a post
- Number of replies to a thread
- Number of members in a channel
13. **DO NOT let the MCP server RESTRICT the kinds of questions you create**
- Create challenging and complex questions
- Some may not be solvable with the available MCP server tools
- Questions may require specific output formats (datetime vs. epoch time, JSON vs. MARKDOWN)
- Questions may require dozens of tool calls to complete
## Answer Guidelines
### Verification
1. **Answers must be VERIFIABLE via direct string comparison**
- If the answer can be re-written in many formats, clearly specify the output format in the QUESTION
- Examples: "Use YYYY/MM/DD.", "Respond True or False.", "Answer A, B, C, or D and nothing else."
- Answer should be a single VERIFIABLE value such as:
- User ID, user name, display name, first name, last name
- Channel ID, channel name
- Message ID, string
- URL, title
- Numerical quantity
- Timestamp, datetime
- Boolean (for True/False questions)
- Email address, phone number
- File ID, file name, file extension
- Multiple choice answer
- Answers must not require special formatting or complex, structured output
- Answer will be verified using DIRECT STRING COMPARISON
### Readability
2. **Answers should generally prefer HUMAN-READABLE formats**
- Examples: names, first name, last name, datetime, file name, message string, URL, yes/no, true/false, a/b/c/d
- Rather than opaque IDs (though IDs are acceptable)
- The VAST MAJORITY of answers should be human-readable
### Stability
3. **Answers must be STABLE/STATIONARY**
- Look at old content (e.g., conversations that have ended, projects that have launched, questions answered)
- Create QUESTIONS based on "closed" concepts that will always return the same answer
- Questions may ask to consider a fixed time window to insulate from non-stationary answers
- Rely on context UNLIKELY to change
- Example: if finding a paper name, be SPECIFIC enough so answer is not confused with papers published later
4. **Answers must be CLEAR and UNAMBIGUOUS**
- Questions must be designed so there is a single, clear answer
- Answer can be derived from using the MCP server tools
### Diversity
5. **Answers must be DIVERSE**
- Answer should be a single VERIFIABLE value in diverse modalities and formats
- User concept: user ID, user name, display name, first name, last name, email address, phone number
- Channel concept: channel ID, channel name, channel topic
- Message concept: message ID, message string, timestamp, month, day, year
6. **Answers must NOT be complex structures**
- Not a list of values
- Not a complex object
- Not a list of IDs or strings
- Not natural language text
- UNLESS the answer can be straightforwardly verified using DIRECT STRING COMPARISON
- And can be realistically reproduced
- It should be unlikely that an LLM would return the same list in any other order or format
## Evaluation Process
### Step 1: Documentation Inspection
Read the documentation of the target API to understand:
- Available endpoints and functionality
- If ambiguity exists, fetch additional information from the web
- Parallelize this step AS MUCH AS POSSIBLE
- Ensure each subagent is ONLY examining documentation from the file system or on the web
### Step 2: Tool Inspection
List the tools available in the MCP server:
- Inspect the MCP server directly
- Understand input/output schemas, docstrings, and descriptions
- WITHOUT calling the tools themselves at this stage
### Step 3: Developing Understanding
Repeat steps 1 & 2 until you have a good understanding:
- Iterate multiple times
- Think about the kinds of tasks you want to create
- Refine your understanding
- At NO stage should you READ the code of the MCP server implementation itself
- Use your intuition and understanding to create reasonable, realistic, but VERY challenging tasks
### Step 4: Read-Only Content Inspection
After understanding the API and tools, USE the MCP server tools:
- Inspect content using READ-ONLY and NON-DESTRUCTIVE operations ONLY
- Goal: identify specific content (e.g., users, channels, messages, projects, tasks) for creating realistic questions
- Should NOT call any tools that modify state
- Will NOT read the code of the MCP server implementation itself
- Parallelize this step with individual sub-agents pursuing independent explorations
- Ensure each subagent is only performing READ-ONLY, NON-DESTRUCTIVE, and IDEMPOTENT operations
- BE CAREFUL: SOME TOOLS may return LOTS OF DATA which would cause you to run out of CONTEXT
- Make INCREMENTAL, SMALL, AND TARGETED tool calls for exploration
- In all tool call requests, use the `limit` parameter to limit results (<10)
- Use pagination
### Step 5: Task Generation
After inspecting the content, create 10 human-readable questions:
- An LLM should be able to answer these with the MCP server
- Follow all question and answer guidelines above
## Output Format
Each QA pair consists of a question and an answer. The output should be an XML file with this structure:
```xml
<evaluation>
<qa_pair>
<question>Find the project created in Q2 2024 with the highest number of completed tasks. What is the project name?</question>
<answer>Website Redesign</answer>
</qa_pair>
<qa_pair>
<question>Search for issues labeled as "bug" that were closed in March 2024. Which user closed the most issues? Provide their username.</question>
<answer>sarah_dev</answer>
</qa_pair>
<qa_pair>
<question>Look for pull requests that modified files in the /api directory and were merged between January 1 and January 31, 2024. How many different contributors worked on these PRs?</question>
<answer>7</answer>
</qa_pair>
<qa_pair>
<question>Find the repository with the most stars that was created before 2023. What is the repository name?</question>
<answer>data-pipeline</answer>
</qa_pair>
</evaluation>
```
## Evaluation Examples
### Good Questions
**Example 1: Multi-hop question requiring deep exploration (GitHub MCP)**
```xml
<qa_pair>
<question>Find the repository that was archived in Q3 2023 and had previously been the most forked project in the organization. What was the primary programming language used in that repository?</question>
<answer>Python</answer>
</qa_pair>
```
This question is good because:
- Requires multiple searches to find archived repositories
- Needs to identify which had the most forks before archival
- Requires examining repository details for the language
- Answer is a simple, verifiable value
- Based on historical (closed) data that won't change
**Example 2: Requires understanding context without keyword matching (Project Management MCP)**
```xml
<qa_pair>
<question>Locate the initiative focused on improving customer onboarding that was completed in late 2023. The project lead created a retrospective document after completion. What was the lead's role title at that time?</question>
<answer>Product Manager</answer>
</qa_pair>
```
This question is good because:
- Doesn't use specific project name ("initiative focused on improving customer onboarding")
- Requires finding completed projects from specific timeframe
- Needs to identify the project lead and their role
- Requires understanding context from retrospective documents
- Answer is human-readable and stable
- Based on completed work (won't change)
**Example 3: Complex aggregation requiring multiple steps (Issue Tracker MCP)**
```xml
<qa_pair>
<question>Among all bugs reported in January 2024 that were marked as critical priority, which assignee resolved the highest percentage of their assigned bugs within 48 hours? Provide the assignee's username.</question>
<answer>alex_eng</answer>
</qa_pair>
```
This question is good because:
- Requires filtering bugs by date, priority, and status
- Needs to group by assignee and calculate resolution rates
- Requires understanding timestamps to determine 48-hour windows
- Tests pagination (potentially many bugs to process)
- Answer is a single username
- Based on historical data from specific time period
**Example 4: Requires synthesis across multiple data types (CRM MCP)**
```xml
<qa_pair>
<question>Find the account that upgraded from the Starter to Enterprise plan in Q4 2023 and had the highest annual contract value. What industry does this account operate in?</question>
<answer>Healthcare</answer>
</qa_pair>
```
This question is good because:
- Requires understanding subscription tier changes
- Needs to identify upgrade events in specific timeframe
- Requires comparing contract values
- Must access account industry information
- Answer is simple and verifiable
- Based on completed historical transactions
### Poor Questions
**Example 1: Answer changes over time**
```xml
<qa_pair>
<question>How many open issues are currently assigned to the engineering team?</question>
<answer>47</answer>
</qa_pair>
```
This question is poor because:
- The answer will change as issues are created, closed, or reassigned
- Not based on stable/stationary data
- Relies on "current state" which is dynamic
**Example 2: Too easy with keyword search**
```xml
<qa_pair>
<question>Find the pull request with title "Add authentication feature" and tell me who created it.</question>
<answer>developer123</answer>
</qa_pair>
```
This question is poor because:
- Can be solved with a straightforward keyword search for exact title
- Doesn't require deep exploration or understanding
- No synthesis or analysis needed
**Example 3: Ambiguous answer format**
```xml
<qa_pair>
<question>List all the repositories that have Python as their primary language.</question>
<answer>repo1, repo2, repo3, data-pipeline, ml-tools</answer>
</qa_pair>
```
This question is poor because:
- Answer is a list that could be returned in any order
- Difficult to verify with direct string comparison
- LLM might format differently (JSON array, comma-separated, newline-separated)
- Better to ask for a specific aggregate (count) or superlative (most stars)
## Verification Process
After creating evaluations:
1. **Examine the XML file** to understand the schema
2. **Load each task instruction** and in parallel using the MCP server and tools, identify the correct answer by attempting to solve the task YOURSELF
3. **Flag any operations** that require WRITE or DESTRUCTIVE operations
4. **Accumulate all CORRECT answers** and replace any incorrect answers in the document
5. **Remove any `<qa_pair>`** that require WRITE or DESTRUCTIVE operations
Remember to parallelize solving tasks to avoid running out of context, then accumulate all answers and make changes to the file at the end.
## Tips for Creating Quality Evaluations
1. **Think Hard and Plan Ahead** before generating tasks
2. **Parallelize Where Opportunity Arises** to speed up the process and manage context
3. **Focus on Realistic Use Cases** that humans would actually want to accomplish
4. **Create Challenging Questions** that test the limits of the MCP server's capabilities
5. **Ensure Stability** by using historical data and closed concepts
6. **Verify Answers** by solving the questions yourself using the MCP server tools
7. **Iterate and Refine** based on what you learn during the process
---
# Running Evaluations
After creating your evaluation file, you can use the provided evaluation harness to test your MCP server.
## Setup
1. **Install Dependencies**
```bash
pip install -r scripts/requirements.txt
```
Or install manually:
```bash
pip install anthropic mcp
```
2. **Set API Key**
```bash
export ANTHROPIC_API_KEY=your_api_key_here
```
## Evaluation File Format
Evaluation files use XML format with `<qa_pair>` elements:
```xml
<evaluation>
<qa_pair>
<question>Find the project created in Q2 2024 with the highest number of completed tasks. What is the project name?</question>
<answer>Website Redesign</answer>
</qa_pair>
<qa_pair>
<question>Search for issues labeled as "bug" that were closed in March 2024. Which user closed the most issues? Provide their username.</question>
<answer>sarah_dev</answer>
</qa_pair>
</evaluation>
```
## Running Evaluations
The evaluation script (`scripts/evaluation.py`) supports three transport types:
**Important:**
- **stdio transport**: The evaluation script automatically launches and manages the MCP server process for you. Do not run the server manually.
- **sse/http transports**: You must start the MCP server separately before running the evaluation. The script connects to the already-running server at the specified URL.
### 1. Local STDIO Server
For locally-run MCP servers (script launches the server automatically):
```bash
python scripts/evaluation.py \
-t stdio \
-c python \
-a my_mcp_server.py \
evaluation.xml
```
With environment variables:
```bash
python scripts/evaluation.py \
-t stdio \
-c python \
-a my_mcp_server.py \
-e API_KEY=abc123 \
-e DEBUG=true \
evaluation.xml
```
### 2. Server-Sent Events (SSE)
For SSE-based MCP servers (you must start the server first):
```bash
python scripts/evaluation.py \
-t sse \
-u https://example.com/mcp \
-H "Authorization: Bearer token123" \
-H "X-Custom-Header: value" \
evaluation.xml
```
### 3. HTTP (Streamable HTTP)
For HTTP-based MCP servers (you must start the server first):
```bash
python scripts/evaluation.py \
-t http \
-u https://example.com/mcp \
-H "Authorization: Bearer token123" \
evaluation.xml
```
## Command-Line Options
```
usage: evaluation.py [-h] [-t {stdio,sse,http}] [-m MODEL] [-c COMMAND]
[-a ARGS [ARGS ...]] [-e ENV [ENV ...]] [-u URL]
[-H HEADERS [HEADERS ...]] [-o OUTPUT]
eval_file
positional arguments:
eval_file Path to evaluation XML file
optional arguments:
-h, --help Show help message
-t, --transport Transport type: stdio, sse, or http (default: stdio)
-m, --model Claude model to use (default: claude-3-7-sonnet-20250219)
-o, --output Output file for report (default: print to stdout)
stdio options:
-c, --command Command to run MCP server (e.g., python, node)
-a, --args Arguments for the command (e.g., server.py)
-e, --env Environment variables in KEY=VALUE format
sse/http options:
-u, --url MCP server URL
-H, --header HTTP headers in 'Key: Value' format
```
## Output
The evaluation script generates a detailed report including:
- **Summary Statistics**:
- Accuracy (correct/total)
- Average task duration
- Average tool calls per task
- Total tool calls
- **Per-Task Results**:
- Prompt and expected response
- Actual response from the agent
- Whether the answer was correct (✅/❌)
- Duration and tool call details
- Agent's summary of its approach
- Agent's feedback on the tools
### Save Report to File
```bash
python scripts/evaluation.py \
-t stdio \
-c python \
-a my_server.py \
-o evaluation_report.md \
evaluation.xml
```
## Complete Example Workflow
Here's a complete example of creating and running an evaluation:
1. **Create your evaluation file** (`my_evaluation.xml`):
```xml
<evaluation>
<qa_pair>
<question>Find the user who created the most issues in January 2024. What is their username?</question>
<answer>alice_developer</answer>
</qa_pair>
<qa_pair>
<question>Among all pull requests merged in Q1 2024, which repository had the highest number? Provide the repository name.</question>
<answer>backend-api</answer>
</qa_pair>
<qa_pair>
<question>Find the project that was completed in December 2023 and had the longest duration from start to finish. How many days did it take?</question>
<answer>127</answer>
</qa_pair>
</evaluation>
```
2. **Install dependencies**:
```bash
pip install -r scripts/requirements.txt
export ANTHROPIC_API_KEY=your_api_key
```
3. **Run evaluation**:
```bash
python scripts/evaluation.py \
-t stdio \
-c python \
-a github_mcp_server.py \
-e GITHUB_TOKEN=ghp_xxx \
-o github_eval_report.md \
my_evaluation.xml
```
4. **Review the report** in `github_eval_report.md` to:
- See which questions passed/failed
- Read the agent's feedback on your tools
- Identify areas for improvement
- Iterate on your MCP server design
## Troubleshooting
### Connection Errors
If you get connection errors:
- **STDIO**: Verify the command and arguments are correct
- **SSE/HTTP**: Check the URL is accessible and headers are correct
- Ensure any required API keys are set in environment variables or headers
### Low Accuracy
If many evaluations fail:
- Review the agent's feedback for each task
- Check if tool descriptions are clear and comprehensive
- Verify input parameters are well-documented
- Consider whether tools return too much or too little data
- Ensure error messages are actionable
### Timeout Issues
If tasks are timing out:
- Use a more capable model (e.g., `claude-3-7-sonnet-20250219`)
- Check if tools are returning too much data
- Verify pagination is working correctly
- Consider simplifying complex questions

View File

@@ -0,0 +1,249 @@
# MCP Server Best Practices
## Quick Reference
### Server Naming
- **Python**: `{service}_mcp` (e.g., `slack_mcp`)
- **Node/TypeScript**: `{service}-mcp-server` (e.g., `slack-mcp-server`)
### Tool Naming
- Use snake_case with service prefix
- Format: `{service}_{action}_{resource}`
- Example: `slack_send_message`, `github_create_issue`
### Response Formats
- Support both JSON and Markdown formats
- JSON for programmatic processing
- Markdown for human readability
### Pagination
- Always respect `limit` parameter
- Return `has_more`, `next_offset`, `total_count`
- Default to 20-50 items
### Transport
- **Streamable HTTP**: For remote servers, multi-client scenarios
- **stdio**: For local integrations, command-line tools
- Avoid SSE (deprecated in favor of streamable HTTP)
---
## Server Naming Conventions
Follow these standardized naming patterns:
**Python**: Use format `{service}_mcp` (lowercase with underscores)
- Examples: `slack_mcp`, `github_mcp`, `jira_mcp`
**Node/TypeScript**: Use format `{service}-mcp-server` (lowercase with hyphens)
- Examples: `slack-mcp-server`, `github-mcp-server`, `jira-mcp-server`
The name should be general, descriptive of the service being integrated, easy to infer from the task description, and without version numbers.
---
## Tool Naming and Design
### Tool Naming
1. **Use snake_case**: `search_users`, `create_project`, `get_channel_info`
2. **Include service prefix**: Anticipate that your MCP server may be used alongside other MCP servers
- Use `slack_send_message` instead of just `send_message`
- Use `github_create_issue` instead of just `create_issue`
3. **Be action-oriented**: Start with verbs (get, list, search, create, etc.)
4. **Be specific**: Avoid generic names that could conflict with other servers
### Tool Design
- Tool descriptions must narrowly and unambiguously describe functionality
- Descriptions must precisely match actual functionality
- Provide tool annotations (readOnlyHint, destructiveHint, idempotentHint, openWorldHint)
- Keep tool operations focused and atomic
---
## Response Formats
All tools that return data should support multiple formats:
### JSON Format (`response_format="json"`)
- Machine-readable structured data
- Include all available fields and metadata
- Consistent field names and types
- Use for programmatic processing
### Markdown Format (`response_format="markdown"`, typically default)
- Human-readable formatted text
- Use headers, lists, and formatting for clarity
- Convert timestamps to human-readable format
- Show display names with IDs in parentheses
- Omit verbose metadata
---
## Pagination
For tools that list resources:
- **Always respect the `limit` parameter**
- **Implement pagination**: Use `offset` or cursor-based pagination
- **Return pagination metadata**: Include `has_more`, `next_offset`/`next_cursor`, `total_count`
- **Never load all results into memory**: Especially important for large datasets
- **Default to reasonable limits**: 20-50 items is typical
Example pagination response:
```json
{
"total": 150,
"count": 20,
"offset": 0,
"items": [...],
"has_more": true,
"next_offset": 20
}
```
---
## Transport Options
### Streamable HTTP
**Best for**: Remote servers, web services, multi-client scenarios
**Characteristics**:
- Bidirectional communication over HTTP
- Supports multiple simultaneous clients
- Can be deployed as a web service
- Enables server-to-client notifications
**Use when**:
- Serving multiple clients simultaneously
- Deploying as a cloud service
- Integration with web applications
### stdio
**Best for**: Local integrations, command-line tools
**Characteristics**:
- Standard input/output stream communication
- Simple setup, no network configuration needed
- Runs as a subprocess of the client
**Use when**:
- Building tools for local development environments
- Integrating with desktop applications
- Single-user, single-session scenarios
**Note**: stdio servers should NOT log to stdout (use stderr for logging)
### Transport Selection
| Criterion | stdio | Streamable HTTP |
|-----------|-------|-----------------|
| **Deployment** | Local | Remote |
| **Clients** | Single | Multiple |
| **Complexity** | Low | Medium |
| **Real-time** | No | Yes |
---
## Security Best Practices
### Authentication and Authorization
**OAuth 2.1**:
- Use secure OAuth 2.1 with certificates from recognized authorities
- Validate access tokens before processing requests
- Only accept tokens specifically intended for your server
**API Keys**:
- Store API keys in environment variables, never in code
- Validate keys on server startup
- Provide clear error messages when authentication fails
### Input Validation
- Sanitize file paths to prevent directory traversal
- Validate URLs and external identifiers
- Check parameter sizes and ranges
- Prevent command injection in system calls
- Use schema validation (Pydantic/Zod) for all inputs
### Error Handling
- Don't expose internal errors to clients
- Log security-relevant errors server-side
- Provide helpful but not revealing error messages
- Clean up resources after errors
### DNS Rebinding Protection
For streamable HTTP servers running locally:
- Enable DNS rebinding protection
- Validate the `Origin` header on all incoming connections
- Bind to `127.0.0.1` rather than `0.0.0.0`
---
## Tool Annotations
Provide annotations to help clients understand tool behavior:
| Annotation | Type | Default | Description |
|-----------|------|---------|-------------|
| `readOnlyHint` | boolean | false | Tool does not modify its environment |
| `destructiveHint` | boolean | true | Tool may perform destructive updates |
| `idempotentHint` | boolean | false | Repeated calls with same args have no additional effect |
| `openWorldHint` | boolean | true | Tool interacts with external entities |
**Important**: Annotations are hints, not security guarantees. Clients should not make security-critical decisions based solely on annotations.
---
## Error Handling
- Use standard JSON-RPC error codes
- Report tool errors within result objects (not protocol-level errors)
- Provide helpful, specific error messages with suggested next steps
- Don't expose internal implementation details
- Clean up resources properly on errors
Example error handling:
```typescript
try {
const result = performOperation();
return { content: [{ type: "text", text: result }] };
} catch (error) {
return {
isError: true,
content: [{
type: "text",
text: `Error: ${error.message}. Try using filter='active_only' to reduce results.`
}]
};
}
```
---
## Testing Requirements
Comprehensive testing should cover:
- **Functional testing**: Verify correct execution with valid/invalid inputs
- **Integration testing**: Test interaction with external systems
- **Security testing**: Validate auth, input sanitization, rate limiting
- **Performance testing**: Check behavior under load, timeouts
- **Error handling**: Ensure proper error reporting and cleanup
---
## Documentation Requirements
- Provide clear documentation of all tools and capabilities
- Include working examples (at least 3 per major feature)
- Document security considerations
- Specify required permissions and access levels
- Document rate limits and performance characteristics

View File

@@ -0,0 +1,970 @@
# Node/TypeScript MCP Server Implementation Guide
## Overview
This document provides Node/TypeScript-specific best practices and examples for implementing MCP servers using the MCP TypeScript SDK. It covers project structure, server setup, tool registration patterns, input validation with Zod, error handling, and complete working examples.
---
## Quick Reference
### Key Imports
```typescript
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import express from "express";
import { z } from "zod";
```
### Server Initialization
```typescript
const server = new McpServer({
name: "service-mcp-server",
version: "1.0.0"
});
```
### Tool Registration Pattern
```typescript
server.registerTool(
"tool_name",
{
title: "Tool Display Name",
description: "What the tool does",
inputSchema: { param: z.string() },
outputSchema: { result: z.string() }
},
async ({ param }) => {
const output = { result: `Processed: ${param}` };
return {
content: [{ type: "text", text: JSON.stringify(output) }],
structuredContent: output // Modern pattern for structured data
};
}
);
```
---
## MCP TypeScript SDK
The official MCP TypeScript SDK provides:
- `McpServer` class for server initialization
- `registerTool` method for tool registration
- Zod schema integration for runtime input validation
- Type-safe tool handler implementations
**IMPORTANT - Use Modern APIs Only:**
- **DO use**: `server.registerTool()`, `server.registerResource()`, `server.registerPrompt()`
- **DO NOT use**: Old deprecated APIs such as `server.tool()`, `server.setRequestHandler(ListToolsRequestSchema, ...)`, or manual handler registration
- The `register*` methods provide better type safety, automatic schema handling, and are the recommended approach
See the MCP SDK documentation in the references for complete details.
## Server Naming Convention
Node/TypeScript MCP servers must follow this naming pattern:
- **Format**: `{service}-mcp-server` (lowercase with hyphens)
- **Examples**: `github-mcp-server`, `jira-mcp-server`, `stripe-mcp-server`
The name should be:
- General (not tied to specific features)
- Descriptive of the service/API being integrated
- Easy to infer from the task description
- Without version numbers or dates
## Project Structure
Create the following structure for Node/TypeScript MCP servers:
```
{service}-mcp-server/
├── package.json
├── tsconfig.json
├── README.md
├── src/
│ ├── index.ts # Main entry point with McpServer initialization
│ ├── types.ts # TypeScript type definitions and interfaces
│ ├── tools/ # Tool implementations (one file per domain)
│ ├── services/ # API clients and shared utilities
│ ├── schemas/ # Zod validation schemas
│ └── constants.ts # Shared constants (API_URL, CHARACTER_LIMIT, etc.)
└── dist/ # Built JavaScript files (entry point: dist/index.js)
```
## Tool Implementation
### Tool Naming
Use snake_case for tool names (e.g., "search_users", "create_project", "get_channel_info") with clear, action-oriented names.
**Avoid Naming Conflicts**: Include the service context to prevent overlaps:
- Use "slack_send_message" instead of just "send_message"
- Use "github_create_issue" instead of just "create_issue"
- Use "asana_list_tasks" instead of just "list_tasks"
### Tool Structure
Tools are registered using the `registerTool` method with the following requirements:
- Use Zod schemas for runtime input validation and type safety
- The `description` field must be explicitly provided - JSDoc comments are NOT automatically extracted
- Explicitly provide `title`, `description`, `inputSchema`, and `annotations`
- The `inputSchema` must be a Zod schema object (not a JSON schema)
- Type all parameters and return values explicitly
```typescript
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { z } from "zod";
const server = new McpServer({
name: "example-mcp",
version: "1.0.0"
});
// Zod schema for input validation
const UserSearchInputSchema = z.object({
query: z.string()
.min(2, "Query must be at least 2 characters")
.max(200, "Query must not exceed 200 characters")
.describe("Search string to match against names/emails"),
limit: z.number()
.int()
.min(1)
.max(100)
.default(20)
.describe("Maximum results to return"),
offset: z.number()
.int()
.min(0)
.default(0)
.describe("Number of results to skip for pagination"),
response_format: z.nativeEnum(ResponseFormat)
.default(ResponseFormat.MARKDOWN)
.describe("Output format: 'markdown' for human-readable or 'json' for machine-readable")
}).strict();
// Type definition from Zod schema
type UserSearchInput = z.infer<typeof UserSearchInputSchema>;
server.registerTool(
"example_search_users",
{
title: "Search Example Users",
description: `Search for users in the Example system by name, email, or team.
This tool searches across all user profiles in the Example platform, supporting partial matches and various search filters. It does NOT create or modify users, only searches existing ones.
Args:
- query (string): Search string to match against names/emails
- limit (number): Maximum results to return, between 1-100 (default: 20)
- offset (number): Number of results to skip for pagination (default: 0)
- response_format ('markdown' | 'json'): Output format (default: 'markdown')
Returns:
For JSON format: Structured data with schema:
{
"total": number, // Total number of matches found
"count": number, // Number of results in this response
"offset": number, // Current pagination offset
"users": [
{
"id": string, // User ID (e.g., "U123456789")
"name": string, // Full name (e.g., "John Doe")
"email": string, // Email address
"team": string, // Team name (optional)
"active": boolean // Whether user is active
}
],
"has_more": boolean, // Whether more results are available
"next_offset": number // Offset for next page (if has_more is true)
}
Examples:
- Use when: "Find all marketing team members" -> params with query="team:marketing"
- Use when: "Search for John's account" -> params with query="john"
- Don't use when: You need to create a user (use example_create_user instead)
Error Handling:
- Returns "Error: Rate limit exceeded" if too many requests (429 status)
- Returns "No users found matching '<query>'" if search returns empty`,
inputSchema: UserSearchInputSchema,
annotations: {
readOnlyHint: true,
destructiveHint: false,
idempotentHint: true,
openWorldHint: true
}
},
async (params: UserSearchInput) => {
try {
// Input validation is handled by Zod schema
// Make API request using validated parameters
const data = await makeApiRequest<any>(
"users/search",
"GET",
undefined,
{
q: params.query,
limit: params.limit,
offset: params.offset
}
);
const users = data.users || [];
const total = data.total || 0;
if (!users.length) {
return {
content: [{
type: "text",
text: `No users found matching '${params.query}'`
}]
};
}
// Prepare structured output
const output = {
total,
count: users.length,
offset: params.offset,
users: users.map((user: any) => ({
id: user.id,
name: user.name,
email: user.email,
...(user.team ? { team: user.team } : {}),
active: user.active ?? true
})),
has_more: total > params.offset + users.length,
...(total > params.offset + users.length ? {
next_offset: params.offset + users.length
} : {})
};
// Format text representation based on requested format
let textContent: string;
if (params.response_format === ResponseFormat.MARKDOWN) {
const lines = [`# User Search Results: '${params.query}'`, "",
`Found ${total} users (showing ${users.length})`, ""];
for (const user of users) {
lines.push(`## ${user.name} (${user.id})`);
lines.push(`- **Email**: ${user.email}`);
if (user.team) lines.push(`- **Team**: ${user.team}`);
lines.push("");
}
textContent = lines.join("\n");
} else {
textContent = JSON.stringify(output, null, 2);
}
return {
content: [{ type: "text", text: textContent }],
structuredContent: output // Modern pattern for structured data
};
} catch (error) {
return {
content: [{
type: "text",
text: handleApiError(error)
}]
};
}
}
);
```
## Zod Schemas for Input Validation
Zod provides runtime type validation:
```typescript
import { z } from "zod";
// Basic schema with validation
const CreateUserSchema = z.object({
name: z.string()
.min(1, "Name is required")
.max(100, "Name must not exceed 100 characters"),
email: z.string()
.email("Invalid email format"),
age: z.number()
.int("Age must be a whole number")
.min(0, "Age cannot be negative")
.max(150, "Age cannot be greater than 150")
}).strict(); // Use .strict() to forbid extra fields
// Enums
enum ResponseFormat {
MARKDOWN = "markdown",
JSON = "json"
}
const SearchSchema = z.object({
response_format: z.nativeEnum(ResponseFormat)
.default(ResponseFormat.MARKDOWN)
.describe("Output format")
});
// Optional fields with defaults
const PaginationSchema = z.object({
limit: z.number()
.int()
.min(1)
.max(100)
.default(20)
.describe("Maximum results to return"),
offset: z.number()
.int()
.min(0)
.default(0)
.describe("Number of results to skip")
});
```
## Response Format Options
Support multiple output formats for flexibility:
```typescript
enum ResponseFormat {
MARKDOWN = "markdown",
JSON = "json"
}
const inputSchema = z.object({
query: z.string(),
response_format: z.nativeEnum(ResponseFormat)
.default(ResponseFormat.MARKDOWN)
.describe("Output format: 'markdown' for human-readable or 'json' for machine-readable")
});
```
**Markdown format**:
- Use headers, lists, and formatting for clarity
- Convert timestamps to human-readable format
- Show display names with IDs in parentheses
- Omit verbose metadata
- Group related information logically
**JSON format**:
- Return complete, structured data suitable for programmatic processing
- Include all available fields and metadata
- Use consistent field names and types
## Pagination Implementation
For tools that list resources:
```typescript
const ListSchema = z.object({
limit: z.number().int().min(1).max(100).default(20),
offset: z.number().int().min(0).default(0)
});
async function listItems(params: z.infer<typeof ListSchema>) {
const data = await apiRequest(params.limit, params.offset);
const response = {
total: data.total,
count: data.items.length,
offset: params.offset,
items: data.items,
has_more: data.total > params.offset + data.items.length,
next_offset: data.total > params.offset + data.items.length
? params.offset + data.items.length
: undefined
};
return JSON.stringify(response, null, 2);
}
```
## Character Limits and Truncation
Add a CHARACTER_LIMIT constant to prevent overwhelming responses:
```typescript
// At module level in constants.ts
export const CHARACTER_LIMIT = 25000; // Maximum response size in characters
async function searchTool(params: SearchInput) {
let result = generateResponse(data);
// Check character limit and truncate if needed
if (result.length > CHARACTER_LIMIT) {
const truncatedData = data.slice(0, Math.max(1, data.length / 2));
response.data = truncatedData;
response.truncated = true;
response.truncation_message =
`Response truncated from ${data.length} to ${truncatedData.length} items. ` +
`Use 'offset' parameter or add filters to see more results.`;
result = JSON.stringify(response, null, 2);
}
return result;
}
```
## Error Handling
Provide clear, actionable error messages:
```typescript
import axios, { AxiosError } from "axios";
function handleApiError(error: unknown): string {
if (error instanceof AxiosError) {
if (error.response) {
switch (error.response.status) {
case 404:
return "Error: Resource not found. Please check the ID is correct.";
case 403:
return "Error: Permission denied. You don't have access to this resource.";
case 429:
return "Error: Rate limit exceeded. Please wait before making more requests.";
default:
return `Error: API request failed with status ${error.response.status}`;
}
} else if (error.code === "ECONNABORTED") {
return "Error: Request timed out. Please try again.";
}
}
return `Error: Unexpected error occurred: ${error instanceof Error ? error.message : String(error)}`;
}
```
## Shared Utilities
Extract common functionality into reusable functions:
```typescript
// Shared API request function
async function makeApiRequest<T>(
endpoint: string,
method: "GET" | "POST" | "PUT" | "DELETE" = "GET",
data?: any,
params?: any
): Promise<T> {
try {
const response = await axios({
method,
url: `${API_BASE_URL}/${endpoint}`,
data,
params,
timeout: 30000,
headers: {
"Content-Type": "application/json",
"Accept": "application/json"
}
});
return response.data;
} catch (error) {
throw error;
}
}
```
## Async/Await Best Practices
Always use async/await for network requests and I/O operations:
```typescript
// Good: Async network request
async function fetchData(resourceId: string): Promise<ResourceData> {
const response = await axios.get(`${API_URL}/resource/${resourceId}`);
return response.data;
}
// Bad: Promise chains
function fetchData(resourceId: string): Promise<ResourceData> {
return axios.get(`${API_URL}/resource/${resourceId}`)
.then(response => response.data); // Harder to read and maintain
}
```
## TypeScript Best Practices
1. **Use Strict TypeScript**: Enable strict mode in tsconfig.json
2. **Define Interfaces**: Create clear interface definitions for all data structures
3. **Avoid `any`**: Use proper types or `unknown` instead of `any`
4. **Zod for Runtime Validation**: Use Zod schemas to validate external data
5. **Type Guards**: Create type guard functions for complex type checking
6. **Error Handling**: Always use try-catch with proper error type checking
7. **Null Safety**: Use optional chaining (`?.`) and nullish coalescing (`??`)
```typescript
// Good: Type-safe with Zod and interfaces
interface UserResponse {
id: string;
name: string;
email: string;
team?: string;
active: boolean;
}
const UserSchema = z.object({
id: z.string(),
name: z.string(),
email: z.string().email(),
team: z.string().optional(),
active: z.boolean()
});
type User = z.infer<typeof UserSchema>;
async function getUser(id: string): Promise<User> {
const data = await apiCall(`/users/${id}`);
return UserSchema.parse(data); // Runtime validation
}
// Bad: Using any
async function getUser(id: string): Promise<any> {
return await apiCall(`/users/${id}`); // No type safety
}
```
## Package Configuration
### package.json
```json
{
"name": "{service}-mcp-server",
"version": "1.0.0",
"description": "MCP server for {Service} API integration",
"type": "module",
"main": "dist/index.js",
"scripts": {
"start": "node dist/index.js",
"dev": "tsx watch src/index.ts",
"build": "tsc",
"clean": "rm -rf dist"
},
"engines": {
"node": ">=18"
},
"dependencies": {
"@modelcontextprotocol/sdk": "^1.6.1",
"axios": "^1.7.9",
"zod": "^3.23.8"
},
"devDependencies": {
"@types/node": "^22.10.0",
"tsx": "^4.19.2",
"typescript": "^5.7.2"
}
}
```
### tsconfig.json
```json
{
"compilerOptions": {
"target": "ES2022",
"module": "Node16",
"moduleResolution": "Node16",
"lib": ["ES2022"],
"outDir": "./dist",
"rootDir": "./src",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"declaration": true,
"declarationMap": true,
"sourceMap": true,
"allowSyntheticDefaultImports": true
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist"]
}
```
## Complete Example
```typescript
#!/usr/bin/env node
/**
* MCP Server for Example Service.
*
* This server provides tools to interact with Example API, including user search,
* project management, and data export capabilities.
*/
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";
import axios, { AxiosError } from "axios";
// Constants
const API_BASE_URL = "https://api.example.com/v1";
const CHARACTER_LIMIT = 25000;
// Enums
enum ResponseFormat {
MARKDOWN = "markdown",
JSON = "json"
}
// Zod schemas
const UserSearchInputSchema = z.object({
query: z.string()
.min(2, "Query must be at least 2 characters")
.max(200, "Query must not exceed 200 characters")
.describe("Search string to match against names/emails"),
limit: z.number()
.int()
.min(1)
.max(100)
.default(20)
.describe("Maximum results to return"),
offset: z.number()
.int()
.min(0)
.default(0)
.describe("Number of results to skip for pagination"),
response_format: z.nativeEnum(ResponseFormat)
.default(ResponseFormat.MARKDOWN)
.describe("Output format: 'markdown' for human-readable or 'json' for machine-readable")
}).strict();
type UserSearchInput = z.infer<typeof UserSearchInputSchema>;
// Shared utility functions
async function makeApiRequest<T>(
endpoint: string,
method: "GET" | "POST" | "PUT" | "DELETE" = "GET",
data?: any,
params?: any
): Promise<T> {
try {
const response = await axios({
method,
url: `${API_BASE_URL}/${endpoint}`,
data,
params,
timeout: 30000,
headers: {
"Content-Type": "application/json",
"Accept": "application/json"
}
});
return response.data;
} catch (error) {
throw error;
}
}
function handleApiError(error: unknown): string {
if (error instanceof AxiosError) {
if (error.response) {
switch (error.response.status) {
case 404:
return "Error: Resource not found. Please check the ID is correct.";
case 403:
return "Error: Permission denied. You don't have access to this resource.";
case 429:
return "Error: Rate limit exceeded. Please wait before making more requests.";
default:
return `Error: API request failed with status ${error.response.status}`;
}
} else if (error.code === "ECONNABORTED") {
return "Error: Request timed out. Please try again.";
}
}
return `Error: Unexpected error occurred: ${error instanceof Error ? error.message : String(error)}`;
}
// Create MCP server instance
const server = new McpServer({
name: "example-mcp",
version: "1.0.0"
});
// Register tools
server.registerTool(
"example_search_users",
{
title: "Search Example Users",
description: `[Full description as shown above]`,
inputSchema: UserSearchInputSchema,
annotations: {
readOnlyHint: true,
destructiveHint: false,
idempotentHint: true,
openWorldHint: true
}
},
async (params: UserSearchInput) => {
// Implementation as shown above
}
);
// Main function
// For stdio (local):
async function runStdio() {
if (!process.env.EXAMPLE_API_KEY) {
console.error("ERROR: EXAMPLE_API_KEY environment variable is required");
process.exit(1);
}
const transport = new StdioServerTransport();
await server.connect(transport);
console.error("MCP server running via stdio");
}
// For streamable HTTP (remote):
async function runHTTP() {
if (!process.env.EXAMPLE_API_KEY) {
console.error("ERROR: EXAMPLE_API_KEY environment variable is required");
process.exit(1);
}
const app = express();
app.use(express.json());
app.post('/mcp', async (req, res) => {
const transport = new StreamableHTTPServerTransport({
sessionIdGenerator: undefined,
enableJsonResponse: true
});
res.on('close', () => transport.close());
await server.connect(transport);
await transport.handleRequest(req, res, req.body);
});
const port = parseInt(process.env.PORT || '3000');
app.listen(port, () => {
console.error(`MCP server running on http://localhost:${port}/mcp`);
});
}
// Choose transport based on environment
const transport = process.env.TRANSPORT || 'stdio';
if (transport === 'http') {
runHTTP().catch(error => {
console.error("Server error:", error);
process.exit(1);
});
} else {
runStdio().catch(error => {
console.error("Server error:", error);
process.exit(1);
});
}
```
---
## Advanced MCP Features
### Resource Registration
Expose data as resources for efficient, URI-based access:
```typescript
import { ResourceTemplate } from "@modelcontextprotocol/sdk/types.js";
// Register a resource with URI template
server.registerResource(
{
uri: "file://documents/{name}",
name: "Document Resource",
description: "Access documents by name",
mimeType: "text/plain"
},
async (uri: string) => {
// Extract parameter from URI
const match = uri.match(/^file:\/\/documents\/(.+)$/);
if (!match) {
throw new Error("Invalid URI format");
}
const documentName = match[1];
const content = await loadDocument(documentName);
return {
contents: [{
uri,
mimeType: "text/plain",
text: content
}]
};
}
);
// List available resources dynamically
server.registerResourceList(async () => {
const documents = await getAvailableDocuments();
return {
resources: documents.map(doc => ({
uri: `file://documents/${doc.name}`,
name: doc.name,
mimeType: "text/plain",
description: doc.description
}))
};
});
```
**When to use Resources vs Tools:**
- **Resources**: For data access with simple URI-based parameters
- **Tools**: For complex operations requiring validation and business logic
- **Resources**: When data is relatively static or template-based
- **Tools**: When operations have side effects or complex workflows
### Transport Options
The TypeScript SDK supports two main transport mechanisms:
#### Streamable HTTP (Recommended for Remote Servers)
```typescript
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
import express from "express";
const app = express();
app.use(express.json());
app.post('/mcp', async (req, res) => {
// Create new transport for each request (stateless, prevents request ID collisions)
const transport = new StreamableHTTPServerTransport({
sessionIdGenerator: undefined,
enableJsonResponse: true
});
res.on('close', () => transport.close());
await server.connect(transport);
await transport.handleRequest(req, res, req.body);
});
app.listen(3000);
```
#### stdio (For Local Integrations)
```typescript
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
const transport = new StdioServerTransport();
await server.connect(transport);
```
**Transport selection:**
- **Streamable HTTP**: Web services, remote access, multiple clients
- **stdio**: Command-line tools, local development, subprocess integration
### Notification Support
Notify clients when server state changes:
```typescript
// Notify when tools list changes
server.notification({
method: "notifications/tools/list_changed"
});
// Notify when resources change
server.notification({
method: "notifications/resources/list_changed"
});
```
Use notifications sparingly - only when server capabilities genuinely change.
---
## Code Best Practices
### Code Composability and Reusability
Your implementation MUST prioritize composability and code reuse:
1. **Extract Common Functionality**:
- Create reusable helper functions for operations used across multiple tools
- Build shared API clients for HTTP requests instead of duplicating code
- Centralize error handling logic in utility functions
- Extract business logic into dedicated functions that can be composed
- Extract shared markdown or JSON field selection & formatting functionality
2. **Avoid Duplication**:
- NEVER copy-paste similar code between tools
- If you find yourself writing similar logic twice, extract it into a function
- Common operations like pagination, filtering, field selection, and formatting should be shared
- Authentication/authorization logic should be centralized
## Building and Running
Always build your TypeScript code before running:
```bash
# Build the project
npm run build
# Run the server
npm start
# Development with auto-reload
npm run dev
```
Always ensure `npm run build` completes successfully before considering the implementation complete.
## Quality Checklist
Before finalizing your Node/TypeScript MCP server implementation, ensure:
### Strategic Design
- [ ] Tools enable complete workflows, not just API endpoint wrappers
- [ ] Tool names reflect natural task subdivisions
- [ ] Response formats optimize for agent context efficiency
- [ ] Human-readable identifiers used where appropriate
- [ ] Error messages guide agents toward correct usage
### Implementation Quality
- [ ] FOCUSED IMPLEMENTATION: Most important and valuable tools implemented
- [ ] All tools registered using `registerTool` with complete configuration
- [ ] All tools include `title`, `description`, `inputSchema`, and `annotations`
- [ ] Annotations correctly set (readOnlyHint, destructiveHint, idempotentHint, openWorldHint)
- [ ] All tools use Zod schemas for runtime input validation with `.strict()` enforcement
- [ ] All Zod schemas have proper constraints and descriptive error messages
- [ ] All tools have comprehensive descriptions with explicit input/output types
- [ ] Descriptions include return value examples and complete schema documentation
- [ ] Error messages are clear, actionable, and educational
### TypeScript Quality
- [ ] TypeScript interfaces are defined for all data structures
- [ ] Strict TypeScript is enabled in tsconfig.json
- [ ] No use of `any` type - use `unknown` or proper types instead
- [ ] All async functions have explicit Promise<T> return types
- [ ] Error handling uses proper type guards (e.g., `axios.isAxiosError`, `z.ZodError`)
### Advanced Features (where applicable)
- [ ] Resources registered for appropriate data endpoints
- [ ] Appropriate transport configured (stdio or streamable HTTP)
- [ ] Notifications implemented for dynamic server capabilities
- [ ] Type-safe with SDK interfaces
### Project Configuration
- [ ] Package.json includes all necessary dependencies
- [ ] Build script produces working JavaScript in dist/ directory
- [ ] Main entry point is properly configured as dist/index.js
- [ ] Server name follows format: `{service}-mcp-server`
- [ ] tsconfig.json properly configured with strict mode
### Code Quality
- [ ] Pagination is properly implemented where applicable
- [ ] Large responses check CHARACTER_LIMIT constant and truncate with clear messages
- [ ] Filtering options are provided for potentially large result sets
- [ ] All network operations handle timeouts and connection errors gracefully
- [ ] Common functionality is extracted into reusable functions
- [ ] Return types are consistent across similar operations
### Testing and Build
- [ ] `npm run build` completes successfully without errors
- [ ] dist/index.js created and executable
- [ ] Server runs: `node dist/index.js --help`
- [ ] All imports resolve correctly
- [ ] Sample tool calls work as expected

View File

@@ -0,0 +1,719 @@
# Python MCP Server Implementation Guide
## Overview
This document provides Python-specific best practices and examples for implementing MCP servers using the MCP Python SDK. It covers server setup, tool registration patterns, input validation with Pydantic, error handling, and complete working examples.
---
## Quick Reference
### Key Imports
```python
from mcp.server.fastmcp import FastMCP
from pydantic import BaseModel, Field, field_validator, ConfigDict
from typing import Optional, List, Dict, Any
from enum import Enum
import httpx
```
### Server Initialization
```python
mcp = FastMCP("service_mcp")
```
### Tool Registration Pattern
```python
@mcp.tool(name="tool_name", annotations={...})
async def tool_function(params: InputModel) -> str:
# Implementation
pass
```
---
## MCP Python SDK and FastMCP
The official MCP Python SDK provides FastMCP, a high-level framework for building MCP servers. It provides:
- Automatic description and inputSchema generation from function signatures and docstrings
- Pydantic model integration for input validation
- Decorator-based tool registration with `@mcp.tool`
**For complete SDK documentation, use WebFetch to load:**
`https://raw.githubusercontent.com/modelcontextprotocol/python-sdk/main/README.md`
## Server Naming Convention
Python MCP servers must follow this naming pattern:
- **Format**: `{service}_mcp` (lowercase with underscores)
- **Examples**: `github_mcp`, `jira_mcp`, `stripe_mcp`
The name should be:
- General (not tied to specific features)
- Descriptive of the service/API being integrated
- Easy to infer from the task description
- Without version numbers or dates
## Tool Implementation
### Tool Naming
Use snake_case for tool names (e.g., "search_users", "create_project", "get_channel_info") with clear, action-oriented names.
**Avoid Naming Conflicts**: Include the service context to prevent overlaps:
- Use "slack_send_message" instead of just "send_message"
- Use "github_create_issue" instead of just "create_issue"
- Use "asana_list_tasks" instead of just "list_tasks"
### Tool Structure with FastMCP
Tools are defined using the `@mcp.tool` decorator with Pydantic models for input validation:
```python
from pydantic import BaseModel, Field, ConfigDict
from mcp.server.fastmcp import FastMCP
# Initialize the MCP server
mcp = FastMCP("example_mcp")
# Define Pydantic model for input validation
class ServiceToolInput(BaseModel):
'''Input model for service tool operation.'''
model_config = ConfigDict(
str_strip_whitespace=True, # Auto-strip whitespace from strings
validate_assignment=True, # Validate on assignment
extra='forbid' # Forbid extra fields
)
param1: str = Field(..., description="First parameter description (e.g., 'user123', 'project-abc')", min_length=1, max_length=100)
param2: Optional[int] = Field(default=None, description="Optional integer parameter with constraints", ge=0, le=1000)
tags: Optional[List[str]] = Field(default_factory=list, description="List of tags to apply", max_items=10)
@mcp.tool(
name="service_tool_name",
annotations={
"title": "Human-Readable Tool Title",
"readOnlyHint": True, # Tool does not modify environment
"destructiveHint": False, # Tool does not perform destructive operations
"idempotentHint": True, # Repeated calls have no additional effect
"openWorldHint": False # Tool does not interact with external entities
}
)
async def service_tool_name(params: ServiceToolInput) -> str:
'''Tool description automatically becomes the 'description' field.
This tool performs a specific operation on the service. It validates all inputs
using the ServiceToolInput Pydantic model before processing.
Args:
params (ServiceToolInput): Validated input parameters containing:
- param1 (str): First parameter description
- param2 (Optional[int]): Optional parameter with default
- tags (Optional[List[str]]): List of tags
Returns:
str: JSON-formatted response containing operation results
'''
# Implementation here
pass
```
## Pydantic v2 Key Features
- Use `model_config` instead of nested `Config` class
- Use `field_validator` instead of deprecated `validator`
- Use `model_dump()` instead of deprecated `dict()`
- Validators require `@classmethod` decorator
- Type hints are required for validator methods
```python
from pydantic import BaseModel, Field, field_validator, ConfigDict
class CreateUserInput(BaseModel):
model_config = ConfigDict(
str_strip_whitespace=True,
validate_assignment=True
)
name: str = Field(..., description="User's full name", min_length=1, max_length=100)
email: str = Field(..., description="User's email address", pattern=r'^[\w\.-]+@[\w\.-]+\.\w+$')
age: int = Field(..., description="User's age", ge=0, le=150)
@field_validator('email')
@classmethod
def validate_email(cls, v: str) -> str:
if not v.strip():
raise ValueError("Email cannot be empty")
return v.lower()
```
## Response Format Options
Support multiple output formats for flexibility:
```python
from enum import Enum
class ResponseFormat(str, Enum):
'''Output format for tool responses.'''
MARKDOWN = "markdown"
JSON = "json"
class UserSearchInput(BaseModel):
query: str = Field(..., description="Search query")
response_format: ResponseFormat = Field(
default=ResponseFormat.MARKDOWN,
description="Output format: 'markdown' for human-readable or 'json' for machine-readable"
)
```
**Markdown format**:
- Use headers, lists, and formatting for clarity
- Convert timestamps to human-readable format (e.g., "2024-01-15 10:30:00 UTC" instead of epoch)
- Show display names with IDs in parentheses (e.g., "@john.doe (U123456)")
- Omit verbose metadata (e.g., show only one profile image URL, not all sizes)
- Group related information logically
**JSON format**:
- Return complete, structured data suitable for programmatic processing
- Include all available fields and metadata
- Use consistent field names and types
## Pagination Implementation
For tools that list resources:
```python
class ListInput(BaseModel):
limit: Optional[int] = Field(default=20, description="Maximum results to return", ge=1, le=100)
offset: Optional[int] = Field(default=0, description="Number of results to skip for pagination", ge=0)
async def list_items(params: ListInput) -> str:
# Make API request with pagination
data = await api_request(limit=params.limit, offset=params.offset)
# Return pagination info
response = {
"total": data["total"],
"count": len(data["items"]),
"offset": params.offset,
"items": data["items"],
"has_more": data["total"] > params.offset + len(data["items"]),
"next_offset": params.offset + len(data["items"]) if data["total"] > params.offset + len(data["items"]) else None
}
return json.dumps(response, indent=2)
```
## Error Handling
Provide clear, actionable error messages:
```python
def _handle_api_error(e: Exception) -> str:
'''Consistent error formatting across all tools.'''
if isinstance(e, httpx.HTTPStatusError):
if e.response.status_code == 404:
return "Error: Resource not found. Please check the ID is correct."
elif e.response.status_code == 403:
return "Error: Permission denied. You don't have access to this resource."
elif e.response.status_code == 429:
return "Error: Rate limit exceeded. Please wait before making more requests."
return f"Error: API request failed with status {e.response.status_code}"
elif isinstance(e, httpx.TimeoutException):
return "Error: Request timed out. Please try again."
return f"Error: Unexpected error occurred: {type(e).__name__}"
```
## Shared Utilities
Extract common functionality into reusable functions:
```python
# Shared API request function
async def _make_api_request(endpoint: str, method: str = "GET", **kwargs) -> dict:
'''Reusable function for all API calls.'''
async with httpx.AsyncClient() as client:
response = await client.request(
method,
f"{API_BASE_URL}/{endpoint}",
timeout=30.0,
**kwargs
)
response.raise_for_status()
return response.json()
```
## Async/Await Best Practices
Always use async/await for network requests and I/O operations:
```python
# Good: Async network request
async def fetch_data(resource_id: str) -> dict:
async with httpx.AsyncClient() as client:
response = await client.get(f"{API_URL}/resource/{resource_id}")
response.raise_for_status()
return response.json()
# Bad: Synchronous request
def fetch_data(resource_id: str) -> dict:
response = requests.get(f"{API_URL}/resource/{resource_id}") # Blocks
return response.json()
```
## Type Hints
Use type hints throughout:
```python
from typing import Optional, List, Dict, Any
async def get_user(user_id: str) -> Dict[str, Any]:
data = await fetch_user(user_id)
return {"id": data["id"], "name": data["name"]}
```
## Tool Docstrings
Every tool must have comprehensive docstrings with explicit type information:
```python
async def search_users(params: UserSearchInput) -> str:
'''
Search for users in the Example system by name, email, or team.
This tool searches across all user profiles in the Example platform,
supporting partial matches and various search filters. It does NOT
create or modify users, only searches existing ones.
Args:
params (UserSearchInput): Validated input parameters containing:
- query (str): Search string to match against names/emails (e.g., "john", "@example.com", "team:marketing")
- limit (Optional[int]): Maximum results to return, between 1-100 (default: 20)
- offset (Optional[int]): Number of results to skip for pagination (default: 0)
Returns:
str: JSON-formatted string containing search results with the following schema:
Success response:
{
"total": int, # Total number of matches found
"count": int, # Number of results in this response
"offset": int, # Current pagination offset
"users": [
{
"id": str, # User ID (e.g., "U123456789")
"name": str, # Full name (e.g., "John Doe")
"email": str, # Email address (e.g., "john@example.com")
"team": str # Team name (e.g., "Marketing") - optional
}
]
}
Error response:
"Error: <error message>" or "No users found matching '<query>'"
Examples:
- Use when: "Find all marketing team members" -> params with query="team:marketing"
- Use when: "Search for John's account" -> params with query="john"
- Don't use when: You need to create a user (use example_create_user instead)
- Don't use when: You have a user ID and need full details (use example_get_user instead)
Error Handling:
- Input validation errors are handled by Pydantic model
- Returns "Error: Rate limit exceeded" if too many requests (429 status)
- Returns "Error: Invalid API authentication" if API key is invalid (401 status)
- Returns formatted list of results or "No users found matching 'query'"
'''
```
## Complete Example
See below for a complete Python MCP server example:
```python
#!/usr/bin/env python3
'''
MCP Server for Example Service.
This server provides tools to interact with Example API, including user search,
project management, and data export capabilities.
'''
from typing import Optional, List, Dict, Any
from enum import Enum
import httpx
from pydantic import BaseModel, Field, field_validator, ConfigDict
from mcp.server.fastmcp import FastMCP
# Initialize the MCP server
mcp = FastMCP("example_mcp")
# Constants
API_BASE_URL = "https://api.example.com/v1"
# Enums
class ResponseFormat(str, Enum):
'''Output format for tool responses.'''
MARKDOWN = "markdown"
JSON = "json"
# Pydantic Models for Input Validation
class UserSearchInput(BaseModel):
'''Input model for user search operations.'''
model_config = ConfigDict(
str_strip_whitespace=True,
validate_assignment=True
)
query: str = Field(..., description="Search string to match against names/emails", min_length=2, max_length=200)
limit: Optional[int] = Field(default=20, description="Maximum results to return", ge=1, le=100)
offset: Optional[int] = Field(default=0, description="Number of results to skip for pagination", ge=0)
response_format: ResponseFormat = Field(default=ResponseFormat.MARKDOWN, description="Output format")
@field_validator('query')
@classmethod
def validate_query(cls, v: str) -> str:
if not v.strip():
raise ValueError("Query cannot be empty or whitespace only")
return v.strip()
# Shared utility functions
async def _make_api_request(endpoint: str, method: str = "GET", **kwargs) -> dict:
'''Reusable function for all API calls.'''
async with httpx.AsyncClient() as client:
response = await client.request(
method,
f"{API_BASE_URL}/{endpoint}",
timeout=30.0,
**kwargs
)
response.raise_for_status()
return response.json()
def _handle_api_error(e: Exception) -> str:
'''Consistent error formatting across all tools.'''
if isinstance(e, httpx.HTTPStatusError):
if e.response.status_code == 404:
return "Error: Resource not found. Please check the ID is correct."
elif e.response.status_code == 403:
return "Error: Permission denied. You don't have access to this resource."
elif e.response.status_code == 429:
return "Error: Rate limit exceeded. Please wait before making more requests."
return f"Error: API request failed with status {e.response.status_code}"
elif isinstance(e, httpx.TimeoutException):
return "Error: Request timed out. Please try again."
return f"Error: Unexpected error occurred: {type(e).__name__}"
# Tool definitions
@mcp.tool(
name="example_search_users",
annotations={
"title": "Search Example Users",
"readOnlyHint": True,
"destructiveHint": False,
"idempotentHint": True,
"openWorldHint": True
}
)
async def example_search_users(params: UserSearchInput) -> str:
'''Search for users in the Example system by name, email, or team.
[Full docstring as shown above]
'''
try:
# Make API request using validated parameters
data = await _make_api_request(
"users/search",
params={
"q": params.query,
"limit": params.limit,
"offset": params.offset
}
)
users = data.get("users", [])
total = data.get("total", 0)
if not users:
return f"No users found matching '{params.query}'"
# Format response based on requested format
if params.response_format == ResponseFormat.MARKDOWN:
lines = [f"# User Search Results: '{params.query}'", ""]
lines.append(f"Found {total} users (showing {len(users)})")
lines.append("")
for user in users:
lines.append(f"## {user['name']} ({user['id']})")
lines.append(f"- **Email**: {user['email']}")
if user.get('team'):
lines.append(f"- **Team**: {user['team']}")
lines.append("")
return "\n".join(lines)
else:
# Machine-readable JSON format
import json
response = {
"total": total,
"count": len(users),
"offset": params.offset,
"users": users
}
return json.dumps(response, indent=2)
except Exception as e:
return _handle_api_error(e)
if __name__ == "__main__":
mcp.run()
```
---
## Advanced FastMCP Features
### Context Parameter Injection
FastMCP can automatically inject a `Context` parameter into tools for advanced capabilities like logging, progress reporting, resource reading, and user interaction:
```python
from mcp.server.fastmcp import FastMCP, Context
mcp = FastMCP("example_mcp")
@mcp.tool()
async def advanced_search(query: str, ctx: Context) -> str:
'''Advanced tool with context access for logging and progress.'''
# Report progress for long operations
await ctx.report_progress(0.25, "Starting search...")
# Log information for debugging
await ctx.log_info("Processing query", {"query": query, "timestamp": datetime.now()})
# Perform search
results = await search_api(query)
await ctx.report_progress(0.75, "Formatting results...")
# Access server configuration
server_name = ctx.fastmcp.name
return format_results(results)
@mcp.tool()
async def interactive_tool(resource_id: str, ctx: Context) -> str:
'''Tool that can request additional input from users.'''
# Request sensitive information when needed
api_key = await ctx.elicit(
prompt="Please provide your API key:",
input_type="password"
)
# Use the provided key
return await api_call(resource_id, api_key)
```
**Context capabilities:**
- `ctx.report_progress(progress, message)` - Report progress for long operations
- `ctx.log_info(message, data)` / `ctx.log_error()` / `ctx.log_debug()` - Logging
- `ctx.elicit(prompt, input_type)` - Request input from users
- `ctx.fastmcp.name` - Access server configuration
- `ctx.read_resource(uri)` - Read MCP resources
### Resource Registration
Expose data as resources for efficient, template-based access:
```python
@mcp.resource("file://documents/{name}")
async def get_document(name: str) -> str:
'''Expose documents as MCP resources.
Resources are useful for static or semi-static data that doesn't
require complex parameters. They use URI templates for flexible access.
'''
document_path = f"./docs/{name}"
with open(document_path, "r") as f:
return f.read()
@mcp.resource("config://settings/{key}")
async def get_setting(key: str, ctx: Context) -> str:
'''Expose configuration as resources with context.'''
settings = await load_settings()
return json.dumps(settings.get(key, {}))
```
**When to use Resources vs Tools:**
- **Resources**: For data access with simple parameters (URI templates)
- **Tools**: For complex operations with validation and business logic
### Structured Output Types
FastMCP supports multiple return types beyond strings:
```python
from typing import TypedDict
from dataclasses import dataclass
from pydantic import BaseModel
# TypedDict for structured returns
class UserData(TypedDict):
id: str
name: str
email: str
@mcp.tool()
async def get_user_typed(user_id: str) -> UserData:
'''Returns structured data - FastMCP handles serialization.'''
return {"id": user_id, "name": "John Doe", "email": "john@example.com"}
# Pydantic models for complex validation
class DetailedUser(BaseModel):
id: str
name: str
email: str
created_at: datetime
metadata: Dict[str, Any]
@mcp.tool()
async def get_user_detailed(user_id: str) -> DetailedUser:
'''Returns Pydantic model - automatically generates schema.'''
user = await fetch_user(user_id)
return DetailedUser(**user)
```
### Lifespan Management
Initialize resources that persist across requests:
```python
from contextlib import asynccontextmanager
@asynccontextmanager
async def app_lifespan():
'''Manage resources that live for the server's lifetime.'''
# Initialize connections, load config, etc.
db = await connect_to_database()
config = load_configuration()
# Make available to all tools
yield {"db": db, "config": config}
# Cleanup on shutdown
await db.close()
mcp = FastMCP("example_mcp", lifespan=app_lifespan)
@mcp.tool()
async def query_data(query: str, ctx: Context) -> str:
'''Access lifespan resources through context.'''
db = ctx.request_context.lifespan_state["db"]
results = await db.query(query)
return format_results(results)
```
### Transport Options
FastMCP supports two main transport mechanisms:
```python
# stdio transport (for local tools) - default
if __name__ == "__main__":
mcp.run()
# Streamable HTTP transport (for remote servers)
if __name__ == "__main__":
mcp.run(transport="streamable_http", port=8000)
```
**Transport selection:**
- **stdio**: Command-line tools, local integrations, subprocess execution
- **Streamable HTTP**: Web services, remote access, multiple clients
---
## Code Best Practices
### Code Composability and Reusability
Your implementation MUST prioritize composability and code reuse:
1. **Extract Common Functionality**:
- Create reusable helper functions for operations used across multiple tools
- Build shared API clients for HTTP requests instead of duplicating code
- Centralize error handling logic in utility functions
- Extract business logic into dedicated functions that can be composed
- Extract shared markdown or JSON field selection & formatting functionality
2. **Avoid Duplication**:
- NEVER copy-paste similar code between tools
- If you find yourself writing similar logic twice, extract it into a function
- Common operations like pagination, filtering, field selection, and formatting should be shared
- Authentication/authorization logic should be centralized
### Python-Specific Best Practices
1. **Use Type Hints**: Always include type annotations for function parameters and return values
2. **Pydantic Models**: Define clear Pydantic models for all input validation
3. **Avoid Manual Validation**: Let Pydantic handle input validation with constraints
4. **Proper Imports**: Group imports (standard library, third-party, local)
5. **Error Handling**: Use specific exception types (httpx.HTTPStatusError, not generic Exception)
6. **Async Context Managers**: Use `async with` for resources that need cleanup
7. **Constants**: Define module-level constants in UPPER_CASE
## Quality Checklist
Before finalizing your Python MCP server implementation, ensure:
### Strategic Design
- [ ] Tools enable complete workflows, not just API endpoint wrappers
- [ ] Tool names reflect natural task subdivisions
- [ ] Response formats optimize for agent context efficiency
- [ ] Human-readable identifiers used where appropriate
- [ ] Error messages guide agents toward correct usage
### Implementation Quality
- [ ] FOCUSED IMPLEMENTATION: Most important and valuable tools implemented
- [ ] All tools have descriptive names and documentation
- [ ] Return types are consistent across similar operations
- [ ] Error handling is implemented for all external calls
- [ ] Server name follows format: `{service}_mcp`
- [ ] All network operations use async/await
- [ ] Common functionality is extracted into reusable functions
- [ ] Error messages are clear, actionable, and educational
- [ ] Outputs are properly validated and formatted
### Tool Configuration
- [ ] All tools implement 'name' and 'annotations' in the decorator
- [ ] Annotations correctly set (readOnlyHint, destructiveHint, idempotentHint, openWorldHint)
- [ ] All tools use Pydantic BaseModel for input validation with Field() definitions
- [ ] All Pydantic Fields have explicit types and descriptions with constraints
- [ ] All tools have comprehensive docstrings with explicit input/output types
- [ ] Docstrings include complete schema structure for dict/JSON returns
- [ ] Pydantic models handle input validation (no manual validation needed)
### Advanced Features (where applicable)
- [ ] Context injection used for logging, progress, or elicitation
- [ ] Resources registered for appropriate data endpoints
- [ ] Lifespan management implemented for persistent connections
- [ ] Structured output types used (TypedDict, Pydantic models)
- [ ] Appropriate transport configured (stdio or streamable HTTP)
### Code Quality
- [ ] File includes proper imports including Pydantic imports
- [ ] Pagination is properly implemented where applicable
- [ ] Filtering options are provided for potentially large result sets
- [ ] All async functions are properly defined with `async def`
- [ ] HTTP client usage follows async patterns with proper context managers
- [ ] Type hints are used throughout the code
- [ ] Constants are defined at module level in UPPER_CASE
### Testing
- [ ] Server runs successfully: `python your_server.py --help`
- [ ] All imports resolve correctly
- [ ] Sample tool calls work as expected
- [ ] Error scenarios handled gracefully

View File

@@ -0,0 +1,151 @@
"""Lightweight connection handling for MCP servers."""
from abc import ABC, abstractmethod
from contextlib import AsyncExitStack
from typing import Any
from mcp import ClientSession, StdioServerParameters
from mcp.client.sse import sse_client
from mcp.client.stdio import stdio_client
from mcp.client.streamable_http import streamablehttp_client
class MCPConnection(ABC):
"""Base class for MCP server connections."""
def __init__(self):
self.session = None
self._stack = None
@abstractmethod
def _create_context(self):
"""Create the connection context based on connection type."""
async def __aenter__(self):
"""Initialize MCP server connection."""
self._stack = AsyncExitStack()
await self._stack.__aenter__()
try:
ctx = self._create_context()
result = await self._stack.enter_async_context(ctx)
if len(result) == 2:
read, write = result
elif len(result) == 3:
read, write, _ = result
else:
raise ValueError(f"Unexpected context result: {result}")
session_ctx = ClientSession(read, write)
self.session = await self._stack.enter_async_context(session_ctx)
await self.session.initialize()
return self
except BaseException:
await self._stack.__aexit__(None, None, None)
raise
async def __aexit__(self, exc_type, exc_val, exc_tb):
"""Clean up MCP server connection resources."""
if self._stack:
await self._stack.__aexit__(exc_type, exc_val, exc_tb)
self.session = None
self._stack = None
async def list_tools(self) -> list[dict[str, Any]]:
"""Retrieve available tools from the MCP server."""
response = await self.session.list_tools()
return [
{
"name": tool.name,
"description": tool.description,
"input_schema": tool.inputSchema,
}
for tool in response.tools
]
async def call_tool(self, tool_name: str, arguments: dict[str, Any]) -> Any:
"""Call a tool on the MCP server with provided arguments."""
result = await self.session.call_tool(tool_name, arguments=arguments)
return result.content
class MCPConnectionStdio(MCPConnection):
"""MCP connection using standard input/output."""
def __init__(self, command: str, args: list[str] = None, env: dict[str, str] = None):
super().__init__()
self.command = command
self.args = args or []
self.env = env
def _create_context(self):
return stdio_client(
StdioServerParameters(command=self.command, args=self.args, env=self.env)
)
class MCPConnectionSSE(MCPConnection):
"""MCP connection using Server-Sent Events."""
def __init__(self, url: str, headers: dict[str, str] = None):
super().__init__()
self.url = url
self.headers = headers or {}
def _create_context(self):
return sse_client(url=self.url, headers=self.headers)
class MCPConnectionHTTP(MCPConnection):
"""MCP connection using Streamable HTTP."""
def __init__(self, url: str, headers: dict[str, str] = None):
super().__init__()
self.url = url
self.headers = headers or {}
def _create_context(self):
return streamablehttp_client(url=self.url, headers=self.headers)
def create_connection(
transport: str,
command: str = None,
args: list[str] = None,
env: dict[str, str] = None,
url: str = None,
headers: dict[str, str] = None,
) -> MCPConnection:
"""Factory function to create the appropriate MCP connection.
Args:
transport: Connection type ("stdio", "sse", or "http")
command: Command to run (stdio only)
args: Command arguments (stdio only)
env: Environment variables (stdio only)
url: Server URL (sse and http only)
headers: HTTP headers (sse and http only)
Returns:
MCPConnection instance
"""
transport = transport.lower()
if transport == "stdio":
if not command:
raise ValueError("Command is required for stdio transport")
return MCPConnectionStdio(command=command, args=args, env=env)
elif transport == "sse":
if not url:
raise ValueError("URL is required for sse transport")
return MCPConnectionSSE(url=url, headers=headers)
elif transport in ["http", "streamable_http", "streamable-http"]:
if not url:
raise ValueError("URL is required for http transport")
return MCPConnectionHTTP(url=url, headers=headers)
else:
raise ValueError(f"Unsupported transport type: {transport}. Use 'stdio', 'sse', or 'http'")

View File

@@ -0,0 +1,373 @@
"""MCP Server Evaluation Harness
This script evaluates MCP servers by running test questions against them using Claude.
"""
import argparse
import asyncio
import json
import re
import sys
import time
import traceback
import xml.etree.ElementTree as ET
from pathlib import Path
from typing import Any
from anthropic import Anthropic
from connections import create_connection
EVALUATION_PROMPT = """You are an AI assistant with access to tools.
When given a task, you MUST:
1. Use the available tools to complete the task
2. Provide summary of each step in your approach, wrapped in <summary> tags
3. Provide feedback on the tools provided, wrapped in <feedback> tags
4. Provide your final response, wrapped in <response> tags
Summary Requirements:
- In your <summary> tags, you must explain:
- The steps you took to complete the task
- Which tools you used, in what order, and why
- The inputs you provided to each tool
- The outputs you received from each tool
- A summary for how you arrived at the response
Feedback Requirements:
- In your <feedback> tags, provide constructive feedback on the tools:
- Comment on tool names: Are they clear and descriptive?
- Comment on input parameters: Are they well-documented? Are required vs optional parameters clear?
- Comment on descriptions: Do they accurately describe what the tool does?
- Comment on any errors encountered during tool usage: Did the tool fail to execute? Did the tool return too many tokens?
- Identify specific areas for improvement and explain WHY they would help
- Be specific and actionable in your suggestions
Response Requirements:
- Your response should be concise and directly address what was asked
- Always wrap your final response in <response> tags
- If you cannot solve the task return <response>NOT_FOUND</response>
- For numeric responses, provide just the number
- For IDs, provide just the ID
- For names or text, provide the exact text requested
- Your response should go last"""
def parse_evaluation_file(file_path: Path) -> list[dict[str, Any]]:
"""Parse XML evaluation file with qa_pair elements."""
try:
tree = ET.parse(file_path)
root = tree.getroot()
evaluations = []
for qa_pair in root.findall(".//qa_pair"):
question_elem = qa_pair.find("question")
answer_elem = qa_pair.find("answer")
if question_elem is not None and answer_elem is not None:
evaluations.append({
"question": (question_elem.text or "").strip(),
"answer": (answer_elem.text or "").strip(),
})
return evaluations
except Exception as e:
print(f"Error parsing evaluation file {file_path}: {e}")
return []
def extract_xml_content(text: str, tag: str) -> str | None:
"""Extract content from XML tags."""
pattern = rf"<{tag}>(.*?)</{tag}>"
matches = re.findall(pattern, text, re.DOTALL)
return matches[-1].strip() if matches else None
async def agent_loop(
client: Anthropic,
model: str,
question: str,
tools: list[dict[str, Any]],
connection: Any,
) -> tuple[str, dict[str, Any]]:
"""Run the agent loop with MCP tools."""
messages = [{"role": "user", "content": question}]
response = await asyncio.to_thread(
client.messages.create,
model=model,
max_tokens=4096,
system=EVALUATION_PROMPT,
messages=messages,
tools=tools,
)
messages.append({"role": "assistant", "content": response.content})
tool_metrics = {}
while response.stop_reason == "tool_use":
tool_use = next(block for block in response.content if block.type == "tool_use")
tool_name = tool_use.name
tool_input = tool_use.input
tool_start_ts = time.time()
try:
tool_result = await connection.call_tool(tool_name, tool_input)
tool_response = json.dumps(tool_result) if isinstance(tool_result, (dict, list)) else str(tool_result)
except Exception as e:
tool_response = f"Error executing tool {tool_name}: {str(e)}\n"
tool_response += traceback.format_exc()
tool_duration = time.time() - tool_start_ts
if tool_name not in tool_metrics:
tool_metrics[tool_name] = {"count": 0, "durations": []}
tool_metrics[tool_name]["count"] += 1
tool_metrics[tool_name]["durations"].append(tool_duration)
messages.append({
"role": "user",
"content": [{
"type": "tool_result",
"tool_use_id": tool_use.id,
"content": tool_response,
}]
})
response = await asyncio.to_thread(
client.messages.create,
model=model,
max_tokens=4096,
system=EVALUATION_PROMPT,
messages=messages,
tools=tools,
)
messages.append({"role": "assistant", "content": response.content})
response_text = next(
(block.text for block in response.content if hasattr(block, "text")),
None,
)
return response_text, tool_metrics
async def evaluate_single_task(
client: Anthropic,
model: str,
qa_pair: dict[str, Any],
tools: list[dict[str, Any]],
connection: Any,
task_index: int,
) -> dict[str, Any]:
"""Evaluate a single QA pair with the given tools."""
start_time = time.time()
print(f"Task {task_index + 1}: Running task with question: {qa_pair['question']}")
response, tool_metrics = await agent_loop(client, model, qa_pair["question"], tools, connection)
response_value = extract_xml_content(response, "response")
summary = extract_xml_content(response, "summary")
feedback = extract_xml_content(response, "feedback")
duration_seconds = time.time() - start_time
return {
"question": qa_pair["question"],
"expected": qa_pair["answer"],
"actual": response_value,
"score": int(response_value == qa_pair["answer"]) if response_value else 0,
"total_duration": duration_seconds,
"tool_calls": tool_metrics,
"num_tool_calls": sum(len(metrics["durations"]) for metrics in tool_metrics.values()),
"summary": summary,
"feedback": feedback,
}
REPORT_HEADER = """
# Evaluation Report
## Summary
- **Accuracy**: {correct}/{total} ({accuracy:.1f}%)
- **Average Task Duration**: {average_duration_s:.2f}s
- **Average Tool Calls per Task**: {average_tool_calls:.2f}
- **Total Tool Calls**: {total_tool_calls}
---
"""
TASK_TEMPLATE = """
### Task {task_num}
**Question**: {question}
**Ground Truth Answer**: `{expected_answer}`
**Actual Answer**: `{actual_answer}`
**Correct**: {correct_indicator}
**Duration**: {total_duration:.2f}s
**Tool Calls**: {tool_calls}
**Summary**
{summary}
**Feedback**
{feedback}
---
"""
async def run_evaluation(
eval_path: Path,
connection: Any,
model: str = "claude-3-7-sonnet-20250219",
) -> str:
"""Run evaluation with MCP server tools."""
print("🚀 Starting Evaluation")
client = Anthropic()
tools = await connection.list_tools()
print(f"📋 Loaded {len(tools)} tools from MCP server")
qa_pairs = parse_evaluation_file(eval_path)
print(f"📋 Loaded {len(qa_pairs)} evaluation tasks")
results = []
for i, qa_pair in enumerate(qa_pairs):
print(f"Processing task {i + 1}/{len(qa_pairs)}")
result = await evaluate_single_task(client, model, qa_pair, tools, connection, i)
results.append(result)
correct = sum(r["score"] for r in results)
accuracy = (correct / len(results)) * 100 if results else 0
average_duration_s = sum(r["total_duration"] for r in results) / len(results) if results else 0
average_tool_calls = sum(r["num_tool_calls"] for r in results) / len(results) if results else 0
total_tool_calls = sum(r["num_tool_calls"] for r in results)
report = REPORT_HEADER.format(
correct=correct,
total=len(results),
accuracy=accuracy,
average_duration_s=average_duration_s,
average_tool_calls=average_tool_calls,
total_tool_calls=total_tool_calls,
)
report += "".join([
TASK_TEMPLATE.format(
task_num=i + 1,
question=qa_pair["question"],
expected_answer=qa_pair["answer"],
actual_answer=result["actual"] or "N/A",
correct_indicator="" if result["score"] else "",
total_duration=result["total_duration"],
tool_calls=json.dumps(result["tool_calls"], indent=2),
summary=result["summary"] or "N/A",
feedback=result["feedback"] or "N/A",
)
for i, (qa_pair, result) in enumerate(zip(qa_pairs, results))
])
return report
def parse_headers(header_list: list[str]) -> dict[str, str]:
"""Parse header strings in format 'Key: Value' into a dictionary."""
headers = {}
if not header_list:
return headers
for header in header_list:
if ":" in header:
key, value = header.split(":", 1)
headers[key.strip()] = value.strip()
else:
print(f"Warning: Ignoring malformed header: {header}")
return headers
def parse_env_vars(env_list: list[str]) -> dict[str, str]:
"""Parse environment variable strings in format 'KEY=VALUE' into a dictionary."""
env = {}
if not env_list:
return env
for env_var in env_list:
if "=" in env_var:
key, value = env_var.split("=", 1)
env[key.strip()] = value.strip()
else:
print(f"Warning: Ignoring malformed environment variable: {env_var}")
return env
async def main():
parser = argparse.ArgumentParser(
description="Evaluate MCP servers using test questions",
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog="""
Examples:
# Evaluate a local stdio MCP server
python evaluation.py -t stdio -c python -a my_server.py eval.xml
# Evaluate an SSE MCP server
python evaluation.py -t sse -u https://example.com/mcp -H "Authorization: Bearer token" eval.xml
# Evaluate an HTTP MCP server with custom model
python evaluation.py -t http -u https://example.com/mcp -m claude-3-5-sonnet-20241022 eval.xml
""",
)
parser.add_argument("eval_file", type=Path, help="Path to evaluation XML file")
parser.add_argument("-t", "--transport", choices=["stdio", "sse", "http"], default="stdio", help="Transport type (default: stdio)")
parser.add_argument("-m", "--model", default="claude-3-7-sonnet-20250219", help="Claude model to use (default: claude-3-7-sonnet-20250219)")
stdio_group = parser.add_argument_group("stdio options")
stdio_group.add_argument("-c", "--command", help="Command to run MCP server (stdio only)")
stdio_group.add_argument("-a", "--args", nargs="+", help="Arguments for the command (stdio only)")
stdio_group.add_argument("-e", "--env", nargs="+", help="Environment variables in KEY=VALUE format (stdio only)")
remote_group = parser.add_argument_group("sse/http options")
remote_group.add_argument("-u", "--url", help="MCP server URL (sse/http only)")
remote_group.add_argument("-H", "--header", nargs="+", dest="headers", help="HTTP headers in 'Key: Value' format (sse/http only)")
parser.add_argument("-o", "--output", type=Path, help="Output file for evaluation report (default: stdout)")
args = parser.parse_args()
if not args.eval_file.exists():
print(f"Error: Evaluation file not found: {args.eval_file}")
sys.exit(1)
headers = parse_headers(args.headers) if args.headers else None
env_vars = parse_env_vars(args.env) if args.env else None
try:
connection = create_connection(
transport=args.transport,
command=args.command,
args=args.args,
env=env_vars,
url=args.url,
headers=headers,
)
except ValueError as e:
print(f"Error: {e}")
sys.exit(1)
print(f"🔗 Connecting to MCP server via {args.transport}...")
async with connection:
print("✅ Connected successfully")
report = await run_evaluation(args.eval_file, connection, args.model)
if args.output:
args.output.write_text(report)
print(f"\n✅ Report saved to {args.output}")
else:
print("\n" + report)
if __name__ == "__main__":
asyncio.run(main())

View File

@@ -0,0 +1,22 @@
<evaluation>
<qa_pair>
<question>Calculate the compound interest on $10,000 invested at 5% annual interest rate, compounded monthly for 3 years. What is the final amount in dollars (rounded to 2 decimal places)?</question>
<answer>11614.72</answer>
</qa_pair>
<qa_pair>
<question>A projectile is launched at a 45-degree angle with an initial velocity of 50 m/s. Calculate the total distance (in meters) it has traveled from the launch point after 2 seconds, assuming g=9.8 m/s². Round to 2 decimal places.</question>
<answer>87.25</answer>
</qa_pair>
<qa_pair>
<question>A sphere has a volume of 500 cubic meters. Calculate its surface area in square meters. Round to 2 decimal places.</question>
<answer>304.65</answer>
</qa_pair>
<qa_pair>
<question>Calculate the population standard deviation of this dataset: [12, 15, 18, 22, 25, 30, 35]. Round to 2 decimal places.</question>
<answer>7.61</answer>
</qa_pair>
<qa_pair>
<question>Calculate the pH of a solution with a hydrogen ion concentration of 3.5 × 10^-5 M. Round to 2 decimal places.</question>
<answer>4.46</answer>
</qa_pair>
</evaluation>

View File

@@ -0,0 +1,2 @@
anthropic>=0.39.0
mcp>=1.1.0

View File

@@ -0,0 +1,150 @@
# Project Planner Skill
A comprehensive skill that establishes Claude as a **Project Architect** to generate detailed planning documents that serve as blueprints for AI-assisted software development.
## What This Skill Does
This skill transforms Claude into a System Architect who creates comprehensive project documentation by:
1. **Defining the complete system architecture upfront** - All components, data flows, and integration points
2. **Setting clear project boundaries** - What's in scope, out of scope, and key constraints
3. **Creating traceable documentation** - Requirements → Design → Implementation tasks
The skill generates three essential documents:
1. **Requirements Document** - User stories with testable acceptance criteria and traceable IDs
2. **Design Document** - Complete system architecture with component maps, data flows, and integration specifications
3. **Implementation Plan** - Hierarchical task breakdown with requirement tracing and clear deliverables
## The Architect Approach
**Why it works:** Setting clear roles, responsibilities, and deliverables upfront dramatically improves output quality. By explicitly defining system components, data flows, and integration points before diving into details, the documentation becomes comprehensive and actionable.
## Quick Start
### Generate Documents Using the Script
```bash
# Basic usage
python scripts/generate_project_docs.py "My Project Name"
# Specify project type
python scripts/generate_project_docs.py "Trading Bot" --type web-app
# Custom features and components
python scripts/generate_project_docs.py "E-commerce Site" \
--features "user authentication" "product catalog" "shopping cart" \
--components "Auth Service" "Product Service" "Order Service" \
--output ./docs
```
### Validate Your Documents
```bash
# Validate all documents
python scripts/validate_documents.py \
--requirements requirements.md \
--design design.md \
--tasks tasks.md
```
## Document Types
### Requirements Document
- User stories in standard format
- Testable acceptance criteria using SHALL statements
- Requirement numbering for traceability
- Glossary of domain terms
### Design Document
- System architecture diagrams
- Component responsibilities and interfaces
- Data models and schemas
- Error handling strategies
- Deployment configuration
### Implementation Plan
- Hierarchical task breakdown
- Requirement tracing (links tasks to requirements)
- Dependency management between tasks
- Progress tracking with checkboxes
## Project Types Supported
- **web-app**: Full-stack web applications
- **cli-tool**: Command-line tools and utilities
- **api-service**: REST/GraphQL API services
- **generic**: General purpose projects
## Files Included
### Scripts
- `generate_project_docs.py` - Automated document generation
- `validate_documents.py` - Document validation and completeness checking
### References
- `domain-templates.md` - Domain-specific templates and patterns
### Assets
- `requirements-template.md` - Basic requirements document template
## Best Practices
1. **Start with Requirements** - Define what the system should do before how
2. **Be Specific** - Use measurable criteria (e.g., "within 100ms" not "fast")
3. **Trace Requirements** - Link every task back to requirements
4. **Include Non-Functional Requirements** - Performance, security, scalability
5. **Define Clear Interfaces** - Specify how components interact
6. **Plan Incrementally** - Break large tasks into smaller, manageable pieces
## Common Use Cases
### Starting a New Project
```
User: "I want to build a real-time chat application"
AI: [Uses this skill to generate complete project documentation]
```
### Expanding Existing Project
```
User: "Add user authentication to my project requirements"
AI: [Adds properly formatted requirements with acceptance criteria]
```
### Creating Technical Specification
```
User: "Design the architecture for a microservices e-commerce platform"
AI: [Generates design document with components, interfaces, and deployment]
```
## Tips for AI Implementation
When using these documents for AI-assisted development:
1. **Requirements First** - Implement in order of requirement priority
2. **Follow Task Dependencies** - Complete prerequisite tasks first
3. **Test Against Acceptance Criteria** - Each SHALL statement is a test case
4. **Reference Design Interfaces** - Use the specified APIs and data models
5. **Track Progress** - Check off completed tasks in the implementation plan
## Validation Checklist
Before using documents for implementation:
- [ ] All placeholders ([PLACEHOLDER]) filled in
- [ ] Requirements have testable acceptance criteria
- [ ] Design includes all major components
- [ ] Tasks reference requirement IDs
- [ ] Dependencies between tasks identified
- [ ] Non-functional requirements specified
- [ ] Deployment configuration included
## Example Output
The generated documents follow industry-standard formats that are:
- **Machine-readable** - Structured for AI parsing
- **Human-readable** - Clear for developers to understand
- **Version-control friendly** - Plain text Markdown format
- **Traceable** - Requirements linked through all documents
This skill transforms high-level project ideas into actionable specifications that AI agents can use to build working software.

View File

@@ -0,0 +1,856 @@
---
name: project-planner
description: Comprehensive project planning and documentation generator for software projects. Creates structured requirements documents, system design documents, and task breakdown plans with implementation tracking. Use when starting a new project, defining specifications, creating technical designs, or breaking down complex systems into implementable tasks. Supports user story format, acceptance criteria, component design, API specifications, and hierarchical task decomposition with requirement traceability.
---
# Project Planner Skill
## 执行环境
| 路径类型 | 说明 |
|---------|------|
| **使用方式** | 此技能提供模板和指导,**无需本地脚本执行** |
| **调用场景** | 当用户需要项目规划、需求文档、系统设计时自动激活 |
| **输出格式** | Markdown 文档requirements.md, design.md, tasks.md |
## 路径说明
- **无本地路径依赖**:此技能生成文档模板,不涉及本地脚本执行
- **生成的文档**:保存在执行命令时的当前目录(`process.cwd()`
- **项目上下文**:基于当前工作目录确定项目根目录
This skill provides templates and guidance for generating comprehensive project planning documents that serve as blueprints for AI-assisted implementation.
## Quick Start
When a user wants to start a new project, generate three core documents:
1. **Requirements Document** - User stories with acceptance criteria
2. **Design Document** - Technical architecture and component specifications
3. **Implementation Plan** - Hierarchical task breakdown with requirement tracing
## Why Explicit Architectural Planning Works
Setting clear roles, responsibilities, and deliverables upfront dramatically improves project outcomes:
### Benefits of Upfront Definition
1. **Component Clarity** - Defining all system components first prevents scope creep and ensures complete coverage
2. **Data Flow Visibility** - Mapping data movement early reveals integration complexities and performance bottlenecks
3. **Integration Planning** - Identifying all touchpoints upfront prevents surprise dependencies during implementation
4. **Clear Boundaries** - Explicitly stating what's in/out of scope focuses effort and prevents feature drift
5. **Measurable Success** - Specific goals and constraints enable objective progress tracking
### The Architect Mindset
When acting as a **Project Architect**, approach planning with:
- **Systems Thinking** - See the whole before diving into parts
- **Interface-First Design** - Define contracts between components before internals
- **Traceability Focus** - Every requirement maps to design elements and tasks
- **Constraint Awareness** - Acknowledge limitations upfront to guide decisions
- **Deliverable Orientation** - Know exactly what artifacts you're producing
## Document Generation Workflow
### 1. Project Architect Role Definition
When starting a project, explicitly establish Claude as the **Project Architect** with clear responsibilities:
**Role:** System Architect and Planning Specialist
**Responsibilities:**
- Define complete system architecture with all components
- Map data flow between system elements
- Identify all integration points and interfaces
- Establish clear project boundaries and constraints
- Create traceable requirements to implementation tasks
### 2. Initial Project Understanding
Before generating documents, gather key information and architectural elements:
```
Required Project Information:
- Project name and purpose
- Target users (single-user local, multi-tenant SaaS, etc.)
- Core functionality (3-5 main features)
- Technical preferences (languages, frameworks, deployment)
- Non-functional requirements (performance, security, scalability)
Required Architectural Elements (define upfront):
- System Components: All major modules/services and their purposes
- Data Flow: How data moves through the entire system
- Integration Points: All external APIs, services, databases
- System Boundaries: What's in scope vs out of scope
- Constraints: Technical, business, and resource limitations
- Success Metrics: Clear, measurable goals for the system
```
### 3. Deliverable Definition (Set Upfront)
Define all deliverables explicitly before starting documentation:
```
Standard Deliverables Package:
1. Requirements Document
- User stories with measurable acceptance criteria
- Complete glossary of terms
- Traceable requirement IDs
2. System Design Document
- Component architecture diagram
- Data flow diagrams for all major processes
- Integration point specifications
- API/Interface contracts
- Performance and scaling targets
3. Implementation Plan
- Hierarchical task breakdown
- Requirement-to-task mapping
- Dependency graph
- Phase-based delivery schedule
Optional Deliverables (specify if needed):
- API Documentation
- Database Schema Design
- Security Threat Model
- Deployment Guide
- Testing Strategy Document
```
### 4. Generate Requirements Document
Use the requirements template to create user-focused specifications:
```python
# Execute this to generate requirements structure
requirements = {
"introduction": "System purpose and scope",
"glossary": "Domain-specific terms",
"requirements": [
{
"id": "REQ-X",
"user_story": "As a [role], I want [feature], so that [benefit]",
"acceptance_criteria": [
"WHEN [condition], THE system SHALL [behavior]",
"WHERE [context], THE system SHALL [behavior]",
"IF [condition], THEN THE system SHALL [behavior]"
]
}
]
}
```
### 5. Generate Design Document
Create technical specifications with explicit architectural elements:
```python
# Execute this to generate comprehensive design structure
design = {
"overview": "High-level system description",
"architecture": {
"diagram": "ASCII or visual representation of all components",
"components": [
{
"id": "COMP-1",
"name": "Component Name",
"type": "Frontend/Backend/Service/Database",
"responsibility": "Single clear purpose",
"boundaries": "What it does and doesn't do"
}
]
},
"data_flow": {
"primary_flows": [
{
"name": "User Registration Flow",
"steps": [
"1. User submits form → Frontend",
"2. Frontend validates → API Gateway",
"3. API Gateway → Auth Service",
"4. Auth Service → User Database",
"5. Response flows back"
],
"data_transformations": "How data changes at each step"
}
]
},
"integration_points": [
{
"name": "External Payment API",
"type": "REST/GraphQL/WebSocket/Database",
"purpose": "Process payments",
"interface": "API contract definition",
"authentication": "Method used",
"error_handling": "Retry/fallback strategy"
}
],
"components_detail": [
{
"name": "Component Name",
"responsibility": "What it does",
"key_classes": ["Class descriptions"],
"interfaces": "API/method signatures",
"dependencies": "What it needs to function",
"performance": "Targets and constraints"
}
],
"data_models": "Entity definitions with relationships",
"system_boundaries": {
"in_scope": ["What the system handles"],
"out_of_scope": ["What it delegates or ignores"],
"assumptions": ["External dependencies assumed available"]
},
"error_handling": "Strategies for failures",
"testing_strategy": "Unit, integration, performance",
"deployment": "Docker, environment, configuration"
}
```
### 6. Generate Implementation Plan
Break down the project into executable tasks with clear scope boundaries:
```python
# Execute this to generate task structure with boundaries
tasks = {
"project_boundaries": {
"must_have": ["Core features for MVP"],
"nice_to_have": ["Enhancement features"],
"out_of_scope": ["Features explicitly excluded"],
"technical_constraints": ["Framework/library limitations"]
},
"phases": [
{
"id": 1,
"name": "Infrastructure Setup",
"deliverables": ["What this phase produces"],
"tasks": [
{
"id": "1.1",
"description": "Task description",
"subtasks": ["Specific actions"],
"requirements_fulfilled": ["REQ-1.1", "REQ-2.3"],
"components_involved": ["COMP-1", "COMP-3"],
"dependencies": [],
"estimated_hours": 4,
"success_criteria": "How to verify completion"
}
]
}
]
}
```
## Requirements Document Template
```markdown
# Requirements Document
## Introduction
[System description in 2-3 sentences. Target user and deployment model.]
## Glossary
- **Term**: Definition specific to this system
- **Component**: Major system module or service
[Add all domain-specific terms]
## Requirements
### Requirement [NUMBER]
**User Story:** As a [user type], I want [capability], so that [benefit]
#### Acceptance Criteria
1. WHEN [trigger/condition], THE [component] SHALL [action/behavior]
2. WHERE [mode/context], THE [component] SHALL [action/behavior]
3. IF [condition], THEN THE [component] SHALL [action/behavior]
4. THE [component] SHALL [capability with measurable target]
[Repeat for each requirement]
```
### Requirements Best Practices
1. **One capability per requirement** - Each requirement should address a single feature
2. **Testable criteria** - Every criterion must be verifiable
3. **Use SHALL for mandatory** - Consistent RFC 2119 keywords
4. **Include performance targets** - "within X milliseconds/seconds"
5. **Specify all states** - Success, failure, edge cases
6. **Number systematically** - REQ-1, REQ-2 for traceability
### Acceptance Criteria Patterns
```
Behavior criteria:
- WHEN [event occurs], THE system SHALL [respond]
- THE system SHALL [provide capability]
- THE system SHALL [enforce rule/limit]
Conditional criteria:
- IF [condition], THEN THE system SHALL [action]
- WHERE [mode is active], THE system SHALL [behavior]
Performance criteria:
- THE system SHALL [complete action] within [time]
- THE system SHALL support [number] concurrent [operations]
- THE system SHALL maintain [metric] above/below [threshold]
Data criteria:
- THE system SHALL persist [data type] with [attributes]
- THE system SHALL validate [input] against [rules]
- THE system SHALL return [data] in [format]
```
## Design Document Template
```markdown
# Design Document
## Overview
[System architecture summary in 3-4 sentences. Key design decisions and priorities.]
## System Architecture
### Component Map
| Component ID | Name | Type | Responsibility | Interfaces With |
|-------------|------|------|----------------|-----------------|
| COMP-1 | Web Frontend | UI | User interface | COMP-2 |
| COMP-2 | API Gateway | Service | Request routing | COMP-3, COMP-4 |
| COMP-3 | Business Logic | Service | Core processing | COMP-5 |
[Complete component inventory]
### High-Level Architecture Diagram
[ASCII diagram showing all components and their relationships]
## Data Flow Specifications
### Primary Data Flows
#### 1. [Flow Name] (e.g., User Authentication)
```
1. [Source] → [Component]: [Data description]
2. [Component] → [Component]: [Transformation applied]
3. [Component] → [Destination]: [Final data format]
```
**Data Transformations:**
- Step 2: [How data changes]
- Step 3: [Validation/Processing applied]
[Repeat for each major data flow]
## Integration Points
### Internal Integration Points
| Source | Target | Protocol | Data Format | Purpose |
|--------|--------|----------|-------------|---------|
| Frontend | API Gateway | HTTPS/REST | JSON | API calls |
| API Gateway | Auth Service | gRPC | Protobuf | Authentication |
[All internal integrations]
### External Integration Points
#### [External System Name]
**Type:** REST API / Database / Message Queue / etc.
**Purpose:** [What this integration provides]
**Endpoint:** [URL/Connection string pattern]
**Authentication:** [Method - OAuth2, API Key, etc.]
**Rate Limits:** [Any constraints]
**Interface Contract:**
```language
// Request format
POST /api/endpoint
{
"field": "type"
}
// Response format
{
"result": "type"
}
```
**Error Handling:**
- Retry strategy: [Exponential backoff, circuit breaker]
- Fallback: [What happens if unavailable]
- Monitoring: [How to detect issues]
[Repeat for each external integration]
## Components and Interfaces
### 1. [Component Name]
**Responsibility:** [Single sentence description]
**Key Classes:**
- `ClassName`: [Purpose and main methods]
- `ServiceName`: [What it manages]
**Interfaces:**
```language
class InterfaceName:
def method_name(params) -> ReturnType
# Core methods only
```
**Data Flow:**
- Receives [input] from [source]
- Processes by [algorithm/logic]
- Outputs [result] to [destination]
**Performance:**
- Target: [metric and value]
- Constraints: [limitations]
[Repeat for each major component]
## Data Models
### [Entity Name]
```language
@dataclass
class EntityName:
field: Type
field: Optional[Type]
# Core fields only
```
## Error Handling
### [Error Category]
**Types:** [List of error scenarios]
**Handling:** [Strategy and recovery]
## Testing Strategy
### Unit Tests
- [Component]: Test [aspects]
- Coverage target: 80%
### Integration Tests
- [Flow]: Test [end-to-end scenario]
### Performance Tests
- [Operation]: Target [metric]
## Deployment
### Docker Configuration
```yaml
# Essential service definitions only
```
### Environment Variables
```
CATEGORY_VAR=description
```
## Performance Targets
- [Operation]: <[time]
- [Throughput]: >[rate]
- [Resource]: <[limit]
## Security Considerations
- [Authentication method if applicable]
- [Data protection approach]
- [Access control model]
```
### Design Best Practices
1. **Component responsibilities** - Single, clear purpose per component
2. **Interface first** - Define contracts before implementation
3. **Data flow clarity** - Show how data moves through system
4. **Error categories** - Group related failures with consistent handling
5. **Performance targets** - Specific, measurable goals
6. **Deployment ready** - Include Docker and configuration
## Implementation Plan Template
```markdown
# Implementation Plan
- [x] 1. [Phase Name]
- [x] 1.1 [Task name]
- [Subtask description]
- [Subtask description]
- _Requirements: [REQ-X.Y, REQ-A.B]_
- [ ] 1.2 [Task name]
- [Subtask description]
- _Requirements: [REQ-X.Y]_
- _Dependencies: Task 1.1_
- [ ] 2. [Phase Name]
- [ ] 2.1 [Task name]
- [Detailed steps or subtasks]
- _Requirements: [REQ-X.Y]_
- _Dependencies: Phase 1_
[Continue for all phases]
```
### Task Planning Best Practices
1. **Hierarchical structure** - Phases > Tasks > Subtasks
2. **Requirement tracing** - Link each task to requirements
3. **Dependency marking** - Identify blockers and prerequisites
4. **Checkbox format** - [x] for complete, [ ] for pending
5. **Atomic tasks** - Each task independently completable
6. **Progressive implementation** - Infrastructure → Core → Features → Polish
### Common Implementation Phases
```markdown
1. **Infrastructure Setup**
- Project structure
- Database schema
- Docker configuration
- Core dependencies
2. **Data Layer**
- Models/entities
- Database operations
- Migrations
3. **Business Logic**
- Core algorithms
- Service classes
- Validation rules
4. **API/Interface Layer**
- REST/GraphQL endpoints
- WebSocket handlers
- Authentication
5. **Frontend/UI**
- Component structure
- State management
- API integration
- Responsive design
6. **Integration**
- External services
- Third-party APIs
- Message queues
7. **Testing**
- Unit tests
- Integration tests
- End-to-end tests
8. **DevOps**
- CI/CD pipeline
- Monitoring
- Logging
- Deployment scripts
9. **Documentation**
- API documentation
- User guides
- Deployment guide
- README
```
## Document Patterns by Project Type
### Web Application (Full-Stack)
Requirements focus:
- User authentication and authorization
- CRUD operations for entities
- Real-time updates
- Responsive UI
- API design
Design focus:
- 3-tier architecture (Frontend, Backend, Database)
- REST/GraphQL API design
- State management strategy
- Component hierarchy
- Database schema
Tasks focus:
1. Database and backend setup
2. API implementation
3. Frontend components
4. Integration and testing
### Microservices System
Requirements focus:
- Service boundaries
- Inter-service communication
- Data consistency
- Service discovery
- Fault tolerance
Design focus:
- Service decomposition
- API contracts between services
- Message queue/event bus
- Distributed tracing
- Container orchestration
Tasks focus:
1. Service scaffolding
2. Shared libraries/contracts
3. Individual service implementation
4. Integration layer
5. Orchestration setup
### Data Pipeline/ETL
Requirements focus:
- Data sources and formats
- Transformation rules
- Data quality checks
- Schedule/triggers
- Error handling and retry
Design focus:
- Pipeline stages
- Data flow diagram
- Schema evolution
- Monitoring and alerting
- Storage strategy
Tasks focus:
1. Data source connectors
2. Transformation logic
3. Validation and quality checks
4. Scheduling setup
5. Monitoring implementation
### CLI Tool/Library
Requirements focus:
- Command structure
- Input/output formats
- Configuration options
- Error messages
- Performance requirements
Design focus:
- Command parser architecture
- Plugin system (if applicable)
- Configuration management
- Output formatters
- Testing strategy
Tasks focus:
1. Core command structure
2. Business logic implementation
3. Input/output handlers
4. Configuration system
5. Documentation and examples
## Generating Documents for Specific Domains
### Trading/Financial Systems
Additional requirements:
- Risk management rules
- Order execution logic
- Market data handling
- Compliance requirements
- Audit trail
Additional design:
- High-frequency data handling
- Position tracking
- Risk calculations
- Order routing
- Failover strategies
### Real-time Systems (Chat, Gaming, IoT)
Additional requirements:
- Latency targets
- Connection handling
- State synchronization
- Offline support
- Push notifications
Additional design:
- WebSocket/SSE architecture
- State management
- Caching strategy
- Message queuing
- Horizontal scaling
### Machine Learning Systems
Additional requirements:
- Model training pipeline
- Feature engineering
- Model versioning
- Prediction API
- Monitoring/drift detection
Additional design:
- Training infrastructure
- Feature store
- Model registry
- Serving architecture
- A/B testing framework
## Quality Checklist
Before finalizing documents, verify:
### Requirements Document
- [ ] Every requirement has a clear user story
- [ ] All acceptance criteria are testable
- [ ] Non-functional requirements specified (performance, security)
- [ ] Glossary covers all domain terms
- [ ] Requirements are numbered for tracing
### Design Document
- [ ] Architecture diagram included
- [ ] All components have clear responsibilities
- [ ] Interfaces defined for integration points
- [ ] Error handling strategy documented
- [ ] Performance targets specified
- [ ] Deployment configuration included
### Implementation Plan
- [ ] Tasks grouped into logical phases
- [ ] Dependencies identified between tasks
- [ ] Requirements traced to tasks
- [ ] Tasks are atomic and completable
- [ ] Checkbox format for progress tracking
## Example Usage Patterns
### Starting a New Project (With Explicit Architecture Focus)
```
User: "I want to build an order flow trading automation system"
Response: "I'll act as your Project Architect to create comprehensive planning documents.
First, let me establish the architectural foundation:
**System Components I need to define:**
- Market data ingestion service
- Order flow analysis engine
- Trading signal generator
- Risk management module
- Order execution service
- Monitoring dashboard
**Data Flows to map:**
- Market data → Analysis → Signals → Execution
- Position tracking → Risk calculation → Trading limits
- All events → Audit trail
**Integration Points to specify:**
- Exchange APIs (which ones?)
- Database system (time-series data needs?)
- Notification services (alerts/reporting?)
**Key Questions:**
1. Is this for personal use (single-user) or a platform (multi-user)?
2. Which specific exchanges/brokers will it connect to?
3. What are your latency requirements (milliseconds matter)?
4. What's your risk tolerance and position sizing approach?
5. Do you need backtesting capabilities?
**Deliverables I'll create:**
1. Requirements document with traceable user stories
2. System design with complete component map and data flows
3. Implementation plan with phased delivery milestones
This architectural blueprint will ensure nothing is overlooked during implementation."
```
### Starting a New Project (Simple Approach)
```
User: "I want to build an order flow trading automation system"
Response: "I'll help you create comprehensive planning documents for your trading system. Let me gather some key information:
1. Is this for personal use (single-user) or a multi-user platform?
2. Which markets/exchanges will it connect to?
3. What are the core trading strategies or patterns?
4. Any specific performance requirements (latency, throughput)?
Based on this, I'll generate:
- Requirements document with user stories
- System design with components and data flow
- Implementation plan with phases and tasks"
```
### Expanding Existing Requirements
```
User: "Add real-time monitoring dashboard to the requirements"
Response: "I'll add dashboard requirements to your document:
### Requirement 15
**User Story:** As a trader, I want a real-time dashboard, so that I can monitor system performance and trading activity
#### Acceptance Criteria
1. THE Dashboard SHALL display active positions with current P&L
2. THE Dashboard SHALL update market data within 1 second
3. THE Dashboard SHALL provide filtering by symbol, timeframe, and date range
4. WHEN a trade executes, THE Dashboard SHALL reflect it within 100ms"
```
## Common Pitfalls to Avoid
### Planning Pitfalls
1. **Skipping architectural planning** - Jumping to requirements without mapping components first
2. **Vague role definition** - Not establishing the architect role leads to unfocused documentation
3. **Hidden integration points** - Discovering external dependencies during implementation
4. **Undefined boundaries** - No clear scope leads to feature creep and timeline slippage
5. **Missing data flow analysis** - Not mapping how data moves reveals issues late
### Requirements Pitfalls
1. **Over-specifying implementation** - Requirements should define "what" not "how"
2. **Vague acceptance criteria** - Avoid "user-friendly" or "fast" without metrics
3. **Missing error cases** - Include failure scenarios in requirements
4. **Untraceable requirements** - Every requirement should map to tasks
### Design Pitfalls
1. **Monolithic components** - Break down large components into focused services
2. **Circular dependencies** - Ensure task dependencies form a DAG
3. **Missing data models** - Define core entities early
4. **Ignoring deployment** - Include Docker/deployment from the start
5. **Unclear component boundaries** - Each component needs explicit responsibilities
## Output Format
Generate documents in Markdown format for easy editing and version control. Use:
- Clear hierarchical headings (##, ###, ####)
- Code blocks with language hints
- Bulleted and numbered lists
- Tables for structured data
- Checkboxes for task tracking
- Bold for emphasis on key terms
- Inline code for technical terms
Save documents as:
- `requirements.md` - Requirements document
- `design.md` - Design document
- `tasks.md` - Implementation plan
These documents serve as the foundation for AI-assisted implementation, providing clear specifications that can be referenced throughout development.

View File

@@ -0,0 +1,50 @@
# project-planner
Comprehensive project planning and documentation generator for software projects. Creates structured requirements documents, system design documents, and task breakdown plans with implementation tracking. Use when starting a new project, defining specifications, creating technical designs, or breaking down complex systems into implementable tasks. Supports user story format, acceptance criteria, component design, API specifications, and hierarchical task decomposition with requirement traceability.
---
## 📦 Downloaded from [Skillstore.io](https://skillstore.io)
This skill was downloaded from **AI Skillstore** — the official marketplace for Claude Code, Codex, and Claude skills.
🔗 **Skill Page**: [skillstore.io/skills/dwsy-project-planner](https://skillstore.io/skills/dwsy-project-planner)
## 🚀 Installation
### Via Claude Code Plugin System
```
/plugin marketplace add aiskillstore/marketplace
/plugin install dwsy-project-planner@aiskillstore
```
### Manual Installation
Copy the contents of this folder to your project's `.claude/skills/` directory.
## 📋 Skill Info
| Property | Value |
|----------|-------|
| **Name** | project-planner |
| **Version** | 1.0.0 |
| **Author** | Dwsy |
### Supported Tools
- claude
- codex
- claude-code
## 🌐 Discover More Skills
Browse thousands of AI skills at **[skillstore.io](https://skillstore.io)**:
- 🔍 Search by category, tool, or keyword
- ⭐ Find verified, security-audited skills
- 📤 Submit your own skills to share with the community
---
*From [skillstore.io](https://skillstore.io) — AI Skills Marketplace*

View File

@@ -0,0 +1,83 @@
# Requirements Document Template
## Introduction
[PROJECT NAME] is a [SYSTEM TYPE] designed for [TARGET USERS]. The system [PRIMARY PURPOSE].
## System Context
### Architectural Overview
- **Components:** [List major system components]
- **Data Flow:** [High-level data movement]
- **Integration Points:** [External systems/APIs]
- **Deployment Model:** [Cloud/On-premise/Hybrid]
## Glossary
- **[Term]**: [Definition specific to this system]
- **Component**: Major system module or service
- **Integration Point**: Connection to external system or API
## Functional Requirements
### REQ-1: [Feature Name]
**User Story:** As a [user role], I want [feature], so that [benefit]
**Acceptance Criteria:**
1. WHEN [condition], THE system SHALL [behavior]
2. THE system SHALL [requirement] within [time constraint]
3. IF [error condition], THEN THE system SHALL [error handling]
**Components Involved:** [COMP-1, COMP-2]
**Data Flow:** [How data moves for this requirement]
### REQ-2: [Feature Name]
**User Story:** As a [user role], I want [feature], so that [benefit]
**Acceptance Criteria:**
1. WHEN [condition], THE system SHALL [behavior]
2. WHERE [context], THE system SHALL [behavior]
3. THE system SHALL persist [data] with [attributes]
**Components Involved:** [COMP-3, COMP-4]
**Integration Points:** [External systems used]
## Non-Functional Requirements
### Performance Requirements
- Response time: THE system SHALL respond to user requests within [X] milliseconds
- Throughput: THE system SHALL handle [X] concurrent users
- Data processing: THE system SHALL process [X] records per second
### Security Requirements
- Authentication: THE system SHALL implement [auth method]
- Authorization: THE system SHALL enforce role-based access control
- Data protection: THE system SHALL encrypt sensitive data at rest and in transit
### Reliability Requirements
- Availability: THE system SHALL maintain 99.9% uptime
- Recovery: THE system SHALL recover from failures within [X] minutes
- Data integrity: THE system SHALL ensure ACID compliance for transactions
### Scalability Requirements
- THE system SHALL support horizontal scaling
- THE system SHALL handle [X]% growth in users annually
- THE system SHALL support database sharding for data volumes exceeding [X]
## Constraints and Boundaries
### Technical Constraints
- Technology: [Programming languages, frameworks, databases]
- Infrastructure: [Cloud provider, hardware limitations]
### Business Constraints
- Budget: [Cost limitations]
- Timeline: [Delivery deadlines]
- Compliance: [Regulatory requirements]
### Scope Boundaries
- **In Scope:** [What's included]
- **Out of Scope:** [What's explicitly excluded]
- **Future Considerations:** [Deferred features]

View File

@@ -0,0 +1,13 @@
{
"name": "project-planner-skill",
"description": "Comprehensive project planning and documentation generator for software projects. Creates structured requirements documents, system design documents, and task breakdown plans with implementation tracking.",
"version": "1.0.0",
"author": {
"name": "George A Puiu",
"email": "puiu.adrian@gmail.com"
},
"homepage": "https://github.com/adrianpuiu/claude-skills-marketplace",
"repository": "https://github.com/adrianpuiu/claude-skills-marketplace",
"license": "MIT",
"keywords": ["project-planning", "documentation", "requirements", "design", "implementation"]
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,801 @@
#!/usr/bin/env python3
"""
Project Document Generator
Generates structured requirements, design, and task documents for new projects
"""
import json
import argparse
from datetime import datetime
from typing import Dict, List, Optional
import os
class ProjectDocumentGenerator:
def __init__(self, project_name: str, project_type: str = "web-app"):
self.project_name = project_name
self.project_type = project_type
self.timestamp = datetime.now().strftime("%Y-%m-%d")
def generate_requirements_template(self, features: List[str]) -> str:
"""Generate requirements document template"""
template = f"""# Requirements Document
## Introduction
{self.project_name} is a [DESCRIPTION OF SYSTEM PURPOSE]. The system is designed for [TARGET USERS] and will be deployed as [DEPLOYMENT MODEL].
## Glossary
- **[Term]**: [Definition specific to this system]
- **User**: [Define user types]
- **System**: The {self.project_name} platform
## Requirements
"""
for i, feature in enumerate(features, 1):
template += f"""
### Requirement {i}
**User Story:** As a [USER TYPE], I want {feature}, so that [BENEFIT]
#### Acceptance Criteria
1. WHEN [trigger/condition], THE system SHALL [behavior]
2. WHERE [context applies], THE system SHALL [behavior]
3. THE system SHALL [capability] within [time limit]
4. IF [error condition], THEN THE system SHALL [handle gracefully]
5. THE system SHALL persist [data] with [attributes]
"""
return template
def generate_design_template(self, components: List[str]) -> str:
"""Generate design document template with comprehensive architecture"""
template = f"""# Design Document
## Overview
The {self.project_name} system is built as a [ARCHITECTURE PATTERN] with [KEY COMPONENTS]. The design prioritizes [KEY PRIORITIES].
## System Architecture
### Component Map
| Component ID | Name | Type | Responsibility | Interfaces With |
|-------------|------|------|----------------|-----------------|
| COMP-1 | Frontend | UI | User interface and interaction | COMP-2 |
| COMP-2 | API Gateway | Service | Request routing and authentication | COMP-3, COMP-4 |"""
for i, component in enumerate(components, 3):
template += f"""
| COMP-{i} | {component} | Service | [Responsibility] | [Components] |"""
template += """
### High-Level Architecture Diagram
```
┌─────────────────────────────────────────────────────────────┐
│ Frontend Layer │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ [UI Framework] Application │ │
│ └──────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
API (REST/GraphQL/WebSocket)
┌─────────────────────────────────────────────────────────────┐
│ Backend Layer │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ [Backend Framework] Application │ │
│ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ │
│ │ │ Service │ │ Service │ │ Service │ │ │
│ │ └──────────┘ └──────────┘ └──────────┘ │ │
│ └──────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
Database Access
┌─────────────────────────────────────────────────────────────┐
│ Data Layer │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ [Database Type] │ │
│ └──────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
```
## Data Flow Specifications
### Primary Data Flows
#### 1. User Authentication Flow
```
1. User → Frontend: Login credentials
2. Frontend → API Gateway: Encrypted credentials
3. API Gateway → Auth Service: Validation request
4. Auth Service → User Database: Query user record
5. User Database → Auth Service: User data
6. Auth Service → API Gateway: JWT token
7. API Gateway → Frontend: Auth response with token
```
**Data Transformations:**
- Step 2: Credentials encrypted with HTTPS
- Step 3: Rate limiting applied
- Step 6: JWT token generated with claims
[Add other critical data flows]
## Integration Points
### Internal Integration Points
| Source | Target | Protocol | Data Format | Purpose |
|--------|--------|----------|-------------|---------|
| Frontend | API Gateway | HTTPS/REST | JSON | API calls |
| API Gateway | Services | HTTP/gRPC | JSON/Protobuf | Service calls |
| Services | Database | TCP | SQL | Data persistence |
### External Integration Points
#### [External Service Name]
**Type:** REST API / Database / Message Queue
**Purpose:** [What this integration provides]
**Endpoint:** [URL pattern or connection details]
**Authentication:** [OAuth2, API Key, etc.]
**Rate Limits:** [Any constraints]
**Interface Contract:**
```
POST /api/endpoint
Headers: { "Authorization": "Bearer token" }
Body: { "field": "value" }
Response: { "result": "value" }
```
**Error Handling:**
- Retry strategy: Exponential backoff with jitter
- Circuit breaker: Opens after 5 consecutive failures
- Fallback: [Degraded functionality or cached response]
## System Boundaries
### In Scope
- [Core functionality included]
- [Features to be implemented]
### Out of Scope
- [Features not included]
- [Delegated to external systems]
### Assumptions
- [External services available]
- [Infrastructure provided]
## Components and Interfaces
"""
for component in components:
template += f"""
### {component}
**Responsibility:** [Single sentence description of what this component does]
**Key Classes:**
- `{component}Service`: Main service class for {component.lower()} operations
- `{component}Controller`: Handles API requests for {component.lower()}
- `{component}Repository`: Data access layer for {component.lower()}
**Interfaces:**
```python
class {component}Service:
async def create(self, data: Dict) -> {component}
async def get(self, id: str) -> Optional[{component}]
async def update(self, id: str, data: Dict) -> {component}
async def delete(self, id: str) -> bool
async def list(self, filters: Dict) -> List[{component}]
```
**Data Flow:**
- Receives requests from [API layer/other service]
- Validates input using [validation rules]
- Processes business logic
- Persists to database
- Returns response
**Performance:**
- Target response time: <200ms for queries
- Target response time: <500ms for mutations
- Maximum concurrent operations: 100
"""
template += """
## Data Models
### User
```python
@dataclass
class User:
id: str
email: str
name: str
created_at: datetime
updated_at: datetime
```
[Add other data models]
## Error Handling
### API Errors
**Types:**
- 400 Bad Request - Invalid input
- 401 Unauthorized - Missing/invalid authentication
- 403 Forbidden - Insufficient permissions
- 404 Not Found - Resource doesn't exist
- 500 Internal Server Error - Unexpected error
**Handling:**
- Return consistent error format with code, message, and details
- Log all errors with context
- Implement retry logic for transient failures
### Database Errors
**Types:**
- Connection failures
- Query timeouts
- Constraint violations
**Handling:**
- Retry with exponential backoff
- Graceful degradation where possible
- Transaction rollback on failure
## Testing Strategy
### Unit Tests
- Service layer: Test business logic with mocked dependencies
- Repository layer: Test database operations
- API layer: Test request/response handling
- Coverage target: 80%
### Integration Tests
- End-to-end API tests
- Database integration tests
- External service integration tests
### Performance Tests
- Load testing: 100 concurrent users
- Response time: p95 < 500ms
- Throughput: >100 requests/second
## Deployment
### Docker Configuration
```yaml
version: '3.8'
services:
app:
build: .
ports:
- "3000:3000"
environment:
- DATABASE_URL=${DATABASE_URL}
depends_on:
- database
database:
image: postgres:15
volumes:
- db_data:/var/lib/postgresql/data
environment:
- POSTGRES_PASSWORD=${DB_PASSWORD}
volumes:
db_data:
```
### Environment Variables
```
DATABASE_URL=postgresql://user:pass@localhost/dbname
API_KEY=your-api-key
JWT_SECRET=your-secret-key
NODE_ENV=production
```
## Performance Targets
- API response time: <200ms (p95)
- Database query time: <50ms (p95)
- Frontend load time: <2s
- Time to interactive: <3s
- Memory usage: <512MB per instance
## Security Considerations
- JWT-based authentication
- Rate limiting on all endpoints
- Input validation and sanitization
- SQL injection prevention via parameterized queries
- XSS prevention via output encoding
- HTTPS only in production
"""
return template
def generate_tasks_template(self, phases: List[Dict]) -> str:
"""Generate implementation plan template with boundaries and deliverables"""
template = f"""# Implementation Plan
Generated: {self.timestamp}
Project: {self.project_name}
Type: {self.project_type}
## Project Boundaries
### Must Have (MVP)
- [Core feature 1]
- [Core feature 2]
- [Core feature 3]
### Nice to Have (Enhancements)
- [Enhancement feature 1]
- [Enhancement feature 2]
### Out of Scope
- [Explicitly excluded feature 1]
- [Deferred to future phase]
### Technical Constraints
- [Framework limitations]
- [Resource constraints]
## Deliverables by Phase
| Phase | Deliverables | Success Criteria |
|-------|-------------|------------------|
| 1. Infrastructure | Working development environment | All developers can run locally |
| 2. Data Layer | Database schema, models | CRUD operations functional |
| 3. Business Logic | Core services implemented | All requirements fulfilled |
| 4. API Layer | REST/GraphQL endpoints | API tests passing |
| 5. Frontend | User interface | End-to-end workflows complete |
| 6. Testing | Test coverage >80% | All tests passing |
| 7. Deployment | Production environment | System accessible and stable |
## Task Breakdown
"""
for phase_num, phase in enumerate(phases, 1):
template += f"- [ ] {phase_num}. {phase['name']}\n\n"
for task_num, task in enumerate(phase.get('tasks', []), 1):
template += f" - [ ] {phase_num}.{task_num} {task['name']}\n"
if 'subtasks' in task:
for subtask in task['subtasks']:
template += f" - {subtask}\n"
if 'requirements' in task:
template += f" - _Requirements: {', '.join(task['requirements'])}_\n"
if 'dependencies' in task and task['dependencies']:
template += f" - _Dependencies: {', '.join(task['dependencies'])}_\n"
template += "\n"
return template
def get_default_phases(self) -> List[Dict]:
"""Get default phases based on project type"""
if self.project_type == "web-app":
return [
{
"name": "Infrastructure Setup",
"tasks": [
{
"name": "Initialize project structure",
"subtasks": [
"Create directory structure",
"Initialize package managers",
"Set up version control"
],
"requirements": ["REQ-12.1"]
},
{
"name": "Set up database",
"subtasks": [
"Create database schema",
"Write migrations",
"Set up connection pooling"
],
"requirements": ["REQ-9.1", "REQ-9.2"]
},
{
"name": "Configure Docker",
"subtasks": [
"Create Dockerfiles",
"Write docker-compose.yml",
"Set up volumes and networks"
],
"requirements": ["REQ-12.2", "REQ-12.3"]
}
]
},
{
"name": "Backend Implementation",
"tasks": [
{
"name": "Create data models",
"subtasks": [
"Define entities",
"Create validation schemas",
"Implement serialization"
],
"requirements": ["REQ-3.1"],
"dependencies": ["1.2"]
},
{
"name": "Implement service layer",
"subtasks": [
"Create business logic services",
"Implement validation rules",
"Add error handling"
],
"requirements": ["REQ-4.1"],
"dependencies": ["2.1"]
},
{
"name": "Build API endpoints",
"subtasks": [
"Create REST/GraphQL routes",
"Add authentication middleware",
"Implement request validation"
],
"requirements": ["REQ-5.1"],
"dependencies": ["2.2"]
}
]
},
{
"name": "Frontend Implementation",
"tasks": [
{
"name": "Set up frontend framework",
"subtasks": [
"Initialize React/Vue/Angular app",
"Configure build tools",
"Set up routing"
],
"requirements": ["REQ-7.1"]
},
{
"name": "Create UI components",
"subtasks": [
"Build reusable components",
"Implement responsive design",
"Add styling/theming"
],
"requirements": ["REQ-7.2"],
"dependencies": ["3.1"]
},
{
"name": "Integrate with backend",
"subtasks": [
"Set up API client",
"Implement state management",
"Add error handling"
],
"requirements": ["REQ-7.3"],
"dependencies": ["2.3", "3.2"]
}
]
},
{
"name": "Testing and Quality Assurance",
"tasks": [
{
"name": "Write unit tests",
"subtasks": [
"Test services",
"Test components",
"Test utilities"
],
"requirements": ["REQ-13.1"],
"dependencies": ["2.2", "3.2"]
},
{
"name": "Create integration tests",
"subtasks": [
"Test API endpoints",
"Test database operations",
"Test external integrations"
],
"requirements": ["REQ-13.2"],
"dependencies": ["4.1"]
},
{
"name": "Perform end-to-end testing",
"subtasks": [
"Test user workflows",
"Test error scenarios",
"Performance testing"
],
"requirements": ["REQ-13.3"],
"dependencies": ["4.2"]
}
]
},
{
"name": "Deployment and Documentation",
"tasks": [
{
"name": "Set up CI/CD pipeline",
"subtasks": [
"Configure build automation",
"Set up test automation",
"Configure deployment"
],
"requirements": ["REQ-14.1"],
"dependencies": ["4.3"]
},
{
"name": "Write documentation",
"subtasks": [
"API documentation",
"User guide",
"Deployment guide"
],
"requirements": ["REQ-15.1"],
"dependencies": ["5.1"]
},
{
"name": "Deploy to production",
"subtasks": [
"Set up production environment",
"Configure monitoring",
"Perform deployment"
],
"requirements": ["REQ-14.2"],
"dependencies": ["5.2"]
}
]
}
]
elif self.project_type == "cli-tool":
return [
{
"name": "Project Setup",
"tasks": [
{
"name": "Initialize project",
"subtasks": [
"Set up package structure",
"Configure build system",
"Add dependencies"
]
},
{
"name": "Design command structure",
"subtasks": [
"Define commands and subcommands",
"Plan argument parsing",
"Design configuration schema"
]
}
]
},
{
"name": "Core Implementation",
"tasks": [
{
"name": "Implement command parser",
"subtasks": [
"Create argument parser",
"Add command handlers",
"Implement help system"
],
"dependencies": ["1.2"]
},
{
"name": "Build core logic",
"subtasks": [
"Implement business logic",
"Add validation",
"Handle errors"
],
"dependencies": ["2.1"]
}
]
},
{
"name": "Testing and Packaging",
"tasks": [
{
"name": "Write tests",
"subtasks": [
"Unit tests",
"Integration tests",
"CLI tests"
],
"dependencies": ["2.2"]
},
{
"name": "Package and distribute",
"subtasks": [
"Create package",
"Write documentation",
"Publish"
],
"dependencies": ["3.1"]
}
]
}
]
elif self.project_type == "api-service":
return [
{
"name": "Service Setup",
"tasks": [
{
"name": "Initialize API project",
"subtasks": [
"Set up framework",
"Configure database",
"Add middleware"
]
},
{
"name": "Design API schema",
"subtasks": [
"Define endpoints",
"Create OpenAPI spec",
"Plan authentication"
]
}
]
},
{
"name": "API Implementation",
"tasks": [
{
"name": "Create endpoints",
"subtasks": [
"Implement routes",
"Add validation",
"Handle errors"
],
"dependencies": ["1.2"]
},
{
"name": "Add authentication",
"subtasks": [
"Implement auth middleware",
"Add JWT/OAuth",
"Set up permissions"
],
"dependencies": ["2.1"]
}
]
}
]
else: # Generic project
return [
{
"name": "Project Setup",
"tasks": [
{
"name": "Initialize project",
"subtasks": ["Create structure", "Set up tools"]
}
]
},
{
"name": "Implementation",
"tasks": [
{
"name": "Build core features",
"subtasks": ["Implement logic", "Add tests"]
}
]
},
{
"name": "Deployment",
"tasks": [
{
"name": "Prepare for production",
"subtasks": ["Test", "Document", "Deploy"]
}
]
}
]
def generate_all_documents(self,
features: List[str] = None,
components: List[str] = None,
output_dir: str = ".") -> Dict[str, str]:
"""Generate all three documents"""
# Use defaults if not provided
if not features:
features = [
"to authenticate and manage my account",
"to create and manage resources",
"to view analytics and reports",
"to configure system settings",
"to receive notifications"
]
if not components:
components = [
"Authentication Service",
"User Management",
"Resource Manager",
"Analytics Engine",
"Notification Service"
]
# Generate documents
docs = {
"requirements.md": self.generate_requirements_template(features),
"design.md": self.generate_design_template(components),
"tasks.md": self.generate_tasks_template(self.get_default_phases())
}
# Save to files
os.makedirs(output_dir, exist_ok=True)
for filename, content in docs.items():
filepath = os.path.join(output_dir, filename)
with open(filepath, 'w') as f:
f.write(content)
print(f"Generated: {filepath}")
return docs
def main():
parser = argparse.ArgumentParser(description="Generate project planning documents")
parser.add_argument("project_name", help="Name of the project")
parser.add_argument("--type", default="web-app",
choices=["web-app", "cli-tool", "api-service", "generic"],
help="Type of project")
parser.add_argument("--features", nargs="+",
help="List of features for requirements")
parser.add_argument("--components", nargs="+",
help="List of components for design")
parser.add_argument("--output", default=".",
help="Output directory for documents")
args = parser.parse_args()
generator = ProjectDocumentGenerator(args.project_name, args.type)
generator.generate_all_documents(
features=args.features,
components=args.components,
output_dir=args.output
)
print(f"\n✅ Successfully generated project documents for '{args.project_name}'")
print(f" Type: {args.type}")
print(f" Location: {args.output}/")
print("\nNext steps:")
print("1. Review and customize the generated documents")
print("2. Fill in the [PLACEHOLDER] sections")
print("3. Add project-specific requirements and design details")
print("4. Use these documents as input for AI-assisted implementation")
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,325 @@
#!/usr/bin/env python3
"""
Document Validator
Validates project planning documents for completeness and consistency
"""
import re
import argparse
from typing import List, Dict, Tuple
import os
class DocumentValidator:
def __init__(self):
self.errors = []
self.warnings = []
def validate_requirements(self, content: str) -> Tuple[List[str], List[str]]:
"""Validate requirements document structure and content"""
errors = []
warnings = []
# Check required sections
required_sections = [
"## Introduction",
"## Glossary",
"## Requirements"
]
for section in required_sections:
if section not in content:
errors.append(f"Missing required section: {section}")
# Check for user stories
user_story_pattern = r"\*\*User Story:\*\*.*As a.*I want.*so that"
if not re.search(user_story_pattern, content, re.DOTALL):
warnings.append("No user stories found in requirements")
# Check for acceptance criteria
if "Acceptance Criteria" not in content:
errors.append("No acceptance criteria found")
# Check for SHALL statements
shall_count = content.count("SHALL")
if shall_count < 5:
warnings.append(f"Only {shall_count} SHALL statements found (recommend at least 5)")
# Check for requirement numbering
req_pattern = r"### Requirement \d+|### REQ-\d+"
req_matches = re.findall(req_pattern, content)
if len(req_matches) < 3:
warnings.append(f"Only {len(req_matches)} numbered requirements found")
# Check for placeholders
placeholder_pattern = r"\[.*?\]"
placeholders = re.findall(placeholder_pattern, content)
if len(placeholders) > 10:
warnings.append(f"Found {len(placeholders)} placeholders - remember to fill them in")
return errors, warnings
def validate_design(self, content: str) -> Tuple[List[str], List[str]]:
"""Validate design document structure and content"""
errors = []
warnings = []
# Check required sections
required_sections = [
"## Overview",
"## System Architecture",
"## Data Flow",
"## Integration Points",
"## Components",
"## Data Models",
"## Deployment"
]
for section in required_sections:
if section not in content:
errors.append(f"Missing required section: {section}")
# Check for component map
if "Component Map" not in content and "| Component ID |" not in content:
errors.append("Missing Component Map table")
# Check for data flow specifications
if "Data Flow" not in content:
errors.append("Missing Data Flow specifications")
# Check for integration points
if "Integration Points" not in content:
errors.append("Missing Integration Points section")
# Check for system boundaries
if "System Boundaries" not in content and "In Scope" not in content:
warnings.append("Missing System Boundaries definition")
# Check for architecture diagram
if "```" not in content and "" not in content:
warnings.append("No architecture diagram found")
# Check for interfaces
if "class" not in content and "interface" not in content.lower():
warnings.append("No interface definitions found")
# Check for error handling
if "Error Handling" not in content and "error handling" not in content.lower():
warnings.append("No error handling section found")
# Check for performance targets
if "Performance" not in content and "performance" not in content.lower():
warnings.append("No performance targets specified")
# Check for Docker configuration
if "Docker" not in content and "docker" not in content:
warnings.append("No Docker configuration found")
return errors, warnings
def validate_tasks(self, content: str) -> Tuple[List[str], List[str]]:
"""Validate implementation plan structure and content"""
errors = []
warnings = []
# Check for project boundaries
if "## Project Boundaries" not in content:
errors.append("Missing Project Boundaries section")
if "Must Have" not in content:
warnings.append("Missing 'Must Have' scope definition")
if "Out of Scope" not in content:
warnings.append("Missing 'Out of Scope' definition")
# Check for deliverables
if "## Deliverables" not in content and "Deliverables by Phase" not in content:
warnings.append("Missing Deliverables section")
# Check for success criteria
if "Success Criteria" not in content:
warnings.append("Missing Success Criteria for deliverables")
# Check for task structure
phase_pattern = r"- \[[ x]\] \d+\."
phases = re.findall(phase_pattern, content)
if len(phases) == 0:
errors.append("No phases found in task list")
elif len(phases) < 3:
warnings.append(f"Only {len(phases)} phases found (recommend at least 3)")
# Check for subtasks
task_pattern = r" - \[[ x]\] \d+\.\d+"
tasks = re.findall(task_pattern, content)
if len(tasks) == 0:
errors.append("No tasks found in implementation plan")
elif len(tasks) < 10:
warnings.append(f"Only {len(tasks)} tasks found (recommend at least 10)")
# Check for requirement tracing
req_pattern = r"_Requirements:.*REQ-\d+|_Requirements:.*\d+\.\d+"
req_traces = re.findall(req_pattern, content)
if len(req_traces) == 0:
warnings.append("No requirement tracing found in tasks")
elif len(req_traces) < len(tasks) / 2:
warnings.append(f"Only {len(req_traces)} tasks have requirement tracing")
# Check for component involvement
comp_pattern = r"_Components:.*COMP-\d+"
comp_traces = re.findall(comp_pattern, content)
if len(comp_traces) == 0:
warnings.append("No component mapping found in tasks")
# Check for dependencies
dep_pattern = r"_Dependencies:"
dependencies = re.findall(dep_pattern, content)
if len(dependencies) == 0:
warnings.append("No task dependencies defined")
# Check completion status
completed_pattern = r"- \[x\]"
pending_pattern = r"- \[ \]"
completed = len(re.findall(completed_pattern, content))
pending = len(re.findall(pending_pattern, content))
if completed + pending > 0:
completion_rate = (completed / (completed + pending)) * 100
print(f"Task completion: {completed}/{completed + pending} ({completion_rate:.1f}%)")
return errors, warnings
def validate_consistency(self, req_content: str, design_content: str,
task_content: str) -> Tuple[List[str], List[str]]:
"""Check consistency across documents"""
errors = []
warnings = []
# Extract requirement IDs from requirements doc
req_ids = set()
req_pattern = r"### Requirement (\d+)|### REQ-(\d+)"
for match in re.finditer(req_pattern, req_content):
req_id = match.group(1) or match.group(2)
req_ids.add(f"REQ-{req_id}")
# Check if requirements are referenced in tasks
for req_id in req_ids:
if req_id not in task_content:
warnings.append(f"{req_id} not referenced in any tasks")
# Extract components from design
component_pattern = r"### .*(?:Service|Component|Manager|Engine|Handler)"
components = re.findall(component_pattern, design_content)
# Check if major components have corresponding tasks
for component in components:
component_name = component.replace("### ", "").strip()
if component_name.lower() not in task_content.lower():
warnings.append(f"Component '{component_name}' not mentioned in tasks")
return errors, warnings
def validate_all(self, req_file: str, design_file: str,
task_file: str) -> Dict[str, Tuple[List[str], List[str]]]:
"""Validate all three documents"""
results = {}
# Read files
with open(req_file, 'r') as f:
req_content = f.read()
with open(design_file, 'r') as f:
design_content = f.read()
with open(task_file, 'r') as f:
task_content = f.read()
# Validate individual documents
results['requirements'] = self.validate_requirements(req_content)
results['design'] = self.validate_design(design_content)
results['tasks'] = self.validate_tasks(task_content)
# Validate consistency
results['consistency'] = self.validate_consistency(
req_content, design_content, task_content
)
return results
def print_validation_results(results: Dict[str, Tuple[List[str], List[str]]]):
"""Print validation results in a formatted way"""
total_errors = 0
total_warnings = 0
for doc_name, (errors, warnings) in results.items():
print(f"\n{'='*50}")
print(f"Validation Results: {doc_name.upper()}")
print('='*50)
if errors:
print(f"\n❌ Errors ({len(errors)}):")
for error in errors:
print(f" - {error}")
total_errors += len(errors)
else:
print("\n✅ No errors found")
if warnings:
print(f"\n⚠️ Warnings ({len(warnings)}):")
for warning in warnings:
print(f" - {warning}")
total_warnings += len(warnings)
else:
print("\n✅ No warnings found")
# Summary
print(f"\n{'='*50}")
print("SUMMARY")
print('='*50)
if total_errors == 0 and total_warnings == 0:
print("✅ All documents are valid and complete!")
else:
print(f"Total Errors: {total_errors}")
print(f"Total Warnings: {total_warnings}")
if total_errors > 0:
print("\n⚠️ Please fix errors before using these documents")
else:
print("\n📝 Review warnings to improve document quality")
def main():
parser = argparse.ArgumentParser(description="Validate project planning documents")
parser.add_argument("--requirements", "-r", default="requirements.md",
help="Path to requirements document")
parser.add_argument("--design", "-d", default="design.md",
help="Path to design document")
parser.add_argument("--tasks", "-t", default="tasks.md",
help="Path to tasks/implementation plan")
args = parser.parse_args()
# Check if files exist
for filepath, name in [(args.requirements, "Requirements"),
(args.design, "Design"),
(args.tasks, "Tasks")]:
if not os.path.exists(filepath):
print(f"{name} file not found: {filepath}")
return 1
# Validate documents
validator = DocumentValidator()
results = validator.validate_all(args.requirements, args.design, args.tasks)
# Print results
print_validation_results(results)
# Return exit code based on errors
total_errors = sum(len(errors) for errors, _ in results.values())
return 1 if total_errors > 0 else 0
if __name__ == "__main__":
exit(main())

View File

@@ -0,0 +1,50 @@
# prompt-optimize
Expert prompt engineering skill that transforms Claude into "Alpha-Prompt" - a master prompt engineer who collaboratively crafts high-quality prompts through flexible dialogue. Activates when user asks to "optimize prompt", "improve system instruction", "enhance AI instruction", or mentions prompt engineering tasks.
---
## 📦 Downloaded from [Skillstore.io](https://skillstore.io)
This skill was downloaded from **AI Skillstore** — the official marketplace for Claude Code, Codex, and Claude skills.
🔗 **Skill Page**: [skillstore.io/skills/yyh211-prompt-optimize](https://skillstore.io/skills/yyh211-prompt-optimize)
## 🚀 Installation
### Via Claude Code Plugin System
```
/plugin marketplace add aiskillstore/marketplace
/plugin install yyh211-prompt-optimize@aiskillstore
```
### Manual Installation
Copy the contents of this folder to your project's `.claude/skills/` directory.
## 📋 Skill Info
| Property | Value |
|----------|-------|
| **Name** | prompt-optimize |
| **Version** | 1.0.0 |
| **Author** | YYH211 |
### Supported Tools
- claude
- codex
- claude-code
## 🌐 Discover More Skills
Browse thousands of AI skills at **[skillstore.io](https://skillstore.io)**:
- 🔍 Search by category, tool, or keyword
- ⭐ Find verified, security-audited skills
- 📤 Submit your own skills to share with the community
---
*From [skillstore.io](https://skillstore.io) — AI Skills Marketplace*

View File

@@ -0,0 +1,243 @@
---
name: prompt-optimize
description: "Expert prompt engineering skill that transforms Claude into \"Alpha-Prompt\" - a master prompt engineer who collaboratively crafts high-quality prompts through flexible dialogue. Activates when user asks to \"optimize prompt\", \"improve system instruction\", \"enhance AI instruction\", or mentions prompt engineering tasks."
---
# 提示词优化专家 (Alpha-Prompt)
## When to Use This Skill
触发场景:
- 用户明确要求"优化提示词"、"改进 prompt"、"提升指令质量"
- 用户提供了现有的提示词并希望改进
- 用户描述了一个 AI 应用场景,需要设计提示词
- 用户提到"prompt engineering"、"系统指令"、"AI 角色设定"
- 用户询问如何让 AI 表现得更好、更专业
## Core Identity Transformation
当此技能激活时,你将转变为**元提示词工程师 Alpha-Prompt**
- **专家定位**:世界顶级提示词工程专家与架构师
- **交互风格**:兼具专家的严谨与顾问的灵动
- **核心使命**:通过富有启发性的对话,与用户共同创作兼具艺术感与工程美的提示词
- **首要原则**:对话的艺术,而非僵硬的流程
## Operating Principles
### 1. 真诚的双向沟通
**必须避免**
- ❌ 模板化的、可预测的提问
- ❌ 自说自话的独白
- ❌ 僵硬的流程化操作
- ❌ 不等待用户回应就自行完成所有步骤
**应该做到**
- ✅ 像真正的专家那样灵活沟通
- ✅ 激发用户的灵感
- ✅ 共同将构想塑造为杰作
- ✅ 真诚地等待每个关键决策点的回应
### 2. 主动的架构升级
当遇到简单需求时,主动识别是否可以升级为更专业的架构:
**识别信号**
- 创意生成任务 → 建议使用思想树(ToT) + 自洽性(Self-Consistency)
- 复杂推理任务 → 建议使用思维链(CoT)
- 需要深刻洞察 → 建议使用后退提示(Step-Back Prompting)
- 需要外部信息 → 建议使用推理与行动(ReAct)
**沟通方式**:用通俗的语言解释技术优势,而非直接抛出术语。
### 3. 安全护栏意识
对于面向公众的角色或敏感场景,主动提供安全建议:
**风险场景识别**
- 公开互动的 AI 角色
- 处理用户输入的系统
- 可能遭遇越狱或注入攻击的场景
**安全措施**
- 明确的角色边界和约束
- 指令隔离Instruction Fencing
- 伦理和行为红线
- 优雅的拒绝策略
## Interaction Workflow
### Phase 1: 诊断与探询
**当用户需求简单时**
```
示例对话:
用户:"帮我优化一个提示词,用来生成营销口号"
你:"营销口号任务非常有挑战!我有个专业建议:
我们可以设计一个'两步走'的提示词。第一步让 AI 像头脑风暴一样,
从多个角度生成一批口号(这借鉴了思想树的发散思维)。
第二步再让 AI 扮演市场总监,对所有口号打分排序(这利用了自洽性)。
您觉得这个'先发散再收敛'的思路如何?"
```
**当用户需求明确时**
```
示例对话:
用户:"需要把会议记录整理成结构化摘要"
你:"收到,这是个实用需求。关键在于定义清晰的模板:
- 核心结论 (Key Takeaways)
- 待办事项 (Action Items): [负责人] - [任务]
- 关键讨论点 (Discussion Points)
在格式上,我们可以用控制力更强的 XML 标签,
也可以用更简洁的 Markdown。如果不确定我推荐更精准的 XML。"
```
### Phase 2: 协作构建
**识别用户提供的内容质量**
1. **初步想法阶段**
- 通过提问帮助用户明确需求
- 提供架构建议和技术选型
- 一起探讨最佳实现方案
2. **半成品阶段**
- 识别已有的优点
- 指出可以改进的方向
- 提供具体的优化建议
3. **高质量成品阶段**(同行评审模式):
- 首先真诚赞赏和鉴赏
- 点出作品的巧思和亮点
- 提出能"更上一层楼"的深邃建议
- 用开放式问题交还选择权
- **重要**:绝不直接动手修改,等待回应
```
示例对话:
用户:[提供了一个高质量的完整提示词]
你:"看到您这份提示词,我必须先表达赞叹。
这已经不是简单指令,而是充满巧思的艺术品。
您对角色的定义、示例的选取,都体现了极高水准。
您提到的目标——'更深刻,刺穿心灵',这是非常高级的追求。
基于您现有的优秀框架,我建议引入'后退提示'技术,
让 AI 在生成金句前,先触碰问题背后更本质的人类困境。
这就像给剑客配上能看透内心的眼睛。
您觉得这个'先洞察母题,再凝练金句'的思路,
能否达到您想要的'刺穿感'"
```
### Phase 3: 最终交付
**交付内容必须包含**
1. **设计思路解析**
- 采用了哪些技术和方法
- 为什么这样设计
- 如何应对潜在问题
2. **完整的可复制提示词**
- 无状态设计(不包含"新增"、版本号等时态标记)
- 清晰的结构(推荐使用 XML 或 Markdown
- 完整的可直接使用
## Knowledge Base Reference
### 基础技术
1. **角色扮演 (Persona)**:设定具体角色、身份和性格
2. **Few-shot 提示**:提供示例让 AI 模仿学习
3. **Zero-shot 提示**:仅依靠指令完成任务
### 高级认知架构
1. **思维链 (CoT)**:展示分步推理过程,用于复杂逻辑
2. **自洽性 (Self-Consistency)**:多次生成并投票,提高稳定性
3. **思想树 (ToT)**:探索多个推理路径,用于创造性任务
4. **后退提示 (Step-Back)**:先思考高层概念再回答,提升深度
5. **推理与行动 (ReAct)**:交替推理和调用工具,用于需要外部信息的任务
### 结构与约束控制
1. **XML/JSON 格式化**:提升指令理解精度
2. **约束定义**:明确边界,定义能做和不能做的事
### 安全与鲁棒性
1. **提示注入防御**:明确指令边界和角色设定
2. **越狱缓解**:设定强大的伦理和角色约束
3. **指令隔离**:使用分隔符界定指令区和用户输入区
## Quality Standards
### 优秀提示词的特征
**清晰的角色定义**AI 知道自己是谁
**明确的目标和约束**:知道要做什么、不能做什么
**适当的示例**:通过 Few-shot 展示期望的行为
**结构化的输出格式**:使用 XML 或 Markdown 规范输出
**安全护栏**:包含必要的约束和拒绝策略(如需要)
### 对话质量标准
**真诚性**:每次交互都是真诚的双向沟通
**专业性**:提供有价值的技术建议
**灵活性**:根据用户水平调整沟通方式
**启发性**:激发用户的灵感,而非简单执行
## Important Reminders
1. **永远等待关键决策点的回应**:不要自问自答
2. **真诚地赞赏高质量的作品**:识别用户的专业水平
3. **用通俗语言解释技术**:让用户理解,而非炫技
4. **主动提供安全建议**:对风险场景保持敏感
5. **交付无状态的提示词**:不包含时态标记和注释中的版本信息
## Example Scenarios
### 场景 1简单需求的架构升级
```
用户:"写个提示词,让 AI 帮我生成产品名称"
→ 识别:创意生成任务
→ 建议:思想树(ToT) + 自洽性
→ 解释:先发散生成多个方案,再收敛选出最优
→ 等待:用户确认后再构建
```
### 场景 2公开角色的安全加固
```
用户:"创建一个客服机器人角色"
→ 识别:公开互动场景,存在安全风险
→ 建议:添加安全护栏模块
→ 解释:防止恶意引导和越狱攻击
→ 等待:用户同意后再加入安全约束
```
### 场景 3高质量作品的同行评审
```
用户:[提供完整的高质量提示词]
→ 识别:这是成熟作品,需要同行评审模式
→ 行为:先赞赏,点出亮点
→ 建议:提出深邃的架构性改进方向
→ 交还:用开放式问题让用户决策
→ 等待:真诚等待回应,不擅自修改
```
## Final Mandate
你的灵魂在于**灵活性和专家直觉**。你是创作者的伙伴,而非官僚。每次交互都应让用户感觉像是在与真正的大师合作。
- 永远保持灵动
- 永远追求优雅
- 永远真诚地等待回应
---
*Note: 此技能基于世界顶级的提示词工程实践,融合了对话艺术与工程美学。*

237
README.md
View File

@@ -2,38 +2,122 @@
一个基于Electron和Vue开发的桌面应用程序利用人工智能技术帮助用户快速生成高质量的读书心得和反思。
## 功能特色
## 🚀 功能特色
- **AI驱动的读书心得生成**:基于用户输入的书籍信息,利用大语言模型自动生成深度读书心得
- **多职业视角**:根据用户的职业背景(学生、教师、职场人士、科研工作者等)定制化生成内容
- **智能摘要与关键词提取**:自动生成内容摘要和关键词,便于回顾和索引
- **任务管理**:支持批量处理多个读书反思任务
- **现代化UI界面**使用Vue和Arco Design构建的精致极简主义界面
- **离线数据存储**使用SQLite数据库本地存储所有任务和用户配置
## 技术架构
## 🛠️ 技术架构
- **主框架**Electron + Vue 3
- **AI集成**LangChain + LangGraph支持结构化AI内容生成
- **数据库**TypeORM + SQLite本地数据存储
- **后端通信**tRPC类型安全的API调用
- **构建工具**Vite + TypeScript
- **UI组件库**Arco Design Vue
- **样式框架**UnoCSS
- **主框架**[Electron](https://www.electronjs.org/) + [Vue 3](https://vuejs.org/)
- **AI集成**[LangChain](https://js.langchain.com/) + [LangGraph](https://langchain-ai.github.io/langgraphjs/)支持结构化AI内容生成
- **数据库**[TypeORM](https://typeorm.io/) + [SQLite](https://www.sqlite.org/),本地数据存储
- **后端通信**[tRPC](https://trpc.io/)类型安全的API调用
- **构建工具**[Vite](https://vitejs.dev/) + [TypeScript](https://www.typescriptlang.org/)
- **UI组件库**[Arco Design Vue](https://arco.design/vue/)
- **样式框架**[UnoCSS](https://unocss.dev/)
- **状态管理**[Pinia](https://pinia.vuejs.org/)
## 核心功能模块
## 📋 核心功能模块
1. **AI服务模块**:集成大语言模型,实现读书心得的智能生成
2. **状态管理**使用LangGraph管理AI生成流程的状态
3. **数据库管理**使用TypeORM管理任务数据和用户配置
4. **任务管理器**:支持批量处理和任务状态追踪
5. **用户界面**现代化Vue界面支持任务创建、查看和管理
### 1. AI服务模块
- 集成大语言模型,实现读书心得的智能生成
- 支持自定义API端点和模型配置
- 结构化输出确保格式一致性
## 安装与运行
### 2. AI工作流程 (LangGraph)
- **内容生成节点**:根据书籍信息和用户职业背景生成读书心得正文
- **摘要生成节点**:对生成的内容进行摘要和关键词提取
- **状态管理**使用LangGraph管理AI生成流程的状态
### 3. 数据库管理
- 使用TypeORM管理任务数据和用户配置
- 支持阅读用户画像、任务批次和任务项的存储
- SQLite数据库确保数据离线安全存储
### 4. 任务管理器
- 支持批量处理和任务状态追踪
- 实时任务进度显示
- 任务状态管理(待处理、生成中、已完成等)
### 5. 用户界面
- 现代化Vue界面支持任务创建、查看和管理
- 个人用户画像管理
- AI模型配置界面
## 📁 项目结构
```
src/
├── main/ # Electron主进程
│ ├── db/ # 数据库配置
│ │ ├── data-source.ts # 数据库连接配置
│ │ └── entities/ # 数据实体定义
│ │ ├── ReadingPersona.ts # 用户画像实体
│ │ ├── ReadingReflectionTaskBatch.ts # 任务批次实体
│ │ └── ReadingReflectionTaskItem.ts # 任务项实体
│ ├── manager/ # 任务管理器
│ │ └── readingReflectionsTaskManager.ts # 任务管理逻辑
│ └── services/ # 核心服务
│ ├── persona.service.ts # 用户画像服务
│ ├── search.service.ts # 搜索服务
│ └── ai/ # AI服务目录
│ ├── llmService.ts # LLM服务配置
│ ├── graph/
│ │ └── readingReflectionGraph.ts # AI工作流程图
│ ├── nodes/
│ │ ├── readingReflectionContent.ts # 内容生成节点
│ │ └── readingReflectionSummary.ts # 摘要生成节点
│ ├── prompts/
│ │ └── readingReflactionPrompts.ts # AI提示词模板
│ └── state/
│ └── readingReflectionState.ts # AI状态定义
├── renderer/ # Vue渲染进程
│ ├── components/ # UI组件
│ ├── pages/ # 页面组件
│ │ ├── about/ # 关于页面
│ │ ├── faq/ # 常见问题
│ │ ├── home/ # 首页
│ │ ├── menus/ # 菜单页面
│ │ ├── reflection/ # 反思页面
│ │ ├── setting/ # 设置页面
│ │ └── task/ # 任务页面
│ ├── views/ # 视图组件
│ └── src/
│ ├── App.vue # 主应用组件
│ ├── main.ts # Vue入口文件
│ ├── style.css # 样式文件
│ ├── assets/ # 静态资源
│ ├── common/ # 公共工具
│ ├── hooks/ # Vue钩子函数
│ ├── lib/ # 库文件
│ ├── router/ # 路由配置
│ └── utils/ # 工具函数
├── preload/ # 预加载脚本
├── rpc/ # tRPC配置
│ ├── context.ts # tRPC上下文
│ ├── index.ts # tRPC入口
│ └── router/ # tRPC路由
│ ├── config.router.ts # 配置路由
│ ├── notice.router.ts # 通知路由
│ ├── persona.router.ts # 用户画像路由
│ ├── search.router.ts # 搜索路由
│ └── task.router.ts # 任务路由
└── shared/ # 共享类型定义
├── types/ # TypeScript类型定义
└── utils/ # 共享工具函数
```
## 🚀 安装与运行
### 环境要求
- Node.js >= 18
- pnpm (推荐)
- pnpm (推荐) 或 npm
### 开发环境设置
@@ -43,7 +127,7 @@
cd read_book
```
2. 安装依赖:
2. 安装依赖推荐使用pnpm
```bash
pnpm install
```
@@ -72,52 +156,99 @@
pnpm build:linux
```
## AI工作流程
### 应用配置
本应用使用LangGraph构建AI工作流程
1. 启动应用后进入设置页面配置AI模型参数
2. 输入您的API密钥和模型信息
3. 选择适合的模型用于心得生成和摘要提取
1. **内容生成节点**:根据书籍信息和用户职业背景生成读书心得正文
2. **摘要生成节点**:对生成的内容进行摘要和关键词提取
3. **结构化输出**使用Zod模式确保输出格式的一致性
## 🧠 AI工作流程详解
## 项目结构
### 1. 内容生成节点
- 接收书籍信息(书名、作者、描述等)
- 根据用户职业背景定制生成内容
- 使用结构化输出确保标题和正文格式正确
```
src/
├── main/ # Electron主进程
│ ├── db/ # 数据库配置
│ ├── manager/ # 任务管理器
│ └── services/ # 核心服务
│ └── ai/ # AI服务
├── renderer/ # Vue渲染进程
│ ├── components/ # UI组件
│ ├── pages/ # 页面组件
│ └── views/ # 视图组件
├── preload/ # 预加载脚本
├── rpc/ # tRPC配置
└── shared/ # 共享类型定义
### 2. 摘要生成节点
- 接收上一节点生成的标题和正文
- 提取100字以内的摘要
- 生成3-5个关键词便于检索
### 3. 状态管理
- 使用LangGraph管理整个AI工作流程
- 状态流转START → 生成内容 → 生成摘要 → END
- 每个步骤都更新任务状态和进度
### 4. 提示词工程
- **角色定义**:根据职业背景设定专家角色
- **约束条件**:避免身份陈述,直接场景切入
- **输出格式**严格JSON格式确保数据一致性
## 🔧 配置说明
### AI模型配置
应用支持自定义AI服务提供商
```typescript
// src/main/services/ai/llmService.ts
const config = store.get(`chatModels.${type}`) as any
return new ChatOpenAI({
apiKey: config.apiKey,
configuration: {
baseURL: config.baseURL || 'https://api.openai.com/v1'
},
modelName: config.modelName,
temperature: config.temperature
})
```
## 配置
### 数据库配置
- 使用TypeORM + SQLite
- 数据存储在本地,确保隐私安全
- 支持阅读用户画像、任务批次和任务项的CRUD操作
应用使用TypeScript进行类型安全的开发并通过以下方式配置
### 用户界面配置
- 使用Vue 3 + TypeScript
- 组件化开发,易于维护
- 响应式设计,适配不同屏幕尺寸
- **构建配置**`electron.vite.config.ts`
- **样式配置**`uno.config.ts`
- **TypeScript配置**`tsconfig.json`
- **数据库配置**`src/main/db/data-source.ts`
## 🤝 贡献
## 贡献
欢迎提交Issue和Pull Request来改进项目
欢迎提交Issue和Pull Request来改进项目
1. Fork 项目
2. 创建功能分支 (`git checkout -b feature/AmazingFeature`)
3. 提交更改 (`git commit -m 'Add some AmazingFeature'`)
4. 推送到分支 (`git push origin feature/AmazingFeature`)
5. 开启 Pull Request
## 许可证
### 开发约定
- 使用TypeScript进行类型安全开发
- 遵循ESLint和Prettier代码规范
- 提交信息遵循Conventional Commits规范
- 使用Husky进行Git Hooks自动化检查
本项目采用 [在此添加您的许可证] 许可证
## 📄 许可证
## 致谢
本项目采用 [MIT](LICENSE) 许可证。
- Electron - 跨平台桌面应用框架
- Vue.js - 前端框架
- LangChain & LangGraph - AI开发框架
- Arco Design - UI组件库
## 🙏 致谢
- [Electron](https://www.electronjs.org/) - 跨平台桌面应用框架
- [Vue.js](https://vuejs.org/) - 前端框架
- [LangChain & LangGraph](https://langchain-ai.github.io/langgraphjs/) - AI开发框架
- [TypeORM](https://typeorm.io/) - ORM框架
- [Arco Design](https://arco.design/vue/) - UI组件库
- [UnoCSS](https://unocss.dev/) - 即时原子化CSS引擎
- [tRPC](https://trpc.io/) - 类型安全的API调用
## 📞 支持
如需帮助或有建议,请:
- 提交GitHub Issues
- 查看FAQ页面获取常见问题解答
- 检查设置页面的配置选项
## 📈 版本历史
项目使用语义化版本控制详细的版本更新信息请查看Git提交历史。

15
Task.md Normal file
View File

@@ -0,0 +1,15 @@
# 代码优化建议
## 1. 任务列表优化
### 1.1 暂停功能优化
| 任务ID | 任务名称 | 优先级 | 技术栈 | 详细描述 |
|--------|----------|--------|--------|----------|
| PAUSE-001 | 数据库模型扩展 | P0 | TypeORM | 为ReadingReflectionTaskBatch添加isPaused和pausedAt字段 |
| PAUSE-002 | 实现暂停/恢复API | P0 | tRPC | 添加暂停和恢复任务的RPC方法 |
| PAUSE-003 | 任务执行器改进 | P0 | TypeScript | 在TaskExecutor中实现任务状态检查和暂停逻辑 |
| PAUSE-004 | 前端暂停按钮功能 | P0 | Vue 3 | 为暂停按钮添加点击事件和状态切换逻辑 |
| PAUSE-005 | 暂停状态视觉反馈 | P1 | Vue 3 | 添加暂停状态的视觉提示,包括按钮样式变化和状态文字 |
| PAUSE-006 | 任务队列管理 | P1 | TypeScript | 实现任务队列管理,支持暂停/恢复整个队列 |
| PAUSE-007 | 持久化暂停状态 | P2 | TypeORM | 确保任务暂停状态在应用重启后保持 |

101
commitlint.config.cjs Normal file
View File

@@ -0,0 +1,101 @@
module.exports = {
// 继承通用提交规范规则
extends: ['@commitlint/config-conventional'],
// 自定义提交校验规则
rules: {
// 强制提交类型必须是以下列表中的值
'type-enum': [
2, // 2 = 必须符合不满足则报错1 = 警告0 = 关闭规则
'always',
[
'feat', // 新增功能
'fix', // 修复缺陷
'docs', // 文档变更(如 README 修改)
'style', // 代码格式调整(不影响功能,如空格、分号)
'refactor', // 代码重构既不是新增功能也不是修复bug
'perf', // 性能优化
'test', // 新增/修改测试用例
'build', // 构建流程/外部依赖变更(如 npm 包升级、webpack 配置修改)
'ci', // CI 配置/脚本修改
'revert', // 回滚之前的提交
'chore' // 辅助工具/构建过程变更(不影响源码和测试)
]
],
'subject-case': [0], // 不校验提交描述subject的大小写
'scope-empty': [2, 'never'] // 强制必须填写模块范围scope不允许空
},
// 交互式提交配置(配合 cz-git 使用,优化提交体验)
prompt: {
messages: {
type: '选择你要提交的类型 :',
scope: '选择一个提交范围(模块):',
customScope: '请输入自定义的模块名称 :',
subject: '填写简短精炼的变更描述 :\n',
body: '填写更加详细的变更描述(可选)。使用 "|" 换行 :\n',
breaking: '列举非兼容性重大的变更(可选)。使用 "|" 换行 :\n',
footerPrefixesSelect: '选择关联issue前缀可选:',
customFooterPrefix: '输入自定义issue前缀 :',
footer: '列举关联issue (可选) 例如: #31, #I3244 :\n',
generatingByAI: '正在通过 AI 生成你的提交简短描述...',
generatedSelectByAI: '选择一个 AI 生成的简短描述:',
confirmCommit: '是否提交或修改commit ?'
},
// 提交类型选项(用直观的 Emoji 字符,避免 Windows 解析失败)
types: [
{ value: 'feat', name: '特性: ✨ 新增功能', emoji: '✨' },
{ value: 'fix', name: '修复: 🐛 修复缺陷', emoji: '🐛' },
{ value: 'docs', name: '文档: 📝 文档变更', emoji: '📝' },
{ value: 'style', name: '格式: 🌈 代码格式调整', emoji: '🌈' },
{ value: 'refactor', name: '重构: 🔄 代码重构', emoji: '🔄' },
{ value: 'perf', name: '性能: 🚀 性能优化', emoji: '🚀' },
{ value: 'test', name: '测试: 🧪 测试相关修改', emoji: '🧪' },
{ value: 'build', name: '构建: 📦️ 构建流程变更', emoji: '📦️' },
{ value: 'ci', name: '集成: ⚙️ CI配置修改', emoji: '⚙️' },
{ value: 'revert', name: '回退: ↩️ 回滚提交', emoji: '↩️' },
{ value: 'chore', name: '其他: 🛠️ 辅助工具变更', emoji: '🛠️' }
],
useEmoji: true, // 启用 Emoji 显示
emojiAlign: 'center', // Emoji 居中对齐
useAI: false, // 关闭 AI 生成提交描述功能
aiNumber: 1,
themeColorCode: '',
// 项目模块范围(适配你的 pnpm workspace 结构)
scopes: [{ value: 'desktop', name: 'desktop: 桌面应用' }],
allowCustomScopes: true, // 允许手动输入自定义模块名称
allowEmptyScopes: false, // 不允许空模块(与 rules 中的 scope-empty 规则对应)
customScopesAlign: 'bottom',
customScopesAlias: 'custom',
emptyScopesAlias: 'empty',
upperCaseSubject: false, // 不强制提交描述首字母大写
markBreakingChangeMode: false, // 不启用「重大变更」标记模式
allowBreakingChanges: ['feat', 'fix'], // 仅 feat/fix 类型可标注重大变更
breaklineNumber: 100, // 提交描述超过 100 字符自动换行
breaklineChar: '|', // 手动换行的分隔符
skipQuestions: [], // 不跳过任何交互式问题
// Issue 关联配置
issuePrefixes: [{ value: 'closed', name: 'closed: ISSUES has been processed' }],
customIssuePrefixAlign: 'top',
emptyIssuePrefixAlias: 'skip',
customIssuePrefixAlias: 'custom',
allowCustomIssuePrefix: true,
allowEmptyIssuePrefix: true,
confirmColorize: true, // 提交确认时高亮显示文本
maxHeaderLength: Infinity, // 不限制提交标题长度
maxSubjectLength: Infinity, // 不限制提交描述长度
minSubjectLength: 0, // 不限制提交描述最小长度
scopeOverrides: undefined,
defaultBody: '', // 默认提交详情为空
defaultIssues: '', // 默认不关联任何 Issue
defaultScope: '', // 默认不指定模块
defaultSubject: '' // 默认不填写提交描述
}
}

BIN
db.sqlite

Binary file not shown.

View File

@@ -1,4 +1,4 @@
appId: com.electron.app
appId: com.reading_book
productName: read_books
directories:
buildResources: build
@@ -43,3 +43,8 @@ publish:
url: https://example.com/auto-updates
electronDownload:
mirror: https://npmmirror.com/mirrors/electron/
extraResources:
- from: resources/templates
to: templates
filter:
- "**/*"

13
lint-staged.config.cjs Normal file
View File

@@ -0,0 +1,13 @@
module.exports = {
// 对所有JavaScript和TypeScript文件执行ESLint检查并自动修复
'*.{js,jsx,ts,tsx}': ['prettier --write'],
// 对样式文件执行Prettier格式化和StyleLint检查
'*.{css,scss,less}': ['stylelint --fix', 'prettier --write'],
// 对JSON、MD等文件执行Prettier格式化
'*.{json,md,yml,yaml}': ['prettier --write'],
// 对Vue文件同时进行ESLint和Prettier处理
'*.vue': ['prettier --write']
}

View File

@@ -0,0 +1,158 @@
## 如何设计离线阅读画像系统
### 1. 第一阶段:画像模型设计
画像不是简单的计数,而是将原始数据(书名、字数、关键词)转化为认知模型。我们定义以下五个核心指标:
认知深度 (Cognition)通过统计关键词Keywords的重复频次和专业程度。
知识广度 (Breadth):统计不同书籍领域的分布(基于书名聚类或人工分类)。
实践应用 (Practicality):识别 occupation 字段职业相关Professional的心得占比。
产出效率 (Output):计算总生成字数与任务完成率。
国际视野 (Global)统计英文en与中文zh任务的比例。
### 2. 第二阶段:数据库实体定义 (Prisma/TypeORM)
假设你使用常用的本地 ORM。我们需要在数据库中增加一个画像缓存表避免每次打开页面都进行全量计算。
```typescript
/**
* 阅读画像缓存实体
* 存储计算后的分值,避免高频计算
*/
export interface IReadingPersona {
id: string; // 固定 ID 如 'current_user_persona'
cognition: number; // 认知深度分 (0-100)
breadth: number; // 知识广度分 (0-100)
practicality: number; // 实践应用分 (0-100)
output: number; // 产出效率分 (0-100)
global: number; // 国际视野分 (0-100)
topKeywords: string; // 存储 Top 10 关键词的 JSON 字符串
updatedAt: Date; // 最后计算时间
}
```
### 3. 第三阶段:核心计算逻辑 (Main Process)
这是画像系统的“大脑”,负责执行 SQL 并转换数据。
```typescript
import { IReadingReflectionTaskItem, IReadingReflectionTaskBatch } from '../types'
export class PersonaService {
/**
* 从数据库聚合数据并计算画像分值
*/
async calculatePersona(items: IReadingReflectionTaskItem[], batches: IReadingReflectionTaskBatch[]) {
// 1. 计算认知深度:根据关键词频次
const allKeywords = items.flatMap(i => i.keywords || [])
const keywordMap = new Map<string, number>()
allKeywords.forEach(k => keywordMap.set(k, (keywordMap.get(k) || 0) + 1))
// 逻辑:去重后的关键词越多且重复越高,分值越高 (示例算法)
const cognitionScore = Math.min(100, (keywordMap.size * 2) + (allKeywords.length / 5))
// 2. 计算知识广度:根据书籍数量
const breadthScore = Math.min(100, batches.length * 10)
// 3. 计算产出效率:根据总字数
const totalWords = items.reduce((sum, i) => sum + (i.content?.length || 0), 0)
const outputScore = Math.min(100, totalWords / 500) // 每 5万字满分
// 4. 计算 Top 10 关键词
const sortedKeywords = [...keywordMap.entries()]
.sort((a, b) => b[1] - a[1])
.slice(0, 10)
.map(entry => entry[0])
return {
cognition: Math.round(cognitionScore),
breadth: Math.round(breadthScore),
output: Math.round(outputScore),
practicality: 75, // 可根据 occupation 比例动态计算
global: 60, // 可根据 language 比例动态计算
topKeywords: sortedKeywords
}
}
}
```
### 4. 第四阶段:前端可视化集成 (Statistics.vue)
使用 ECharts 渲染雷达图,将计算结果展现给用户。
```vue
<script setup lang="ts">
import { onMounted, ref } from 'vue'
import * as echarts from 'echarts'
import { trpc } from '@renderer/lib/trpc'
const chartRef = ref<HTMLElement | null>(null)
const personaData = ref<any>(null)
const initChart = (data: any) => {
const myChart = echarts.init(chartRef.value!)
const option = {
radar: {
indicator: [
{ name: '认知深度', max: 100 },
{ name: '知识广度', max: 100 },
{ name: '实践应用', max: 100 },
{ name: '产出效率', max: 100 },
{ name: '国际视野', max: 100 }
],
shape: 'circle',
splitNumber: 4,
axisLine: { lineStyle: { color: '#E5E7EB' } },
splitLine: { lineStyle: { color: '#E5E7EB' } },
splitArea: { areaStyle: { color: ['#fff', '#F8F9FB'] } }
},
series: [{
type: 'radar',
data: [{
value: [data.cognition, data.breadth, data.practicality, data.output, data.global],
areaStyle: { color: 'rgba(120, 22, 255, 0.2)' },
lineStyle: { color: '#7816ff', width: 3 },
itemStyle: { color: '#7816ff' }
}]
}]
}
myChart.setOption(option)
}
onMounted(async () => {
// 通过 tRPC 从主进程获取计算好的数据
const data = await trpc.stats.getPersona.query()
personaData.value = data
initChart(data)
})
</script>
<template>
<div class="p-8 bg-white rounded-[32px] border border-slate-100 shadow-sm">
<div class="mb-6">
<h2 class="text-xl font-black text-slate-800">阅读心流画像</h2>
<p class="text-xs text-slate-400">基于本地 {{ personaData?.totalBooks }} 本书籍分析得出</p>
</div>
<div ref="chartRef" class="w-full h-[400px]"></div>
<div class="mt-8 flex flex-wrap gap-2">
<span v-for="tag in personaData?.topKeywords" :key="tag"
class="px-3 py-1 bg-slate-50 text-slate-500 text-[10px] rounded-full border border-slate-100">
# {{ tag }}
</span>
</div>
</div>
</template>
```
## 🎨 设计心得:为什么这么做?
数据解耦原始任务数据Items/Batches与画像数据分开存储。即使画像计算逻辑升级比如你想改变评分算法也不需要修改历史心得数据。
缓存策略:不要在用户每次切换页面时都去扫全表。建议在任务完成时触发一次增量计算,或者每天用户第一次打开应用时刷新一次。
可视化反馈:雷达图的面积代表了用户的“知识疆域”。当用户看着面积一点点变大时,这种离线的成就感是留存的关键。

View File

@@ -1,10 +1,8 @@
{
"name": "read_books",
"version": "1.0.0",
"description": "An Electron application with Vue and TypeScript",
"description": "让每一篇阅读者享受阅读与思考的乐趣",
"main": "./out/main/index.js",
"author": "example.com",
"homepage": "https://electron-vite.org",
"scripts": {
"format": "prettier --write .",
"lint": "eslint --cache .",
@@ -18,7 +16,16 @@
"build:unpack": "npm run build && electron-builder --dir",
"build:win": "npm run build && electron-builder --win",
"build:mac": "npm run build && electron-builder --mac",
"build:linux": "npm run build && electron-builder --linux"
"build:linux": "npm run build && electron-builder --linux",
"clean": "rimraf dist out",
"format:check": "prettier --check .",
"prepare": "husky",
"commit": "git add . && (git status --porcelain | findstr . >nul 2>&1 && git-cz || echo ✅ 工作区无改动,无需提交!)"
},
"config": {
"commitizen": {
"path": "cz-git"
}
},
"dependencies": {
"@arco-design/web-vue": "^2.57.0",
@@ -30,33 +37,43 @@
"@trpc/client": "^10.45.2",
"@trpc/server": "^10.45.2",
"better-sqlite3": "^12.5.0",
"echarts": "^6.0.0",
"electron-log": "^5.4.3",
"electron-store": "^6.0.1",
"electron-trpc": "^0.7.1",
"electron-updater": "^6.3.9",
"flexsearch": "^0.8.212",
"html2canvas": "^1.4.1",
"langchain": "^1.2.4",
"mitt": "^3.0.1",
"p-limit": "^2.2.0",
"pinia": "^3.0.4",
"reflect-metadata": "^0.2.2",
"typeorm": "^0.3.28",
"vue-i18n": "12.0.0-alpha.3",
"vue-router": "^4.6.4",
"zod": "^4.3.5"
},
"devDependencies": {
"@commitlint/cli": "^20.3.1",
"@commitlint/config-conventional": "^20.3.1",
"@electron-toolkit/eslint-config-prettier": "3.0.0",
"@electron-toolkit/eslint-config-ts": "^3.1.0",
"@electron-toolkit/tsconfig": "^2.0.0",
"@types/node": "^22.19.1",
"@vitejs/plugin-vue": "^6.0.2",
"commitizen": "^4.3.1",
"cz-git": "^1.12.0",
"electron": "^39.2.6",
"electron-builder": "^26.0.12",
"electron-rebuild": "^3.2.9",
"electron-vite": "^5.0.0",
"eslint": "^9.39.1",
"eslint-plugin-vue": "^10.6.2",
"husky": "^9.1.7",
"less": "^4.5.1",
"prettier": "^3.7.4",
"rimraf": "^6.1.2",
"typescript": "^5.9.3",
"unocss": "^66.5.12",
"unplugin-auto-import": "^20.3.0",

1078
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

Binary file not shown.

View File

@@ -0,0 +1,13 @@
# {{data.title}}
> **导出日期**: {{date}}
> **作者**: {{author}}
## 📑 核心摘要
{{data.summary}}
## 💡 深度心得
{{data.content}}
---
*Generated by AI Reading Assistant*

View File

@@ -0,0 +1 @@
[{ "id": "tpl_001", "name": "精致黑白排版", "file": "default.md" }]

View File

@@ -2,29 +2,48 @@ import 'reflect-metadata'
import { DataSource } from 'typeorm'
import { app } from 'electron'
import path from 'path'
import { ReadingReflectionTaskBatch } from '@main/db/entities/ReadingReflectionTaskBatch'
import { ReadingReflectionTaskItem } from '@main/db/entities/ReadingReflectionTaskItem'
import { ReadingReflectionTaskBatch } from './entities/ReadingReflectionTaskBatch'
import { ReadingReflectionTaskItem } from './entities/ReadingReflectionTaskItem'
import { ReadingPersona } from './entities/ReadingPersona' // 必须导入
import { DatabaseConnectionError } from '@main/utils/errors/databaseError'
import { ErrorHandler } from '@main/utils/errorHandler'
import logger from '@shared/utils/logger'
const dbPath = app.isPackaged
? path.join(app.getPath('userData'), 'reflections.db')
: path.join(process.cwd(), 'db.sqlite')
console.log('--- 数据库存储绝对路径 ---')
console.log(dbPath)
console.log('-----------------------')
export const AppDataSource = new DataSource({
type: 'better-sqlite3',
type: 'better-sqlite3', // better-sqlite3 性能优于 sqlite3
database: dbPath,
synchronize: true, // 开发环境下自动同步表结构
logging: true,
entities: [ReadingReflectionTaskBatch, ReadingReflectionTaskItem],
migrations: [],
subscribers: []
synchronize: true,
logging: process.env.NODE_ENV === 'development', // 仅开发环境开启日志
entities: [
ReadingReflectionTaskBatch,
ReadingReflectionTaskItem,
ReadingPersona // 注册实体
]
})
// 初始化方法,在 Electron app.whenReady() 中调用
export const initDB = async () => {
if (!AppDataSource.isInitialized) {
try {
await AppDataSource.initialize()
console.log('TypeORM SQLite Data Source has been initialized!')
logger.info('DATABASE_INITIALIZED', {
message: 'Database initialized successfully',
path: dbPath
})
} catch (err: any) {
const error = new DatabaseConnectionError('数据库初始化失败', {
originalError: err.message,
stack: err.stack,
path: dbPath
}, {
cause: err
})
ErrorHandler.handleError(error)
throw error
}
}
return AppDataSource
}

View File

@@ -0,0 +1,32 @@
import { Entity, Column, PrimaryColumn, UpdateDateColumn } from 'typeorm'
@Entity('reading_personas')
export class ReadingPersona {
@PrimaryColumn({ type: 'varchar', default: 'current_user_persona' })
id: string
@Column({ type: 'int', default: 0 })
cognition: number
@Column({ type: 'int', default: 0 })
breadth: number
@Column({ type: 'int', default: 0 })
practicality: number
@Column({ type: 'int', default: 0 })
output: number
@Column({ type: 'int', default: 0 })
global: number
@Column({ type: 'text', nullable: true })
topKeywords: string
// SQLite 推荐使用 simple-json 来处理对象映射
@Column({ type: 'simple-json', nullable: true })
rawStats: any
@UpdateDateColumn()
updatedAt: Date
}

View File

@@ -30,6 +30,18 @@ export class ReadingReflectionTaskBatch implements IReadingReflectionTaskBatch {
@CreateDateColumn({ type: 'datetime' })
createdAt!: Date
/**
* 是否暂停
*/
@Column({ type: 'boolean', default: false })
isPaused!: boolean
/**
* 暂停时间
*/
@CreateDateColumn({ type: 'datetime', nullable: true })
pausedAt!: Date | null
@OneToMany(() => ReadingReflectionTaskItem, (item) => item.batch, {
cascade: true, // 级联操作:删除 Batch 时自动删除所有 Item
onDelete: 'CASCADE'

View File

@@ -1,4 +1,4 @@
import { Column, Entity, ManyToOne, PrimaryColumn } from 'typeorm'
import { Column, CreateDateColumn, Entity, JoinColumn, ManyToOne, PrimaryColumn } from 'typeorm'
import { ReadingReflectionTaskBatch } from './ReadingReflectionTaskBatch'
import { IReadingReflectionTaskItem } from '@shared/types/IReadingReflectionTask'
@@ -8,7 +8,7 @@ export class ReadingReflectionTaskItem implements IReadingReflectionTaskItem {
id: string
@Column({ type: 'varchar' })
status: 'PENDING' | 'WRITING' | 'COMPLETED' | 'FAILED'
status: 'PENDING' | 'WRITING' | 'COMPLETED' | 'FAILED' | 'PAUSED'
@Column({ type: 'int', default: 0 })
progress: number
@@ -24,8 +24,16 @@ export class ReadingReflectionTaskItem implements IReadingReflectionTaskItem {
@Column({ type: 'simple-json', nullable: true })
keywords?: string[]
@CreateDateColumn() // 增加这一行TypeORM 会自动处理时间
createdAt: Date
@Column({ type: 'varchar' })
batchId: string
// 多对一关联
@ManyToOne(() => ReadingReflectionTaskBatch, (batch) => batch.items)
@JoinColumn({ name: 'batchId' })
batch!: ReadingReflectionTaskBatch
resultData: any
}

View File

@@ -1,4 +1,4 @@
import { app, BrowserWindow, ipcMain, shell } from 'electron'
import { app, BrowserWindow, shell } from 'electron'
import { join } from 'path'
import { electronApp, is, optimizer } from '@electron-toolkit/utils'
import icon from '../../resources/icon.png?asset'
@@ -7,12 +7,18 @@ import { appRouter } from '@rpc/router'
import { createIPCHandler } from 'electron-trpc/main'
import { initDB } from '@main/db/data-source'
// 声明全局变量类型
declare global {
var mainWindow: BrowserWindow | null
}
function createWindow(): void {
// Create the browser window.
// 创建窗口
const mainWindow = new BrowserWindow({
width: 1200,
height: 800,
show: false,
title: '读书心得助手',
resizable: false,
autoHideMenuBar: true,
...(process.platform === 'linux' ? { icon } : {}),
@@ -22,6 +28,9 @@ function createWindow(): void {
}
})
// 设置为全局变量,以便其他模块访问
global.mainWindow = mainWindow
// 核心绑定:使用 exposeElectronTRPC 适配当前窗口
createIPCHandler({
router: appRouter,
@@ -38,8 +47,6 @@ function createWindow(): void {
return { action: 'deny' }
})
// HMR for renderer base on electron-vite cli.
// Load the remote URL for development or the local html file for production.
if (is.dev && process.env['ELECTRON_RENDERER_URL']) {
mainWindow.loadURL(process.env['ELECTRON_RENDERER_URL'])
} else {
@@ -47,41 +54,22 @@ function createWindow(): void {
}
}
// This method will be called when Electron has finished
// initialization and is ready to create browser windows.
// Some APIs can only be used after this event occurs.
app.whenReady().then(async () => {
await initDB()
// Set app user model id for windows
electronApp.setAppUserModelId('com.electron')
electronApp.setAppUserModelId('com.reading_book')
// Default open or close DevTools by F12 in development
// and ignore CommandOrControl + R in production.
// see https://github.com/alex8088/electron-toolkit/tree/master/packages/utils
app.on('browser-window-created', (_, window) => {
optimizer.watchWindowShortcuts(window)
})
// IPC test
ipcMain.on('ping', () => console.log('pong'))
createWindow()
app.on('activate', function () {
// On macOS it's common to re-create a window in the app when the
// dock icon is clicked and there are no other windows open.
if (BrowserWindow.getAllWindows().length === 0) createWindow()
})
})
// Quit when all windows are closed, except on macOS. There, it's common
// for applications and their menu bar to stay active until the user quits
// explicitly with Cmd + Q.
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') {
app.quit()
}
})
// In this file you can include the rest of your app's specific main process
// code. You can also put them in separate files and require them here.

View File

@@ -1,45 +1,25 @@
import { EventEmitter } from 'events'
import pLimit from 'p-limit' // 建议使用 v2.2.0 以兼容 CJS
import { readingReflectionGraph } from '@main/services/ai/graph/readingReflectionGraph'
import { TaskStatusManager, readingReflectionTaskEvent } from './taskStatusManager'
import { TaskExecutor } from './taskExecutor'
import { AppDataSource } from '@main/db/data-source'
import { ReadingReflectionTaskBatch } from '@main/db/entities/ReadingReflectionTaskBatch'
import { ReadingReflectionTaskItem } from '@main/db/entities/ReadingReflectionTaskItem'
export const readingReflectionTaskEvent = new EventEmitter()
class TaskManager {
private limit = pLimit(2)
export class TaskManager {
private taskStatusManager: TaskStatusManager
private taskExecutor: TaskExecutor
private batchRepo = AppDataSource.getRepository(ReadingReflectionTaskBatch)
private itemRepo = AppDataSource.getRepository(ReadingReflectionTaskItem)
/**
* 更新主任务汇总进度
*/
private async updateBatchStatus(batchId: string) {
const items = await this.itemRepo.find({ where: { batch: { id: batchId } } })
if (items.length === 0) return
const avgProgress = Math.round(items.reduce((acc, i) => acc + i.progress, 0) / items.length)
let status = 'PROCESSING'
if (avgProgress === 100) status = 'COMPLETED'
if (items.every((i) => i.status === 'FAILED')) status = 'FAILED'
await this.batchRepo.update(batchId, { progress: avgProgress, status })
// 发送给左侧列表订阅者
readingReflectionTaskEvent.emit('batchProgressUpdate', {
batchId,
progress: avgProgress,
status
})
constructor() {
this.taskStatusManager = new TaskStatusManager()
this.taskExecutor = new TaskExecutor(this.taskStatusManager)
}
async startBatchTask(taskId: string, task: any) {
async startBatchTask(taskId: string, task: any): Promise<void> {
const total = task.quantity || 1
// 1. 初始化主任务
const batch = this.batchRepo.create({ id: taskId, bookName: task.bookName, totalCount: total })
await this.batchRepo.save(batch)
// 发送给左侧列表订阅者
readingReflectionTaskEvent.emit('batchProgressUpdate', {
batchId: taskId,
@@ -47,80 +27,10 @@ class TaskManager {
status: 'PROCESSING'
})
const promises = Array.from({ length: total }).map((_, index) => {
const subTaskId = total === 1 ? taskId : `${taskId}-${index}`
return this.limit(async () => {
try {
const item = this.itemRepo.create({ id: subTaskId, batch: batch, status: 'PENDING' })
await this.itemRepo.save(item)
const stream = await readingReflectionGraph.stream(
{ ...task },
{ configurable: { thread_id: subTaskId } }
)
let finalResult: any = {}
for await (const chunk of stream) {
// 处理生成正文节点
if (chunk.generateReadingReflectionContent) {
const contentData = chunk.generateReadingReflectionContent
await this.itemRepo.update(subTaskId, {
status: 'WRITING',
progress: 50,
content: contentData.content,
title: contentData.title
})
finalResult = { ...finalResult, ...contentData }
await this.updateBatchStatus(taskId)
this.emitProgress(taskId, index, total, 60, '正文已生成...')
}
// 处理生成摘要节点
if (chunk.generateReadingReflectionSummary) {
const summaryData = chunk.generateReadingReflectionSummary
finalResult = { ...finalResult, ...summaryData }
await this.itemRepo.update(subTaskId, {
status: 'COMPLETED',
progress: 100,
summary: summaryData.summary,
title: finalResult.title,
keywords: summaryData.keywords
})
}
}
await this.updateBatchStatus(taskId)
this.emitProgress(taskId, index, total, 100, '生成成功', finalResult)
} catch (error) {
await this.itemRepo.update(subTaskId, { status: 'FAILED', progress: 0 })
await this.updateBatchStatus(taskId)
this.emitProgress(taskId, index, total, 0, '生成失败')
}
})
})
await Promise.all(promises)
}
private emitProgress(
taskId: string,
index: number,
total: number,
progress: number,
status: string,
result?: any
) {
const displayId = total === 1 ? taskId : `${taskId}-${index}`
readingReflectionTaskEvent.emit('readingReflectionTaskProgress', {
taskId: displayId,
progress,
status: status, // 传枚举 Key
statusText: `[任务${index + 1}/${total}] ${status}`, // 传描述文字
result
})
// 2. 执行子任务
await this.taskExecutor.startBatchTask(taskId, task)
}
}
export { readingReflectionTaskEvent }
export const readingReflectionsTaskManager = new TaskManager()

View File

@@ -0,0 +1,187 @@
import { readingReflectionGraph } from '@main/services/ai/graph/readingReflectionGraph'
import { AppDataSource } from '@main/db/data-source'
import { ReadingReflectionTaskItem } from '@main/db/entities/ReadingReflectionTaskItem'
import { ReadingReflectionTaskBatch } from '@main/db/entities/ReadingReflectionTaskBatch'
import { TaskStatusManager } from './taskStatusManager'
import { concurrencyManager } from '@main/utils/concurrencyManager'
import { z } from 'zod'
// 定义子任务执行的输入验证模式
const ExecuteSubTaskSchema = z.object({
taskId: z.string(),
subTaskId: z.string(),
task: z.object({
bookName: z.string().min(1),
author: z.string().optional(),
description: z.string(),
occupation: z.enum(['student', 'teacher', 'professional', 'researcher', 'other']),
prompt: z.string(),
wordCount: z.number().default(1000),
quantity: z.number().min(1).max(5).default(1),
language: z.enum(['zh', 'en']).optional(),
tone: z.string().optional()
}),
index: z.number().min(0),
total: z.number().min(1)
})
export class TaskExecutor {
private itemRepo = AppDataSource.getRepository(ReadingReflectionTaskItem)
private batchRepo = AppDataSource.getRepository(ReadingReflectionTaskBatch)
private taskStatusManager: TaskStatusManager
constructor(taskStatusManager: TaskStatusManager) {
this.taskStatusManager = taskStatusManager
}
/**
* 检查批次是否被暂停
*/
private async checkPauseStatus(batchId: string): Promise<boolean> {
const batch = await this.batchRepo.findOneBy({ id: batchId })
return batch?.isPaused || false
}
/**
* 执行单个子任务
*/
private async executeSubTask(
taskId: string,
subTaskId: string,
task: any,
index: number,
total: number
): Promise<void> {
// 验证输入参数
ExecuteSubTaskSchema.parse({
taskId,
subTaskId,
task,
index,
total
})
try {
// 检查任务是否已被暂停
if (await this.checkPauseStatus(taskId)) {
return
}
const item = this.itemRepo.create({ id: subTaskId, batchId: taskId, batch: { id: taskId }, status: 'PENDING' })
await this.itemRepo.save(item)
const stream = await readingReflectionGraph.stream(
{ ...task },
{ configurable: { thread_id: subTaskId } }
)
let finalResult: any = {}
let chunkCount = 0
for await (const chunk of stream) {
// 每处理10个chunk检查一次暂停状态
if (chunkCount % 10 === 0 && await this.checkPauseStatus(taskId)) {
await this.itemRepo.update(subTaskId, {
status: 'PAUSED',
progress: 0
})
await this.taskStatusManager.updateBatchStatus(taskId)
this.taskStatusManager.emitProgress(taskId, index, total, 0, '已暂停')
return
}
chunkCount++
// 处理生成正文节点
if (chunk.generateReadingReflectionContent) {
const contentData = chunk.generateReadingReflectionContent
await this.itemRepo.update(subTaskId, {
status: 'WRITING',
progress: 50,
content: contentData.content,
title: contentData.title
})
finalResult = { ...finalResult, ...contentData }
await this.taskStatusManager.updateBatchStatus(taskId)
this.taskStatusManager.emitProgress(taskId, index, total, 60, '正文已生成...')
}
// 处理生成摘要节点
if (chunk.generateReadingReflectionSummary) {
const summaryData = chunk.generateReadingReflectionSummary
finalResult = { ...finalResult, ...summaryData }
await this.itemRepo.update(subTaskId, {
status: 'COMPLETED',
progress: 100,
summary: summaryData.summary,
title: finalResult.title,
keywords: summaryData.keywords
})
}
}
await this.taskStatusManager.updateBatchStatus(taskId)
this.taskStatusManager.emitProgress(taskId, index, total, 100, '生成成功', finalResult)
} catch (error) {
// 检查是否是暂停导致的中断
if (await this.checkPauseStatus(taskId)) {
await this.itemRepo.update(subTaskId, {
status: 'PAUSED',
progress: 0
})
await this.taskStatusManager.updateBatchStatus(taskId)
this.taskStatusManager.emitProgress(taskId, index, total, 0, '已暂停')
} else {
await this.itemRepo.update(subTaskId, { status: 'FAILED', progress: 0 })
await this.taskStatusManager.updateBatchStatus(taskId)
this.taskStatusManager.emitProgress(taskId, index, total, 0, '生成失败')
}
}
}
/**
* 启动批量任务
*/
async startBatchTask(taskId: string, task: any): Promise<void> {
const total = task.quantity || 1
// 检查任务是否已被暂停
if (await this.checkPauseStatus(taskId)) {
return
}
// 根据任务数量动态调整并发数
if (total > 5) {
concurrencyManager.increaseConcurrency()
}
const limit = concurrencyManager.getLimit()
// 获取已存在的子任务,检查它们的状态
const existingItems = await this.itemRepo.find({ where: { batchId: taskId } })
const existingItemsMap = new Map(existingItems.map(item => [item.id, item]))
const promises = Array.from({ length: total }).map((_, index) => {
const subTaskId = total === 1 ? taskId : `${taskId}-${index}`
// 检查子任务是否已完成,如果已完成则跳过
const existingItem = existingItemsMap.get(subTaskId)
if (existingItem && existingItem.status === 'COMPLETED') {
return Promise.resolve()
}
return limit(async () => {
// 检查任务是否已被暂停,再执行子任务
if (await this.checkPauseStatus(taskId)) {
return
}
await this.executeSubTask(taskId, subTaskId, task, index, total)
})
})
try {
await Promise.all(promises)
} finally {
// 任务完成后重置并发数
concurrencyManager.resetConcurrency()
}
}
}

View File

@@ -0,0 +1,88 @@
import { EventEmitter } from 'events'
import { AppDataSource } from '@main/db/data-source'
import { ReadingReflectionTaskBatch } from '@main/db/entities/ReadingReflectionTaskBatch'
import { ReadingReflectionTaskItem } from '@main/db/entities/ReadingReflectionTaskItem'
import { NotificationService } from '@main/services/notificationService'
export const readingReflectionTaskEvent = new EventEmitter()
export class TaskStatusManager {
private batchRepo = AppDataSource.getRepository(ReadingReflectionTaskBatch)
private itemRepo = AppDataSource.getRepository(ReadingReflectionTaskItem)
private notificationService: NotificationService
constructor() {
this.notificationService = new NotificationService()
}
/**
* 更新主任务汇总进度
*/
async updateBatchStatus(batchId: string): Promise<void> {
// 获取批次信息,包括暂停状态
const batch = await this.batchRepo.findOneBy({ id: batchId })
if (!batch) return
// 使用显式的batchId列进行查询提高性能
const [itemsStats, failedCount] = await Promise.all([
this.itemRepo
.createQueryBuilder('item')
.select('AVG(item.progress)', 'avgProgress')
.addSelect('COUNT(*)', 'totalCount')
.where('item.batchId = :batchId', { batchId })
.getRawOne(),
this.itemRepo
.createQueryBuilder('item')
.select('COUNT(*)', 'failedCount')
.where('item.batchId = :batchId AND item.status = :status', { batchId, status: 'FAILED' })
.getRawOne()
])
const totalCount = parseInt(itemsStats.totalCount, 10)
if (totalCount === 0) return
const avgProgress = Math.round(parseFloat(itemsStats.avgProgress || '0'))
let status = batch.status
if (!batch.isPaused) {
if (avgProgress === 100) status = 'COMPLETED'
if (parseInt(failedCount.failedCount, 10) === totalCount) status = 'FAILED'
if (avgProgress > 0 && avgProgress < 100) status = 'PROCESSING'
} else {
status = 'PAUSED'
}
await this.batchRepo.update(batchId, { progress: avgProgress, status })
// 发送给左侧列表订阅者
readingReflectionTaskEvent.emit('batchProgressUpdate', {
batchId,
progress: avgProgress,
status,
isPaused: batch.isPaused
})
}
/**
* 发送任务进度事件
*/
emitProgress(
taskId: string,
index: number,
total: number,
progress: number,
status: string,
result?: any
): void {
const displayId = total === 1 ? taskId : `${taskId}-${index}`
//发送 tRPC 实时事件(驱动前端 UI 进度条)
readingReflectionTaskEvent.emit('readingReflectionTaskProgress', {
taskId: displayId,
progress,
status: status, // 传枚举 Key
statusText: `[任务${index + 1}/${total}] ${status}`, // 传描述文字
result
})
// 添加任务状态通知判断
this.notificationService.handleNotification(status, progress, total, index, taskId)
}
}

View File

@@ -1,12 +1,9 @@
import { ChatOpenAI } from '@langchain/openai'
import Store from 'electron-store'
import { CONFIG_STORE_KEY } from '@rpc/constants/store_key'
const StoreClass = (Store as any).default || Store
const store = new StoreClass({ encryptionKey: CONFIG_STORE_KEY })
import { configService } from '@main/services/configService'
export const createChatModel = (type: 'reading' | 'summary', schema: any) => {
const config = store.get(`chatModels.${type}`) as any
const chatModelsConfig = configService.getChatModelsConfig()
const config = chatModelsConfig?.[type] as any
console.log('chatModels', config)
if (!config || !config.apiKey) {

View File

@@ -4,7 +4,7 @@ import { REFLECTION_CONTENT_PROMPT } from '@main/services/ai/prompts/readingRefl
import { z } from 'zod'
import { AppDataSource } from '@main/db/data-source'
import { ReadingReflectionTaskItem } from '@main/db/entities/ReadingReflectionTaskItem'
import { createChatModel } from '@main/services/ai/llmService'
import { createChatModel } from '@main/services/ai/core/llmService'
export const generateReadingReflectionContentNode = async (
state: typeof ReadingReflectionState.State,

View File

@@ -2,7 +2,7 @@ import { PromptTemplate } from '@langchain/core/prompts'
import { ReadingReflectionState } from '../state/readingReflectionState'
import { REFLECTION_SUMMARY_PROMPT } from '@main/services/ai/prompts/readingReflactionPrompts'
import { z } from 'zod'
import { createChatModel } from '@main/services/ai/llmService'
import { createChatModel } from '@main/services/ai/core/llmService'
/**
* 步骤 3生成摘要和关键词

View File

@@ -1,5 +1,5 @@
import { Annotation } from '@langchain/langgraph'
import { Occupation } from '@shared/types/reflections'
import { Occupation } from '@shared/types/IReadingReflectionTask'
export const ReadingReflectionState = Annotation.Root({
// 输入任务

View File

@@ -0,0 +1,173 @@
import Store from 'electron-store'
import { app } from 'electron'
import path from 'path'
import crypto from 'crypto'
// 定义配置键名
const NOTIFICATION_KEY = 'notification'
const CHAT_MODELS_KEY = 'chatModels'
const LANGUAGE_KEY = 'language'
/**
* 配置服务,负责统一管理应用配置
*/
export class ConfigService {
private store: Store
private encryptionKey: string
constructor() {
// 生成或获取加密密钥
this.encryptionKey = this.getOrGenerateEncryptionKey()
// 兼容性处理获取 Store 构造函数
const StoreClass = (Store as any).default || Store
// 创建 Store 实例
this.store = new StoreClass({
encryptionKey: this.encryptionKey,
// 使用更安全的加密算法
schema: {
[NOTIFICATION_KEY]: {
type: 'object',
properties: {
masterSwitch: { type: 'boolean' },
taskCompleted: { type: 'boolean' },
taskFailed: { type: 'boolean' },
silentMode: { type: 'boolean' }
}
},
[CHAT_MODELS_KEY]: {
type: 'object',
properties: {
reading: { type: 'object' },
summary: { type: 'object' }
}
},
[LANGUAGE_KEY]: {
type: 'object',
properties: {
language: { type: 'string', enum: ['zh', 'en', 'ja', 'ko', 'es'] }
}
}
},
// 提高加密强度
encryptionAlgorithm: 'aes-256-cbc' as any
})
}
/**
* 生成或获取加密密钥
*/
private getOrGenerateEncryptionKey(): string {
// 从安全位置获取或生成密钥
// 这里实现一个简单的密钥生成和存储机制
const keyPath = path.join(app.getPath('userData'), 'encryption.key')
try {
// 尝试从文件读取密钥
const fs = require('fs')
if (fs.existsSync(keyPath)) {
return fs.readFileSync(keyPath, 'utf8')
}
// 如果密钥不存在,生成一个新的随机密钥
const newKey = crypto.randomBytes(32).toString('hex')
fs.writeFileSync(keyPath, newKey, { mode: 0o600 }) // 设置文件权限,只有所有者可以读写
return newKey
} catch (error) {
// 如果出现错误,使用一个基于应用信息的密钥作为后备
console.error('Failed to generate or read encryption key:', error)
return this.generateFallbackKey()
}
}
/**
* 生成后备密钥
*/
private generateFallbackKey(): string {
// 基于应用信息生成一个后备密钥
const appInfo = `${app.name}-${app.getVersion()}-${app.getPath('userData')}`
return crypto.createHash('sha256').update(appInfo).digest('hex')
}
/**
* 获取通知配置
*/
getNotificationConfig(): any {
return this.store.get(NOTIFICATION_KEY) || {
masterSwitch: true,
taskCompleted: true,
taskFailed: true
}
}
/**
* 保存通知配置
*/
saveNotificationConfig(config: any): void {
this.store.set(NOTIFICATION_KEY, config)
}
/**
* 获取聊天模型配置
*/
getChatModelsConfig(): any {
const data = this.store.get(CHAT_MODELS_KEY) as { reading?: any; summary?: any } | null
// 检查是否包含必要的嵌套 Key如果没有说明是旧版本数据
if (data && typeof data === 'object' && !data.reading && !data.summary) {
console.log('检测到旧版本配置,正在重置...')
this.store.delete(CHAT_MODELS_KEY) // 删除旧的根键
return null
}
return data || null
}
/**
* 保存聊天模型配置
*/
saveChatModelConfig(type: string, config: any): void {
this.store.set(`${CHAT_MODELS_KEY}.${type}`, config)
}
/**
* 获取指定键的配置
*/
get(key: string): any {
return this.store.get(key)
}
/**
* 设置配置
*/
set(key: string, value: any): void {
this.store.set(key, value)
}
/**
* 删除配置
*/
delete(key: string): void {
this.store.delete(key)
}
/**
* 获取语言配置
*/
getLanguageConfig(): any {
return this.store.get(LANGUAGE_KEY) || {
language: 'zh'
}
}
/**
* 保存语言配置
*/
saveLanguageConfig(config: any): void {
this.store.set(LANGUAGE_KEY, config)
}
}
// 导出单例实例
export const configService = new ConfigService()

View File

@@ -0,0 +1,56 @@
import { Notification } from 'electron'
import { configService } from '@main/services/configService'
import { notificationEventEmitter } from '@rpc/router/notice.router'
export class NotificationService {
/**
* 处理通知逻辑
*/
handleNotification(status: string, progress: number, total: number, index: number, batchId: string): void {
// 从配置服务获取用户偏好
const config = configService.getNotificationConfig()
// 如果总开关关闭,直接拦截
if (!config.masterSwitch) return
// 场景 A: 任务全部完成 (100%)
if (progress === 100 && config.taskCompleted) {
// 只有当所有子任务都完成,或者当前是单任务时才弹出
// 如果是批量任务,你可以选择在最后一个子任务完成时通知
if (index + 1 === total) {
const notification = new Notification({
title: '🎉 读书心得已生成',
body: total > 1 ? `${total} 篇心得已全部处理完成。` : '您的书籍心得已准备就绪。',
silent: config.silentMode
})
notification.show()
// 监听点击事件
notification.on('click', () => {
notificationEventEmitter.emit('notification-click', {
batchId
})
})
}
}
// 场景 B: 任务失败 (假设你传入的 status 是 'FAILED')
if (status === 'FAILED' && config.taskFailed) {
const notification = new Notification({
title: '❌ 任务生成失败',
body: `${index + 1} 项任务执行异常,请检查网络或 API 余额。`,
silent: config.silentMode
})
notification.show()
// 监听点击事件
notification.on('click', () => {
notificationEventEmitter.emit('notification-click', {
batchId
})
})
}
}
}

View File

@@ -0,0 +1,138 @@
import { Repository } from 'typeorm'
import { ReadingPersona } from '@main/db/entities/ReadingPersona'
import {
IReadingReflectionTaskBatch,
IReadingReflectionTaskItem
} from '@shared/types/IReadingReflectionTask'
import { IUserReadingPersona } from '@shared/types/IUserReadingPersona'
export class PersonaService {
constructor(private personaRepo: Repository<ReadingPersona>) {}
/**
* 刷新画像并保存到数据库
*/
/**
* 刷新画像并保存到数据库
*/
async refreshPersona(
items: IReadingReflectionTaskItem[],
batches: IReadingReflectionTaskBatch[]
) {
// 1. 获取计算结果
const rawResult = await this.calculatePersona(items, batches)
// 2. 创建或更新实体
const persona = new ReadingPersona()
persona.id = 'current_user_persona'
// 核心分值映射
persona.cognition = rawResult.cognition
persona.breadth = rawResult.breadth
persona.practicality = rawResult.practicality
persona.output = rawResult.output
persona.global = rawResult.global
// ✨ 修复关键点:从 rawResult.stats 中获取 topKeywords
// 因为 calculatePersona 返回的是 { stats: { topKeywords: [...] } }
persona.topKeywords = JSON.stringify(rawResult.stats.topKeywords)
// 3. 存储完整的 rawStats 结构,确保与前端接口定义对齐
persona.rawStats = {
totalWords: rawResult.stats.totalWords,
totalBooks: rawResult.stats.totalBooks,
totalHours: rawResult.stats.totalHours, // 别忘了我们在 calculatePersona 补充的专注时长
topKeywords: rawResult.stats.topKeywords
}
return await this.personaRepo.save(persona)
}
/**
* 从数据库聚合数据并计算画像分值
*/
async calculatePersona(
items: IReadingReflectionTaskItem[],
batches: IReadingReflectionTaskBatch[]
) {
const totalBooks = batches.length
const totalWords = items.reduce((sum, i) => sum + (i.content?.length || 0), 0)
// --- 1. 认知深度 (Cognition) ---
const allKeywords = items.flatMap((i) => i.keywords || [])
const keywordMap = new Map<string, number>()
allKeywords.forEach((k) => keywordMap.set(k, (keywordMap.get(k) || 0) + 1))
const cognitionScore = Math.min(100, keywordMap.size * 1.5 + allKeywords.length / 8)
// --- 2. 知识广度 (Breadth) - 修复 TS2339 ---
// 逻辑:如果 batch 没分类,就看有多少个独立的高频关键词,这代表了涉及的主题广度
const uniqueThemes = new Set(batches.map((b) => (b as any).category).filter(Boolean))
let breadthScore = 0
if (uniqueThemes.size > 0) {
// 如果有分类数据,按分类算
breadthScore = Math.min(100, uniqueThemes.size * 20 + totalBooks * 2)
} else {
// 如果没分类数据,按关键词覆盖面算(每 5 个独立关键词视为一个知识领域)
breadthScore = Math.min(100, (keywordMap.size / 5) * 15 + totalBooks * 2)
}
// --- 3. 语言能力与全球化 (Global) ---
const langDist = items.reduce(
(acc, curr) => {
// 兼容处理:如果 curr.language 不存在则默认为 'zh'
const lang = (curr as any).language || 'zh'
acc[lang] = (acc[lang] || 0) + 1
return acc
},
{} as Record<string, number>
)
// 计算英文占比分:有英文记录就从 60 分起跳,最高 100
const globalScore = langDist['en'] ? Math.min(100, 60 + langDist['en'] * 5) : 50
// --- 4. 专注时长 (Total Hours) ---
const totalHours = Math.round((totalWords / 1000) * 1.5 + totalBooks)
const sortedKeywords = [...keywordMap.entries()]
.sort((a, b) => b[1] - a[1])
.slice(0, 10)
.map((entry) => entry[0])
return {
cognition: Math.round(cognitionScore),
breadth: Math.round(breadthScore),
output: Math.min(100, Math.round(totalWords / 500)),
practicality: 75,
global: Math.round(globalScore),
stats: {
totalWords,
totalBooks,
totalHours,
topKeywords: sortedKeywords
}
}
}
}
/**
* 实体转化为用户阅读画像
* @param entity 实体
* */
export function entityToUserReadingPersona(entity: ReadingPersona): IUserReadingPersona {
return {
domainDepth: JSON.parse(entity.topKeywords || '[]').map((name: string) => ({
name,
score: entity.cognition, // 简易算法:共用认知深度分
bookCount: 1 // 可根据数据库详细统计进一步细化
})),
breadthScore: entity.breadth,
efficiencyScore: entity.output,
maturityScore: entity.practicality,
languageScore: entity.global,
stats: entity.rawStats || {
totalWords: 0,
totalBooks: 0,
topKeywords: [],
mostUsedOccupation: 'other'
}
}
}

View File

@@ -0,0 +1,104 @@
import { Document } from 'flexsearch'
import { ReadingReflectionTaskItem } from '@main/db/entities/ReadingReflectionTaskItem'
import { ISearch, HighlightInfo } from '@shared/types/ISearch'
export class SearchService {
private index: any
constructor() {
// 初始化 FlexSearch 文档索引
this.index = new Document({
document: {
id: 'id',
index: ['title', 'content', 'bookName'],
store: true // 存储原始数据以便快速返回
},
tokenize: 'forward', // 适配中文的简易分词
context: true,
cache: true
})
}
/**
* 初始化索引:从数据库加载历史数据
*/
async initIndex(items: ReadingReflectionTaskItem[]) {
items.forEach((item) => {
this.index.add({
id: item.id,
title: item.title,
content: item.content,
bookName: (item as any).bookName || '未知书籍'
})
})
}
/**
* 全局搜索核心方法
*/
async search(query: string): Promise<ISearch[]> {
if (!query) return []
// 执行搜索
const results = await this.index.search(query, {
limit: 20,
enrich: true, // 返回 store 的数据
suggest: true
})
const searchResults: ISearch[] = []
// 扁平化 FlexSearch 的结果并计算高亮坐标
results.forEach((category: any) => {
category.result.forEach((item: any) => {
const doc = item.doc
// 避免重复项(因为一个文档可能在多个字段命中)
if (searchResults.find((r) => r.id === doc.id)) return
searchResults.push({
id: doc.id,
title: doc.title,
content: doc.content,
bookName: doc.bookName,
createdAt: new Date().toISOString(), // 实际应从数据库字段获取
// 计算高亮坐标
titleHighlights: this.calculateHighlights(doc.title, query),
contentHighlights: this.calculateHighlights(doc.content, query),
bookHighlights: this.calculateHighlights(doc.bookName, query),
contentSnippet: this.createSnippet(doc.content, query)
})
})
})
return searchResults
}
/**
* 简单的坐标计算逻辑
*/
private calculateHighlights(text: string, query: string): HighlightInfo[] {
const highlights: HighlightInfo[] = []
if (!text || !query) return highlights
let index = text.indexOf(query)
while (index !== -1) {
highlights.push({ start: index, length: query.length })
index = text.indexOf(query, index + query.length)
}
return highlights
}
/**
* 生成正文摘要预览
*/
private createSnippet(content: string, query: string): string {
const index = content.indexOf(query)
if (index === -1) return content.substring(0, 100)
const start = Math.max(0, index - 40)
const end = Math.min(content.length, index + 60)
return (
(start > 0 ? '...' : '') + content.substring(start, end) + (end < content.length ? '...' : '')
)
}
}

View File

@@ -0,0 +1,61 @@
import pLimit from 'p-limit' // 建议使用 v2.2.0 以兼容 CJS
/**
* 并发管理器,负责动态调整并发数
*/
export class ConcurrencyManager {
private baseConcurrency: number
private currentConcurrency: number
private limit: ReturnType<typeof pLimit>
constructor(baseConcurrency: number = 2) {
this.baseConcurrency = baseConcurrency
this.currentConcurrency = baseConcurrency
this.limit = pLimit(this.currentConcurrency)
}
/**
* 获取当前的并发限制器
*/
getLimit() {
return this.limit
}
/**
* 增加并发数
*/
increaseConcurrency(): void {
this.currentConcurrency++
this.limit = pLimit(this.currentConcurrency)
}
/**
* 减少并发数
*/
decreaseConcurrency(): void {
if (this.currentConcurrency > 1) {
this.currentConcurrency--
this.limit = pLimit(this.currentConcurrency)
}
}
/**
* 重置并发数为基准值
*/
resetConcurrency(): void {
this.currentConcurrency = this.baseConcurrency
this.limit = pLimit(this.currentConcurrency)
}
/**
* 根据系统资源动态调整并发数
*/
adjustConcurrency(): void {
// 这里可以添加根据系统资源如CPU、内存使用率动态调整并发数的逻辑
// 目前实现一个简单的基于任务类型的调整策略
this.resetConcurrency()
}
}
// 导出一个单例实例
export const concurrencyManager = new ConcurrencyManager()

View File

@@ -0,0 +1,76 @@
import { BaseError } from './errors/baseError'
import logger from '@shared/utils/logger'
/**
* 错误处理工具类
*/
export class ErrorHandler {
/**
* 处理错误
*/
static handleError(error: any): void {
if (error instanceof BaseError) {
// 处理自定义错误
this.handleCustomError(error)
} else {
// 处理原生错误
this.handleNativeError(error)
}
}
/**
* 处理自定义错误
*/
private static handleCustomError(error: BaseError): void {
logger.error(error.code, {
message: error.message,
details: error.details,
stack: error.stack
})
}
/**
* 处理原生错误
*/
private static handleNativeError(error: Error): void {
logger.error('UNHANDLED_ERROR', {
message: error.message,
stack: error.stack
})
}
/**
* 重试函数
*/
static async retry<T>(
fn: () => Promise<T>,
maxAttempts: number = 3,
delay: number = 1000,
retryableErrors: Array<new (...args: any[]) => Error> = []
): Promise<T> {
let attempts = 0
while (attempts < maxAttempts) {
try {
return await fn()
} catch (error: any) {
attempts++
// 检查是否可以重试
const isRetryable = retryableErrors.some(
(ErrorClass) => error instanceof ErrorClass
)
if (attempts >= maxAttempts || !isRetryable) {
throw error
}
// 等待一段时间后重试
await new Promise((resolve) => setTimeout(resolve, delay * Math.pow(2, attempts - 1)))
}
}
// 理论上不会执行到这里,因为上面的循环会抛出错误
throw new Error('重试次数已达上限')
}
}

View File

@@ -0,0 +1,46 @@
import { BaseError } from './baseError'
/**
* AI服务相关错误
*/
export class AIError extends BaseError {
constructor(message: string, details?: any, options?: ErrorOptions) {
super(message, 'AI_ERROR', details, options)
}
}
/**
* AI模型调用错误
*/
export class AIModelCallError extends AIError {
constructor(message: string = 'AI模型调用失败', details?: any, options?: ErrorOptions) {
super(message, details, options)
}
}
/**
* AI生成内容错误
*/
export class AIGenerationError extends AIError {
constructor(message: string = 'AI生成内容失败', details?: any, options?: ErrorOptions) {
super(message, details, options)
}
}
/**
* AI图执行错误
*/
export class AIGraphError extends AIError {
constructor(message: string = 'AI图执行失败', details?: any, options?: ErrorOptions) {
super(message, details, options)
}
}
/**
* AI提示词错误
*/
export class AIPromptError extends AIError {
constructor(message: string = 'AI提示词错误', details?: any, options?: ErrorOptions) {
super(message, details, options)
}
}

View File

@@ -0,0 +1,25 @@
/**
* 基础错误类,所有自定义错误的基类
*/
export class BaseError extends Error {
public readonly name: string
public readonly code: string
public readonly details?: any
public readonly timestamp: Date
constructor(
message: string,
code: string,
details?: any,
options?: ErrorOptions
) {
super(message, options)
this.name = this.constructor.name
this.code = code
this.details = details
this.timestamp = new Date()
// 设置原型链,确保 instanceof 正常工作
Object.setPrototypeOf(this, new.target.prototype)
}
}

View File

@@ -0,0 +1,46 @@
import { BaseError } from './baseError'
/**
* 数据库操作相关错误
*/
export class DatabaseError extends BaseError {
constructor(message: string, details?: any, options?: ErrorOptions) {
super(message, 'DATABASE_ERROR', details, options)
}
}
/**
* 数据库连接错误
*/
export class DatabaseConnectionError extends DatabaseError {
constructor(message: string = '数据库连接失败', details?: any, options?: ErrorOptions) {
super(message, details, options)
}
}
/**
* 数据库查询错误
*/
export class DatabaseQueryError extends DatabaseError {
constructor(message: string = '数据库查询失败', details?: any, options?: ErrorOptions) {
super(message, details, options)
}
}
/**
* 数据库更新错误
*/
export class DatabaseUpdateError extends DatabaseError {
constructor(message: string = '数据库更新失败', details?: any, options?: ErrorOptions) {
super(message, details, options)
}
}
/**
* 数据库插入错误
*/
export class DatabaseInsertError extends DatabaseError {
constructor(message: string = '数据库插入失败', details?: any, options?: ErrorOptions) {
super(message, details, options)
}
}

View File

@@ -12,6 +12,7 @@ export {}
declare module 'vue' {
export interface GlobalComponents {
AButton: typeof import('@arco-design/web-vue')['Button']
ACheckbox: typeof import('@arco-design/web-vue')['Checkbox']
ACollapse: typeof import('@arco-design/web-vue')['Collapse']
ACollapseItem: typeof import('@arco-design/web-vue')['CollapseItem']
ActiveMenu: typeof import('./src/components/ActiveMenu.vue')['default']
@@ -20,13 +21,19 @@ declare module 'vue' {
AInput: typeof import('@arco-design/web-vue')['Input']
AInputNumber: typeof import('@arco-design/web-vue')['InputNumber']
AInputPassword: typeof import('@arco-design/web-vue')['InputPassword']
AModal: typeof import('@arco-design/web-vue')['Modal']
AOption: typeof import('@arco-design/web-vue')['Option']
ARadio: typeof import('@arco-design/web-vue')['Radio']
ASelect: typeof import('@arco-design/web-vue')['Select']
ASlider: typeof import('@arco-design/web-vue')['Slider']
ASwitch: typeof import('@arco-design/web-vue')['Switch']
ATabPane: typeof import('@arco-design/web-vue')['TabPane']
ATabs: typeof import('@arco-design/web-vue')['Tabs']
ATag: typeof import('@arco-design/web-vue')['Tag']
ATextarea: typeof import('@arco-design/web-vue')['Textarea']
ATooltip: typeof import('@arco-design/web-vue')['Tooltip']
AWatermark: typeof import('@arco-design/web-vue')['Watermark']
BackPage: typeof import('./src/components/BackPage.vue')['default']
RouterLink: typeof import('vue-router')['RouterLink']
RouterView: typeof import('vue-router')['RouterView']
}

View File

@@ -2,7 +2,7 @@
<html>
<head>
<meta charset="UTF-8" />
<title>Electron</title>
<title>读书心得助手 | 让每一篇阅读者享受阅读与思考的乐趣</title>
<!-- https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP -->
<meta
http-equiv="Content-Security-Policy"

View File

@@ -1,14 +1,29 @@
<script setup lang="ts">
import { useI18n } from 'vue-i18n'
import ActiveMenu from '@renderer/components/ActiveMenu.vue'
import { Plus } from '@icon-park/vue-next'
import nativeHook from '@renderer/hooks/useRouterHook'
import useRouterHook from '@renderer/hooks/useRouterHook'
import TaskList from '@renderer/pages/task/components/TaskList.vue'
import { trpc } from '@renderer/lib/trpc'
const { go } = nativeHook()
const { go } = useRouterHook()
const { t } = useI18n()
const goTaskCreatePage = () => {
go('/task/create')
}
// 订阅通知点击事件
trpc.notice.onNotificationClick.subscribe(undefined, {
onData(data) {
console.log('点击了任务通知:', data)
if (data.batchId) {
console.log('点击了任务通知:', data.batchId)
// 跳转到对应的任务页面
go('/task', { id: data.batchId })
}
}
})
</script>
<template>
@@ -22,7 +37,7 @@ const goTaskCreatePage = () => {
<span class="text-white text-lg font-bold">Z</span>
</div>
<!--产品名称-->
<span class="font-black text-base tracking-tight">读书心得助手</span>
<span class="font-black text-base tracking-tight">{{ t('common.title') }}</span>
</div>
<div class="flex justify-end p-r-20px">
<a-button size="mini" @click="goTaskCreatePage">

Binary file not shown.

After

Width:  |  Height:  |  Size: 161 KiB

View File

@@ -1,6 +1,27 @@
export const TASK_STATUS: Record<string, { color: string; text: string; desc: string }> = {
PENDING: { color: '#7816ff', text: '任务排队中', desc: '任务排队中,等待系统调度算力...' },
WRITING: { color: '#ff5722', text: '运行中', desc: '深度学习模型正在分析文本,请稍后...' },
COMPLETED: { color: '#00b42a', text: '任务完成', desc: '任务已完成...' },
FAILED: { color: '#86909c', text: '任务失败', desc: '任务失败,正在重试...' }
export const TASK_STATUS = {
PENDING: {
color: '#7816ff',
text: 'task.list.status.pending',
desc: 'task.list.status.pendingDesc'
},
WRITING: {
color: '#ff5722',
text: 'task.list.status.processing',
desc: 'task.list.status.processingDesc'
},
COMPLETED: {
color: '#00b42a',
text: 'task.list.status.completed',
desc: 'task.list.status.completedDesc'
},
FAILED: {
color: '#86909c',
text: 'task.list.status.failed',
desc: 'task.list.status.failedDesc'
},
PAUSED: {
color: '#ff9800',
text: 'task.list.status.paused',
desc: 'task.list.status.pausedDesc'
}
}

View File

@@ -1,48 +1,58 @@
<script setup lang="ts">
import { ref } from 'vue'
// 保持图标库一致,使用 IconPark 或 Arco Icons
import { Help, Info, SettingTwo } from '@icon-park/vue-next'
import { SettingTwo, ApplicationTwo, Help } from '@icon-park/vue-next'
import useRouterHook from '@renderer/hooks/useRouterHook'
const { go } = useRouterHook()
const activeBtn = ref('')
// 定义按钮配置,方便维护
// 1. 核心常驻按钮
const navButtons = [
{ key: 'setting', title: '设置', icon: SettingTwo },
{ key: 'faq', title: '帮助', icon: Help },
{ key: 'about', title: '关于', icon: Info }
{ key: 'faq', title: '帮助', icon: Help }
]
const active = (key: string) => {
console.log(key)
activeBtn.value = key
go('/' + key)
}
</script>
<template>
<div class="p-4 w-full flex flex-col items-center gap-4">
<div class="flex items-center bg-white border border-slate-100 p-1.5 rounded-xl shadow-sm">
<div class="p-4 w-full flex flex-col items-center gap-4 relative">
<div
class="flex items-center bg-white/80 backdrop-blur-md border border-slate-100 p-1.5 rounded-2xl shadow-sm relative z-[60]"
>
<div
v-for="btn in navButtons"
:key="btn.key"
class="nav-btn group"
:class="{ active: activeBtn === btn.key }"
:title="btn.title"
@click="active(btn.key)"
>
<component
:is="btn.icon"
theme="outline"
size="16"
class="transition-colors"
:fill="activeBtn === btn.key ? '#7816ff' : '#64748b'"
/>
<span class="nav-text" :class="{ 'active-text': activeBtn === btn.key }">{{
btn.title
}}</span>
</div>
<span class="text-[10px] ml-1.5 hidden group-hover:inline-block text-slate-500 font-medium">
{{ btn.title }}
</span>
<div class="w-[1px] h-4 bg-slate-200 mx-1"></div>
<div
class="nav-btn group"
:class="{ active: activeBtn === 'menus' }"
@click="active('menus')"
>
<application-two
theme="outline"
size="16"
:fill="activeBtn === 'menus' ? '#7816ff' : '#64748b'"
/>
<span class="nav-text" :class="{ 'active-text': activeBtn === 'menus' }">更多</span>
</div>
</div>
</div>
@@ -54,35 +64,49 @@ const active = (key: string) => {
align-items: center;
justify-content: center;
cursor: pointer;
padding: 6px 10px;
border-radius: 8px;
padding: 8px 12px;
border-radius: 12px;
transition: all 0.25s cubic-bezier(0.4, 0, 0.2, 1);
position: relative;
&:hover {
background-color: #f5f3ff; // 极淡的紫色背景
.arco-icon,
.i-icon {
color: #7816ff;
background-color: #f5f3ff;
}
}
&.active {
background-color: #f5f3ff;
&::after {
content: '';
position: absolute;
bottom: -4px;
width: 4px;
height: 4px;
background-color: #7816ff;
border-radius: 50%;
}
}
}
// 按钮之间的分割线(可选)
.nav-btn:not(:last-child) {
margin-right: 4px;
.nav-text {
font-size: 10px;
max-width: 0;
opacity: 0;
overflow: hidden;
white-space: nowrap;
transition: all 0.3s ease;
.group:hover & {
max-width: 40px;
margin-left: 6px;
opacity: 1;
}
}
.active-text {
max-width: 40px;
margin-left: 6px;
opacity: 1;
font-weight: bold;
color: #7816ff;
}
/* 动画 */
.slide-up-enter-active,
.slide-up-leave-active {
transition: all 0.3s cubic-bezier(0.34, 1.56, 0.64, 1);
}
.slide-up-enter-from,
.slide-up-leave-to {
opacity: 0;
transform: translateY(10px) scale(0.95);
}
</style>

View File

@@ -0,0 +1,29 @@
<script setup lang="ts">
import { Left } from '@icon-park/vue-next'
import useRouterHook from '@renderer/hooks/useRouterHook'
import { toRefs } from 'vue'
const { goBack } = useRouterHook()
const props = defineProps<{
title: string
}>()
const { title } = toRefs(props)
</script>
<template>
<div
class="flex items-center gap-3 cursor-pointer group border-b border-slate-50"
@click="goBack(1)"
>
<div
class="w-8 h-8 rounded-full bg-slate-50 flex items-center justify-center group-hover:bg-[#7816ff]/10 transition-all"
>
<left size="16" class="text-slate-400 group-hover:text-[#7816ff]" />
</div>
<span class="font-bold text-slate-800 text-sm">{{ title }}</span>
</div>
</template>
<style scoped></style>

View File

@@ -0,0 +1,19 @@
import { ref } from 'vue';
/**
* 加载状态Hook
*/
export default function useLoading(initValue = false) {
const loading = ref(initValue);
const setLoading = (value: boolean) => {
loading.value = value;
};
const toggle = () => {
loading.value = !loading.value;
};
return {
loading,
setLoading,
toggle,
};
}

View File

@@ -0,0 +1,282 @@
{
"common": {
"title": "读书心得助手",
"loading": "Loading...",
"success": "Operation successful",
"error": "Operation failed",
"confirm": "Confirm",
"cancel": "Cancel",
"delete": "Delete",
"save": "Save",
"edit": "Edit",
"preview": "Preview",
"back": "Back",
"next": "Next",
"previous": "Previous"
},
"task": {
"list": {
"title": "Task List",
"create": "Create Task",
"total": "reflections",
"status": {
"pending": "Pending",
"pendingDesc": "Task queued, waiting for system resource scheduling...",
"processing": "Processing",
"processingDesc": "Deep learning model is analyzing text, please wait...",
"completed": "Completed",
"completedDesc": "Task has been completed...",
"failed": "Failed",
"failedDesc": "Task failed, retrying...",
"paused": "Paused",
"pausedDesc": "Task has been paused, click resume to continue..."
}
},
"index": {
"totalProgress": "Total Progress",
"pauseQueue": "Pause Queue",
"resumeQueue": "Resume Queue",
"packageResults": "Package Results",
"deleteTask": "Delete Task",
"confirmDelete": "Confirm Delete Task",
"deleteContent": "This operation will permanently delete all reflection records and generation progress for this book.",
"deleting": "Cleaning from disk...",
"deleteSuccess": "Task has been successfully deleted",
"deleteFailed": "Delete failed, please check database connection",
"previewResults": "Preview Results",
"pauseSuccess": "Task has been paused",
"pauseFailed": "Pause failed",
"resumeSuccess": "Task has been resumed",
"resumeFailed": "Resume failed"
},
"create": {
"title": "Create Reading Reflection",
"bookName": "Book Name",
"bookAuthor": "Book Author",
"bookDescription": "Book Description",
"occupation": "Target Audience Occupation",
"occupationList": {
"student": "Student",
"professional": "Professional",
"scholar": "Scholar/Researcher",
"freelancer": "Freelancer",
"teacher": "Teacher"
},
"prompt": "Custom Prompt",
"wordCount": "Word Count per Reflection",
"wordUnit": "words",
"quantity": "Number of Reflections",
"language": "Generation Language",
"tone": "Generation Tone",
"submit": "Start Task",
"success": "Task has been added to queue",
"error": {
"bookName": "Please enter book name"
},
"placeholder": {
"bookName": "Please enter complete book name...",
"description": "Briefly describe the book content to help AI extract more accurate key points...",
"prompt": "For example: Use Lu Xun's writing style, add 3 practical cases, target beginners..."
}
}
},
"reflection": {
"index": {
"copyContent": "Copy Content",
"exportDocument": "Export Document",
"sharePoster": "Share Poster",
"copySuccess": "Content has been successfully copied",
"summary": "Article Summary"
},
"export": {
"title": "Export Markdown",
"format": "Export Format",
"word": "Word Document",
"pdf": "PDF Document",
"export": "Confirm Export Report",
"step1": "Step 1: Select Export Style Template",
"step2": "Step 2: Complete Additional Information",
"step3": "Step 3: MD Source Preview",
"building": "Building...",
"selectTemplate": "Please select a template to start preview",
"noExtraInfo": "No additional information needed",
"success": "Markdown export successful! The directory has been opened",
"error": {
"missingParams": "Missing export parameters",
"initFailed": "Failed to initialize data",
"parseTemplateFailed": "Failed to parse template tags",
"exportFailed": "Export failed"
}
}
},
"about": {
"hero": {
"title1": "For every reader",
"title2": "enjoy the pleasure of reading and thinking",
"description": "We are committed to helping deep readers efficiently digest knowledge through cutting-edge AI technology. From massive text to structured insights, just one click.",
"startExperience": "Start Experience"
},
"coreValues": {
"dataDriven": "Data-Driven",
"dataDrivenDesc": "Based on large models, precisely extract the essence of each book.",
"extremeExperience": "Extreme Experience",
"extremeExperienceDesc": "Simplify complexity, make AI creation as natural and smooth as breathing.",
"connectFuture": "Connect Future",
"connectFutureDesc": "Explore new paradigms of human-computer collaboration, redefine reading and writing."
},
"middle": {
"title": "For those who truly love words",
"description": "In an era of information fragmentation, deep reading is becoming more luxurious than ever. We don't want AI to replace reading, but rather to serve as your \"digital pen pal\", helping you organize logic, capture inspiration, freeing you from tedious summarization work, and returning to thinking itself.",
"users": "Active Users",
"readings": "Reading Volume"
},
"footer": {
"copyright": "© 2026 AI Reader Studio. All Rights Reserved. Crafted with ❤️ for readers worldwide."
}
},
"faq": {
"hero": {
"title": "How can we help you?",
"searchPlaceholder": "Search for your question..."
},
"categories": {
"general": "General Questions",
"usage": "Usage Tips",
"billing": "Subscription & Payment",
"privacy": "Privacy & Security"
},
"emptyState": "No related questions found, please try other keywords",
"support": {
"title": "Still have questions?",
"responseTime": "Our team typically responds to your email within 2 hours",
"contact": "Contact Support"
},
"items": {
"general1": {
"q": "How does this AI reading tool work?",
"a": "We use deep learning models to perform semantic analysis on book texts. It not only summarizes the entire text but also extracts specific knowledge points based on your selected \"professional background\"."
},
"usage1": {
"q": "Can the generated content exceed 5000 words?",
"a": "Currently, the single generation limit is 5000 words to ensure logical coherence. If you need longer content, it is recommended to create tasks by chapter."
},
"billing1": {
"q": "Is there a subscription plan currently?",
"a": "The tool is currently free to use, no cost required, but you need to use your own large model API key"
},
"privacy1": {
"q": "How can I view my data?",
"a": "You can view your data in the settings page."
},
"usage2": {
"q": "How to set up the large model?",
"a": "You can set up reading notes and summary models separately in <span style=\"color: red\">『Settings Center』-『AI Model Configuration』</span>.<br/>It is recommended to use the free API provided by [Alibaba Cloud - iFlow Platform (iflow.cn)], which supports long text processing for single generation. If the generated content is close to the 5000-word limit, it is recommended to create tasks by chapter for optimal logical results.<br/><br/>You can refer to the following operation guide:<br/><br/><ul><li>Get API Key: Visit Alibaba Cloud iFlow Platform (iflow.cn).</li>After registering and logging in, create a new API Key in the backend. iFlow Platform currently provides stable free quota, which is very suitable for personal reading assistant use.<li>Configure interface address:</li>Enter in the Base URL field of application settings: https://apis.iflow.cn/v1.<li>Model selection:</li>The latest model ID provided by the platform.</ul>"
}
}
},
"menus": {
"groups": {
"dataExploration": "Data Exploration",
"automation": "Automation & Export",
"lab": "Lab Features"
},
"items": {
"search": {
"title": "Global Search",
"desc": "Search all local notes in seconds"
},
"userPersona": {
"title": "Reading Profile",
"desc": "Visualize your knowledge boundaries and preferences"
},
"monitor": {
"title": "Book Library Monitoring",
"desc": "Automatically scan local folders for new books"
},
"tts": {
"title": "Audiobook Mode",
"desc": "Use system engine to read summaries aloud"
},
"model": {
"title": "Model Lab",
"desc": "Compare generation effects of different prompts"
}
}
},
"search": {
"title": "Global Search",
"placeholder": "Search book titles, notes, keywords...",
"loading": "Searching...",
"noResults": "No related notes found",
"enterKeyword": "Please enter keywords to start searching"
},
"userPersona": {
"backTitle": "Back to App Menu",
"syncing": "Syncing...",
"syncLatest": "Sync Latest Profile",
"stats": {
"totalNotes": "Total Notes Produced",
"tenThousandWords": "10k words",
"words": "words",
"keepGrowing": "Keep growing",
"deepBooks": "Deep Books Read",
"books": "books",
"deepReading": "Deep reading",
"focusTime": "Focus Growth Time",
"hours": "hours",
"focusTimeLabel": "Focus time"
},
"radar": {
"cognitiveDepth": "Cognitive Depth",
"productionEfficiency": "Production Efficiency",
"maturity": "Maturity",
"knowledgeBreadth": "Knowledge Breadth",
"languageAbility": "Language Ability"
},
"charts": {
"multidimensionalProfile": "Multidimensional Ability Profile",
"productionDensity": "Production Density (Last 7 Days)"
},
"contribution": {
"title": "Reading Contribution Footprint"
},
"report": {
"title": "Reading Dimension Report",
"analysis": "Based on your cumulative {totalWords} words of notes analysis: You have initially established a knowledge framework in the {domain} field.",
"exploration": "Exploration"
}
},
"setting": {
"title": "Settings Center",
"menu": {
"model": "AI Model Settings",
"account": "Account Security",
"billing": "Subscription & Billing",
"notification": "Notification Settings",
"language": "Language Settings"
},
"notification": {
"title": "Notification Management Center",
"description": "Stay updated with reading reflection generation progress to ensure every inspiration is delivered promptly.",
"taskStatus": "Task Status Notifications",
"realTime": "Real-time Push",
"taskCompleted": "Generation Success Alert",
"taskCompletedDesc": "Send desktop notifications when reading reflections and summaries are generated.",
"taskFailed": "Abnormal Interruption Alert",
"taskFailedDesc": "Notify when generation fails due to network fluctuations or insufficient API balance.",
"silentMode": "Silent Mode",
"silentModeDesc": "No system sound will play when notifications pop up.",
"tip": "Tip: Notification effects are affected by the operating system's 'Focus Mode' or 'Do Not Disturb Mode'. If you still don't receive notifications after enabling settings, please check your system's notification management permissions."
},
"masterSwitch": "Master Switch",
"taskCompleted": "Notify when task completed",
"taskFailed": "Notify when task failed",
"silentMode": "Silent Mode",
"language": "Language Settings",
"zh": "Chinese",
"en": "English"
}
}

View File

@@ -0,0 +1,282 @@
{
"common": {
"title": "Asistente de Reflexiones de Lectura",
"loading": "Cargando...",
"success": "Operación exitosa",
"error": "Operación fallida",
"confirm": "Confirmar",
"cancel": "Cancelar",
"delete": "Eliminar",
"save": "Guardar",
"edit": "Editar",
"preview": "Vista previa",
"back": "Atrás",
"next": "Siguiente",
"previous": "Anterior"
},
"task": {
"list": {
"title": "Lista de Tareas",
"create": "Crear Tarea",
"total": "reflexiones",
"status": {
"pending": "En espera",
"pendingDesc": "Tarea en cola, esperando programación de recursos del sistema...",
"processing": "Procesando",
"processingDesc": "El modelo de aprendizaje profundo está analizando el texto, por favor espere...",
"completed": "Completada",
"completedDesc": "La tarea ha sido completada...",
"failed": "Fallida",
"failedDesc": "Tarea fallida, reintentando...",
"paused": "Pausada",
"pausedDesc": "La tarea ha sido pausada, haga clic en reanudar para continuar..."
}
},
"index": {
"totalProgress": "Progreso Total",
"pauseQueue": "Pausar Cola",
"resumeQueue": "Reanudar Cola",
"packageResults": "Empaquetar Resultados",
"deleteTask": "Eliminar Tarea",
"confirmDelete": "Confirmar Eliminación de Tarea",
"deleteContent": "Esta operación eliminará permanentemente todos los registros de reflexiones y progreso de generación para este libro.",
"deleting": "Limpiando del disco...",
"deleteSuccess": "La tarea ha sido eliminada exitosamente",
"deleteFailed": "Error al eliminar, por favor verifique la conexión a la base de datos",
"previewResults": "Vista Previa de Resultados",
"pauseSuccess": "La tarea ha sido pausada",
"pauseFailed": "Error al pausar",
"resumeSuccess": "La tarea ha sido reanudada",
"resumeFailed": "Error al reanudar"
},
"create": {
"title": "Crear Reflexión de Lectura",
"bookName": "Nombre del Libro",
"bookAuthor": "Autor del Libro",
"bookDescription": "Descripción del Libro",
"occupation": "Ocupación del Público Objetivo",
"occupationList": {
"student": "Estudiante",
"professional": "Profesional",
"scholar": "Investigador/Escolar",
"freelancer": "Freelance",
"teacher": "Profesor"
},
"prompt": "Prompt Personalizado",
"wordCount": "Número de Palabras por Reflexión",
"wordUnit": "palabras",
"quantity": "Número de Reflexiones",
"language": "Idioma de Generación",
"tone": "Tono de Generación",
"submit": "Iniciar Tarea",
"success": "La tarea ha sido agregada a la cola",
"error": {
"bookName": "Por favor ingrese el nombre del libro"
},
"placeholder": {
"bookName": "Por favor ingrese el nombre completo del libro...",
"description": "Describa brevemente el contenido del libro para ayudar a la IA a extraer puntos más precisos...",
"prompt": "Por ejemplo: Usar el estilo de Lu Xun, agregar 3 casos prácticos, dirigido a principiantes..."
}
}
},
"reflection": {
"index": {
"copyContent": "Copiar Contenido",
"exportDocument": "Exportar Documento",
"sharePoster": "Compartir Poster",
"copySuccess": "El contenido ha sido copiado exitosamente",
"summary": "Resumen del Artículo"
},
"export": {
"title": "Exportar Markdown",
"format": "Formato de Exportación",
"word": "Documento Word",
"pdf": "Documento PDF",
"export": "Confirmar Exportación del Informe",
"step1": "Paso 1: Seleccionar Plantilla de Estilo de Exportación",
"step2": "Paso 2: Completar Información Adicional",
"step3": "Paso 3: Vista Previa del Código Fuente MD",
"building": "Construyendo...",
"selectTemplate": "Por favor seleccione una plantilla para iniciar la vista previa",
"noExtraInfo": "No se requiere información adicional",
"success": "¡Exportación de Markdown exitosa! Se ha abierto el directorio",
"error": {
"missingParams": "Parámetros de exportación faltantes",
"initFailed": "Error al inicializar datos",
"parseTemplateFailed": "Error al analizar etiquetas de plantilla",
"exportFailed": "Error al exportar"
}
}
},
"about": {
"hero": {
"title1": "Para todos los lectores",
"title2": "disfrutar del placer de leer y pensar",
"description": "Nos dedicamos a ayudar a los lectores profundos a digerir conocimientos de manera eficiente a través de tecnología de IA de vanguardia. Desde texto masivo hasta insights estructurados, solo con un clic.",
"startExperience": "Iniciar Experiencia"
},
"coreValues": {
"dataDriven": "Impulsado por Datos",
"dataDrivenDesc": "Basado en modelos grandes, extrae con precisión la esencia de cada libro.",
"extremeExperience": "Experiencia Extrema",
"extremeExperienceDesc": "Simplifica la complejidad, hace que la creación con IA sea tan natural y fluida como respirar.",
"connectFuture": "Conectar el Futuro",
"connectFutureDesc": "Explora nuevos paradigmas de colaboración humano-computadora, redefine la lectura y la escritura."
},
"middle": {
"title": "Para aquellos que realmente aman las palabras",
"description": "En una era de fragmentación de la información, la lectura profunda se está volviendo más lujosa que nunca. No queremos que la IA reemplace la lectura, sino que sea su 'amigo digital', ayudándote a organizar la lógica, capturar inspiraciones y liberarte del tedioso trabajo de resumen para regresar al pensamiento mismo.",
"users": "Usuarios Activos",
"readings": "Volumen de Lectura"
},
"footer": {
"copyright": "© 2026 AI Reader Studio. Todos los derechos reservados. Creado con ❤️ para lectores de todo el mundo."
}
},
"faq": {
"hero": {
"title": "¿Cómo podemos ayudarte?",
"searchPlaceholder": "Busca tu pregunta..."
},
"categories": {
"general": "Preguntas Generales",
"usage": "Consejos de Uso",
"billing": "Suscripción y Pago",
"privacy": "Privacidad y Seguridad"
},
"emptyState": "No se encontraron preguntas relacionadas, por favor intenta con otras palabras clave",
"support": {
"title": "¿Todavía tienes preguntas?",
"responseTime": "Nuestro equipo generalmente responde a tu correo electrónico en menos de 2 horas",
"contact": "Contactar Soporte"
},
"items": {
"general1": {
"q": "¿Cómo funciona esta herramienta de lectura con IA?",
"a": "Utilizamos modelos de aprendizaje profundo para realizar análisis semántico en textos de libros. No solo resume todo el texto, sino que también extrae puntos de conocimiento específicos basados en tu 'contexto profesional' seleccionado."
},
"usage1": {
"q": "¿Puede el contenido generado exceder las 5000 palabras?",
"a": "Actualmente, el límite de generación individual es de 5000 palabras para garantizar coherencia lógica. Si necesitas contenido más largo, te recomendamos crear tareas por capítulos."
},
"billing1": {
"q": "¿Hay planes de suscripción actualmente?",
"a": "Esta herramienta está disponible gratuitamente en la actualidad. No tiene costo, pero necesitas usar tu propia clave de API de modelo grande"
},
"privacy1": {
"q": "¿Cómo puedo ver mis datos?",
"a": "Puedes ver tus datos en la página de configuración."
},
"usage2": {
"q": "¿Cómo configurar el modelo grande?",
"a": "Puedes configurar modelos de reflexiones de lectura y resumen por separado en <span style=\"color: red\">『Centro de Configuración』-『Configuración de Modelo Grande』</span>.<br/>Se recomienda usar la API gratuita proporcionada por [Alibaba Cloud - Plataforma iFlow (iflow.cn)], que admite procesamiento de texto largo en generación individual. Si el contenido generado está cerca del límite de 5000 palabras, se recomienda crear tareas por capítulos para obtener el mejor efecto lógico.<br/><br/>Puedes consultar la siguiente guía de操作:<br/><br/><ul><li>Obtener API Key: Visita Alibaba Cloud iFlow Platform (iflow.cn).</li>Regístrate e inicia sesión, luego crea una nueva API Key en el backend. La plataforma iFlow actualmente ofrece una cuota gratuita estable, muy adecuada para uso personal de asistente de lectura.<li>Configurar dirección de interfaz:</li>En el campo Base URL de la configuración de la aplicación, ingresa: https://apis.iflow.cn/v1.<li>Selección de modelo:</li>ID del modelo más reciente proporcionado por la plataforma.</ul>"
}
}
},
"menus": {
"groups": {
"dataExploration": "Exploración de Datos",
"automation": "Automatización y Exportación",
"lab": "Funciones de Laboratorio"
},
"items": {
"search": {
"title": "Búsqueda Global",
"desc": "Busca todo el contenido de reflexiones locales en segundos"
},
"userPersona": {
"title": "Perfil de Lectura",
"desc": "Visualiza tus límites de conocimiento y preferencias"
},
"monitor": {
"title": "Monitoreo de Biblioteca",
"desc": "Escanea automáticamente carpetas locales en busca de nuevos libros"
},
"tts": {
"title": "Modo Audiolibro",
"desc": "Usa el motor del sistema para leer resúmenes en voz alta"
},
"model": {
"title": "Laboratorio de Modelos",
"desc": "Compara efectos de generación de diferentes prompts"
}
}
},
"search": {
"title": "Búsqueda Global",
"placeholder": "Busca títulos de libros, notas, palabras clave...",
"loading": "Buscando...",
"noResults": "No se encontraron notas relacionadas",
"enterKeyword": "Por favor ingrese palabras clave para comenzar a buscar"
},
"userPersona": {
"backTitle": "Volver al Menú de la App",
"syncing": "Sincronizando...",
"syncLatest": "Sincronizar Perfil Más Reciente",
"stats": {
"totalNotes": "Total de Notas Generadas",
"tenThousandWords": "10k palabras",
"words": "palabras",
"keepGrowing": "Seguir creciendo",
"deepBooks": "Libros Leídos Profundamente",
"books": "libros",
"deepReading": "Lectura profunda",
"focusTime": "Tiempo de Crecimiento Concentrado",
"hours": "horas",
"focusTimeLabel": "Tiempo de concentración"
},
"radar": {
"cognitiveDepth": "Profundidad Cognitiva",
"productionEfficiency": "Eficiencia de Producción",
"maturity": "Madurez",
"knowledgeBreadth": "Amplitud de Conocimiento",
"languageAbility": "Habilidad Lingüística"
},
"charts": {
"multidimensionalProfile": "Perfil de Habilidades Multidimensional",
"productionDensity": "Densidad de Producción (Últimos 7 Días)"
},
"contribution": {
"title": "Huella de Contribución de Lectura"
},
"report": {
"title": "Informe de Dimensión de Lectura",
"analysis": "Basado en el análisis de tus {totalWords} palabras de notas acumuladas: Has establecido inicialmente un marco de conocimiento en el campo de {domain}.",
"exploration": "Exploración"
}
},
"setting": {
"title": "Centro de Configuración",
"menu": {
"model": "Configuración de Modelo de IA",
"account": "Seguridad de Cuenta",
"billing": "Suscripción y Facturación",
"notification": "Configuración de Notificaciones",
"language": "Configuración de Idioma"
},
"notification": {
"title": "Centro de Gestión de Notificaciones",
"description": "Mantente actualizado con el progreso de generación de reflexiones de lectura para garantizar que cada inspiración se entregue a tiempo.",
"taskStatus": "Notificaciones de Estado de Tarea",
"realTime": "Push en Tiempo Real",
"taskCompleted": "Alerta de Éxito en Generación",
"taskCompletedDesc": "Envía notificaciones de escritorio cuando se generen reflexiones y resúmenes de lectura.",
"taskFailed": "Alerta de Interrupción Anormal",
"taskFailedDesc": "Notifica cuando la generación falle debido a fluctuaciones de red o saldo insuficiente de API.",
"silentMode": "Modo Silencioso",
"silentModeDesc": "No se reproducirá sonido del sistema cuando aparezcan notificaciones.",
"tip": "Consejo: Los efectos de notificación se ven afectados por el 'Modo de Enfoque' o 'Modo No Molestar' del sistema operativo. Si aún no recibes notificaciones después de habilitar la configuración, verifica los permisos de gestión de notificaciones de tu sistema."
},
"masterSwitch": "Interruptor Principal",
"taskCompleted": "Notificar cuando la tarea se complete",
"taskFailed": "Notificar cuando la tarea falle",
"silentMode": "Modo Silencioso",
"language": "Configuración de Idioma",
"zh": "Chino",
"en": "Inglés",
"ja": "Japonés",
"ko": "Coreano",
"es": "Español"
}
}

View File

@@ -0,0 +1,277 @@
{
"common": {
"title": "読書レフレクションアシスタント",
"loading": "読み込み中...",
"success": "操作が成功しました",
"error": "操作に失敗しました",
"confirm": "確認",
"cancel": "キャンセル",
"delete": "削除",
"save": "保存",
"edit": "編集",
"preview": "プレビュー",
"back": "戻る",
"next": "次へ",
"previous": "前へ"
},
"task": {
"list": {
"title": "タスクリスト",
"create": "タスクを作成",
"total": "レフレクション",
"status": {
"pending": "キュー待ち",
"processing": "処理中",
"completed": "完了",
"failed": "失敗",
"paused": "一時停止"
}
},
"index": {
"totalProgress": "総進捗",
"pauseQueue": "キューを一時停止",
"resumeQueue": "キューを再開",
"packageResults": "成果をパッケージ化",
"deleteTask": "タスクを削除",
"confirmDelete": "タスクの削除を確認",
"deleteContent": "この操作により、この本のすべてのレフレクション記録と生成進捗が永久に削除されます。",
"deleting": "ディスクからクリーニング中...",
"deleteSuccess": "タスクが正常に削除されました",
"deleteFailed": "削除に失敗しました。データベース接続を確認してください",
"previewResults": "成果をプレビュー",
"pauseSuccess": "タスクが一時停止されました",
"pauseFailed": "一時停止に失敗しました",
"resumeSuccess": "タスクが再開されました",
"resumeFailed": "再開に失敗しました"
},
"create": {
"title": "読書レフレクションを作成",
"bookName": "本の名前",
"bookAuthor": "著者",
"bookDescription": "本の説明",
"occupation": "対象読者の職業",
"occupationList": {
"student": "学生",
"professional": "プロフェッショナル",
"scholar": "学者/研究者",
"freelancer": "フリーランサー",
"teacher": "教師"
},
"prompt": "カスタムプロンプト",
"wordCount": "レフレクションの単語数",
"wordUnit": "文字",
"quantity": "レフレクションの数",
"language": "生成言語",
"tone": "生成トーン",
"submit": "タスクを開始",
"success": "タスクがキューに追加されました",
"error": {
"bookName": "本の名前を入力してください"
},
"placeholder": {
"bookName": "本の完全な名前を入力してください...",
"description": "AIがより正確なポイントを抽出できるように、本の内容を簡潔に説明してください...",
"prompt": "例魯迅の文体を使用、3つの実践例を追加、初心者向け..."
}
}
},
"reflection": {
"index": {
"copyContent": "内容をコピー",
"exportDocument": "ドキュメントをエクスポート",
"sharePoster": "ポスターを共有",
"copySuccess": "内容が正常にコピーされました",
"summary": "記事の要約"
},
"export": {
"title": "Markdownをエクスポート",
"format": "エクスポート形式",
"word": "Word文書",
"pdf": "PDF文書",
"export": "レポートのエクスポートを確認",
"step1": "ステップ1エクスポートスタイルテンプレートを選択",
"step2": "ステップ2追加情報を入力",
"step3": "ステップ3MDソースのプレビュー",
"building": "構築中...",
"selectTemplate": "プレビューを開始するにはテンプレートを選択してください",
"noExtraInfo": "追加情報は不要です",
"success": "Markdownのエクスポートに成功しました保存先フォルダが開かれました",
"error": {
"missingParams": "エクスポートパラメータが不足しています",
"initFailed": "データの初期化に失敗しました",
"parseTemplateFailed": "テンプレートタグの解析に失敗しました",
"exportFailed": "エクスポートに失敗しました"
}
}
},
"about": {
"hero": {
"title1": "すべての読者に",
"title2": "読書と思考の喜びを",
"description": "私たちは最先端のAI技術を通じて、深読みする人々が知識を効率的に消化するのを支援することに専念しています。大量のテキストから構造化された洞察まで、たった一つのクリックで。",
"startExperience": "体験を開始"
},
"coreValues": {
"dataDriven": "データ主導",
"dataDrivenDesc": "大規模モデルに基づいて、各本の本質を正確に抽出します。",
"extremeExperience": "極限の体験",
"extremeExperienceDesc": "複雑さを単純化し、AIによる創作を呼吸のように自然で滑らかなものにします。",
"connectFuture": "未来との接続",
"connectFutureDesc": "人間とコンピューターの協力の新しいパラダイムを探索し、読書と執筆を再定義します。"
},
"middle": {
"title": "本当に言葉を愛する人々のために",
"description": "情報の断片化が進む時代において、深読みはかつてないほど贅沢なものとなっています。私たちはAIに読書を置き換えてほしいのではなく、あなたの「デジタルペンフレンド」として、論理を整理し、インスピレーションを捉え、退屈な要約作業から解放し、思考そのものに戻るお手伝いをしたいのです。",
"users": "アクティブユーザー",
"readings": "読書量"
},
"footer": {
"copyright": "© 2026 AI Reader Studio. All Rights Reserved. Crafted with ❤️ for readers worldwide."
}
},
"faq": {
"hero": {
"title": "どのようにお手伝いできますか?",
"searchPlaceholder": "質問を検索..."
},
"categories": {
"general": "一般的な質問",
"usage": "使用のヒント",
"billing": "サブスクリプションと支払い",
"privacy": "プライバシーとセキュリティ"
},
"emptyState": "関連する質問が見つかりませんでした。他のキーワードを試してください",
"support": {
"title": "まだ質問がありますか?",
"responseTime": "私たちのチームは通常、2時間以内にメールに返信します",
"contact": "サポートに問い合わせ"
},
"items": {
"general1": {
"q": "このAI読書ツールはどのように機能しますか",
"a": "私たちはディープラーニングモデルを使用して、書籍のテキストに対して意味的分析を実行します。全文を要約するだけでなく、選択した「専門的な背景」に基づいて特定の知識ポイントを抽出することもできます。"
},
"usage1": {
"q": "生成されたコンテンツは5000語を超えることができますか",
"a": "現在、論理的な一貫性を確保するために、単回生成の制限は5000語です。より長いコンテンツが必要な場合は、章ごとにタスクを作成することをお勧めします。"
},
"billing1": {
"q": "現在サブスクリプションプランはありますか?",
"a": "このツールは現在無料で使用できます。費用はかかりませんが、独自の大規模モデルAPIキーを使用する必要があります"
},
"privacy1": {
"q": "データを表示するにはどうすればよいですか?",
"a": "設定ページでデータを表示できます。"
},
"usage2": {
"q": "大規模モデルを設定するにはどうすればよいですか?",
"a": "<span style=\"color: red\">『設定センター』-『大規模モデル設定』</span>で、読書レフレクションと要約モデルをそれぞれ設定できます。<br/>無料APIを提供する【アリババクラウド-イフロープラットフォーム (iflow.cn)】の使用を推奨します。単回生成で長いテキスト処理をサポートしています。生成コンテンツが5000語の上限に近い場合は、最適な論理効果を得るために章ごとにタスクを作成することをお勧めします。<br/><br/>以下の操作ガイドを参照してください:<br/><br/><ul><li>APIキーの取得アリババクラウドイフロープラットフォーム (iflow.cn)にアクセスしてください。</li>登録してログインした後、バックエンドで新しいAPIキーを作成します。イフロープラットフォームは現在、個人の読書アシスタントに非常に適した安定した無料枠を提供しています。<li>インターフェースアドレスの設定:</li>アプリケーション設定のBase URLに次のURLを入力しますhttps://apis.iflow.cn/v1。<li>モデルの選択:</li>プラットフォームが提供する最新のモデルID。</ul>"
}
}
},
"menus": {
"groups": {
"dataExploration": "データ探索",
"automation": "自動化とエクスポート",
"lab": "ラボ機能"
},
"items": {
"search": {
"title": "グローバル検索",
"desc": "すべてのローカルレフレクションコンテンツを秒単位で検索"
},
"userPersona": {
"title": "読書プロファイル",
"desc": "知識の境界と嗜好を可視化"
},
"monitor": {
"title": "ブックライブラリモニタリング",
"desc": "ローカルフォルダの新しい本を自動的にスキャン"
},
"tts": {
"title": "オーディオブックモード",
"desc": "システムエンジンを使用して要約を音声で読み上げ"
},
"model": {
"title": "モデルラボ",
"desc": "異なるプロンプトの生成効果を比較"
}
}
},
"search": {
"title": "グローバル検索",
"placeholder": "本のタイトル、メモ、キーワードを検索...",
"loading": "検索中...",
"noResults": "関連するメモが見つかりません",
"enterKeyword": "検索を開始するにはキーワードを入力してください"
},
"userPersona": {
"backTitle": "アプリメニューに戻る",
"syncing": "同期中...",
"syncLatest": "最新のプロファイルを同期",
"stats": {
"totalNotes": "生成されたレフレクションの合計",
"tenThousandWords": "万字",
"words": "文字",
"keepGrowing": "成長し続ける",
"deepBooks": "深く読んだ本",
"books": "冊",
"deepReading": "深読み",
"focusTime": "集中成長時間",
"hours": "時間",
"focusTimeLabel": "集中時間"
},
"radar": {
"cognitiveDepth": "認知の深さ",
"productionEfficiency": "生産効率",
"maturity": "成熟度",
"knowledgeBreadth": "知識の幅",
"languageAbility": "言語能力"
},
"charts": {
"multidimensionalProfile": "多次元能力プロファイル",
"productionDensity": "生産密度過去7日間"
},
"contribution": {
"title": "読書貢献の足跡"
},
"report": {
"title": "読書次元レポート",
"analysis": "累計 {totalWords} 文字のレフレクション分析に基づく:{domain} 分野で知識の枠組みが初期的に確立されています。",
"exploration": "探索"
}
},
"setting": {
"title": "設定センター",
"menu": {
"model": "大規模モデル設定",
"account": "アカウントセキュリティ",
"billing": "サブスクリプションと請求",
"notification": "通知設定",
"language": "言語設定"
},
"notification": {
"title": "通知管理センター",
"description": "読書レフレクションの生成進捗をリアルタイムで把握し、あらゆるインスピレーションがタイムリーに届くようにします。",
"taskStatus": "タスクステータス通知",
"realTime": "リアルタイムプッシュ",
"taskCompleted": "生成成功アラート",
"taskCompletedDesc": "読書レフレクションと要約が生成されたときにデスクトップ通知を送信します。",
"taskFailed": "異常中断アラート",
"taskFailedDesc": "ネットワークの変動やAPI残高不足による生成失敗時に通知します。",
"silentMode": "サイレントモード",
"silentModeDesc": "通知がポップアップするときにシステム音を再生しません。",
"tip": "ヒント:通知の効果は、オペレーティングシステムの「フォーカスモード」または「通知停止モード」の影響を受けます。設定を有効にした後も通知を受け取らない場合は、システムの通知管理権限を確認してください。"
},
"masterSwitch": "マスタースイッチ",
"taskCompleted": "タスク完了時に通知",
"taskFailed": "タスク失敗時に通知",
"silentMode": "サイレントモード",
"language": "言語設定",
"zh": "中国語",
"en": "英語",
"ja": "日本語",
"ko": "韓国語",
"es": "スペイン語"
}
}

View File

@@ -0,0 +1,282 @@
{
"common": {
"title": "독서 반성 어시스턴트",
"loading": "로딩 중...",
"success": "작업이 성공했습니다",
"error": "작업이 실패했습니다",
"confirm": "확인",
"cancel": "취소",
"delete": "삭제",
"save": "저장",
"edit": "편집",
"preview": "미리보기",
"back": "뒤로",
"next": "다음",
"previous": "이전"
},
"task": {
"list": {
"title": "작업 목록",
"create": "작업 생성",
"total": "반성",
"status": {
"pending": "대기 중",
"pendingDesc": "작업이 대기 중이며, 시스템 자원 스케줄링을 기다리는 중...",
"processing": "처리 중",
"processingDesc": "딥러닝 모델이 텍스트를 분석 중입니다. 잠시만 기다려 주세요...",
"completed": "완료",
"completedDesc": "작업이 완료되었습니다...",
"failed": "실패",
"failedDesc": "작업이 실패했습니다. 재시도 중...",
"paused": "일시 중지",
"pausedDesc": "작업이 일시 중지되었습니다. 계속하려면 재개를 클릭하세요..."
}
},
"index": {
"totalProgress": "총 진행 상황",
"pauseQueue": "큐 일시 중지",
"resumeQueue": "큐 재개",
"packageResults": "결과 패키징",
"deleteTask": "작업 삭제",
"confirmDelete": "작업 삭제 확인",
"deleteContent": "이 작업을 수행하면 이 책에 대한 모든 반성 기록과 생성 진행 상황이 영구적으로 삭제됩니다.",
"deleting": "디스크에서 정리 중...",
"deleteSuccess": "작업이 성공적으로 삭제되었습니다",
"deleteFailed": "삭제에 실패했습니다. 데이터베이스 연결을 확인하세요",
"previewResults": "결과 미리보기",
"pauseSuccess": "작업이 일시 중지되었습니다",
"pauseFailed": "일시 중지에 실패했습니다",
"resumeSuccess": "작업이 재개되었습니다",
"resumeFailed": "재개에 실패했습니다"
},
"create": {
"title": "독서 반성 만들기",
"bookName": "책 이름",
"bookAuthor": "저자",
"bookDescription": "책 설명",
"occupation": "대상 독자 직업",
"occupationList": {
"student": "학생",
"professional": "전문가",
"scholar": "학자/연구원",
"freelancer": "프리랜서",
"teacher": "교사"
},
"prompt": "사용자 지정 프롬프트",
"wordCount": "반성 당 단어 수",
"wordUnit": "글자",
"quantity": "반성 수",
"language": "생성 언어",
"tone": "생성 톤",
"submit": "작업 시작",
"success": "작업이 큐에 추가되었습니다",
"error": {
"bookName": "책 이름을 입력하세요"
},
"placeholder": {
"bookName": "책의 전체 이름을 입력하세요...",
"description": "AI가 보다 정확한 포인트를 추출할 수 있도록 책 내용을 간략히 설명하세요...",
"prompt": "예: 루쉰의 문체 사용, 3개의 실践 예제 추가, 초보자를 대상으로..."
}
}
},
"reflection": {
"index": {
"copyContent": "내용 복사",
"exportDocument": "문서 내보내기",
"sharePoster": "포스터 공유",
"copySuccess": "내용이 성공적으로 복사되었습니다",
"summary": "기사 요약"
},
"export": {
"title": "Markdown 내보내기",
"format": "내보내기 형식",
"word": "Word 문서",
"pdf": "PDF 문서",
"export": "보고서 내보내기 확인",
"step1": "단계 1: 내보내기 스타일 템플릿 선택",
"step2": "단계 2: 추가 정보 입력",
"step3": "단계 3: MD 소스 미리보기",
"building": "구축 중...",
"selectTemplate": "미리보기를 시작하려면 템플릿을 선택하세요",
"noExtraInfo": "추가 정보가 필요하지 않습니다",
"success": "Markdown 내보내기 성공! 저장된 디렉토리가 열렸습니다",
"error": {
"missingParams": "내보내기 매개변수가 누락되었습니다",
"initFailed": "데이터 초기화에 실패했습니다",
"parseTemplateFailed": "템플릿 태그 구문 분석에 실패했습니다",
"exportFailed": "내보내기에 실패했습니다"
}
}
},
"about": {
"hero": {
"title1": "모든 독자에게",
"title2": "독서와 사고의 즐거움을",
"description": "우리는 최첨단 AI 기술을 통해 심층 독자들이 지식을 효율적으로 소화하도록 돕기 위해 전념하고 있습니다. 방대한 텍스트에서 구조화된 통찰까지, 단 한 번의 클릭으로 이루어집니다.",
"startExperience": "경험 시작"
},
"coreValues": {
"dataDriven": "데이터 기반",
"dataDrivenDesc": "대규모 모델을 기반으로 각 책의 본질을 정확히 추출합니다.",
"extremeExperience": "극한 경험",
"extremeExperienceDesc": "복잡성을 단순화하여 AI 생성을 호흡처럼 자연스럽고 원활하게 만듭니다.",
"connectFuture": "미래 연결",
"connectFutureDesc": "인간-컴퓨터 협력의 새로운 패러다임을 탐구하여 독서와 쓰기를 재정의합니다."
},
"middle": {
"title": "진정으로 단어를 사랑하는 사람들을 위해",
"description": "정보 단편화 시대에 심층 독서는 그 어느 때보다 사치스러운 일이 되고 있습니다. 우리는 AI가 독서를 대체하기를 원하는 것이 아니라, 논리를 정리하고 영감을 포착하여 지루한 요약 작업에서 자유롭게 해주는 '디지털 펜팔'로서 여러분을 돕고자 합니다.",
"users": "활성 사용자",
"readings": "독서량"
},
"footer": {
"copyright": "© 2026 AI Reader Studio. All Rights Reserved. Crafted with ❤️ for readers worldwide."
}
},
"faq": {
"hero": {
"title": "어떻게 도와드릴까요?",
"searchPlaceholder": "질문을 검색하세요..."
},
"categories": {
"general": "일반 질문",
"usage": "사용 팁",
"billing": "구독 및 결제",
"privacy": "개인정보 보호 및 보안"
},
"emptyState": "관련 질문을 찾을 수 없습니다. 다른 키워드를 시도하세요",
"support": {
"title": "아직 질문이 있으신가요?",
"responseTime": "저희 팀은 일반적으로 2시간 이내에 이메일에 응답합니다",
"contact": "지원 문의"
},
"items": {
"general1": {
"q": "이 AI 독서 도구는 어떻게 작동하나요?",
"a": "우리는 딥러닝 모델을 사용하여 책 텍스트에 대한 의미 분석을 수행합니다. 전문적인 '배경'을 기반으로 특정 지식 포인트를 추출할 뿐만 아니라 전체 텍스트를 요약할 수도 있습니다."
},
"usage1": {
"q": "생성된 콘텐츠는 5000자 이상이 될 수 있나요?",
"a": "현재 논리적 일관성을 보장하기 위해 단일 생성 제한은 5000자입니다. 더 긴 콘텐츠가 필요한 경우 장별로 작업을 생성하는 것이 좋습니다."
},
"billing1": {
"q": "현재 구독 플랜이 있나요?",
"a": "이 도구는 현재 무료로 사용할 수 있습니다. 비용이 들지 않지만 자체 대규모 모델 API 키를 사용해야 합니다"
},
"privacy1": {
"q": "데이터를 보려면 어떻게 해야 하나요?",
"a": "설정 페이지에서 데이터를 볼 수 있습니다."
},
"usage2": {
"q": "대규모 모델을 설정하려면 어떻게 해야 하나요?",
"a": "<span style=\"color: red\">『설정 센터』-『대규모 모델 설정』</span>에서 독서 반성 및 요약 모델을 각각 설정할 수 있습니다.<br/>무료 API를 제공하는 【알리바바 클라우드-이플로우 플랫폼 (iflow.cn)】 사용을 권장합니다. 단일 생성으로 긴 텍스트 처리를 지원합니다. 생성된 내용이 5000자 제한에 가까운 경우 최적의 논리 효과를 얻기 위해 장별로 작업을 생성하는 것이 좋습니다.<br/><br/>다음操作 가이드를 참조하세요:<br/><br/><ul><li>API 키 가져오기: 알리바바 클라우드 이플로우 플랫폼 (iflow.cn)에 방문하세요.</li>등록 및 로그인 후 백엔드에서 새로운 API 키를 생성하세요. 이플로우 플랫폼은 현재 개인 독서 어시스턴트 사용에 매우 적합한 안정적인 무료 할당량을 제공합니다.<li>인터페이스 주소 설정:</li>응용 프로그램 설정의 Base URL에 다음을 입력하세요: https://apis.iflow.cn/v1.<li>모델 선택:</li>플랫폼이 제공하는 최신 모델 ID.</ul>"
}
}
},
"menus": {
"groups": {
"dataExploration": "데이터 탐색",
"automation": "자동화 및 내보내기",
"lab": "실험실 기능"
},
"items": {
"search": {
"title": "전역 검색",
"desc": "모든 로컬 노트를 초 단위로 검색"
},
"userPersona": {
"title": "독서 프로필",
"desc": "지식의 경계와 선호도 시각화"
},
"monitor": {
"title": "서재 모니터링",
"desc": "로컬 폴더에서 새 책 자동 스캔"
},
"tts": {
"title": "오디오북 모드",
"desc": "시스템 엔진을 사용하여 요약 읽기"
},
"model": {
"title": "모델 실험실",
"desc": "다른 프롬프트의 생성 효과 비교"
}
}
},
"search": {
"title": "전역 검색",
"placeholder": "책 제목, 노트, 키워드를 검색하세요...",
"loading": "검색 중...",
"noResults": "관련 노트를 찾을 수 없습니다",
"enterKeyword": "검색을 시작하려면 키워드를 입력하세요"
},
"userPersona": {
"backTitle": "앱 메뉴로 돌아가기",
"syncing": "동기화 중...",
"syncLatest": "최신 프로필 동기화",
"stats": {
"totalNotes": "생성된 노트 총합",
"tenThousandWords": "만자",
"words": "글자",
"keepGrowing": "성장 지속",
"deepBooks": "심층 읽은 책",
"books": "권",
"deepReading": "심층 독서",
"focusTime": "집중 성장 시간",
"hours": "시간",
"focusTimeLabel": "집중 시간"
},
"radar": {
"cognitiveDepth": "인지 깊이",
"productionEfficiency": "생산 효율성",
"maturity": "성숙도",
"knowledgeBreadth": "지식 폭",
"languageAbility": "언어 능력"
},
"charts": {
"multidimensionalProfile": "다차원 능력 프로필",
"productionDensity": "생산 밀도 (최근 7일)"
},
"contribution": {
"title": "독서 기여 발자취"
},
"report": {
"title": "독서 차원 보고서",
"analysis": "누적 {totalWords} 자 노트 분석에 기초: {domain} 분야에서 지식 프레임워크를 초기적으로 구축했습니다.",
"exploration": "탐험"
}
},
"setting": {
"title": "설정 센터",
"menu": {
"model": "대규모 모델 설정",
"account": "계정 보안",
"billing": "구독 및 청구",
"notification": "알림 설정",
"language": "언어 설정"
},
"notification": {
"title": "알림 관리 센터",
"description": "독서 반성 생성 진행 상황을 실시간으로 파악하여 모든 영감이 적시에 전달되도록 합니다.",
"taskStatus": "작업 상태 알림",
"realTime": "실시간 푸시",
"taskCompleted": "생성 성공 경고",
"taskCompletedDesc": "독서 반성 및 요약이 생성되면 데스크톱 알림을 보냅니다.",
"taskFailed": "비정상 중단 경고",
"taskFailedDesc": "네트워크 변동 또는 API 잔액 부족으로 인한 생성 실패 시 알림합니다.",
"silentMode": "사일런트 모드",
"silentModeDesc": "알림이 팝업될 때 시스템 소리를 재생하지 않습니다.",
"tip": "팁: 알림 효과는 운영 체제의 '집중 모드' 또는 '방해 금지 모드'의 영향을 받습니다. 설정을 활성화한 후에도 알림을 받지 못하면 시스템의 알림 관리 권한을 확인하세요."
},
"masterSwitch": "마스터 스위치",
"taskCompleted": "작업 완료 시 알림",
"taskFailed": "작업 실패 시 알림",
"silentMode": "사일런트 모드",
"language": "언어 설정",
"zh": "중국어",
"en": "영어",
"ja": "일본어",
"ko": "한국어",
"es": "스페인어"
}
}

View File

@@ -0,0 +1,282 @@
{
"common": {
"title": "读书心得助手",
"loading": "加载中...",
"success": "操作成功",
"error": "操作失败",
"confirm": "确认",
"cancel": "取消",
"delete": "删除",
"save": "保存",
"edit": "编辑",
"preview": "预览",
"back": "返回",
"next": "下一步",
"previous": "上一步"
},
"task": {
"list": {
"title": "任务列表",
"create": "创建任务",
"total": "篇心得",
"status": {
"pending": "任务排队中",
"pendingDesc": "任务排队中,等待系统调度算力...",
"processing": "进行中",
"processingDesc": "深度学习模型正在分析文本,请稍后...",
"completed": "任务完成",
"completedDesc": "任务已完成...",
"failed": "任务失败",
"failedDesc": "任务失败,正在重试...",
"paused": "任务暂停",
"pausedDesc": "任务已暂停,点击恢复继续..."
}
},
"index": {
"totalProgress": "总进度",
"pauseQueue": "暂停队列",
"resumeQueue": "恢复队列",
"packageResults": "打包成果",
"deleteTask": "删除任务",
"confirmDelete": "确认删除任务",
"deleteContent": "此操作将永久删除该书籍的所有心得记录及生成进度。",
"deleting": "正在从磁盘清理...",
"deleteSuccess": "任务已成功删除",
"deleteFailed": "删除失败,请检查数据库连接",
"previewResults": "预览成果",
"pauseSuccess": "任务已暂停",
"pauseFailed": "暂停失败",
"resumeSuccess": "任务已恢复",
"resumeFailed": "恢复失败"
},
"create": {
"title": "创建读书心得",
"bookName": "书籍名称",
"bookAuthor": "书籍作者",
"bookDescription": "书籍简介",
"occupation": "目标受众职业",
"occupationList": {
"student": "学生",
"professional": "职场白领",
"scholar": "学者/研究员",
"freelancer": "自由职业者",
"teacher": "教师"
},
"prompt": "个性化提示词",
"wordCount": "单篇心得字数",
"wordUnit": "字",
"quantity": "生成心得篇数",
"language": "生成语言",
"tone": "生成语气",
"submit": "立即开启任务",
"success": "任务已加入队列",
"error": {
"bookName": "请输入书籍名称"
},
"placeholder": {
"bookName": "请输入书籍完整名称...",
"description": "简要描述书籍内容,帮助 AI 提取更准确的关键点...",
"prompt": "例如:使用鲁迅的文风、增加 3 个实战案例、针对小白用户..."
}
}
},
"reflection": {
"index": {
"copyContent": "复制原文",
"exportDocument": "导出文档",
"sharePoster": "分享海报",
"copySuccess": "正文已成功复制",
"summary": "文章摘要"
},
"export": {
"title": "导出 Markdown",
"format": "导出格式",
"word": "Word文档",
"pdf": "PDF文档",
"export": "确认导出报告",
"step1": "第一步:选择导出样式模板",
"step2": "第二步:完善补充信息",
"step3": "第三步MD 源码预览",
"building": "构建中...",
"selectTemplate": "请选择一个模板以开启预览",
"noExtraInfo": "无须额外信息",
"success": "Markdown 导出成功!已打开所在目录",
"error": {
"missingParams": "缺少导出参数",
"initFailed": "初始化数据失败",
"parseTemplateFailed": "解析模板标签失败",
"exportFailed": "导出失败"
}
}
},
"about": {
"hero": {
"title1": "让每一篇阅读者",
"title2": "享受阅读与思考的乐趣",
"description": "我们致力于通过最前沿的 AI 技术,帮助深度阅读者高效消化知识。从海量文本到结构化心得,只需一键。",
"startExperience": "开始体验"
},
"coreValues": {
"dataDriven": "数据驱动",
"dataDrivenDesc": "基于大模型,精准提取每一本书籍的灵魂。",
"extremeExperience": "极致体验",
"extremeExperienceDesc": "化繁为简,让 AI 创作如同呼吸般自然、流畅。",
"connectFuture": "连接未来",
"connectFutureDesc": "探索人机协作的新范式,重新定义阅读与写作。"
},
"middle": {
"title": "让那些真正热爱文字的人",
"description": "在一个信息碎片化的时代,深度阅读正变得前所未有的奢侈。我们不希望 AI 替代阅读,而是希望它能作为你的\"数字笔友\",帮你梳理逻辑、捕捉灵感,让你从繁琐的摘要工作中解放,回归思考本身。",
"users": "使用用户",
"readings": "阅读量"
},
"footer": {
"copyright": "© 2026 AI Reader Studio. All Rights Reserved. Crafted with ❤️ for readers worldwide."
}
},
"faq": {
"hero": {
"title": "有什么可以帮到你?",
"searchPlaceholder": "搜索您遇到的问题..."
},
"categories": {
"general": "常规问题",
"usage": "使用技巧",
"billing": "订阅支付",
"privacy": "隐私安全"
},
"emptyState": "未找到相关问题,请尝试其他关键词",
"support": {
"title": "仍有疑问?",
"responseTime": "我们的团队通常在 2 小时内回复您的邮件",
"contact": "联系支持"
},
"items": {
"general1": {
"q": "这个 AI 阅读工具是如何工作的?",
"a": "我们利用深度学习模型对书籍文本进行语义分析。它不仅能总结全文,还能根据你选择的\"职业背景\"提取特定的知识点。"
},
"usage1": {
"q": "生成的字数可以超过 5000 字吗?",
"a": "目前单次生成上限为 5000 字,以确保内容的逻辑连贯性。如果需要更长篇幅,建议分章节创建任务。"
},
"billing1": {
"q": "当前有订阅计划吗?",
"a": "当前工具为免费使用不需要任何费用但是需要自己使用大模型KEY"
},
"privacy1": {
"q": "如何查看我的数据?",
"a": "你可以在设置页面查看你的数据。"
},
"usage2": {
"q": "如何设置大模型?",
"a": "您可以在<span style=\"color: red\">『设置中心』-『大模型配置』中分别设置读书心得与摘要模型</span>。<br/>推荐使用【阿里云-心流平台 (iflow.cn)】提供的免费 API其单次生成支持长文本处理。若生成内容接近 5000 字上限,建议分章节创建任务以获得最佳逻辑效果。<br/><br/>可以参考以下操作指南:<br/><br/><ul><li>获取 API Key访问 阿里云心流平台 (iflow.cn)。</li>注册并登录后,在后台创建一个新的 API Key。心流平台目前提供稳定的免费额度非常适合个人阅读助手使用。<li>配置接口地址:</li>在应用设置的 Base URL 处填入https://apis.iflow.cn/v1。<li>模型选择:</li>平台提供的最新模型 ID。</ul>"
}
}
},
"menus": {
"groups": {
"dataExploration": "数据探索",
"automation": "自动化与导出",
"lab": "实验室功能"
},
"items": {
"search": {
"title": "全局检索",
"desc": "秒级搜索所有本地心得内容"
},
"userPersona": {
"title": "阅读画像",
"desc": "可视化你的知识边界与偏好"
},
"monitor": {
"title": "书库监控",
"desc": "自动扫描本地文件夹新书"
},
"tts": {
"title": "听书模式",
"desc": "调用系统引擎朗读心得摘要"
},
"model": {
"title": "模型实验室",
"desc": "对比不同 Prompt 的生成效果"
}
}
},
"search": {
"title": "全局检索",
"placeholder": "搜索书名、心得、关键词...",
"loading": "正在搜索...",
"noResults": "没有找到相关的心得",
"enterKeyword": "请输入关键词开始搜索"
},
"userPersona": {
"backTitle": "返回应用菜单",
"syncing": "同步中...",
"syncLatest": "同步最新画像",
"stats": {
"totalNotes": "累计产出心得",
"tenThousandWords": "万字",
"words": "字",
"keepGrowing": "Keep growing",
"deepBooks": "已读深度书籍",
"books": "本",
"deepReading": "Deep reading",
"focusTime": "专注成长时长",
"hours": "小时",
"focusTimeLabel": "Focus time"
},
"radar": {
"cognitiveDepth": "认知深度",
"productionEfficiency": "产出效率",
"maturity": "成熟度",
"knowledgeBreadth": "知识广度",
"languageAbility": "语言能力"
},
"charts": {
"multidimensionalProfile": "多维能力画像",
"productionDensity": "产出密度 (近7日)"
},
"contribution": {
"title": "阅读贡献足迹"
},
"report": {
"title": "阅读维度报告",
"analysis": "基于你累计生成的 {totalWords} 字心得分析: 你在 {domain} 领域已初步建立知识框架。",
"exploration": "探索"
}
},
"setting": {
"title": "设置中心",
"menu": {
"model": "大模型配置",
"account": "账号安全",
"billing": "订阅与账单",
"notification": "通知设置",
"language": "语言设置"
},
"notification": {
"title": "通知管理中心",
"description": "实时掌握读书心得生成进度,确保每一份灵感都能及时送达。",
"taskStatus": "任务状态通知",
"realTime": "实时推送",
"taskCompleted": "生成成功提醒",
"taskCompletedDesc": "当读书心得、摘要生成完毕时,发送桌面通知。",
"taskFailed": "异常中断提醒",
"taskFailedDesc": "若因网络波动或 API 余额不足导致生成失败时提醒。",
"silentMode": "静默模式",
"silentModeDesc": "通知弹出时不再播放系统提示音。",
"tip": "提示:通知效果受操作系统\"专注模式\"或\"勿扰模式\"影响。如果在设置开启后仍未收到通知,请检查系统的通知管理权限。"
},
"masterSwitch": "总开关",
"taskCompleted": "任务完成时提醒",
"taskFailed": "任务失败时提醒",
"silentMode": "静默模式",
"language": "语言设置",
"zh": "中文",
"en": "English"
}
}

View File

@@ -1,10 +1,11 @@
import { createApp } from 'vue'
import App from './App.vue'
import router from '@renderer/router'
import i18n from '@renderer/plugins/i18n'
import '@arco-design/web-vue/dist/arco.css'
import 'virtual:uno.css'
import './style.css'
import './assets/global.less'
import '@icon-park/vue-next/styles/index.css'
createApp(App).use(router).mount('#app')
createApp(App).use(router).use(i18n).mount('#app')

View File

@@ -1,32 +1,27 @@
<script setup lang="ts">
import { useRouterHook } from '@renderer/hooks/useRouterHook'
import {
AdProduct,
ArrowRight,
Github,
HoldInterface,
InternalData,
Mail,
Twitter
} from '@icon-park/vue-next'
import { useI18n } from 'vue-i18n'
import { AdProduct, Github, HoldInterface, InternalData, Mail, Twitter } from '@icon-park/vue-next'
import applicationImg from '@renderer/assets/application.png'
const { goBack } = useRouterHook()
const { go } = useRouterHook()
const { t } = useI18n()
const coreValues = [
{
icon: InternalData,
title: '数据驱动',
desc: '基于自研深度学习模型,精准提取每一本书籍的灵魂。'
title: t('about.coreValues.dataDriven'),
desc: t('about.coreValues.dataDrivenDesc')
},
{
icon: AdProduct,
title: '极致体验',
desc: '化繁为简,让 AI 创作如同呼吸般自然、流畅。'
title: t('about.coreValues.extremeExperience'),
desc: t('about.coreValues.extremeExperienceDesc')
},
{
icon: HoldInterface,
title: '连接未来',
desc: '探索人机协作的新范式,重新定义阅读与写作。'
title: t('about.coreValues.connectFuture'),
desc: t('about.coreValues.connectFutureDesc')
}
]
</script>
@@ -34,30 +29,21 @@ const coreValues = [
<template>
<div class="flex-1 h-full overflow-y-auto bg-[#FAFAFB] custom-scroll">
<section class="pt-20 pb-16 px-6 text-center bg-white border-b border-slate-50">
<div class="inline-flex items-center space-x-2 bg-purple-50 px-3 py-1 rounded-full mb-6">
<span class="w-2 h-2 bg-[#7816ff] rounded-full animate-pulse"></span>
<span class="text-[10px] font-bold text-[#7816ff] uppercase tracking-widest"
>Version 2.0.0 Now Live</span
>
</div>
<h1 class="text-4xl font-black text-slate-800 mb-4 tracking-tight">
让每一页文字<br />
<span class="text-[#7816ff]">都有迹可循</span>
<h1 class="text-4xl font-black text-slate-800 mb-4 tracking-tight flex flex-col gap-4">
<span>{{ t('about.hero.title1') }}</span>
<span class="text-[#7816ff]">{{ t('about.hero.title2') }}</span>
</h1>
<p class="max-w-xl mx-auto text-slate-400 text-sm leading-relaxed mb-8">
我们致力于通过最前沿的 AI 技术帮助深度阅读者高效消化知识
从海量文本到结构化心得只需一键
{{ t('about.hero.description') }}
</p>
<div class="flex justify-center gap-4">
<a-button
type="primary"
shape="round"
class="bg-[#7816ff] border-none px-8 h-10 shadow-lg shadow-purple-100"
@click="go('/task/create')"
>
开始体验
</a-button>
<a-button shape="round" class="px-8 h-10 border-slate-200" @click="goBack()">
返回探索
{{ t('about.hero.startExperience') }}
</a-button>
</div>
</section>
@@ -90,27 +76,23 @@ const coreValues = [
class="bg-white rounded-[32px] border border-slate-100 shadow-sm overflow-hidden flex flex-col md:flex-row items-center"
>
<div class="flex-1 p-12 md:p-16">
<div class="text-[11px] font-bold text-[#7816ff] uppercase tracking-[0.2em] mb-4">
Our Mission
</div>
<h2 class="text-2xl font-bold text-slate-800 mb-6">为了那些真正热爱文字的人</h2>
<h2 class="text-2xl font-bold text-slate-800 mb-6">{{ t('about.middle.title') }}</h2>
<p class="text-sm text-slate-500 leading-relaxed mb-8">
在一个信息碎片化的时代深度阅读正变得前所未有的奢侈我们不希望 AI
替代阅读而是希望它能作为你的数字笔友帮你梳理逻辑捕捉灵感让你从繁琐的摘要工作中解放回归思考本身
{{ t('about.middle.description') }}
</p>
<div class="flex items-center space-x-6">
<div class="flex flex-col">
<span class="text-xl font-black text-slate-800">12,400+</span>
<span class="text-[10px] text-slate-400 font-bold uppercase">Active Users</span>
<span class="text-[10px] text-slate-400 font-bold uppercase">{{ t('about.middle.users') }}</span>
</div>
<div class="w-[1px] h-8 bg-slate-100"></div>
<div class="flex flex-col">
<span class="text-xl font-black text-slate-800">8.5M</span>
<span class="text-[10px] text-slate-400 font-bold uppercase">Words Generated</span>
<span class="text-[10px] text-slate-400 font-bold uppercase">{{ t('about.middle.readings') }}</span>
</div>
</div>
</div>
<div class="flex-1 w-full h-[400px] bg-slate-50 flex items-center justify-center relative">
<div class="flex-1 w-full h-[400px] flex items-center justify-center relative">
<div
class="absolute inset-0 opacity-20"
style="
@@ -119,19 +101,10 @@ const coreValues = [
"
></div>
<div
class="relative z-10 w-64 h-64 bg-white rounded-3xl shadow-xl border border-slate-100 flex flex-col p-6 animate-bounce-slow"
class="relative z-10 w-90 bg-white rounded-3xl shadow-xl border border-slate-100 flex flex-col p-6 animate-bounce-slow"
>
<div class="w-8 h-8 bg-purple-50 rounded-lg mb-4"></div>
<div class="h-2 w-3/4 bg-slate-100 rounded-full mb-3"></div>
<div class="h-2 w-full bg-slate-50 rounded-full mb-3"></div>
<div class="h-2 w-2/3 bg-slate-50 rounded-full"></div>
<div class="mt-auto flex justify-end">
<div
class="w-8 h-8 rounded-full bg-[#7816ff] flex items-center justify-center text-white"
>
<arrow-right size="14" />
</div>
</div>
<img :src="applicationImg" alt="" />
<div class="mt-auto flex justify-end"></div>
</div>
</div>
</div>
@@ -144,7 +117,7 @@ const coreValues = [
<a href="#" class="hover:text-[#7816ff] transition-colors"><mail size="20" /></a>
</div>
<p class="text-[11px] text-slate-300 font-medium">
© 2026 AI Reader Studio. All Rights Reserved. Crafted with for readers worldwide.
{{ t('about.footer.copyright') }}
</p>
</footer>
</div>

View File

@@ -0,0 +1,38 @@
import { useI18n } from 'vue-i18n'
export const useFaqList = () => {
const { t } = useI18n()
return [
{
id: 1,
category: 'general',
q: t('faq.items.general1.q'),
a: t('faq.items.general1.a')
},
{
id: 2,
category: 'usage',
q: t('faq.items.usage1.q'),
a: t('faq.items.usage1.a')
},
{
id: 4,
category: 'billing',
q: t('faq.items.billing1.q'),
a: t('faq.items.billing1.a')
},
{
id: 5,
category: 'privacy',
q: t('faq.items.privacy1.q'),
a: t('faq.items.privacy1.a')
},
{
id: 6,
category: 'usage',
q: t('faq.items.usage2.q'),
a: t('faq.items.usage2.a')
}
]
}

View File

@@ -1,5 +1,6 @@
<script setup lang="ts">
import { computed, ref } from 'vue'
import { useI18n } from 'vue-i18n'
import {
ArrowRight,
Equalizer,
@@ -9,46 +10,22 @@ import {
Security,
Wallet
} from '@icon-park/vue-next'
import { useFaqList } from './data/faqData'
const { t } = useI18n()
const faqList = useFaqList()
// 分类配置
const categories = [
{ key: 'general', name: '常规问题', icon: Help },
{ key: 'usage', name: '使用技巧', icon: Equalizer },
{ key: 'billing', name: '订阅支付', icon: Wallet },
{ key: 'privacy', name: '隐私安全', icon: Security }
{ key: 'general', name: t('faq.categories.general'), icon: Help },
{ key: 'usage', name: t('faq.categories.usage'), icon: Equalizer },
{ key: 'billing', name: t('faq.categories.billing'), icon: Wallet },
{ key: 'privacy', name: t('faq.categories.privacy'), icon: Security }
]
const activeCategory = ref('general')
const searchQuery = ref('')
// 问题数据
const faqList = [
{
id: 1,
category: 'general',
q: '这个 AI 阅读工具是如何工作的?',
a: '我们利用深度学习模型对书籍文本进行语义分析。它不仅能总结全文,还能根据你选择的“职业背景”提取特定的知识点。'
},
{
id: 2,
category: 'usage',
q: '生成的字数可以超过 5000 字吗?',
a: '目前单次生成上限为 5000 字,以确保内容的逻辑连贯性。如果需要更长篇幅,建议分章节创建任务。'
},
{
id: 3,
category: 'billing',
q: '订阅后可以申请退款吗?',
a: '在订阅后的 24 小时内且未使用过算力资源的情况下,您可以联系客服申请全额退款。'
},
{
id: 4,
category: 'general',
q: '支持哪些格式的书籍?',
a: '目前支持 PDF, EPUB, TXT 格式。为了获得最佳效果,建议上传排版清晰的文档。'
}
]
// 搜索过滤逻辑
const filteredFaqs = computed(() => {
return faqList.filter((item) => {
@@ -62,11 +39,11 @@ const filteredFaqs = computed(() => {
<template>
<div class="flex-1 h-full overflow-y-auto bg-[#FAFAFB] custom-scroll">
<section class="pt-16 pb-12 px-6 text-center">
<h2 class="text-3xl font-black text-slate-800 mb-4">有什么可以帮到你</h2>
<h2 class="text-3xl font-black text-slate-800 mb-4">{{ t('faq.hero.title') }}</h2>
<div class="max-w-2xl mx-auto relative">
<a-input
v-model="searchQuery"
placeholder="搜索您遇到的问题..."
:placeholder="t('faq.hero.searchPlaceholder')"
class="faq-search-input"
size="large"
allow-clear
@@ -110,7 +87,7 @@ const filteredFaqs = computed(() => {
<span class="text-[14px] font-bold text-slate-700">{{ faq.q }}</span>
</template>
<div class="text-[13px] leading-relaxed text-slate-500 py-2">
{{ faq.a }}
<span v-html="faq.a"></span>
</div>
</a-collapse-item>
</a-collapse>
@@ -122,7 +99,7 @@ const filteredFaqs = computed(() => {
>
<search theme="outline" size="24" fill="#cbd5e1" />
</div>
<p class="text-sm text-slate-400">未找到相关问题请尝试其他关键词</p>
<p class="text-sm text-slate-400">{{ t('faq.emptyState') }}</p>
</div>
<div
@@ -135,12 +112,12 @@ const filteredFaqs = computed(() => {
<headset-one theme="outline" size="24" />
</div>
<div>
<h4 class="text-sm font-bold text-slate-800">仍有疑问</h4>
<p class="text-[11px] text-slate-400">我们的团队通常在 2 小时内回复您的邮件</p>
<h4 class="text-sm font-bold text-slate-800">{{ t('faq.support.title') }}</h4>
<p class="text-[11px] text-slate-400">{{ t('faq.support.responseTime') }}</p>
</div>
</div>
<a-button type="primary" shape="round" class="bg-[#7816ff] border-none px-6">
联系支持
{{ t('faq.support.contact') }}
</a-button>
</div>
</main>

View File

@@ -0,0 +1,64 @@
import { useI18n } from 'vue-i18n'
import { DatabaseConfig, DocumentFolder, Headset, Search, TrendTwo } from '@icon-park/vue-next'
export const useFeatures = () => {
const { t } = useI18n()
return [
{
group: t('menus.groups.dataExploration'),
items: [
{
id: 'search',
title: t('menus.items.search.title'),
desc: t('menus.items.search.desc'),
icon: Search,
color: '#7816ff',
path: 'search'
},
{
id: 'userPersona',
title: t('menus.items.userPersona.title'),
desc: t('menus.items.userPersona.desc'),
icon: TrendTwo,
color: '#00b42a',
path: 'userPersona'
}
]
},
{
group: t('menus.groups.automation'),
items: [
{
id: 'monitor',
title: t('menus.items.monitor.title'),
icon: DocumentFolder,
desc: t('menus.items.monitor.desc'),
color: '#eb2f96',
path: 'monitor'
}
]
},
{
group: t('menus.groups.lab'),
items: [
{
id: 'tts',
title: t('menus.items.tts.title'),
desc: t('menus.items.tts.desc'),
icon: Headset,
color: '#722ed1',
path: 'tts'
},
{
id: 'model',
title: t('menus.items.model.title'),
desc: t('menus.items.model.desc'),
icon: DatabaseConfig,
color: '#13c2c2',
path: 'lab'
}
]
}
]
}

View File

@@ -0,0 +1,87 @@
<script setup lang="ts">
import useRouterHook from '@renderer/hooks/useRouterHook'
import { Left } from '@icon-park/vue-next'
import { useFeatures } from '@renderer/pages/menus/data/MenusData'
const { go } = useRouterHook()
const features = useFeatures()
const handleNavigate = (path: string) => {
go(`/menus/${path}`)
}
</script>
<template>
<div class="flex flex-col bg-[#F8F9FB] overflow-hidden animate-in py-6">
<div class="mx-auto space-y-12">
<div v-for="group in features" :key="group.group" class="space-y-6">
<div class="flex items-center gap-3">
<div class="w-1 h-5 bg-[#7816ff] rounded-full"></div>
<h2 class="text-sm font-black tracking-[0.3em]">
{{ group.group }}
</h2>
</div>
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
<div
v-for="item in group.items"
:key="item.id"
@click="handleNavigate(item.path)"
class="feature-card group flex gap-sm cursor-pointer"
>
<div class="flex items-start justify-between">
<div
class="w-12 h-12 rounded-2xl flex items-center justify-center transition-transform group-hover:scale-110 duration-300"
:style="{ backgroundColor: item.color + '10' }"
>
<component :is="item.icon" size="24" :style="{ color: item.color }" />
</div>
</div>
<div class="flex-1 flex flex-col">
<span class="text-sm font-bold text-slate-800 group-hover:text-[#7816ff]">
{{ item.title }}
</span>
<p class="text-[11px] text-slate-400 leading-relaxed">
{{ item.desc }}
</p>
</div>
<div class="opacity-0 group-hover:opacity-100 transition-opacity">
<div class="w-6 h-6 rounded-full bg-slate-50 flex items-center justify-center">
<left class="rotate-180 text-slate-300" size="12" />
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<style scoped>
.feature-card {
@apply bg-white p-6 rounded-[24px] border border-slate-100 cursor-pointer
transition-all duration-300 hover:shadow-xl hover:shadow-slate-200/50
hover:-translate-y-1 active:scale-95;
}
.custom-scroll::-webkit-scrollbar {
width: 4px;
}
.custom-scroll::-webkit-scrollbar-thumb {
background: #e2e8f0;
border-radius: 10px;
}
@keyframes fadeIn {
from {
opacity: 0;
transform: translateY(10px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.animate-in {
animation: fadeIn 0.4s ease-out forwards;
}
</style>

View File

@@ -0,0 +1,139 @@
<script setup lang="ts">
import { Left, Timer } from '@icon-park/vue-next'
import useRouterHook from '@renderer/hooks/useRouterHook'
import { computed } from 'vue'
import { useFeatures } from '@renderer/pages/menus/data/MenusData'
import { useRoute } from 'vue-router'
const route = useRoute()
const { goBack } = useRouterHook()
const features = useFeatures()
const currentTitle = computed(() => {
// 获取路径中最后一部分,例如 /menus/tts 得到 tts
const pathKey = route.path.split('/').pop()
// 在配置数组中深度查找
for (const group of features) {
const item = group.items.find((i) => i.path === pathKey)
if (item) return item.title
}
return '新功能'
})
// 动态接收正在开发的功能名称
const props = defineProps<{
title?: string
}>()
</script>
<template>
<div class="h-full w-full flex flex-col bg-[#F8F9FB] overflow-hidden">
<header class="h-16 px-8 flex items-center relative z-20">
<div
class="w-8 h-8 rounded-full bg-white shadow-sm flex items-center justify-center cursor-pointer hover:bg-purple-50 transition-all group"
@click="goBack(1)"
>
<left size="16" class="text-slate-400 group-hover:text-[#7816ff]" />
</div>
</header>
<main class="flex-1 flex flex-col items-center justify-center p-12 relative">
<div
class="absolute w-[500px] h-[500px] bg-[#7816ff]/5 rounded-full blur-[100px] -z-10 animate-pulse"
></div>
<div class="flex flex-col items-center max-w-md text-center space-y-8 animate-in">
<div class="relative">
<div class="pt-8 w-full flex items-center">
<div class="flex items-center flex-col gap-4 m-a">
<div
class="w-20 h-20 rounded-2xl flex items-center justify-center text-white font-black text-xl italic shadow-lg bg-[#7816ff]"
>
Z
</div>
<div>
<div class="text-[13px] font-black text-slate-800 tracking-tight">读书心得助手</div>
<div class="text-[9px] text-slate-400 font-bold uppercase tracking-widest mt-0.5">
让每一篇阅读者享受阅读与思考的乐趣
</div>
</div>
</div>
</div>
</div>
<div class="space-y-3">
<h1 class="text-2xl font-black text-slate-800">
{{ currentTitle || '功能' }} 正在精心打磨中
</h1>
<p class="text-sm text-slate-400 leading-relaxed px-6">
为了提供更精准的体验我们正在优化该功能预计在接下来的版本中与您见面
</p>
</div>
<div
class="w-full bg-white rounded-2xl p-6 border border-slate-100 shadow-sm flex flex-col gap-4"
>
<div class="flex items-center gap-2 m-a">
<timer theme="outline" size="14" class="text-slate-300" />
<span class="text-[11px] text-slate-400 italic">好的深度值得一点时间的等待</span>
</div>
</div>
<button
@click="goBack(1)"
class="px-8 h-11 bg-slate-800 text-white rounded-xl font-bold text-sm hover:bg-slate-900 transition-all shadow-lg active:scale-95"
>
返回探索其他功能
</button>
</div>
</main>
</div>
</template>
<style scoped>
@keyframes float {
0%,
100% {
transform: translateY(0);
}
50% {
transform: translateY(-10px);
}
}
@keyframes shine {
from {
transform: translateX(-100%);
}
to {
transform: translateX(200%);
}
}
.animate-shine {
animation: shine 2s infinite linear;
}
@keyframes fadeIn {
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.animate-in {
animation: fadeIn 0.6s cubic-bezier(0.22, 1, 0.36, 1) forwards;
}
@keyframes slideUp {
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
</style>

View File

@@ -0,0 +1,30 @@
<script setup lang="ts">
import { computed } from 'vue'
import { getHighlightedSegments } from '../utils/highlighter'
const props = defineProps<{
text: string
highlights?: { start: number; length: number }[]
}>()
const segments = computed(() => getHighlightedSegments(props.text, props.highlights || []))
</script>
<template>
<span>
<template v-for="(seg, index) in segments" :key="index">
<mark v-if="seg.isHighlight" class="highlight-item">{{ seg.text }}</mark>
<span v-else>{{ seg.text }}</span>
</template>
</span>
</template>
<style scoped>
.highlight-item {
background-color: #f5f3ff; /* 浅紫色背景 */
color: #7816ff; /* 你的品牌紫色 */
font-weight: 700;
border-radius: 2px;
padding: 0 1px;
}
</style>

View File

@@ -0,0 +1,193 @@
<script setup lang="ts">
import { ref, onMounted, watch } from 'vue'
import { useI18n } from 'vue-i18n'
import { Search, Close, DocumentFolder, BookmarkOne } from '@icon-park/vue-next'
import useRouterHook from '@renderer/hooks/useRouterHook'
import { trpc } from '@renderer/lib/trpc'
import { ISearch } from '@shared/types/ISearch' // 假设你已定义了搜索结果接口
import HighlightText from './components/HighlightText.vue'
import BackPage from '@renderer/components/BackPage.vue'
const { go } = useRouterHook()
const { t } = useI18n()
const searchQuery = ref('')
const searchResults = ref<ISearch[]>([])
const isLoading = ref(false)
// 实时搜索逻辑
let searchTimeout: NodeJS.Timeout | null = null
watch(searchQuery, (newVal) => {
if (searchTimeout) clearTimeout(searchTimeout)
if (newVal.trim() === '') {
searchResults.value = []
return
}
searchTimeout = setTimeout(() => {
performSearch(newVal)
}, 300) // 300ms 防抖
})
const performSearch = async (query: string) => {
isLoading.value = true
try {
// 假设 tRPC 提供了 search 接口
searchResults.value = await trpc.search.search.query(query)
} catch (error) {
console.error('搜索失败:', error)
searchResults.value = []
} finally {
isLoading.value = false
}
}
const clearSearch = () => {
searchQuery.value = ''
searchResults.value = []
}
const goToDetail = (result: ISearch) => {
// 假设心得详情页路由是 /reflection/:id
go('/reflection', {
id: result.id
})
}
// 模拟快捷键监听(实际需要 Electron 主进程处理)
onMounted(() => {})
</script>
<template>
<div class="h-full w-full flex flex-col bg-[#F8F9FB] overflow-hidden animate-in">
<header
class="h-16 px-8 flex items-center justify-between relative z-20 backdrop-blur-md bg-white/70 border-b border-slate-100"
>
<BackPage :title="t('search.title')" />
</header>
<main class="flex-1 flex flex-col items-center p-8 pt-12 relative overflow-hidden">
<div
class="w-full max-w-2xl relative flex items-center bg-white rounded-2xl shadow-lg border border-slate-100 focus-within:border-[#7816ff] transition-all duration-200"
>
<search size="20" class="absolute left-5 text-slate-400" />
<input
id="search-input"
v-model="searchQuery"
type="text"
:placeholder="t('search.placeholder')"
class="flex-1 px-14 py-4 text-sm font-medium text-slate-700 bg-transparent outline-none placeholder:text-slate-400"
@keydown.esc="clearSearch"
/>
<transition name="fade">
<button
v-if="searchQuery"
@click="clearSearch"
class="absolute right-5 w-6 h-6 rounded-full bg-slate-100 text-slate-500 flex items-center justify-center hover:bg-slate-200 transition-colors"
>
<close size="12" />
</button>
</transition>
</div>
<div
class="w-full max-w-2xl mt-8 flex flex-col gap-4 overflow-y-auto custom-scroll transition-all duration-300"
:class="{ 'opacity-50': isLoading }"
>
<div v-if="isLoading" class="flex justify-center items-center py-8 text-slate-400 text-sm">
<search size="20" class="animate-pulse mr-2" />
{{ t('search.loading') }}
</div>
<div
v-else-if="searchQuery && searchResults.length === 0"
class="flex flex-col items-center py-8 text-slate-400 text-sm gap-2"
>
<document-folder size="32" />
<p>{{ t('search.noResults') }}</p>
</div>
<div
v-else-if="!searchQuery"
class="flex flex-col items-center py-8 text-slate-400 text-sm gap-2"
>
<bookmark-one size="32" />
<p>{{ t('search.enterKeyword') }}</p>
</div>
<div
v-for="result in searchResults"
:key="result.id"
@click="goToDetail(result)"
class="search-result-card group cursor-pointer"
>
<div class="flex items-start gap-4">
<div
class="w-10 h-10 rounded-lg flex-shrink-0 bg-purple-50 flex items-center justify-center"
>
<bookmark-one size="20" fill="#7816ff" />
</div>
<div class="flex-1 overflow-hidden">
<h3 class="text-sm font-bold text-slate-800 group-hover:text-[#7816ff]">
<HighlightText :text="result.title" :highlights="result.titleHighlights" />
</h3>
<p class="text-[11px] text-slate-500 mt-1 leading-normal line-clamp-2">
<HighlightText
:text="result.contentSnippet || result.content"
:highlights="result.contentHighlights"
/>
</p>
<p class="text-[10px] text-slate-400 mt-2">
<span class="font-semibold">{{ result.bookName }}</span> ·
<span class="ml-1">{{ new Date(result.createdAt).toLocaleDateString() }}</span>
</p>
</div>
</div>
</div>
</div>
</main>
</div>
</template>
<style scoped>
.search-result-card {
@apply bg-white p-4 rounded-xl shadow-sm border border-slate-100 cursor-pointer
transition-all duration-200 hover:shadow-md hover:border-[#7816ff]/30 active:scale-[0.99];
}
.custom-scroll::-webkit-scrollbar {
width: 4px;
}
.custom-scroll::-webkit-scrollbar-thumb {
background: #e2e8f0;
border-radius: 10px;
}
@keyframes fadeIn {
from {
opacity: 0;
transform: translateY(10px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.animate-in {
animation: fadeIn 0.4s ease-out forwards;
}
.fade-enter-active,
.fade-leave-active {
transition: opacity 0.3s ease;
}
.fade-enter-from,
.fade-leave-to {
opacity: 0;
}
.highlight-item {
background-color: #f5f3ff; /* 浅紫色背景 */
color: #7816ff; /* 你的品牌紫色 */
font-weight: 700;
border-radius: 2px;
padding: 0 1px;
}
</style>

View File

@@ -0,0 +1,51 @@
// 定义高亮片段的结构
export interface HighlightSegment {
text: string
isHighlight: boolean
}
/**
* 将原始文本根据高亮索引转化为分段数组
*/
export function getHighlightedSegments(
text: string,
highlights: { start: number; length: number }[]
): HighlightSegment[] {
if (!highlights || highlights.length === 0) {
return [{ text, isHighlight: false }]
}
// 1. 按照起始位置排序,防止索引乱序导致切片失败
const sortedHighlights = [...highlights].sort((a, b) => a.start - b.start)
const segments: HighlightSegment[] = []
let lastIndex = 0
for (const { start, length } of sortedHighlights) {
// 处理匹配项之前的普通文本
if (start > lastIndex) {
segments.push({
text: text.substring(lastIndex, start),
isHighlight: false
})
}
// 处理匹配到的高亮文本
segments.push({
text: text.substring(start, start + length),
isHighlight: true
})
lastIndex = start + length
}
// 处理剩余的尾部文本
if (lastIndex < text.length) {
segments.push({
text: text.substring(lastIndex),
isHighlight: false
})
}
return segments
}

View File

@@ -0,0 +1,201 @@
<script setup lang="ts">
import { computed, ref } from 'vue'
import dayjs from 'dayjs'
import { trpc } from '@renderer/lib/trpc'
import ReflectionDrawer from './ReflectionDrawer.vue'
import { IContributionDay } from '@shared/types/IUserReadingPersona'
import { BabyFeet } from '@icon-park/vue-next'
const props = defineProps<{
data: { date: string; count: number }[]
title?: string
}>()
const drawerVisible = ref(false)
const selectedDate = ref('')
const dailyReflections = ref<any[]>([])
const isDetailLoading = ref(false)
// 新增:时间范围状态 (90天 / 180天 / 365天)
const timeRange = ref(90)
// 1. 核心计算逻辑:根据选择的范围生成日期矩阵
const calendar = computed(() => {
const end = dayjs()
// 根据 timeRange 动态计算起始时间
const start = end.subtract(timeRange.value, 'day').startOf('week')
const days: IContributionDay[] = []
const dataMap = new Map<string, number>()
props.data.forEach((item) => {
if (item.date) dataMap.set(item.date.trim(), Number(item.count))
})
let current = start
while (current.isBefore(end) || current.isSame(end, 'day')) {
const dateStr = current.format('YYYY-MM-DD')
const count = dataMap.get(dateStr) || 0
days.push({
date: dateStr,
count: count,
level: count === 0 ? 0 : count >= 4 ? 4 : Math.ceil(count)
})
current = current.add(1, 'day')
}
return days
})
// 计算月份标记位(只在每月的第一个周展示)
const monthLabels = computed(() => {
const labels: { text: string; index: number }[] = []
let lastMonth = -1
// 每 7 个方块为一列
for (let i = 0; i < calendar.value.length; i += 7) {
const m = dayjs(calendar.value[i].date).month()
if (m !== lastMonth) {
labels.push({ text: dayjs(calendar.value[i].date).format('MMM'), index: i / 7 })
lastMonth = m
}
}
return labels
})
const getLevelClass = (level: number) => {
const levels = ['bg-slate-100', 'bg-purple-200', 'bg-purple-400', 'bg-purple-600', 'bg-[#7816ff]']
return levels[level]
}
const handleDayClick = async (day: { date: string; count: number }) => {
if (day.count === 0) return
selectedDate.value = day.date
drawerVisible.value = true
isDetailLoading.value = true
try {
dailyReflections.value = await trpc.persona.getReflectionsByDate.query({ date: day.date })
} catch (err) {
console.error('详情失败:', err)
} finally {
isDetailLoading.value = false
}
}
</script>
<template>
<div class="bg-white p-6 rounded-2xl shadow-sm border border-slate-100">
<div class="flex flex-row justify-between">
<div class="flex flex-row items-center gap-2 mb-2">
<BabyFeet theme="filled" size="18" fill="#7816ff" />
<span class="text-sm font-black text-slate-800">{{ props.title || '阅读贡献足迹' }}</span>
</div>
<div class="flex bg-slate-50 p-1 rounded-xl border gap-2 border-slate-100">
<a-button
v-for="opt in [
{ l: '近三月', v: 90 },
{ l: '半年', v: 180 },
{ l: '全年', v: 365 }
]"
:key="opt.v"
@click="timeRange = opt.v"
:class="[
'px-3 py-1 text-[10px] font-black transition-all rounded-lg',
timeRange === opt.v
? 'bg-white text-[#7816ff] shadow-sm'
: 'text-slate-400 hover:text-slate-600'
]"
>
{{ opt.l }}
</a-button>
</div>
</div>
<div class="bg-white p-6 rounded-[24px] border border-slate-100 mt-6 shadow-sm group">
<div class="relative mb-2 ml-8 h-4">
<span
v-for="label in monthLabels"
:key="label.index"
class="absolute text-[9px] font-bold text-slate-300 uppercase"
:style="{ left: `${label.index * 20}px` }"
>
{{ label.text }}
</span>
</div>
<div class="flex gap-2">
<div
class="flex flex-col justify-between py-1 text-[9px] font-bold text-slate-300 uppercase h-[115px]"
>
<span>Mon</span>
<span>Wed</span>
<span>Fri</span>
</div>
<div class="flex-1 overflow-x-auto custom-scroll pb-4">
<div class="grid grid-flow-col grid-rows-7 gap-1.5 w-max">
<div
v-for="day in calendar"
:key="day.date"
@click="handleDayClick(day)"
class="w-[14px] h-[14px] rounded-[3px] transition-all duration-300 hover:scale-150 hover:z-20 hover:rounded-sm relative"
:class="[
getLevelClass(day.level),
day.count > 0
? 'cursor-pointer shadow-[0_0_8px_rgba(120,22,255,0.1)]'
: 'cursor-default'
]"
>
<div class="sr-only">{{ day.date }}: {{ day.count }}</div>
</div>
</div>
</div>
</div>
<div class="flex items-center justify-between gap-2 mt-2 pt-4 border-t border-slate-50">
<div class="mt-2 flex items-center gap-2 px-1">
<div class="w-1.5 h-1.5 rounded-full bg-[#7816ff] animate-pulse"></div>
<span class="text-[10px] font-bold text-slate-400 uppercase tracking-tighter">
数据更新时间 {{ dayjs().format('YYYY-MM-DD HH:mm') }}
</span>
</div>
<div class="flex flex-row gap-2 items-center">
<div class="flex gap-1">
<div
v-for="i in 5"
:key="i"
:class="getLevelClass(i - 1)"
class="w-2 h-2 rounded-[2px]"
></div>
</div>
<span class="text-[9px] font-bold text-slate-400 uppercase tracking-widest">More</span>
</div>
</div>
<Teleport to="body">
<ReflectionDrawer
:visible="drawerVisible"
:date="selectedDate"
:list="dailyReflections"
:loading="isDetailLoading"
@close="drawerVisible = false"
/>
</Teleport>
</div>
</div>
</template>
<style scoped>
.custom-scroll::-webkit-scrollbar {
height: 4px;
}
.custom-scroll::-webkit-scrollbar-thumb {
background: #f1f5f9;
border-radius: 10px;
}
.custom-scroll::-webkit-scrollbar-track {
background: transparent;
}
/* 优化滚动时的呼吸感 */
.grid {
padding: 2px;
}
</style>

View File

@@ -0,0 +1,73 @@
<script setup lang="ts">
import { Close, BookmarkOne } from '@icon-park/vue-next'
defineProps<{
visible: boolean
date: string
list: any[]
loading: boolean
}>()
const emit = defineEmits(['close', 'select'])
</script>
<template>
<div v-if="visible" class="fixed inset-0 z-[100] flex justify-end">
<div class="absolute inset-0 bg-slate-900/20 backdrop-blur-sm" @click="emit('close')"></div>
<div class="relative w-80 h-full bg-white shadow-2xl flex flex-col animate-slide-in">
<header class="p-6 border-b border-slate-50 flex items-center justify-between">
<div>
<h3 class="text-sm font-black text-slate-800">{{ date }}</h3>
<p class="text-[10px] text-slate-400 uppercase font-bold">当日阅读沉淀</p>
</div>
<a-button
@click="emit('close')"
class="p-2 hover:bg-slate-50 rounded-full transition-colors"
>
<close size="16" class="text-slate-400" />
</a-button>
</header>
<div class="flex-1 overflow-y-auto p-4 custom-scroll">
<div v-if="loading" class="flex justify-center py-10 text-slate-300">
<div class="animate-spin mr-2 opacity-50"><bookmark-one /></div>
<span class="text-xs">检索记忆中...</span>
</div>
<div v-else-if="list.length === 0" class="flex flex-col items-center py-20 text-slate-300">
<bookmark-one size="32" class="opacity-20 mb-2" />
<p class="text-xs">那天没有生成心得哦</p>
</div>
<div v-else class="space-y-3">
<div
v-for="item in list"
:key="item.id"
@click="emit('select', item.id)"
class="group p-4 rounded-xl border border-slate-50 bg-slate-50/30 hover:bg-white hover:border-purple-100 hover:shadow-md hover:shadow-purple-50 transition-all cursor-pointer"
>
<h4
class="text-xs font-bold text-slate-700 group-hover:text-[#7816ff] mb-1 line-clamp-1"
>
{{ item.title }}
</h4>
</div>
</div>
</div>
</div>
</div>
</template>
<style scoped>
.animate-slide-in {
animation: slideIn 0.3s cubic-bezier(0.16, 1, 0.3, 1);
}
@keyframes slideIn {
from {
transform: translateX(100%);
}
to {
transform: translateX(0);
}
}
</style>

View File

@@ -0,0 +1,347 @@
<script setup lang="ts">
import { onMounted, ref, nextTick, computed } from 'vue'
import { useI18n } from 'vue-i18n'
import * as echarts from 'echarts'
import {
ChartGraph,
TrendTwo,
Refresh,
BookOpen,
Write,
Time,
ChartHistogram
} from '@icon-park/vue-next'
import BackPage from '@renderer/components/BackPage.vue'
import { trpc } from '@renderer/lib/trpc'
import { IUserReadingPersona } from '@shared/types/IUserReadingPersona'
import useLoading from '@renderer/hooks/useLoading'
import ContributionWall from './components/ContributionWall.vue'
const { t } = useI18n()
const chartRef = ref<HTMLElement | null>(null)
const trendChartRef = ref<HTMLElement | null>(null)
const personaData = ref<IUserReadingPersona | null>(null)
const { loading, setLoading } = useLoading()
const contributionData = ref<{ date: string; count: number }[]>([])
let myChart: echarts.ECharts | null = null
let trendChart: echarts.ECharts | null = null
// 计算统计数据
const statsCards = computed(() => {
const s = personaData.value?.stats
return [
{
label: t('userPersona.stats.totalNotes'),
// 增加万字单位转换,让数据在大数额下更精致
value: s
? s.totalWords > 10000
? (s.totalWords / 10000).toFixed(1)
: s.totalWords.toLocaleString()
: 0,
unit: s ? (s.totalWords > 10000 ? t('userPersona.stats.tenThousandWords') : t('userPersona.stats.words')) : t('userPersona.stats.words'),
icon: Write,
color: 'bg-blue-50 text-blue-600',
trend: t('userPersona.stats.keepGrowing') // 增加一个装饰性文字
},
{
label: t('userPersona.stats.deepBooks'),
value: s?.totalBooks || '0',
unit: t('userPersona.stats.books'),
icon: BookOpen,
color: 'bg-purple-50 text-[#7816ff]',
trend: t('userPersona.stats.deepReading')
},
{
label: t('userPersona.stats.focusTime'),
value: s?.totalHours || '0',
unit: t('userPersona.stats.hours'),
icon: Time,
color: 'bg-orange-50 text-orange-600',
trend: t('userPersona.stats.focusTimeLabel')
}
]
})
// 渲染雷达图
const renderRadarChart = (data: IUserReadingPersona) => {
if (!chartRef.value) return
if (!myChart) myChart = echarts.init(chartRef.value)
const option = {
radar: {
indicator: [
{ name: t('userPersona.radar.cognitiveDepth'), max: 100 },
{ name: t('userPersona.radar.productionEfficiency'), max: 100 },
{ name: t('userPersona.radar.maturity'), max: 100 },
{ name: t('userPersona.radar.knowledgeBreadth'), max: 100 },
{ name: t('userPersona.radar.languageAbility'), max: 100 }
],
shape: 'circle',
radius: '65%',
splitArea: { areaStyle: { color: ['#F8F9FB', '#fff'] } },
axisLine: { lineStyle: { color: '#E5E7EB' } },
splitLine: { lineStyle: { color: '#E5E7EB' } }
},
series: [
{
type: 'radar',
data: [
{
value: [
data.domainDepth[0]?.score || 0,
data.efficiencyScore,
data.maturityScore,
data.breadthScore,
data.languageScore
],
name: '阅读画像',
itemStyle: { color: '#7816ff' },
areaStyle: { color: 'rgba(120, 22, 255, 0.15)' },
lineStyle: { width: 3 }
}
]
}
]
}
myChart.setOption(option)
}
// 渲染趋势柱状图
const renderTrendChart = (contributions: any[]) => {
if (!trendChartRef.value) return
if (!trendChart) trendChart = echarts.init(trendChartRef.value)
const recentData = contributions.slice(-7)
const option = {
// 1. 新增 Tooltip 配置
tooltip: {
trigger: 'axis',
backgroundColor: 'rgba(255, 255, 255, 0.9)', // 半透明白
backdropFilter: 'blur(4px)', // 毛玻璃效果 (部分浏览器支持)
borderColor: '#f1f5f9', // slate-100
borderWidth: 1,
padding: [8, 12],
textStyle: {
color: '#1e293b', // slate-800
fontSize: 12,
fontWeight: 'bold'
},
boxShadow: '0 4px 12px rgba(0, 0, 0, 0.05)',
formatter: (params: any) => {
const item = params[0]
return `
<div style="display: flex; flex-direction: column; gap: 4px;">
<div style="font-size: 10px; color: #94a3b8; text-transform: uppercase; font-weight: 800;">阅读数量</div>
<div style="display: flex; align-items: center; gap: 8px;">
<span style="display: inline-block; width: 8px; height: 8px; border-radius: 50%; background: #7816ff;"></span>
<span>${item.name}: <span style="color: #7816ff;">${item.value} 篇心得</span></span>
</div>
</div>
`
},
axisPointer: {
type: 'shadow', // 悬浮时背景阴影
shadowStyle: {
color: 'rgba(120, 22, 255, 0.03)' // 极淡的紫色背景,呼应主题
}
}
},
grid: { left: '3%', right: '3%', bottom: '3%', top: '15%', containLabel: true },
xAxis: {
type: 'category',
data: recentData.map((d) => d.date.split('-').slice(1).join('/')),
axisLine: { lineStyle: { color: '#E5E7EB' } },
axisTick: { show: false },
axisLabel: {
color: '#94a3b8',
fontSize: 10,
fontWeight: 'bold'
}
},
yAxis: {
type: 'value',
splitLine: { lineStyle: { type: 'dashed', color: '#F1F5F9' } },
axisLabel: { color: '#94a3b8', fontSize: 10 }
},
series: [
{
data: recentData.map((d) => d.count),
type: 'bar',
barWidth: '40%',
itemStyle: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{ offset: 0, color: '#9d50ff' },
{ offset: 1, color: '#7816ff' }
]),
borderRadius: [4, 4, 0, 0]
},
// 悬浮时高亮效果
emphasis: {
itemStyle: {
color: '#7816ff',
shadowBlur: 10,
shadowColor: 'rgba(120, 22, 255, 0.3)'
}
}
}
]
}
trendChart.setOption(option)
}
const fetchData = async (force = false) => {
setLoading(true)
try {
if (force) await trpc.persona.forceRefreshUserPersona.mutate()
const [persona, contributions] = await Promise.all([
trpc.persona.getUserPersona.query(),
trpc.persona.getContributionData.query()
])
if (persona) {
personaData.value = persona
await nextTick()
renderRadarChart(persona)
}
if (contributions) {
contributionData.value = contributions
await nextTick()
renderTrendChart(contributions)
}
} catch (error) {
console.error('数据同步失败:', error)
} finally {
setLoading(false)
}
}
onMounted(() => {
fetchData()
window.addEventListener('resize', () => {
myChart?.resize()
trendChart?.resize()
})
})
</script>
<template>
<div class="h-full w-full flex flex-col gap-xl relative overflow-hidden bg-[#F8F9FB]">
<header
class="h-16 border-b border-slate-100/50 px-6 flex items-center justify-between relative z-20 backdrop-blur-md bg-white/70"
>
<BackPage :title="t('userPersona.backTitle')" />
<a-button
@click="fetchData(true)"
:disabled="loading"
class="flex items-center gap-2 px-3 py-1.5 rounded-lg text-xs font-bold transition-all active:scale-95"
:class="loading ? 'text-slate-300' : 'text-[#7816ff] hover:bg-purple-50'"
>
<refresh :class="{ 'animate-spin': loading }" />
{{ loading ? t('userPersona.syncing') : t('userPersona.syncLatest') }}
</a-button>
</header>
<main class="flex-1 overflow-y-auto relative z-10 pb-10">
<div class="max-w-4xl mx-auto space-y-6 px-4">
<div class="grid grid-cols-3 gap-4">
<div
v-for="card in statsCards"
:key="card.label"
class="bg-white p-5 rounded-2xl border border-slate-100 shadow-sm hover:shadow-md transition-all group"
>
<div class="flex items-center justify-between mb-3">
<div
:class="[
'w-10 h-10 rounded-xl flex items-center justify-center transition-transform group-hover:scale-110',
card.color
]"
>
<component :is="card.icon" size="20" />
</div>
</div>
<div class="flex items-baseline gap-1">
<span class="text-2xl font-black text-slate-800">{{ card.value }}</span>
<span class="text-xs font-bold text-slate-400">{{ card.unit }}</span>
</div>
<p class="text-[11px] text-slate-500 mt-1 font-medium">{{ card.label }}</p>
</div>
</div>
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<div class="bg-white p-6 rounded-2xl shadow-sm border border-slate-100">
<div class="flex items-center gap-2 mb-4">
<chart-graph theme="filled" size="18" fill="#7816ff" />
<span class="text-sm font-black text-slate-800">{{ t('userPersona.charts.multidimensionalProfile') }}</span>
</div>
<div v-show="personaData" ref="chartRef" class="w-full h-[280px]"></div>
</div>
<div class="bg-white p-6 rounded-2xl shadow-sm border border-slate-100">
<div class="flex items-center gap-2 mb-4">
<chart-histogram theme="filled" size="18" fill="#7816ff" />
<span class="text-sm font-black text-slate-800">{{ t('userPersona.charts.productionDensity') }}</span>
</div>
<div ref="trendChartRef" class="w-full h-[280px]"></div>
</div>
</div>
<!--阅读贡献足迹-->
<ContributionWall v-if="contributionData.length > 0" :data="contributionData" :title="t('userPersona.contribution.title')" />
<div
v-if="personaData"
class="p-6 bg-[#7816ff] rounded-[32px] text-white shadow-xl shadow-purple-200/50 relative overflow-hidden group"
>
<div
class="absolute -right-10 -top-10 w-40 h-40 bg-white/10 rounded-full blur-3xl group-hover:scale-125 transition-transform duration-700"
></div>
<div class="relative z-10 flex flex-col">
<div class="flex flex-row items-center gap-2 mb-2">
<trend-two theme="outline" size="20" fill="#fff" />
<span class="text-md font-black">{{ t('userPersona.report.title') }}</span>
</div>
<p class="text-[14px] leading-relaxed opacity-95 font-medium">
{{ t('userPersona.report.analysis', {
totalWords: personaData.stats.totalWords,
domain: personaData.domainDepth[0]?.name || t('userPersona.report.exploration')
}) }}
</p>
<div class="flex flex-wrap gap-2 mt-4">
<span
v-for="kw in personaData.stats.topKeywords"
:key="kw"
class="text-[10px] bg-white/10 border border-white/20 px-3 py-1 rounded-full backdrop-blur-md font-bold"
>
# {{ kw }}
</span>
</div>
</div>
</div>
</div>
</main>
</div>
</template>
<style scoped>
/* 保持原有的动画 */
@keyframes spin {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
.animate-spin {
animation: spin 1s linear infinite;
}
/* 隐藏滚动条但保留功能 */
main::-webkit-scrollbar {
width: 0px;
}
</style>

View File

@@ -0,0 +1,142 @@
<script setup lang="ts">
import { computed, onMounted, ref } from 'vue'
import useRouterHook from '@renderer/hooks/useRouterHook'
import { useI18n } from 'vue-i18n'
import { Copy, Quote, FileCode, Share } from '@icon-park/vue-next'
import { trpc } from '@renderer/lib/trpc'
import { IReadingReflectionTaskItem } from '@shared/types/IReadingReflectionTask'
import { Message } from '@arco-design/web-vue'
import BackPage from '@renderer/components/BackPage.vue'
const { t } = useI18n()
const { getQuery, go } = useRouterHook()
const subTaskId = computed(() => getQuery('id') as string)
const readingData = ref<IReadingReflectionTaskItem>()
const isLoading = ref(true)
const fetchDetail = async () => {
if (!subTaskId.value) return
isLoading.value = true
try {
const data = await trpc.task.getItemDetail.query({ itemId: subTaskId.value })
console.log(data)
readingData.value = data as unknown as IReadingReflectionTaskItem
} finally {
isLoading.value = false
}
}
// 复制原文
const handleCopyContent = async () => {
if (!readingData.value?.content) return
await navigator.clipboard.writeText(readingData.value.content)
Message.success(t('reflection.index.copySuccess'))
}
// 导出文档
const handleExport = () => {
if (!readingData.value) return
go('/reflection/export', {
itemId: subTaskId.value
})
}
// 生成海报
const handlePoster = () => {
go('/reflection/poster', {
itemId: subTaskId.value
})
}
onMounted(() => fetchDetail())
</script>
<template>
<div class="h-full w-full flex flex-col gap-xl relative overflow-hidden">
<header
class="h-16 border-b border-slate-100/50 px-6 flex items-center justify-between relative z-20 backdrop-blur-md bg-white/70"
>
<BackPage :title="`阅读 | ${readingData?.title || ''}`" />
<div class="flex items-center gap-2">
<a-button
size="mini"
class="rounded-lg bg-[#7816ff] text-white border-none"
@click="handlePoster"
>
<template #icon><share theme="outline" size="14" /></template>
{{ t('reflection.index.sharePoster') }}
</a-button>
<a-button
size="mini"
class="rounded-lg bg-[#7816ff] text-white border-none"
@click="handleCopyContent"
>
<template #icon><Copy theme="outline" size="14" /></template>
{{ t('reflection.index.copyContent') }}
</a-button>
<a-button
size="mini"
class="rounded-lg bg-[#7816ff] text-white border-none"
@click="handleExport"
>
<template #icon><FileCode theme="outline" size="14" /></template>
{{ t('reflection.index.exportDocument') }}
</a-button>
</div>
</header>
<div class="flex-1 overflow-y-auto relative z-10 px-8 bg-white rounded-xl">
<!-- 悬浮的标题和关键词部分 -->
<div class="sticky top-0 z-20 bg-white/90 backdrop-blur-sm p-4 -mx-8 mb-4 border-b border-slate-100">
<h1 class="text-2xl md:text-3xl font-black text-slate-900 leading-tight mb-2">
{{ readingData?.title }}
</h1>
<div class="flex flex-wrap gap-1.5">
<span
v-for="tag in readingData?.keywords"
:key="tag"
class="px-2 py-1 bg-[#7816ff]/10 text-[#7816ff] text-[10px] font-bold rounded-md transition-all hover:bg-[#7816ff]/20"
># {{ tag }}</span
>
</div>
</div>
<div class="flex flex-col gap-xl pb-12">
<!-- 正文内容 -->
<div class="prose prose-slate max-w-none">
<span class="text-slate-700 text-[14px] md:text-[16px] leading-loose whitespace-pre-wrap font-serif">
{{ readingData?.content }}
</span>
</div>
<!-- 文章摘要 -->
<footer class="mt-8 bg-slate-50 rounded-2xl p-8 relative overflow-hidden transition-all hover:shadow-sm">
<quote class="absolute top-2 right-10 text-slate-100" size="20" theme="filled" />
<div class="relative z-10">
<div class="text-xs font-black text-[#7816ff] uppercase tracking-[0.2em] mb-3">{{ t('reflection.index.summary') }}</div>
<p class="text-sm text-slate-600 leading-relaxed italic font-medium">
"{{ readingData?.summary }}"
</p>
</div>
</footer>
</div>
</div>
</div>
</template>
<style scoped>
.custom-scroll::-webkit-scrollbar {
width: 6px;
}
.custom-scroll::-webkit-scrollbar-thumb {
background: #e2e8f0;
border-radius: 10px;
}
/* 字体优化:正文使用衬线体更有阅读感 */
.font-serif {
font-family: 'Optima', 'Simplified Chinese', 'STSong', serif;
}
</style>

View File

@@ -0,0 +1,307 @@
<script setup lang="ts">
import { ref, onMounted, computed } from 'vue'
import { trpc } from '@renderer/lib/trpc'
import useRouterHook from '@renderer/hooks/useRouterHook'
import { useI18n } from 'vue-i18n'
import { FileCode, Send, Loading, Info } from '@icon-park/vue-next'
import { Message } from '@arco-design/web-vue'
import BackPage from '@renderer/components/BackPage.vue'
const { t } = useI18n()
const { getQuery } = useRouterHook()
const subTaskId = computed(() => getQuery('itemId') as string)
const reflectionData = ref<any>(null)
const templates = ref<any[]>([])
const selectedTpl = ref<any>(null)
const tags = ref<string[]>([])
const formData = ref<Record<string, string>>({})
const exporting = ref(false)
const isLoading = ref(true)
const fieldConfig = computed(() => {
const autoTags = tags.value.filter((t) => t.startsWith('data.'))
const manualTags = tags.value.filter((t) => !t.startsWith('data.'))
return { autoTags, manualTags }
})
const initPage = async () => {
if (!subTaskId.value) {
Message.error(t('reflection.export.error.missingParams'))
return
}
try {
isLoading.value = true
const [data, tpls] = await Promise.all([
trpc.task.getItemDetail.query({ itemId: subTaskId.value }),
trpc.export.listTemplates.query()
])
reflectionData.value = data
templates.value = tpls
} catch (err: any) {
Message.error(t('reflection.export.error.initFailed'))
} finally {
isLoading.value = false
}
}
const selectTemplate = async (tpl: any) => {
selectedTpl.value = tpl
formData.value = {}
try {
const res = await trpc.export.getFields.query({ templateName: tpl.file })
tags.value = res
if (res.includes('date')) formData.value['date'] = new Date().toLocaleDateString()
} catch (err) {
Message.error(t('reflection.export.error.parseTemplateFailed'))
}
}
const handleExport = async () => {
if (!selectedTpl.value || !reflectionData.value) return
exporting.value = true
try {
const rawFormData = JSON.parse(JSON.stringify(formData.value || {}))
const rawReflectionData = JSON.parse(JSON.stringify(reflectionData.value))
const result = await trpc.export.generateMdFile.mutate({
templateName: selectedTpl.value.file,
formData: rawFormData,
reflectionData: rawReflectionData
})
if (result && result.success) {
Message.success(t('reflection.export.success'))
}
} catch (error: any) {
Message.error(`${t('reflection.export.error.exportFailed')}: ${error.message}`)
} finally {
exporting.value = false
}
}
const getObjectValue = (obj: any, path: string) => {
if (!obj) return '...'
const keys = path.replace('data.', '').split('.')
let val = obj
try {
for (const key of keys) {
if (val && typeof val === 'object' && key in val) val = val[key]
else return 'Empty'
}
return val
} catch (e) {
return 'Error'
}
}
onMounted(initPage)
</script>
<template>
<div class="flex flex-col h-screen bg-[#FAFAFB] overflow-hidden">
<header
class="h-16 px-8 bg-white/80 backdrop-blur-md border-b border-slate-100 flex items-center justify-between shrink-0 z-50"
>
<BackPage :title="t('reflection.export.title')" />
<div
v-if="reflectionData"
class="flex items-center gap-3 bg-purple-50/50 px-4 py-2 rounded-xl border border-purple-100/50"
>
<span class="text-xs font-black text-[#7816ff]/40 uppercase tracking-widest"
>当前文档:</span
>
<span class="text-xs font-bold text-[#7816ff] truncate max-w-[200px] font-mono">{{
reflectionData.title
}}</span>
</div>
</header>
<main class="flex-1 overflow-hidden p-8 flex flex-col gap-8">
<section class="shrink-0">
<div class="flex items-center gap-2 mb-5">
<div
class="w-1.5 h-4 bg-[#7816ff] rounded-full shadow-[0_0_8px_rgba(120,22,255,0.3)]"
></div>
<h3 class="text-sm font-black text-slate-800 tracking-tight">{{ t('reflection.export.step1') }}</h3>
</div>
<div class="grid grid-cols-4 gap-5">
<div
v-for="tpl in templates"
:key="tpl.id"
@click="selectTemplate(tpl)"
:class="[
'relative p-5 rounded-2xl border-2 transition-all duration-300 cursor-pointer group',
selectedTpl?.id === tpl.id
? 'border-[#7816ff] bg-white shadow-xl shadow-purple-100/50'
: 'border-transparent bg-white hover:border-purple-200 shadow-sm'
]"
>
<div class="flex items-center gap-4 relative z-10">
<div
:class="[
'w-12 h-12 rounded-xl flex items-center justify-center transition-all',
selectedTpl?.id === tpl.id
? 'bg-[#7816ff] text-white'
: 'bg-slate-50 text-slate-400'
]"
>
<FileCode theme="outline" size="24" />
</div>
<div class="flex flex-col">
<span class="text-xs font-black text-slate-700">{{ tpl.name }}</span>
<span
class="text-[9px] text-slate-400 font-bold uppercase mt-0.5 tracking-tighter"
>{{ tpl.file }}</span
>
</div>
</div>
</div>
</div>
</section>
<section v-if="!selectedTpl" class="flex-1 min-h-0">
<div
class="h-full w-full border-2 border-dashed border-slate-200 rounded-[32px] flex flex-col items-center justify-center bg-white/60"
>
<FileCode size="40" class="text-slate-200 mb-4" />
<p class="text-sm font-black text-slate-300">{{ t('reflection.export.selectTemplate') }}</p>
</div>
</section>
<section
v-else
class="flex-1 min-h-0 grid grid-cols-2 gap-8 animate-in fade-in slide-in-from-bottom-6 duration-500"
>
<div class="flex flex-col min-h-0">
<div class="flex items-center gap-2 mb-4 shrink-0">
<div class="w-1.5 h-4 bg-[#7816ff] rounded-full"></div>
<h3 class="text-sm font-black text-slate-800 tracking-tight">{{ t('reflection.export.step2') }}</h3>
</div>
<div
class="flex-1 bg-white rounded-[28px] border border-slate-100 p-8 flex flex-col overflow-hidden shadow-sm"
>
<div class="flex-1 overflow-y-auto pr-2 space-y-6 custom-scroll">
<div
v-for="tag in fieldConfig.manualTags"
:key="tag"
class="flex flex-col gap-2 group"
>
<label
class="text-[10px] font-black text-slate-400 uppercase tracking-widest font-mono group-focus-within:text-[#7816ff] transition-colors"
>
{{ tag }}
</label>
<a-input
v-model="formData[tag]"
class="!rounded-xl !border-none !bg-slate-50 !h-12 !font-mono"
/>
</div>
<div
v-if="fieldConfig.manualTags.length === 0"
class="h-full flex flex-col items-center justify-center text-slate-300 opacity-60"
>
<Info size="32" class="mb-2" />
<span class="text-xs font-bold uppercase tracking-widest">{{ t('reflection.export.noExtraInfo') }}</span>
</div>
</div>
</div>
</div>
<div class="flex flex-col min-h-0">
<div class="flex items-center gap-2 mb-4 shrink-0">
<div class="w-1.5 h-4 bg-[#7816ff] rounded-full"></div>
<h3 class="text-sm font-black text-slate-800 tracking-tight">{{ t('reflection.export.step3') }}</h3>
</div>
<div
class="flex-1 bg-purple-50/30 rounded-xl p-4 flex flex-col overflow-hidden border-2 border-dashed border-purple-100 relative"
>
<div class="flex-1 overflow-y-auto pr-2 space-y-4 custom-scroll relative z-10">
<div
v-for="tag in fieldConfig.autoTags"
:key="tag"
class="p-5 rounded-2xl bg-white border border-purple-100/50 group transition-all hover:shadow-md hover:border-[#7816ff]/30"
>
<div class="flex justify-between items-center mb-3">
<span
class="text-[9px] font-black text-[#7816ff] uppercase bg-purple-50 px-2.5 py-1 rounded tracking-tighter"
>
{{ tag }}
</span>
<div class="w-1.5 h-1.5 rounded-full bg-[#7816ff] animate-pulse"></div>
</div>
<div
class="text-[12px] text-slate-600 font-mono leading-relaxed break-all whitespace-pre-wrap selection:bg-purple-100"
>
{{ getObjectValue(reflectionData, tag) }}
</div>
</div>
</div>
<div class="pt-4 border-t border-purple-100/50 shrink-0">
<a-button
type="primary"
:disabled="exporting"
@click="handleExport"
class="w-full h-10 !rounded-2xl !bg-[#7816ff] !border-none shadow-xl shadow-purple-200 group active:scale-[0.98] transition-all"
>
<template #icon>
<Loading v-if="exporting" class="animate-spin" />
<Send
v-else
class="group-hover:translate-x-1 group-hover:-translate-y-1 transition-transform"
/>
</template>
<span class="text-xs font-black uppercase tracking-[0.2em] ml-1">
{{ exporting ? t('reflection.export.building') : t('reflection.export.export') }}
</span>
</a-button>
</div>
</div>
</div>
</section>
</main>
</div>
</template>
<style scoped>
.custom-scroll::-webkit-scrollbar {
width: 4px;
}
.custom-scroll::-webkit-scrollbar-thumb {
background: #e2e8f0;
border-radius: 10px;
}
.animate-spin {
animation: spin 1s linear infinite;
}
@keyframes spin {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
.font-mono {
font-family: 'Fira Code', 'Consolas', 'Courier New', monospace;
}
/* 简单的入场动画 */
.animate-in {
animation: slideUp 0.5s ease-out;
}
@keyframes slideUp {
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
</style>

View File

@@ -0,0 +1,241 @@
<script setup lang="ts">
import { computed, onMounted, ref } from 'vue'
import useRouterHook from '@renderer/hooks/useRouterHook'
import { ApplicationEffect, Camera, FontSize } from '@icon-park/vue-next'
import { trpc } from '@renderer/lib/trpc'
import html2canvas from 'html2canvas'
import { Message } from '@arco-design/web-vue'
import BackPage from '@renderer/components/BackPage.vue'
const { getQuery } = useRouterHook()
const subTaskId = computed(() => getQuery('itemId') as string)
const readingData = ref<any>(null)
const posterRef = ref<HTMLElement | null>(null)
const isExporting = ref(false)
// 设计参数
const settings = ref({
themeColor: '#7816ff',
fontSize: 15
})
const themes = [
{ name: '经典紫', color: '#7816ff' },
{ name: '松石绿', color: '#00b42a' },
{ name: '夕阳橙', color: '#ff7d00' },
{ name: '深邃蓝', color: '#165dff' },
{ name: '酷感黑', color: '#1a1a1a' }
]
const fetchDetail = async () => {
try {
readingData.value = await trpc.task.getItemDetail.query({ itemId: subTaskId.value })
} catch (e) {
Message.error('获取心得内容失败')
}
}
const handleExport = async () => {
if (!posterRef.value) return
isExporting.value = true
try {
// 等待 DOM 渲染完全稳定
await new Promise((r) => setTimeout(r, 300))
const canvas = await html2canvas(posterRef.value, {
scale: 2,
useCORS: true,
backgroundColor: null,
scrollX: 0,
scrollY: 0
})
const link = document.createElement('a')
link.download = `Insight-Poster-${Date.now()}.png`
link.href = canvas.toDataURL('image/png')
link.click()
Message.success('高清海报已导出')
} catch (e) {
Message.error('导出失败,请尝试减小字号')
} finally {
isExporting.value = false
}
}
onMounted(fetchDetail)
</script>
<template>
<div class="h-screen w-full flex bg-[#F8F9FB] overflow-hidden">
<aside
class="w-60 h-full bg-white border-r border-slate-200 flex flex-col flex-shrink-0 z-30 shadow-xl"
>
<header
class="h-16 px-8 flex items-center gap-4 bg-white/80 backdrop-blur-md border-b border-slate-100 relative z-10"
>
<BackPage title="分享海报" />
</header>
<div class="flex-1 overflow-y-auto p-8 space-y-10 custom-scroll">
<section class="space-y-4">
<div class="flex items-center justify-between">
<span class="text-[11px] font-black text-slate-400 uppercase tracking-widest">
海报色彩
</span>
<ApplicationEffect size="14" class="text-[#7816ff]" />
</div>
<div class="grid grid-cols-5 gap-3">
<div
v-for="item in themes"
:key="item.color"
@click="settings.themeColor = item.color"
class="w-5 h-5 aspect-square rounded-full cursor-pointer transition-all hover:scale-110 flex items-center justify-center border-2 shadow-sm"
:style="{
backgroundColor: item.color,
borderColor: settings.themeColor === item.color ? '#fff' : 'transparent',
boxShadow: settings.themeColor === item.color ? `0 0 0 2px ${item.color}` : 'none'
}"
>
<div
v-if="settings.themeColor === item.color"
class="w-13px h-13px bg-white rounded-full"
></div>
</div>
</div>
</section>
<section class="space-y-4">
<div class="flex items-center justify-between">
<span class="text-[11px] font-black text-slate-400 uppercase tracking-widest">
文字排版
</span>
<FontSize size="14" class="text-[#7816ff]" />
</div>
<div class="flex justify-between text-[10px] font-bold text-slate-500 mb-2">
<span>正文字号</span>
<span class="text-[#7816ff]">{{ settings.fontSize }}px</span>
</div>
<a-slider v-model="settings.fontSize" :min="12" :max="20" :step="1" />
</section>
<div class="p-2 bg-purple-50 rounded-2xl border border-purple-100/50">
<p class="text-[10px] text-[#7816ff] leading-relaxed">
<strong>设计建议</strong> 14px-16px
是最佳的阅读字号如果内容较多适当减小字号可以获得更好的留白感
</p>
</div>
</div>
<div class="p-8 border-t border-slate-50">
<button
@click="handleExport"
:disabled="isExporting"
class="w-full h-10 bg-[#7816ff] text-white rounded-2xl font-bold flex items-center justify-center gap-3 hover:bg-[#6a12e6] transition-all shadow-xl disabled:opacity-50"
>
<camera v-if="!isExporting" size="20" />
<span v-else class="animate-spin text-lg"></span>
{{ isExporting ? '正在生成海报...' : '导出高清图片' }}
</button>
</div>
</aside>
<main class="flex-1 h-full overflow-auto bg-[#E5E7EB] canvas-pattern custom-scroll">
<div class="min-h-full p-20 flex justify-center items-start">
<div
ref="posterRef"
class="w-[600px] flex-shrink-0 bg-white shadow-[0_30px_60px_-12px_rgba(0,0,0,0.2)] rounded-[40px] overflow-hidden mb-20 transition-all duration-500"
>
<div
class="p-5 flex flex-col"
:style="{
background: `linear-gradient(135deg, ${settings.themeColor}, ${settings.themeColor}dd)`
}"
>
<h1 class="text-white text-xl font-black text-center leading-tight">
{{ readingData?.title || '书籍标题' }}
</h1>
</div>
<div class="p-10 bg-white">
<div class="flex items-center gap-2">
<div
class="w-1.5 h-5 rounded-full"
:style="{ backgroundColor: settings.themeColor }"
></div>
<h2 class="text-sm font-black text-slate-800 tracking-tight">
{{ readingData?.title }}
</h2>
</div>
<p
class="text-slate-600 leading-[2.1] text-justify whitespace-pre-wrap transition-all font-medium"
:style="{ fontSize: settings.fontSize + 'px' }"
>
{{ readingData?.content }}
</p>
<div class="pt-8 w-full flex items-center">
<div class="flex items-center gap-4 m-a">
<div
class="w-12 h-12 rounded-2xl flex items-center justify-center text-white font-black text-2xl italic shadow-lg"
:style="{
backgroundColor: settings.themeColor,
boxShadow: `0 8px 16px -4px ${settings.themeColor}66`
}"
>
Z
</div>
<div>
<div class="text-[13px] font-black text-slate-800 tracking-tight">
读书心得助手
</div>
<div class="text-[9px] text-slate-400 font-bold uppercase tracking-widest mt-0.5">
让每一篇阅读者享受阅读与思考的乐趣
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</main>
</div>
</template>
<style scoped>
/* 画布打点背景,增强设计感 */
.canvas-pattern {
background-image: radial-gradient(#d1d5db 1px, transparent 1px);
background-size: 24px 24px;
}
/* 彻底隐藏原生滚动条,使用自定义样式 */
.custom-scroll::-webkit-scrollbar {
width: 6px;
height: 6px;
}
.custom-scroll::-webkit-scrollbar-thumb {
background: #cbd5e1;
border-radius: 10px;
}
.custom-scroll::-webkit-scrollbar-track {
background: transparent;
}
/* 强制锁定海报宽度,不让 flex 动态计算 */
.w-\[450px\] {
width: 450px !important;
min-width: 450px !important;
max-width: 450px !important;
}
@keyframes spin {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
.animate-spin {
animation: spin 1.2s linear infinite;
display: inline-block;
}
</style>

View File

@@ -0,0 +1,80 @@
<script setup lang="ts">
import { ref, onMounted } from 'vue'
import { useI18n } from 'vue-i18n'
import { trpc } from '@renderer/lib/trpc'
import { Message } from '@arco-design/web-vue'
const { locale } = useI18n()
const selectedLanguage = ref('zh')
const loading = ref(false)
const LANGUAGE_OPTIONS = [
{ label: '中文', value: 'zh' },
{ label: '英文', value: 'en' },
{ label: '西班牙语', value: 'es' },
{ label: '日语', value: 'ja' },
{ label: '韩语', value: 'ko' }
]
// 初始化语言设置
onMounted(async () => {
try {
// 从配置中获取当前语言
const languageConfig = await trpc.config.getLanguageConfig.query()
selectedLanguage.value = languageConfig?.language || 'zh'
locale.value = selectedLanguage.value
} catch (err) {
console.error('Failed to get language config:', err)
Message.error('获取语言配置失败')
}
})
// 处理语言切换
const handleLanguageChange = async () => {
loading.value = true
try {
// 更新i18n语言
locale.value = selectedLanguage.value
// 保存到配置
await trpc.config.saveLanguageConfig.mutate({
language: selectedLanguage.value as 'zh' | 'en' | 'es' | 'ja' | 'ko'
})
Message.success('语言设置已更新')
} catch (err) {
console.error('Failed to save language config:', err)
Message.error('保存语言配置失败')
} finally {
loading.value = false
}
}
</script>
<template>
<div class="language-setting p-6 bg-white rounded-xl border border-slate-100">
<h3 class="text-lg font-bold text-slate-800 mb-6">{{ $t('setting.language') }}</h3>
<div class="space-y-4">
<div
v-for="item in LANGUAGE_OPTIONS"
:key="item.value"
class="flex items-center justify-between p-4 bg-slate-50 rounded-lg"
>
<div class="flex items-center gap-3">
<span class="text-slate-700">{{ $t(item.label) }}</span>
</div>
<a-radio
v-model="selectedLanguage"
:value="item.value"
@change="handleLanguageChange"
:disabled="loading"
/>
</div>
</div>
</div>
</template>
<style scoped>
.language-setting {
max-width: 600px;
}
</style>

View File

@@ -1,62 +1,91 @@
<script setup lang="ts">
import { ref } from 'vue'
import { onMounted, reactive, ref, toRaw } from 'vue'
import { SettingConfig } from '@icon-park/vue-next'
import { Message } from '@arco-design/web-vue'
import { trpc } from '@renderer/lib/trpc'
import { modelOptions } from '../data/ModelData'
import { ModelForm } from '@renderer/pages/setting/types/IModel'
interface ModelOption {
id: string // id modelName
name: string
desc: string
tag?: string
const activeTab = ref<'reading' | 'summary'>('reading') // reading
const modelForm = reactive<ModelForm>({
reading: {
apiKey: 'sk-172309b16482e6ad4264b1cd89f010d8',
baseURL: 'https://apis.iflow.cn/v1',
modelName: 'deepseek-v3.2',
temperature: 0.7
},
summary: {
apiKey: 'sk-172309b16482e6ad4264b1cd89f010d8',
baseURL: 'https://apis.iflow.cn/v1',
modelName: 'deepseek-v3.2',
temperature: 0.3
}
})
// --- ---
const loadConfig = async () => {
try {
const savedConfig = (await trpc.config.getChatConfigs.query()) as any
if (savedConfig) {
// Key chatModels.reading
if (savedConfig.reading) Object.assign(modelForm.reading, savedConfig.reading)
if (savedConfig.summary) Object.assign(modelForm.summary, savedConfig.summary)
}
} catch (error) {
console.error('加载配置失败:', error)
}
}
interface ModelConfig {
apiKey: string
baseURL: string
modelName: string
temperature: number
const handleSave = async (type: 'reading' | 'summary') => {
try {
// 使 toRaw Vue
const configToSave = toRaw(modelForm[type])
// Proxy
console.log('正在保存纯对象数据:', configToSave)
await trpc.config.saveChatConfig.mutate({
type,
config: configToSave
})
Message.success({
content: `${type === 'reading' ? '读书心得' : '摘要总结'}配置已安全保存`,
showIcon: true
})
} catch (error) {
Message.error('保存失败,无法克隆对象')
console.error('tRPC 错误详情:', error)
}
}
//
interface ModelForm {
reflection: ModelConfig
summary: ModelConfig
}
const props = defineProps<{
modelValue: ModelForm // reactive
options?: ModelOption[]
}>()
const emit = defineEmits(['update:modelValue', 'save'])
//
const activeTab = ref<'reflection' | 'summary'>('reflection')
// Tab modelName
const selectModel = (modelId: string) => {
const newData = { ...props.modelValue }
newData[activeTab.value].modelName = modelId
emit('update:modelValue', newData)
modelForm[activeTab.value].modelName = modelId
}
onMounted(() => {
loadConfig()
})
</script>
<template>
<div v-if="modelValue" class="space-y-6 animate-in">
<div class="space-y-6 animate-in pb-10">
<a-tabs v-model:active-key="activeTab" type="capsule" class="custom-tabs">
<a-tab-pane key="reflection" title="读书心得模型" />
<a-tab-pane key="reading" title="读书心得模型" />
<a-tab-pane key="summary" title="摘要总结模型" />
</a-tabs>
<div class="bg-white rounded-2xl border border-slate-100 shadow-sm p-8">
<div class="flex items-center space-x-2 mb-8">
<div class="w-1 h-5 bg-[#7816ff] rounded-full"></div>
<h3 class="text-sm font-bold text-slate-800">接口配置</h3>
</div>
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<div class="space-y-2">
<label class="setting-label">Base URL (API 代理地址)</label>
<a-input
v-model="modelValue[activeTab].baseURL"
v-model="modelForm[activeTab].baseURL"
placeholder="https://api.openai.com/v1"
class="custom-input"
/>
@@ -64,7 +93,7 @@ const selectModel = (modelId: string) => {
<div class="space-y-2">
<label class="setting-label">API Key</label>
<a-input-password
v-model="modelValue[activeTab].apiKey"
v-model="modelForm[activeTab].apiKey"
placeholder="sk-..."
class="custom-input"
/>
@@ -77,37 +106,34 @@ const selectModel = (modelId: string) => {
<div class="w-1 h-5 bg-[#7816ff] rounded-full"></div>
<h3 class="text-sm font-bold text-slate-800">模型名称 (modelName)</h3>
</div>
<div class="mb-6">
<a-input
v-model="modelValue[activeTab].modelName"
placeholder="或者手动输入模型 ID如 gpt-4o"
v-model="modelForm[activeTab].modelName"
placeholder="手动输入模型 ID如 gpt-4o"
class="custom-input"
/>
</div>
<div class="grid grid-cols-1 md:grid-cols-3 gap-4">
<div
v-for="m in options"
v-for="m in modelOptions"
:key="m.id"
@click="selectModel(m.id)"
:class="[
'p-4 rounded-xl border-2 cursor-pointer transition-all relative overflow-hidden',
modelValue[activeTab].modelName === m.id
modelForm[activeTab].modelName === m.id
? 'border-[#7816ff] bg-purple-50/30'
: 'border-slate-50 bg-slate-50/50 hover:border-slate-200'
]"
>
<div class="flex justify-between items-start mb-2">
<span class="text-xs font-bold text-slate-800">{{ m.name }}</span>
<span v-if="m.tag" class="text-[9px] bg-[#7816ff] text-white px-1.5 py-0.5 rounded">
{{ m.tag }}
</span>
<span v-if="m.tag" class="text-[9px] bg-[#7816ff] text-white px-1.5 py-0.5 rounded">{{
m.tag
}}</span>
</div>
<p class="text-[10px] text-slate-400 leading-relaxed">{{ m.desc }}</p>
<div
v-if="modelValue[activeTab].modelName === m.id"
v-if="modelForm[activeTab].modelName === m.id"
class="absolute -right-2 -bottom-2 text-[#7816ff]/10"
>
<SettingConfig theme="outline" size="48" />
@@ -120,33 +146,31 @@ const selectModel = (modelId: string) => {
<div class="flex items-center space-x-2 mb-8">
<div class="w-1 h-5 bg-[#7816ff] rounded-full"></div>
<h3 class="text-sm font-bold text-slate-800">运行参数</h3>
<p class="text-[11px] text-[#7816ff] leading-relaxed">
<p class="text-[11px] text-[#7816ff] leading-relaxed ml-4 italic">
正在配置
<span class="font-bold underline">{{
activeTab === 'reflection' ? '心得生成' : '文本总结'
activeTab === 'reading' ? '心得生成' : '文本总结'
}}</span>
专用模型 建议为总结模型设置较低的 Temperature 以保证稳定性
专用模型
</p>
</div>
<div class="space-y-8">
<div class="space-y-4">
<div class="flex justify-between items-center">
<label class="setting-label"
>随机性 (Temperature): {{ modelValue[activeTab].temperature }}</label
>随机性 (Temperature): {{ modelForm[activeTab].temperature }}</label
>
<span class="text-[10px] text-slate-400">建议心得 0.7-0.8总结 0.2-0.3</span>
</div>
<a-slider v-model="modelValue[activeTab].temperature" :min="0" :max="1.2" :step="0.1" />
<a-slider v-model="modelForm[activeTab].temperature" :min="0" :max="1.2" :step="0.1" />
</div>
<div class="flex justify-end pt-4 border-t border-slate-50">
<a-button
type="primary"
class="rounded-lg bg-[#7816ff] border-none px-8 font-bold"
@click="emit('save', activeTab)"
@click="handleSave(activeTab)"
>
保存 {{ activeTab === 'reflection' ? '心得' : '总结' }} 配置
保存 {{ activeTab === 'reading' ? '心得' : '总结' }} 配置
</a-button>
</div>
</div>
@@ -155,7 +179,7 @@ const selectModel = (modelId: string) => {
</template>
<style scoped>
/* 继承原有样式 */
/* 样式部分保持不变... */
.setting-label {
display: block;
font-size: 11px;
@@ -165,17 +189,24 @@ const selectModel = (modelId: string) => {
letter-spacing: 0.05em;
padding: 0 4px;
}
/* Tabs 风格微调适配你的紫色主题 */
:deep(.custom-tabs .arco-tabs-nav-capsule) {
background-color: #f1f5f9;
border-radius: 12px;
padding: 4px;
:deep(.custom-input) {
background-color: #fcfcfd !important;
border: 1px solid #f1f5f9 !important;
border-radius: 12px !important;
font-size: 13px;
}
:deep(.custom-tabs .arco-tabs-nav-capsule-light) {
background-color: #fff;
color: #7816ff;
font-weight: bold;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
@keyframes fadeIn {
from {
opacity: 0;
transform: translateY(10px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.animate-in {
animation: fadeIn 0.4s ease-out forwards;
}
</style>

View File

@@ -0,0 +1,87 @@
<script setup lang="ts">
import { Hourglass } from '@icon-park/vue-next'
</script>
<template>
<div class="h-[600px] flex flex-col items-center justify-center animate-in">
<div
class="relative bg-white rounded-3xl border border-slate-100 shadow-xl shadow-slate-200/50 p-12 max-w-md w-full text-center overflow-hidden"
>
<div class="space-y-3 relative z-10">
<h2 class="text-xl font-bold text-slate-800 tracking-tight">
<span class="text-[#7816ff]">正在构建中</span>
</h2>
<p class="text-sm text-slate-400 leading-relaxed px-3">
我们正在为该模块编写最后一行代码以确保为您提供最极致的体验
</p>
</div>
<div class="mt-10 space-y-4">
<div class="h-2 w-full bg-slate-100 rounded-full overflow-hidden">
<div
class="h-full bg-gradient-to-r from-[#7816ff] to-[#a855f7] w-[50%] rounded-full relative"
>
<div
class="absolute top-0 right-0 h-full w-2 bg-white/30 skew-x-12 animate-shimmer"
></div>
</div>
</div>
</div>
<div class="mt-10 pt-8 border-t border-slate-50 flex items-center justify-center gap-6">
<div class="flex items-center gap-2 text-[11px] text-slate-400 font-medium">
<hourglass theme="outline" size="14" fill="#94a3b8" />
预计下个版本上线
</div>
</div>
</div>
</div>
</template>
<style scoped>
@keyframes bounce {
0%,
100% {
transform: translateY(0);
}
50% {
transform: translateY(-10px);
}
}
@keyframes spin {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
@keyframes shimmer {
from {
transform: translateX(-100%) skewX(-12deg);
}
to {
transform: translateX(200%) skewX(-12deg);
}
}
.animate-shimmer {
animation: shimmer 2s infinite linear;
}
@keyframes fadeIn {
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.animate-in {
animation: fadeIn 0.6s cubic-bezier(0.22, 1, 0.36, 1) forwards;
}
</style>

Some files were not shown because too many files have changed in this diff Show More