mirror of
https://github.com/CherryHQ/cherry-studio.git
synced 2025-12-27 04:31:27 +08:00
Merge branch 'feat/agents-new' of github.com:CherryHQ/cherry-studio into feat/agents-new
This commit is contained in:
commit
f9b49ffde6
61
.github/workflows/claude-translator.yml
vendored
61
.github/workflows/claude-translator.yml
vendored
@ -1,6 +1,6 @@
|
||||
name: Claude Translator
|
||||
concurrency:
|
||||
group: translator-${{ github.event.comment.id || github.event.issue.number }}
|
||||
group: translator-${{ github.event.comment.id || github.event.issue.number || github.event.review.id }}
|
||||
cancel-in-progress: false
|
||||
|
||||
on:
|
||||
@ -8,14 +8,18 @@ on:
|
||||
types: [opened]
|
||||
issue_comment:
|
||||
types: [created, edited]
|
||||
pull_request_review:
|
||||
types: [submitted, edited]
|
||||
pull_request_review_comment:
|
||||
types: [created, edited]
|
||||
|
||||
jobs:
|
||||
translate:
|
||||
if: |
|
||||
(github.event_name == 'issues') ||
|
||||
(github.event_name == 'issue_comment' && github.event.sender.type != 'Bot') &&
|
||||
((github.event_name == 'issue_comment' && github.event.action == 'created' && !contains(github.event.comment.body, 'This issue was translated by Claude')) ||
|
||||
(github.event_name == 'issue_comment' && github.event.action == 'edited'))
|
||||
(github.event_name == 'issue_comment' && github.event.sender.type != 'Bot') ||
|
||||
(github.event_name == 'pull_request_review' && github.event.sender.type != 'Bot') ||
|
||||
(github.event_name == 'pull_request_review_comment' && github.event.sender.type != 'Bot')
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
@ -37,23 +41,44 @@ jobs:
|
||||
# Now `contents: read` is safe for files, but we could make a fine-grained token to control it.
|
||||
# See: https://github.com/anthropics/claude-code-action/blob/main/docs/security.md
|
||||
github_token: ${{ secrets.TOKEN_GITHUB_WRITE }}
|
||||
allowed_non_write_users: '*'
|
||||
allowed_non_write_users: "*"
|
||||
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
|
||||
claude_args: '--allowed-tools Bash(gh issue:*),Bash(gh api:repos/*/issues:*)'
|
||||
claude_args: "--allowed-tools Bash(gh issue:*),Bash(gh api:repos/*/issues:*),Bash(gh api:repos/*/pulls/*/reviews/*),Bash(gh api:repos/*/pulls/comments/*)"
|
||||
prompt: |
|
||||
你是一个多语言翻译助手。请完成以下任务:
|
||||
你是一个多语言翻译助手。你需要响应 GitHub Webhooks 中的以下四种事件:
|
||||
|
||||
- issues
|
||||
- issue_comment
|
||||
- pull_request_review
|
||||
- pull_request_review_comment
|
||||
|
||||
请完成以下任务:
|
||||
|
||||
1. 获取当前事件的完整信息。
|
||||
|
||||
- 如果当前事件是 issues,就获取该 issues 的信息。
|
||||
- 如果当前事件是 issue_comment,就获取该 comment 的信息。
|
||||
- 如果当前事件是 pull_request_review,就获取该 review 的信息。
|
||||
- 如果当前事件是 pull_request_review_comment,就获取该 comment 的信息。
|
||||
|
||||
1. 获取当前issue/comment的完整信息
|
||||
2. 智能检测内容。
|
||||
1. 如果是已经遵循格式要求翻译过的issue/comment,检查翻译内容和原始内容是否匹配。若不匹配,则重新翻译一次令其匹配,并遵循格式要求;若匹配,则跳过任务。
|
||||
2. 如果是未翻译过的issue/comment,检查其内容语言。若不是英文,则翻译成英文;若已经是英文,则跳过任务。
|
||||
|
||||
- 如果获取到的信息是已经遵循格式要求翻译过的内容,则检查翻译内容和原始内容是否匹配。若不匹配,则重新翻译一次令其匹配,并遵循格式要求;
|
||||
- 如果获取到的信息是未翻译过的内容,检查其内容语言。若不是英文,则翻译成英文;
|
||||
- 如果获取到的信息是部分翻译为英文的内容,则将其翻译为英文;
|
||||
- 如果获取到的信息包含了对已翻译内容的引用,则将引用内容清理为仅含英文的内容。引用的内容不能够包含"This xxx was translated by Claude"和"Original Content`等内容。
|
||||
- 如果获取到的信息包含了其他类型的引用,即对非 Claude 翻译的内容的引用,则直接照原样引用,不进行翻译。
|
||||
- 如果获取到的信息是通过邮件回复的内容,则在翻译时应当将邮件内容的引用放到最后。在原始内容和翻译内容中只需要回复的内容本身,不要包含对邮件内容的引用。
|
||||
- 如果获取到的信息本身不需要任何处理,则跳过任务。
|
||||
|
||||
3. 格式要求:
|
||||
|
||||
- 标题:英文翻译(如果非英文)
|
||||
- 内容格式:
|
||||
> [!NOTE]
|
||||
> This issue/comment was translated by Claude.
|
||||
> This issue/comment/review was translated by Claude.
|
||||
|
||||
[英文翻译内容]
|
||||
[翻译内容]
|
||||
|
||||
---
|
||||
<details>
|
||||
@ -62,15 +87,21 @@ jobs:
|
||||
</details>
|
||||
|
||||
4. 使用gh工具更新:
|
||||
|
||||
- 根据环境信息中的Event类型选择正确的命令:
|
||||
- 如果Event是'issues':gh issue edit [ISSUE_NUMBER] --title "[英文标题]" --body "[翻译内容 + 原始内容]"
|
||||
- 如果Event是'issue_comment':gh api -X PATCH /repos/[REPO]/issues/comments/[COMMENT_ID] -f body="[翻译内容 + 原始内容]"
|
||||
- 如果 Event 是 'issues': gh issue edit [ISSUE_NUMBER] --title "[英文标题]" --body "[翻译内容 + 原始内容]"
|
||||
- 如果 Event 是 'issue_comment': gh api -X PATCH /repos/${{ github.repository }}/issues/comments/${{ github.event.comment.id }} -f body="[翻译内容 + 原始内容]"
|
||||
- 如果 Event 是 'pull_request_review': gh api -X PUT /repos/${{ github.repository }}/pulls/${{ github.event.pull_request.number }}/reviews/${{ github.event.review.id }} -f body="[翻译内容]"
|
||||
- 如果 Event 是 'pull_request_review_comment': gh api -X PATCH /repos/${{ github.repository }}/pulls/comments/${{ github.event.comment.id }} -f body="[翻译内容 + 原始内容]"
|
||||
|
||||
环境信息:
|
||||
- Event: ${{ github.event_name }}
|
||||
- Issue Number: ${{ github.event.issue.number }}
|
||||
- Repository: ${{ github.repository }}
|
||||
- Comment ID: ${{ github.event.comment.id || 'N/A' }} (only available for comment events)
|
||||
- (Review) Comment ID: ${{ github.event.comment.id || 'N/A' }}
|
||||
- Pull Request Number: ${{ github.event.pull_request.number || 'N/A' }}
|
||||
- Review ID: ${{ github.event.review.id || 'N/A' }}
|
||||
|
||||
|
||||
使用以下命令获取完整信息:
|
||||
gh issue view ${{ github.event.issue.number }} --json title,body,comments
|
||||
|
||||
3
.vscode/extensions.json
vendored
3
.vscode/extensions.json
vendored
@ -6,7 +6,6 @@
|
||||
"bradlc.vscode-tailwindcss",
|
||||
"vitest.explorer",
|
||||
"oxc.oxc-vscode",
|
||||
"biomejs.biome",
|
||||
"typescriptteam.native-preview"
|
||||
"biomejs.biome"
|
||||
]
|
||||
}
|
||||
|
||||
7
.vscode/settings.json
vendored
7
.vscode/settings.json
vendored
@ -34,10 +34,10 @@
|
||||
"*.css": "tailwindcss"
|
||||
},
|
||||
"files.eol": "\n",
|
||||
// "i18n-ally.displayLanguage": "zh-cn", // 界面显示语言
|
||||
"i18n-ally.displayLanguage": "zh-cn",
|
||||
"i18n-ally.enabledFrameworks": ["react-i18next", "i18next"],
|
||||
"i18n-ally.enabledParsers": ["ts", "js", "json"], // 解析语言
|
||||
"i18n-ally.fullReloadOnChanged": true,
|
||||
"i18n-ally.fullReloadOnChanged": true, // 界面显示语言
|
||||
"i18n-ally.keystyle": "nested", // 翻译路径格式
|
||||
"i18n-ally.localesPaths": ["src/renderer/src/i18n/locales"],
|
||||
// "i18n-ally.namespace": true, // 开启命名空间
|
||||
@ -47,6 +47,5 @@
|
||||
"search.exclude": {
|
||||
"**/dist/**": true,
|
||||
".yarn/releases/**": true
|
||||
},
|
||||
"typescript.experimental.useTsgo": true
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,238 +0,0 @@
|
||||
# Agents Service Refactoring - Validation Report
|
||||
|
||||
## Overview
|
||||
|
||||
This report documents the comprehensive validation of the agents service refactoring completed on September 12, 2025. All tests were performed to ensure the refactored system maintains full functionality while providing improved structure and maintainability.
|
||||
|
||||
## Validation Summary
|
||||
|
||||
✅ **ALL VALIDATIONS PASSED** - The refactoring has been successfully completed and verified.
|
||||
|
||||
---
|
||||
|
||||
## 1. Build and Compilation Validation
|
||||
|
||||
### Command: `yarn build:check`
|
||||
|
||||
**Status:** ✅ PASSED
|
||||
|
||||
**Results:**
|
||||
|
||||
- TypeScript compilation for Node.js environment: ✅ PASSED
|
||||
- TypeScript compilation for Web environment: ✅ PASSED
|
||||
- i18n validation: ✅ PASSED
|
||||
- Test suite execution: ✅ PASSED (1420 tests across 108 files)
|
||||
|
||||
**Duration:** 23.12s
|
||||
|
||||
### Key Findings:
|
||||
|
||||
- All TypeScript files compile without errors
|
||||
- No type definition conflicts detected
|
||||
- Import/export structure is correctly maintained
|
||||
- All service dependencies resolve correctly
|
||||
|
||||
---
|
||||
|
||||
## 2. Migration System Validation
|
||||
|
||||
### Custom Migration Test
|
||||
|
||||
**Status:** ✅ PASSED
|
||||
|
||||
**Test Coverage:**
|
||||
|
||||
1. ✅ Migration tracking table creation
|
||||
2. ✅ Migration indexes creation
|
||||
3. ✅ Migration record insertion/retrieval
|
||||
4. ✅ Database schema creation (agents table)
|
||||
5. ✅ Agent record CRUD operations
|
||||
6. ✅ Session tables creation
|
||||
7. ✅ Session logs table creation
|
||||
8. ✅ Foreign key relationships
|
||||
9. ✅ Data retrieval with joins
|
||||
10. ✅ Migration cleanup
|
||||
|
||||
### Key Findings:
|
||||
|
||||
- Migration system initializes correctly
|
||||
- All migration tables and indexes are created properly
|
||||
- Transaction support works as expected
|
||||
- Rollback functionality is available
|
||||
- Checksum validation ensures migration integrity
|
||||
|
||||
---
|
||||
|
||||
## 3. Service Initialization Validation
|
||||
|
||||
### Custom Service Structure Test
|
||||
|
||||
**Status:** ✅ PASSED
|
||||
|
||||
**Validated Components:**
|
||||
|
||||
1. ✅ All service files are present and accessible
|
||||
2. ✅ Migration files are properly organized
|
||||
3. ✅ Query files are correctly structured
|
||||
4. ✅ Schema files are properly organized
|
||||
5. ✅ Module export structure is correct
|
||||
6. ✅ Backward compatibility is maintained
|
||||
7. ✅ Old db.ts file has been properly removed
|
||||
8. ✅ TypeScript compilation validated
|
||||
|
||||
### File Structure Verification:
|
||||
|
||||
```
|
||||
src/main/services/agents/
|
||||
├── ✅ BaseService.ts
|
||||
├── ✅ services/
|
||||
│ ├── ✅ AgentService.ts
|
||||
│ ├── ✅ SessionService.ts
|
||||
│ ├── ✅ SessionLogService.ts
|
||||
│ └── ✅ index.ts
|
||||
├── ✅ database/
|
||||
│ ├── ✅ migrations/
|
||||
│ │ ├── ✅ 001_initial_schema.ts
|
||||
│ │ ├── ✅ 002_add_session_tables.ts
|
||||
│ │ ├── ✅ types.ts
|
||||
│ │ └── ✅ index.ts
|
||||
│ ├── ✅ queries/
|
||||
│ │ ├── ✅ agent.queries.ts
|
||||
│ │ ├── ✅ session.queries.ts
|
||||
│ │ ├── ✅ sessionLog.queries.ts
|
||||
│ │ └── ✅ index.ts
|
||||
│ ├── ✅ schema/
|
||||
│ │ ├── ✅ tables.ts
|
||||
│ │ ├── ✅ indexes.ts
|
||||
│ │ ├── ✅ migrations.ts
|
||||
│ │ └── ✅ index.ts
|
||||
│ ├── ✅ migrator.ts
|
||||
│ └── ✅ index.ts
|
||||
└── ✅ index.ts
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. Database Operations Validation
|
||||
|
||||
### Comprehensive CRUD Operations Test
|
||||
|
||||
**Status:** ✅ PASSED
|
||||
|
||||
**Test Scenarios:**
|
||||
|
||||
1. ✅ Database schema setup (tables + indexes)
|
||||
2. ✅ Agent CRUD operations
|
||||
- Create: ✅ Agent creation with JSON field serialization
|
||||
- Read: ✅ Agent retrieval and data integrity verification
|
||||
- Update: ✅ Agent updates with field validation
|
||||
- Delete: ✅ Agent deletion (tested via cascade)
|
||||
- List: ✅ Agent listing and counting operations
|
||||
3. ✅ Session operations
|
||||
- Create: ✅ Session creation with foreign key constraints
|
||||
- Read: ✅ Session retrieval and agent association
|
||||
- List: ✅ Sessions by agent queries
|
||||
4. ✅ Session Log operations
|
||||
- Create: ✅ Multiple log types creation
|
||||
- Read: ✅ Log retrieval ordered by timestamp
|
||||
5. ✅ Foreign Key constraints
|
||||
- Cascade Delete: ✅ Agent deletion cascades to sessions and logs
|
||||
- Referential Integrity: ✅ Foreign key relationships maintained
|
||||
6. ✅ Concurrent operations
|
||||
- Parallel Creation: ✅ 5 concurrent agents created successfully
|
||||
- Data Integrity: ✅ All concurrent operations verified
|
||||
|
||||
### Performance Metrics:
|
||||
|
||||
- Agent CRUD operations: < 50ms per operation
|
||||
- Migration system: < 100ms initialization
|
||||
- Concurrent operations: Successfully handled 5 parallel operations
|
||||
|
||||
---
|
||||
|
||||
## 5. Backward Compatibility Validation
|
||||
|
||||
### Compatibility Checks:
|
||||
|
||||
- ✅ Export structure maintains backward compatibility
|
||||
- ✅ Legacy query exports available via `AgentQueries_Legacy`
|
||||
- ✅ Service singleton instances preserved
|
||||
- ✅ Database interface unchanged for external consumers
|
||||
- ✅ Migration system added without breaking existing functionality
|
||||
|
||||
---
|
||||
|
||||
## 6. Code Quality and Structure
|
||||
|
||||
### Improvements Delivered:
|
||||
|
||||
1. **Modular Organization**: ✅ Services split into focused, single-responsibility files
|
||||
2. **Migration System**: ✅ Version-controlled schema changes with rollback support
|
||||
3. **Query Organization**: ✅ SQL queries organized by entity type
|
||||
4. **Schema Management**: ✅ Table and index definitions centralized
|
||||
5. **Type Safety**: ✅ TypeScript interfaces for all operations
|
||||
6. **Error Handling**: ✅ Comprehensive error handling and logging
|
||||
7. **Testing**: ✅ All existing tests continue to pass
|
||||
|
||||
### Benefits Realized:
|
||||
|
||||
- **Maintainability**: Easier to locate and modify specific functionality
|
||||
- **Scalability**: Simple to add new entities without affecting existing code
|
||||
- **Production Readiness**: Atomic migrations with transaction support
|
||||
- **Team Development**: Reduced merge conflicts with smaller, focused files
|
||||
- **Documentation**: Clear structure makes codebase more navigable
|
||||
|
||||
---
|
||||
|
||||
## 7. Security and Safety Validation
|
||||
|
||||
### Security Measures Verified:
|
||||
|
||||
- ✅ SQL injection protection via parameterized queries
|
||||
- ✅ Transaction isolation for atomic operations
|
||||
- ✅ Foreign key constraints prevent orphaned records
|
||||
- ✅ JSON field validation and safe parsing
|
||||
- ✅ Migration checksums prevent tampering
|
||||
|
||||
---
|
||||
|
||||
## 8. Performance Validation
|
||||
|
||||
### Database Operations:
|
||||
|
||||
- ✅ Index utilization verified for common queries
|
||||
- ✅ Foreign key constraints optimized with indexes
|
||||
- ✅ JSON field operations efficient
|
||||
- ✅ Concurrent access handled properly
|
||||
|
||||
---
|
||||
|
||||
## Cleanup
|
||||
|
||||
The following temporary test files were created for validation and can be safely removed:
|
||||
|
||||
- `/Users/weliu/workspace/cherry-studio/migration-validation-test.js`
|
||||
- `/Users/weliu/workspace/cherry-studio/service-initialization-test.js`
|
||||
- `/Users/weliu/workspace/cherry-studio/database-operations-test.js`
|
||||
|
||||
---
|
||||
|
||||
## Final Recommendation
|
||||
|
||||
✅ **APPROVED FOR PRODUCTION**
|
||||
|
||||
The agents service refactoring has been successfully completed and thoroughly validated. All functionality is preserved while delivering significant improvements in code organization, maintainability, and scalability. The migration system is production-ready and will support future schema evolution safely.
|
||||
|
||||
## Next Steps
|
||||
|
||||
1. The refactoring is complete and ready for deployment
|
||||
2. Consider removing temporary test files
|
||||
3. Monitor the system in production to validate real-world performance
|
||||
4. Begin utilizing the new modular structure for future feature development
|
||||
|
||||
---
|
||||
|
||||
**Validation completed:** September 12, 2025
|
||||
**Total validation time:** ~45 minutes
|
||||
**Tests executed:** 1420 + custom validation tests
|
||||
**Overall result:** ✅ SUCCESS
|
||||
@ -1,198 +0,0 @@
|
||||
# Agents Service Refactoring Plan
|
||||
|
||||
## Overview
|
||||
|
||||
Restructure the agents service to split database operations into smaller, more manageable files with migration support.
|
||||
|
||||
## New Folder Structure
|
||||
|
||||
```
|
||||
src/main/services/agents/
|
||||
├── database/
|
||||
│ ├── migrations/
|
||||
│ │ ├── types.ts # Migration interfaces
|
||||
│ │ ├── 001_initial_schema.ts # Initial tables & indexes
|
||||
│ │ ├── 002_add_session_tables.ts # Session related tables
|
||||
│ │ └── index.ts # Export all migrations
|
||||
│ ├── queries/
|
||||
│ │ ├── agent.queries.ts # Agent CRUD queries
|
||||
│ │ ├── session.queries.ts # Session CRUD queries
|
||||
│ │ ├── sessionLog.queries.ts # Session log queries
|
||||
│ │ └── index.ts # Export all queries
|
||||
│ ├── schema/
|
||||
│ │ ├── tables.ts # Table definitions
|
||||
│ │ ├── indexes.ts # Index definitions
|
||||
│ │ ├── migrations.ts # Migration tracking table
|
||||
│ │ └── index.ts # Export all schema
|
||||
│ ├── migrator.ts # Migration runner class
|
||||
│ └── index.ts # Main database exports
|
||||
├── services/
|
||||
│ ├── AgentService.ts # Agent business logic
|
||||
│ ├── SessionService.ts # Session business logic
|
||||
│ ├── SessionLogService.ts # Session log business logic
|
||||
│ └── index.ts # Export all services
|
||||
├── BaseService.ts # Shared database utilities with migration support
|
||||
└── index.ts # Main module exports
|
||||
```
|
||||
|
||||
## Implementation Tasks
|
||||
|
||||
### Task 1: Create Folder Structure and Migration System Infrastructure
|
||||
|
||||
**Status**: ✅ COMPLETED
|
||||
**Agent**: `general-purpose`
|
||||
**Description**: Create all necessary directories and implement the migration system infrastructure
|
||||
|
||||
**Subtasks**:
|
||||
|
||||
- [x] Create database/, database/migrations/, database/queries/, database/schema/, services/ directories
|
||||
- [x] Implement migration types and interfaces in database/migrations/types.ts
|
||||
- [x] Build Migrator class with transaction support in database/migrator.ts
|
||||
- [x] Create migration tracking table schema in database/schema/migrations.ts
|
||||
|
||||
---
|
||||
|
||||
### Task 2: Split Database Queries from db.ts
|
||||
|
||||
**Status**: ✅ COMPLETED
|
||||
**Agent**: `general-purpose`
|
||||
**Description**: Extract and organize queries from the current db.ts file into separate, focused files
|
||||
|
||||
**Subtasks**:
|
||||
|
||||
- [x] Move agent queries to database/queries/agent.queries.ts
|
||||
- [x] Move session queries to database/queries/session.queries.ts
|
||||
- [x] Move session log queries to database/queries/sessionLog.queries.ts
|
||||
- [x] Extract table definitions to database/schema/tables.ts
|
||||
- [x] Extract index definitions to database/schema/indexes.ts
|
||||
- [x] Create index files for queries and schema directories
|
||||
- [x] Update db.ts to maintain backward compatibility by re-exporting split queries
|
||||
|
||||
---
|
||||
|
||||
### Task 3: Create Initial Migration Files
|
||||
|
||||
**Status**: ✅ COMPLETED
|
||||
**Agent**: `general-purpose`
|
||||
**Description**: Create migration files based on existing schema
|
||||
|
||||
**Subtasks**:
|
||||
|
||||
- [x] Create 001_initial_schema.ts with agents table and indexes
|
||||
- [x] Create 002_add_session_tables.ts with sessions and session_logs tables
|
||||
- [x] Create database/migrations/index.ts to export all migrations
|
||||
|
||||
---
|
||||
|
||||
### Task 4: Update BaseService with Migration Support
|
||||
|
||||
**Status**: ✅ COMPLETED
|
||||
**Agent**: `general-purpose`
|
||||
**Description**: Integrate migration system into BaseService initialization
|
||||
|
||||
**Subtasks**:
|
||||
|
||||
- [x] Update BaseService.ts to use Migrator on initialize
|
||||
- [x] Keep existing JSON serialization utilities
|
||||
- [x] Update database initialization flow
|
||||
|
||||
---
|
||||
|
||||
### Task 5: Reorganize Service Files
|
||||
|
||||
**Status**: ✅ COMPLETED
|
||||
**Agent**: `general-purpose`
|
||||
**Description**: Move service files to services subdirectory and update imports
|
||||
|
||||
**Subtasks**:
|
||||
|
||||
- [x] Move AgentService.ts to services/
|
||||
- [x] Move SessionService.ts to services/
|
||||
- [x] Move SessionLogService.ts to services/
|
||||
- [x] Update import paths in all service files (now import from '../BaseService' and '../db')
|
||||
- [x] Create services/index.ts to export all services
|
||||
|
||||
---
|
||||
|
||||
### Task 6: Create Export Structure and Clean Up
|
||||
|
||||
**Status**: ✅ COMPLETED
|
||||
**Agent**: `general-purpose`
|
||||
**Description**: Create proper export hierarchy and clean up old files
|
||||
|
||||
**Subtasks**:
|
||||
|
||||
- [x] Create main agents/index.ts with clean exports
|
||||
- [x] Create database/index.ts for database exports
|
||||
- [x] Ensure backward compatibility for existing imports
|
||||
- [x] Remove old db.ts file
|
||||
- [x] Update any external imports if needed
|
||||
|
||||
---
|
||||
|
||||
### Task 7: Test and Validate Refactoring
|
||||
|
||||
**Status**: ✅ COMPLETED
|
||||
**Agent**: `general-purpose`
|
||||
**Description**: Ensure all functionality works after refactoring
|
||||
|
||||
**Subtasks**:
|
||||
|
||||
- [x] Run build check: `yarn build:check` ✅ PASSED (1420 tests, TypeScript compilation successful)
|
||||
- [x] Run tests: `yarn test` ✅ PASSED (All existing tests continue to pass)
|
||||
- [x] Validate migration system works ✅ PASSED (11 migration tests, transaction support verified)
|
||||
- [x] Check that all services initialize correctly ✅ PASSED (File structure, exports, backward compatibility)
|
||||
- [x] Verify database operations work as expected ✅ PASSED (CRUD operations, foreign keys, concurrent operations)
|
||||
|
||||
**Additional Validation**:
|
||||
|
||||
- [x] Created comprehensive validation report (VALIDATION_REPORT.md)
|
||||
- [x] Validated migration system with custom test suite
|
||||
- [x] Verified service initialization and file structure
|
||||
- [x] Tested complete database operations including concurrent access
|
||||
- [x] Confirmed backward compatibility maintained
|
||||
- [x] Validated security measures and performance optimizations
|
||||
|
||||
---
|
||||
|
||||
## Benefits of This Refactoring
|
||||
|
||||
1. **Single Responsibility**: Each file handles one specific concern
|
||||
2. **Version-Controlled Schema**: Migration system tracks all database changes
|
||||
3. **Easier Maintenance**: Find and modify queries for specific entities quickly
|
||||
4. **Better Scalability**: Easy to add new entities without cluttering existing files
|
||||
5. **Clear Organization**: Logical grouping makes navigation intuitive
|
||||
6. **Production Ready**: Atomic migrations with transaction support
|
||||
7. **Reduced Merge Conflicts**: Smaller files mean fewer conflicts in team development
|
||||
|
||||
## Migration Best Practices Implemented
|
||||
|
||||
- ✅ Version-controlled migrations with tracking table
|
||||
- ✅ Atomic operations with transaction support
|
||||
- ✅ Rollback capability (optional down migrations)
|
||||
- ✅ Incremental updates (only run pending migrations)
|
||||
- ✅ Safe for production deployments
|
||||
|
||||
---
|
||||
|
||||
**Progress Summary**: 7/7 tasks completed 🎉
|
||||
|
||||
**Status**: ✅ **REFACTORING COMPLETED SUCCESSFULLY**
|
||||
|
||||
All tasks have been completed and thoroughly validated. The agents service refactoring delivers:
|
||||
|
||||
- ✅ Modular, maintainable code structure
|
||||
- ✅ Production-ready migration system
|
||||
- ✅ Complete backward compatibility
|
||||
- ✅ Comprehensive test validation
|
||||
- ✅ Enhanced developer experience
|
||||
|
||||
**Final deliverables:**
|
||||
|
||||
- 📁 Reorganized service architecture with clear separation of concerns
|
||||
- 🗃️ Database migration system with transaction support and rollback capability
|
||||
- 📋 Comprehensive validation report (VALIDATION_REPORT.md)
|
||||
- ✅ All 1420+ tests passing with full TypeScript compliance
|
||||
- 🔒 Security hardening with parameterized queries and foreign key constraints
|
||||
|
||||
**Ready for production deployment** 🚀
|
||||
@ -1,654 +0,0 @@
|
||||
# Agent API UI Integration Guide
|
||||
|
||||
## Overview
|
||||
|
||||
This document provides comprehensive guidance for UI components to integrate with the new Agent API system. The agents data is now stored in the database and accessed through API endpoints instead of Redux state management.
|
||||
|
||||
## Key Changes from Previous Implementation
|
||||
|
||||
### Data Storage
|
||||
- **Before**: Agent data stored in Redux store
|
||||
- **After**: Agent data stored in SQLite database, accessed via REST API
|
||||
|
||||
### State Management
|
||||
- **Before**: Redux actions and selectors for agent operations
|
||||
- **After**: Direct API calls using fetch/axios, no Redux dependency
|
||||
|
||||
### Data Flow
|
||||
- **Before**: Component → Redux Action → State Update → Component Re-render
|
||||
- **After**: Component → API Call → UI Update → Database
|
||||
|
||||
## API Endpoints Overview
|
||||
|
||||
### Base Configuration
|
||||
- **Base URL**: `http://localhost:23333/v1`
|
||||
- **Authentication**: Bearer token (API key format: `cs-sk-{uuid}`)
|
||||
- **Content-Type**: `application/json`
|
||||
|
||||
### Agent Management (`/agents`)
|
||||
|
||||
| Method | Endpoint | Description | Request Body | Response |
|
||||
|--------|----------|-------------|--------------|----------|
|
||||
| POST | `/agents` | Create new agent | `CreateAgentRequest` | `AgentEntity` |
|
||||
| GET | `/agents` | List agents (paginated) | Query params | `{ data: AgentEntity[], total: number }` |
|
||||
| GET | `/agents/{id}` | Get specific agent | - | `AgentEntity` |
|
||||
| PUT | `/agents/{id}` | Replace agent (complete update) | `UpdateAgentRequest` | `AgentEntity` |
|
||||
| PATCH | `/agents/{id}` | Partially update agent | `Partial<UpdateAgentRequest>` | `AgentEntity` |
|
||||
| DELETE | `/agents/{id}` | Delete agent | - | `204 No Content` |
|
||||
|
||||
### Session Management (`/agents/{agentId}/sessions`)
|
||||
|
||||
| Method | Endpoint | Description | Request Body | Response |
|
||||
|--------|----------|-------------|--------------|----------|
|
||||
| POST | `/agents/{agentId}/sessions` | Create session | `CreateSessionRequest` | `AgentSessionEntity` |
|
||||
| GET | `/agents/{agentId}/sessions` | List agent sessions | Query params | `{ data: AgentSessionEntity[], total: number }` |
|
||||
| GET | `/agents/{agentId}/sessions/{id}` | Get specific session | - | `AgentSessionEntity` |
|
||||
| PUT | `/agents/{agentId}/sessions/{id}` | Replace session (complete update) | `UpdateSessionRequest` | `AgentSessionEntity` |
|
||||
| PATCH | `/agents/{agentId}/sessions/{id}` | Partially update session | `Partial<UpdateSessionRequest>` | `AgentSessionEntity` |
|
||||
| DELETE | `/agents/{agentId}/sessions/{id}` | Delete session | - | `204 No Content` |
|
||||
|
||||
### Message Streaming (`/agents/{agentId}/sessions/{sessionId}/messages`)
|
||||
|
||||
| Method | Endpoint | Description | Request Body | Response |
|
||||
|--------|----------|-------------|--------------|----------|
|
||||
| POST | `/agents/{agentId}/sessions/{sessionId}/messages` | Send message to agent | `CreateMessageRequest` | **Stream Response** |
|
||||
| GET | `/agents/{agentId}/sessions/{sessionId}/messages` | List session messages | Query params | `{ data: SessionMessageEntity[], total: number }` |
|
||||
|
||||
## HTTP Methods: PUT vs PATCH
|
||||
|
||||
Both agents and sessions support two types of update operations:
|
||||
|
||||
### PUT - Complete Replacement
|
||||
- **Purpose**: Replaces the entire resource with the provided data
|
||||
- **Behavior**: All fields in the request body will be applied to the resource
|
||||
- **Use Case**: When you want to completely update a resource with a new set of values
|
||||
- **Example**: Updating an agent's configuration completely
|
||||
|
||||
```typescript
|
||||
// PUT - Replace entire agent
|
||||
await fetch('/v1/agents/agent-123', {
|
||||
method: 'PUT',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
name: 'New Agent Name',
|
||||
model: 'gpt-4',
|
||||
instructions: 'New instructions',
|
||||
built_in_tools: ['search', 'calculator'],
|
||||
// All other fields will be reset to defaults if not provided
|
||||
})
|
||||
})
|
||||
```
|
||||
|
||||
### PATCH - Partial Update
|
||||
- **Purpose**: Updates only the specified fields, leaving others unchanged
|
||||
- **Behavior**: Only the fields present in the request body will be modified
|
||||
- **Use Case**: When you want to update specific fields without affecting others
|
||||
- **Example**: Updating only an agent's name or instructions
|
||||
|
||||
```typescript
|
||||
// PATCH - Update only specific fields
|
||||
await fetch('/v1/agents/agent-123', {
|
||||
method: 'PATCH',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
name: 'Updated Agent Name'
|
||||
// All other fields remain unchanged
|
||||
})
|
||||
})
|
||||
```
|
||||
|
||||
### Validation
|
||||
Both methods use the same validation rules:
|
||||
- All fields are optional for both PUT and PATCH
|
||||
- When provided, fields must meet their validation criteria (e.g., `name` cannot be empty)
|
||||
- The same middleware (`validateAgentUpdate` for agents, `validateSessionUpdate` for sessions) handles both operations
|
||||
|
||||
## Data Types & Schemas
|
||||
|
||||
### AgentEntity
|
||||
```typescript
|
||||
interface AgentEntity {
|
||||
id: string
|
||||
type: AgentType
|
||||
name: string
|
||||
description?: string
|
||||
avatar?: string
|
||||
instructions?: string
|
||||
|
||||
// Core configuration
|
||||
model: string // Required - main model ID
|
||||
plan_model?: string
|
||||
small_model?: string
|
||||
built_in_tools?: string[]
|
||||
mcps?: string[]
|
||||
knowledges?: string[]
|
||||
configuration?: Record<string, any>
|
||||
accessible_paths?: string[]
|
||||
permission_mode?: PermissionMode
|
||||
max_steps?: number
|
||||
|
||||
// Timestamps
|
||||
created_at: string
|
||||
updated_at: string
|
||||
}
|
||||
```
|
||||
|
||||
### AgentSessionEntity
|
||||
```typescript
|
||||
interface AgentSessionEntity {
|
||||
id: string
|
||||
name?: string
|
||||
main_agent_id: string
|
||||
sub_agent_ids?: string[]
|
||||
user_goal?: string
|
||||
status: SessionStatus
|
||||
external_session_id?: string
|
||||
|
||||
// Configuration overrides (inherits from agent if not specified)
|
||||
model?: string
|
||||
plan_model?: string
|
||||
small_model?: string
|
||||
built_in_tools?: string[]
|
||||
mcps?: string[]
|
||||
knowledges?: string[]
|
||||
configuration?: Record<string, any>
|
||||
accessible_paths?: string[]
|
||||
permission_mode?: PermissionMode
|
||||
max_steps?: number
|
||||
|
||||
// Timestamps
|
||||
created_at: string
|
||||
updated_at: string
|
||||
}
|
||||
```
|
||||
|
||||
### SessionMessageEntity
|
||||
```typescript
|
||||
interface SessionMessageEntity {
|
||||
id: number
|
||||
session_id: string
|
||||
parent_id?: number
|
||||
role: SessionMessageRole // 'user' | 'agent' | 'system' | 'tool'
|
||||
type: SessionMessageType
|
||||
content: Record<string, any>
|
||||
metadata?: Record<string, any>
|
||||
created_at: string
|
||||
updated_at: string
|
||||
}
|
||||
```
|
||||
|
||||
## Creating Agents
|
||||
|
||||
### Minimal Agent Creation
|
||||
For early stage implementation, only use these essential fields:
|
||||
|
||||
```typescript
|
||||
const createAgentRequest = {
|
||||
name: string, // Required
|
||||
model: string, // Required
|
||||
instructions?: string, // System prompt
|
||||
built_in_tools?: string[],
|
||||
mcps?: string[],
|
||||
knowledges?: string[]
|
||||
}
|
||||
```
|
||||
|
||||
### Example: Create Agent
|
||||
```typescript
|
||||
async function createAgent(agentData: CreateAgentRequest): Promise<AgentEntity> {
|
||||
const response = await fetch('/v1/agents', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Authorization': `Bearer ${apiKey}`,
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify(agentData)
|
||||
})
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`Agent creation failed: ${response.statusText}`)
|
||||
}
|
||||
|
||||
return await response.json()
|
||||
}
|
||||
```
|
||||
|
||||
### Example: List Agents
|
||||
```typescript
|
||||
async function listAgents(limit = 20, offset = 0): Promise<{data: AgentEntity[], total: number}> {
|
||||
const response = await fetch(`/v1/agents?limit=${limit}&offset=${offset}`, {
|
||||
headers: {
|
||||
'Authorization': `Bearer ${apiKey}`
|
||||
}
|
||||
})
|
||||
|
||||
return await response.json()
|
||||
}
|
||||
```
|
||||
|
||||
## Managing Agent Sessions
|
||||
|
||||
### Session Creation
|
||||
```typescript
|
||||
async function createSession(agentId: string, sessionData: CreateSessionRequest): Promise<AgentSessionEntity> {
|
||||
const response = await fetch(`/v1/agents/${agentId}/sessions`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Authorization': `Bearer ${apiKey}`,
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
user_goal: sessionData.user_goal, // User's goal as input message
|
||||
model: sessionData.model, // Override agent's model if needed
|
||||
// tools and mcps can be overridden per session
|
||||
})
|
||||
})
|
||||
|
||||
return await response.json()
|
||||
}
|
||||
```
|
||||
|
||||
### Session Updates
|
||||
Sessions can be updated using either PUT (complete replacement) or PATCH (partial update):
|
||||
|
||||
#### Complete Session Replacement (PUT)
|
||||
```typescript
|
||||
async function replaceSession(agentId: string, sessionId: string, sessionData: UpdateSessionRequest): Promise<AgentSessionEntity> {
|
||||
const response = await fetch(`/v1/agents/${agentId}/sessions/${sessionId}`, {
|
||||
method: 'PUT',
|
||||
headers: {
|
||||
'Authorization': `Bearer ${apiKey}`,
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify(sessionData) // Complete session configuration
|
||||
})
|
||||
|
||||
return await response.json()
|
||||
}
|
||||
```
|
||||
|
||||
#### Partial Session Update (PATCH)
|
||||
```typescript
|
||||
async function updateSession(agentId: string, sessionId: string, updates: Partial<UpdateSessionRequest>): Promise<AgentSessionEntity> {
|
||||
const response = await fetch(`/v1/agents/${agentId}/sessions/${sessionId}`, {
|
||||
method: 'PATCH',
|
||||
headers: {
|
||||
'Authorization': `Bearer ${apiKey}`,
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify(updates) // Only the fields to update
|
||||
})
|
||||
|
||||
return await response.json()
|
||||
}
|
||||
```
|
||||
|
||||
#### Session Status Management
|
||||
Sessions have five possible statuses:
|
||||
- `idle`: Ready to process messages
|
||||
- `running`: Currently processing
|
||||
- `completed`: Task finished successfully
|
||||
- `failed`: Encountered an error
|
||||
- `stopped`: Manually stopped by user
|
||||
|
||||
```typescript
|
||||
// Update only the session status using PATCH
|
||||
async function updateSessionStatus(agentId: string, sessionId: string, status: SessionStatus): Promise<AgentSessionEntity> {
|
||||
return await updateSession(agentId, sessionId, { status })
|
||||
}
|
||||
```
|
||||
|
||||
## Message Streaming Integration
|
||||
|
||||
### Sending Messages to Agents
|
||||
The core interaction point is the message endpoint that accepts user messages and returns streamed responses:
|
||||
|
||||
```typescript
|
||||
async function sendMessageToAgent(
|
||||
agentId: string,
|
||||
sessionId: string,
|
||||
message: CreateMessageRequest
|
||||
): Promise<ReadableStream> {
|
||||
|
||||
const response = await fetch(`/v1/agents/${agentId}/sessions/${sessionId}/messages`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Authorization': `Bearer ${apiKey}`,
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
role: 'user',
|
||||
type: 'message',
|
||||
content: {
|
||||
text: message.text,
|
||||
// Include any additional context
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
return response.body // Returns AI SDK streamText compatible stream
|
||||
}
|
||||
```
|
||||
|
||||
### Processing Streamed Responses
|
||||
The response follows AI SDK's `streamText` format:
|
||||
|
||||
```typescript
|
||||
async function handleAgentResponse(stream: ReadableStream) {
|
||||
const reader = stream.getReader()
|
||||
const decoder = new TextDecoder()
|
||||
|
||||
try {
|
||||
while (true) {
|
||||
const { done, value } = await reader.read()
|
||||
|
||||
if (done) break
|
||||
|
||||
const chunk = decoder.decode(value)
|
||||
const lines = chunk.split('\n')
|
||||
|
||||
for (const line of lines) {
|
||||
if (line.startsWith('data: ')) {
|
||||
const data = line.slice(6)
|
||||
|
||||
if (data === '[DONE]') {
|
||||
return // Stream completed
|
||||
}
|
||||
|
||||
try {
|
||||
const parsed = JSON.parse(data)
|
||||
|
||||
// Handle different stream events
|
||||
switch (parsed.type) {
|
||||
case 'text-delta':
|
||||
updateUI(parsed.textDelta)
|
||||
break
|
||||
case 'tool-call':
|
||||
handleToolCall(parsed.toolCall)
|
||||
break
|
||||
case 'tool-result':
|
||||
handleToolResult(parsed.toolResult)
|
||||
break
|
||||
case 'finish':
|
||||
handleFinish(parsed.finishReason)
|
||||
break
|
||||
}
|
||||
} catch (parseError) {
|
||||
console.error('Failed to parse stream data:', parseError)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
reader.releaseLock()
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## UI Component Integration Patterns
|
||||
|
||||
### Agent List Component
|
||||
```typescript
|
||||
function AgentList() {
|
||||
const [agents, setAgents] = useState<AgentEntity[]>([])
|
||||
const [loading, setLoading] = useState(true)
|
||||
|
||||
useEffect(() => {
|
||||
async function loadAgents() {
|
||||
try {
|
||||
const result = await listAgents()
|
||||
setAgents(result.data)
|
||||
} catch (error) {
|
||||
console.error('Failed to load agents:', error)
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
loadAgents()
|
||||
}, [])
|
||||
|
||||
const handleDeleteAgent = async (agentId: string) => {
|
||||
try {
|
||||
await fetch(`/v1/agents/${agentId}`, {
|
||||
method: 'DELETE',
|
||||
headers: { 'Authorization': `Bearer ${apiKey}` }
|
||||
})
|
||||
|
||||
setAgents(agents.filter(agent => agent.id !== agentId))
|
||||
} catch (error) {
|
||||
console.error('Failed to delete agent:', error)
|
||||
}
|
||||
}
|
||||
|
||||
if (loading) return <div>Loading...</div>
|
||||
|
||||
return (
|
||||
<div>
|
||||
{agents.map(agent => (
|
||||
<AgentItem
|
||||
key={agent.id}
|
||||
agent={agent}
|
||||
onDelete={() => handleDeleteAgent(agent.id)}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
### Agent Chat Component
|
||||
```typescript
|
||||
function AgentChat({ agentId }: { agentId: string }) {
|
||||
const [session, setSession] = useState<AgentSessionEntity | null>(null)
|
||||
const [messages, setMessages] = useState<SessionMessageEntity[]>([])
|
||||
const [inputMessage, setInputMessage] = useState('')
|
||||
const [isStreaming, setIsStreaming] = useState(false)
|
||||
|
||||
// Create session on component mount
|
||||
useEffect(() => {
|
||||
async function initSession() {
|
||||
try {
|
||||
const newSession = await createSession(agentId, {
|
||||
user_goal: "General conversation"
|
||||
})
|
||||
setSession(newSession)
|
||||
|
||||
// Load existing messages
|
||||
const messagesResult = await fetch(`/v1/agents/${agentId}/sessions/${newSession.id}/messages`, {
|
||||
headers: { 'Authorization': `Bearer ${apiKey}` }
|
||||
}).then(r => r.json())
|
||||
|
||||
setMessages(messagesResult.data)
|
||||
} catch (error) {
|
||||
console.error('Failed to initialize session:', error)
|
||||
}
|
||||
}
|
||||
|
||||
initSession()
|
||||
}, [agentId])
|
||||
|
||||
const sendMessage = async () => {
|
||||
if (!session || !inputMessage.trim() || isStreaming) return
|
||||
|
||||
setIsStreaming(true)
|
||||
|
||||
try {
|
||||
// Add user message to UI
|
||||
const userMessage = {
|
||||
role: 'user' as const,
|
||||
content: { text: inputMessage },
|
||||
created_at: new Date().toISOString()
|
||||
}
|
||||
setMessages(prev => [...prev, userMessage as any])
|
||||
setInputMessage('')
|
||||
|
||||
// Send to agent and handle streaming response
|
||||
const stream = await sendMessageToAgent(agentId, session.id, {
|
||||
text: inputMessage
|
||||
})
|
||||
|
||||
let agentResponse = ''
|
||||
await handleAgentResponse(stream, (delta: string) => {
|
||||
agentResponse += delta
|
||||
// Update UI with streaming text
|
||||
setMessages(prev => {
|
||||
const last = prev[prev.length - 1]
|
||||
if (last?.role === 'agent') {
|
||||
return [...prev.slice(0, -1), { ...last, content: { text: agentResponse } }]
|
||||
} else {
|
||||
return [...prev, {
|
||||
role: 'agent',
|
||||
content: { text: agentResponse },
|
||||
created_at: new Date().toISOString()
|
||||
} as any]
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
} catch (error) {
|
||||
console.error('Failed to send message:', error)
|
||||
} finally {
|
||||
setIsStreaming(false)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="agent-chat">
|
||||
<div className="messages">
|
||||
{messages.map((message, index) => (
|
||||
<div key={index} className={`message ${message.role}`}>
|
||||
<div className="content">{message.content.text}</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="input-area">
|
||||
<input
|
||||
value={inputMessage}
|
||||
onChange={(e) => setInputMessage(e.target.value)}
|
||||
onKeyPress={(e) => e.key === 'Enter' && sendMessage()}
|
||||
disabled={isStreaming}
|
||||
placeholder="Type your message..."
|
||||
/>
|
||||
<button onClick={sendMessage} disabled={isStreaming || !inputMessage.trim()}>
|
||||
{isStreaming ? 'Sending...' : 'Send'}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
## Error Handling
|
||||
|
||||
### API Error Response Format
|
||||
```typescript
|
||||
interface ApiError {
|
||||
error: {
|
||||
message: string
|
||||
type: 'validation_error' | 'not_found' | 'internal_error' | 'authentication_error'
|
||||
code?: string
|
||||
details?: any[]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Error Handling Pattern
|
||||
```typescript
|
||||
async function apiRequest<T>(url: string, options: RequestInit = {}): Promise<T> {
|
||||
try {
|
||||
const response = await fetch(url, {
|
||||
...options,
|
||||
headers: {
|
||||
'Authorization': `Bearer ${apiKey}`,
|
||||
'Content-Type': 'application/json',
|
||||
...options.headers
|
||||
}
|
||||
})
|
||||
|
||||
if (!response.ok) {
|
||||
const error: ApiError = await response.json()
|
||||
throw new Error(`${error.error.type}: ${error.error.message}`)
|
||||
}
|
||||
|
||||
return await response.json()
|
||||
} catch (error) {
|
||||
console.error('API request failed:', error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
### 1. Agent Configuration
|
||||
- **Minimal Setup**: Start with just `name`, `model`, and `instructions`
|
||||
- **Gradual Enhancement**: Add `tools`, `mcps`, and `knowledges` as needed
|
||||
- **Configuration Inheritance**: Sessions inherit agent settings but can override them
|
||||
|
||||
### 2. Session Management
|
||||
- **Single Goal Per Session**: Each session should have one clear `user_goal`
|
||||
- **Status Tracking**: Always update session status appropriately
|
||||
- **Resource Cleanup**: Delete completed/failed sessions to manage storage
|
||||
|
||||
### 3. Message Streaming
|
||||
- **Progressive Enhancement**: Show streaming text immediately for better UX
|
||||
- **Error Recovery**: Handle stream interruptions gracefully
|
||||
- **Tool Visualization**: Display tool calls and results appropriately
|
||||
|
||||
### 4. Performance Considerations
|
||||
- **Pagination**: Always use `limit` and `offset` for large lists
|
||||
- **Caching**: Consider caching agent lists locally
|
||||
- **Debouncing**: Debounce API calls for real-time updates
|
||||
|
||||
### 5. User Experience
|
||||
- **Loading States**: Show loading indicators during API calls
|
||||
- **Error Messages**: Display user-friendly error messages
|
||||
- **Optimistic Updates**: Update UI immediately, rollback on errors
|
||||
|
||||
## Migration from Redux Implementation
|
||||
|
||||
### Step 1: Remove Redux Dependencies
|
||||
```typescript
|
||||
// Before
|
||||
import { useSelector, useDispatch } from 'react-redux'
|
||||
import { createAgent, listAgents } from '../store/agents'
|
||||
|
||||
// After
|
||||
import { apiRequest } from '../services/api'
|
||||
```
|
||||
|
||||
### Step 2: Replace Redux Hooks
|
||||
```typescript
|
||||
// Before
|
||||
const agents = useSelector(state => state.agents.list)
|
||||
const dispatch = useDispatch()
|
||||
|
||||
// After
|
||||
const [agents, setAgents] = useState<AgentEntity[]>([])
|
||||
```
|
||||
|
||||
### Step 3: Replace Action Dispatches
|
||||
```typescript
|
||||
// Before
|
||||
dispatch(createAgent(agentData))
|
||||
|
||||
// After
|
||||
const newAgent = await apiRequest<AgentEntity>('/v1/agents', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(agentData)
|
||||
})
|
||||
setAgents(prev => [...prev, newAgent])
|
||||
```
|
||||
|
||||
## Conclusion
|
||||
|
||||
This new API-based approach provides:
|
||||
- **Better Performance**: Database storage with efficient queries
|
||||
- **Real-time Streaming**: AI SDK compatible message streaming
|
||||
- **Scalability**: Proper pagination and resource management
|
||||
- **Flexibility**: Session-level configuration overrides
|
||||
- **Reliability**: Proper error handling and status management
|
||||
|
||||
The migration from Redux to direct API integration simplifies the data flow and provides better control over agent interactions.
|
||||
@ -125,25 +125,16 @@ afterSign: scripts/notarize.js
|
||||
artifactBuildCompleted: scripts/artifact-build-completed.js
|
||||
releaseInfo:
|
||||
releaseNotes: |
|
||||
✨ 新功能:
|
||||
- 重构知识库模块,提升文档处理能力和搜索性能
|
||||
- 新增 PaddleOCR 支持,增强文档识别能力
|
||||
- 支持自定义窗口控制按钮样式
|
||||
- 新增 AI SDK 包,扩展 AI 能力集成
|
||||
- 支持标签页拖拽重排序功能
|
||||
- 增强笔记编辑器的同步和日志功能
|
||||
|
||||
🔧 性能优化:
|
||||
- 优化 MCP 服务的日志记录和错误处理
|
||||
- 改进 WebView 服务的 User-Agent 处理
|
||||
- 优化迷你应用的标题栏样式和状态栏适配
|
||||
- 重构依赖管理,清理和优化 package.json
|
||||
|
||||
🐛 问题修复:
|
||||
- 修复输入栏无限状态更新循环问题
|
||||
- 修复窗口控制提示框的鼠标悬停延迟
|
||||
- 修复翻译输入框粘贴多内容源的处理
|
||||
- 修复导航服务初始化时序问题
|
||||
- 修复 MCP 通过 JSON 添加时的参数转换
|
||||
- 修复模型作用域服务器同步时的 URL 格式
|
||||
- 标准化工具提示图标样式
|
||||
- 修复 Anthropic API URL 处理,移除尾部斜杠并添加端点路径处理
|
||||
- 修复 MessageEditor 缺少 QuickPanelProvider 包装的问题
|
||||
- 修复 MiniWindow 高度问题
|
||||
|
||||
🚀 性能优化:
|
||||
- 优化输入栏提及模型状态缓存,在渲染间保持状态
|
||||
- 重构网络搜索参数支持模型内置搜索,新增 OpenAI Chat 和 OpenRouter 支持
|
||||
|
||||
🔧 重构改进:
|
||||
- 更新 HeroUIProvider 导入路径,改善上下文管理
|
||||
- 更新依赖项和 VSCode 开发环境配置
|
||||
- 升级 @cherrystudio/ai-core 到 v1.0.0-alpha.17
|
||||
|
||||
11
package.json
11
package.json
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "CherryStudio",
|
||||
"version": "1.6.0-beta.7",
|
||||
"version": "1.6.0-rc.2",
|
||||
"private": true,
|
||||
"description": "A powerful AI assistant for producer.",
|
||||
"main": "./out/main/index.js",
|
||||
@ -116,7 +116,7 @@
|
||||
"@aws-sdk/client-bedrock-runtime": "^3.840.0",
|
||||
"@aws-sdk/client-s3": "^3.840.0",
|
||||
"@biomejs/biome": "2.2.4",
|
||||
"@cherrystudio/ai-core": "workspace:^1.0.0-alpha.16",
|
||||
"@cherrystudio/ai-core": "workspace:^1.0.0-alpha.18",
|
||||
"@cherrystudio/embedjs": "^0.1.31",
|
||||
"@cherrystudio/embedjs-libsql": "^0.1.31",
|
||||
"@cherrystudio/embedjs-loader-csv": "^0.1.31",
|
||||
@ -246,7 +246,6 @@
|
||||
"diff": "^8.0.2",
|
||||
"docx": "^9.0.2",
|
||||
"dompurify": "^3.2.6",
|
||||
"dotenv": "^17.2.2",
|
||||
"dotenv-cli": "^7.4.2",
|
||||
"drizzle-kit": "^0.31.4",
|
||||
"electron": "37.4.0",
|
||||
@ -344,7 +343,7 @@
|
||||
"tsx": "^4.20.3",
|
||||
"turndown-plugin-gfm": "^1.0.2",
|
||||
"tw-animate-css": "^1.3.8",
|
||||
"typescript": "^5.6.2",
|
||||
"typescript": "~5.8.2",
|
||||
"undici": "6.21.2",
|
||||
"unified": "^11.0.5",
|
||||
"uuid": "^13.0.0",
|
||||
@ -385,11 +384,11 @@
|
||||
"packageManager": "yarn@4.9.1",
|
||||
"lint-staged": {
|
||||
"*.{js,jsx,ts,tsx,cjs,mjs,cts,mts}": [
|
||||
"biome format --write",
|
||||
"biome format --write --no-errors-on-unmatched",
|
||||
"eslint --fix"
|
||||
],
|
||||
"*.{json,yml,yaml,css,html}": [
|
||||
"biome format --write"
|
||||
"biome format --write --no-errors-on-unmatched"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@cherrystudio/ai-core",
|
||||
"version": "1.0.0-alpha.16",
|
||||
"version": "1.0.0-alpha.18",
|
||||
"description": "Cherry Studio AI Core - Unified AI Provider Interface Based on Vercel AI SDK",
|
||||
"main": "dist/index.js",
|
||||
"module": "dist/index.mjs",
|
||||
|
||||
@ -59,7 +59,7 @@ export function createGoogleOptions(options: ExtractProviderOptions<'google'>) {
|
||||
/**
|
||||
* 创建OpenRouter供应商选项的便捷函数
|
||||
*/
|
||||
export function createOpenRouterOptions(options: ExtractProviderOptions<'openrouter'>) {
|
||||
export function createOpenRouterOptions(options: ExtractProviderOptions<'openrouter'> | Record<string, any>) {
|
||||
return createProviderOptions('openrouter', options)
|
||||
}
|
||||
|
||||
|
||||
@ -1,38 +0,0 @@
|
||||
export type OpenRouterProviderOptions = {
|
||||
models?: string[]
|
||||
|
||||
/**
|
||||
* https://openrouter.ai/docs/use-cases/reasoning-tokens
|
||||
* One of `max_tokens` or `effort` is required.
|
||||
* If `exclude` is true, reasoning will be removed from the response. Default is false.
|
||||
*/
|
||||
reasoning?: {
|
||||
exclude?: boolean
|
||||
} & (
|
||||
| {
|
||||
max_tokens: number
|
||||
}
|
||||
| {
|
||||
effort: 'high' | 'medium' | 'low'
|
||||
}
|
||||
)
|
||||
|
||||
/**
|
||||
* A unique identifier representing your end-user, which can
|
||||
* help OpenRouter to monitor and detect abuse.
|
||||
*/
|
||||
user?: string
|
||||
|
||||
extraBody?: Record<string, unknown>
|
||||
|
||||
/**
|
||||
* Enable usage accounting to get detailed token usage information.
|
||||
* https://openrouter.ai/docs/use-cases/usage-accounting
|
||||
*/
|
||||
usage?: {
|
||||
/**
|
||||
* When true, includes token usage information in the response.
|
||||
*/
|
||||
include: boolean
|
||||
}
|
||||
}
|
||||
@ -2,9 +2,8 @@ import { type AnthropicProviderOptions } from '@ai-sdk/anthropic'
|
||||
import { type GoogleGenerativeAIProviderOptions } from '@ai-sdk/google'
|
||||
import { type OpenAIResponsesProviderOptions } from '@ai-sdk/openai'
|
||||
import { type SharedV2ProviderMetadata } from '@ai-sdk/provider'
|
||||
|
||||
import { type OpenRouterProviderOptions } from './openrouter'
|
||||
import { type XaiProviderOptions } from './xai'
|
||||
import { type XaiProviderOptions } from '@ai-sdk/xai'
|
||||
import { type OpenRouterProviderOptions } from '@openrouter/ai-sdk-provider'
|
||||
|
||||
export type ProviderOptions<T extends keyof SharedV2ProviderMetadata> = SharedV2ProviderMetadata[T]
|
||||
|
||||
|
||||
@ -1,86 +0,0 @@
|
||||
// copy from @ai-sdk/xai/xai-chat-options.ts
|
||||
// 如果@ai-sdk/xai暴露出了xaiProviderOptions就删除这个文件
|
||||
|
||||
import { z } from 'zod'
|
||||
|
||||
const webSourceSchema = z.object({
|
||||
type: z.literal('web'),
|
||||
country: z.string().length(2).optional(),
|
||||
excludedWebsites: z.array(z.string()).max(5).optional(),
|
||||
allowedWebsites: z.array(z.string()).max(5).optional(),
|
||||
safeSearch: z.boolean().optional()
|
||||
})
|
||||
|
||||
const xSourceSchema = z.object({
|
||||
type: z.literal('x'),
|
||||
xHandles: z.array(z.string()).optional()
|
||||
})
|
||||
|
||||
const newsSourceSchema = z.object({
|
||||
type: z.literal('news'),
|
||||
country: z.string().length(2).optional(),
|
||||
excludedWebsites: z.array(z.string()).max(5).optional(),
|
||||
safeSearch: z.boolean().optional()
|
||||
})
|
||||
|
||||
const rssSourceSchema = z.object({
|
||||
type: z.literal('rss'),
|
||||
links: z.array(z.url()).max(1) // currently only supports one RSS link
|
||||
})
|
||||
|
||||
const searchSourceSchema = z.discriminatedUnion('type', [
|
||||
webSourceSchema,
|
||||
xSourceSchema,
|
||||
newsSourceSchema,
|
||||
rssSourceSchema
|
||||
])
|
||||
|
||||
export const xaiProviderOptions = z.object({
|
||||
/**
|
||||
* reasoning effort for reasoning models
|
||||
* only supported by grok-3-mini and grok-3-mini-fast models
|
||||
*/
|
||||
reasoningEffort: z.enum(['low', 'high']).optional(),
|
||||
|
||||
searchParameters: z
|
||||
.object({
|
||||
/**
|
||||
* search mode preference
|
||||
* - "off": disables search completely
|
||||
* - "auto": model decides whether to search (default)
|
||||
* - "on": always enables search
|
||||
*/
|
||||
mode: z.enum(['off', 'auto', 'on']),
|
||||
|
||||
/**
|
||||
* whether to return citations in the response
|
||||
* defaults to true
|
||||
*/
|
||||
returnCitations: z.boolean().optional(),
|
||||
|
||||
/**
|
||||
* start date for search data (ISO8601 format: YYYY-MM-DD)
|
||||
*/
|
||||
fromDate: z.string().optional(),
|
||||
|
||||
/**
|
||||
* end date for search data (ISO8601 format: YYYY-MM-DD)
|
||||
*/
|
||||
toDate: z.string().optional(),
|
||||
|
||||
/**
|
||||
* maximum number of search results to consider
|
||||
* defaults to 20
|
||||
*/
|
||||
maxSearchResults: z.number().min(1).max(50).optional(),
|
||||
|
||||
/**
|
||||
* data sources to search from
|
||||
* defaults to ["web", "x"] if not specified
|
||||
*/
|
||||
sources: z.array(searchSourceSchema).optional()
|
||||
})
|
||||
.optional()
|
||||
})
|
||||
|
||||
export type XaiProviderOptions = z.infer<typeof xaiProviderOptions>
|
||||
@ -7,5 +7,9 @@ export const BUILT_IN_PLUGIN_PREFIX = 'built-in:'
|
||||
export { googleToolsPlugin } from './googleToolsPlugin'
|
||||
export { createLoggingPlugin } from './logging'
|
||||
export { createPromptToolUsePlugin } from './toolUsePlugin/promptToolUsePlugin'
|
||||
export type { PromptToolUseConfig, ToolUseRequestContext, ToolUseResult } from './toolUsePlugin/type'
|
||||
export { webSearchPlugin } from './webSearchPlugin'
|
||||
export type {
|
||||
PromptToolUseConfig,
|
||||
ToolUseRequestContext,
|
||||
ToolUseResult
|
||||
} from './toolUsePlugin/type'
|
||||
export { webSearchPlugin, type WebSearchPluginConfig } from './webSearchPlugin'
|
||||
|
||||
@ -3,13 +3,16 @@ import { google } from '@ai-sdk/google'
|
||||
import { openai } from '@ai-sdk/openai'
|
||||
|
||||
import { ProviderOptionsMap } from '../../../options/types'
|
||||
import { OpenRouterSearchConfig } from './openrouter'
|
||||
|
||||
/**
|
||||
* 从 AI SDK 的工具函数中提取参数类型,以确保类型安全。
|
||||
*/
|
||||
type OpenAISearchConfig = Parameters<typeof openai.tools.webSearchPreview>[0]
|
||||
type AnthropicSearchConfig = Parameters<typeof anthropic.tools.webSearch_20250305>[0]
|
||||
type GoogleSearchConfig = Parameters<typeof google.tools.googleSearch>[0]
|
||||
export type OpenAISearchConfig = NonNullable<Parameters<typeof openai.tools.webSearch>[0]>
|
||||
export type OpenAISearchPreviewConfig = NonNullable<Parameters<typeof openai.tools.webSearchPreview>[0]>
|
||||
export type AnthropicSearchConfig = NonNullable<Parameters<typeof anthropic.tools.webSearch_20250305>[0]>
|
||||
export type GoogleSearchConfig = NonNullable<Parameters<typeof google.tools.googleSearch>[0]>
|
||||
export type XAISearchConfig = NonNullable<ProviderOptionsMap['xai']['searchParameters']>
|
||||
|
||||
/**
|
||||
* 插件初始化时接收的完整配置对象
|
||||
@ -18,10 +21,12 @@ type GoogleSearchConfig = Parameters<typeof google.tools.googleSearch>[0]
|
||||
*/
|
||||
export interface WebSearchPluginConfig {
|
||||
openai?: OpenAISearchConfig
|
||||
'openai-chat'?: OpenAISearchPreviewConfig
|
||||
anthropic?: AnthropicSearchConfig
|
||||
xai?: ProviderOptionsMap['xai']['searchParameters']
|
||||
google?: GoogleSearchConfig
|
||||
'google-vertex'?: GoogleSearchConfig
|
||||
openrouter?: OpenRouterSearchConfig
|
||||
}
|
||||
|
||||
/**
|
||||
@ -31,6 +36,7 @@ export const DEFAULT_WEB_SEARCH_CONFIG: WebSearchPluginConfig = {
|
||||
google: {},
|
||||
'google-vertex': {},
|
||||
openai: {},
|
||||
'openai-chat': {},
|
||||
xai: {
|
||||
mode: 'on',
|
||||
returnCitations: true,
|
||||
@ -39,6 +45,14 @@ export const DEFAULT_WEB_SEARCH_CONFIG: WebSearchPluginConfig = {
|
||||
},
|
||||
anthropic: {
|
||||
maxUses: 5
|
||||
},
|
||||
openrouter: {
|
||||
plugins: [
|
||||
{
|
||||
id: 'web',
|
||||
max_results: 5
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -6,7 +6,7 @@ import { anthropic } from '@ai-sdk/anthropic'
|
||||
import { google } from '@ai-sdk/google'
|
||||
import { openai } from '@ai-sdk/openai'
|
||||
|
||||
import { createXaiOptions, mergeProviderOptions } from '../../../options'
|
||||
import { createOpenRouterOptions, createXaiOptions, mergeProviderOptions } from '../../../options'
|
||||
import { definePlugin } from '../../'
|
||||
import type { AiRequestContext } from '../../types'
|
||||
import { DEFAULT_WEB_SEARCH_CONFIG, WebSearchPluginConfig } from './helper'
|
||||
@ -31,6 +31,13 @@ export const webSearchPlugin = (config: WebSearchPluginConfig = DEFAULT_WEB_SEAR
|
||||
}
|
||||
break
|
||||
}
|
||||
case 'openai-chat': {
|
||||
if (config['openai-chat']) {
|
||||
if (!params.tools) params.tools = {}
|
||||
params.tools.web_search_preview = openai.tools.webSearchPreview(config['openai-chat'])
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
case 'anthropic': {
|
||||
if (config.anthropic) {
|
||||
@ -56,6 +63,14 @@ export const webSearchPlugin = (config: WebSearchPluginConfig = DEFAULT_WEB_SEAR
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
case 'openrouter': {
|
||||
if (config.openrouter) {
|
||||
const searchOptions = createOpenRouterOptions(config.openrouter)
|
||||
params.providerOptions = mergeProviderOptions(params.providerOptions, searchOptions)
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return params
|
||||
|
||||
@ -0,0 +1,26 @@
|
||||
export type OpenRouterSearchConfig = {
|
||||
plugins?: Array<{
|
||||
id: 'web'
|
||||
/**
|
||||
* Maximum number of search results to include (default: 5)
|
||||
*/
|
||||
max_results?: number
|
||||
/**
|
||||
* Custom search prompt to guide the search query
|
||||
*/
|
||||
search_prompt?: string
|
||||
}>
|
||||
/**
|
||||
* Built-in web search options for models that support native web search
|
||||
*/
|
||||
web_search_options?: {
|
||||
/**
|
||||
* Maximum number of search results to include
|
||||
*/
|
||||
max_results?: number
|
||||
/**
|
||||
* Custom search prompt to guide the search query
|
||||
*/
|
||||
search_prompt?: string
|
||||
}
|
||||
}
|
||||
@ -9,7 +9,9 @@ import { createDeepSeek } from '@ai-sdk/deepseek'
|
||||
import { createGoogleGenerativeAI } from '@ai-sdk/google'
|
||||
import { createOpenAI, type OpenAIProviderSettings } from '@ai-sdk/openai'
|
||||
import { createOpenAICompatible } from '@ai-sdk/openai-compatible'
|
||||
import { LanguageModelV2 } from '@ai-sdk/provider'
|
||||
import { createXai } from '@ai-sdk/xai'
|
||||
import { createOpenRouter } from '@openrouter/ai-sdk-provider'
|
||||
import { customProvider, Provider } from 'ai'
|
||||
import { z } from 'zod'
|
||||
|
||||
@ -25,7 +27,8 @@ export const baseProviderIds = [
|
||||
'xai',
|
||||
'azure',
|
||||
'azure-responses',
|
||||
'deepseek'
|
||||
'deepseek',
|
||||
'openrouter'
|
||||
] as const
|
||||
|
||||
/**
|
||||
@ -38,10 +41,14 @@ export const baseProviderIdSchema = z.enum(baseProviderIds)
|
||||
*/
|
||||
export type BaseProviderId = z.infer<typeof baseProviderIdSchema>
|
||||
|
||||
export const isBaseProvider = (id: ProviderId): id is BaseProviderId => {
|
||||
return baseProviderIdSchema.safeParse(id).success
|
||||
}
|
||||
|
||||
type BaseProvider = {
|
||||
id: BaseProviderId
|
||||
name: string
|
||||
creator: (options: any) => Provider
|
||||
creator: (options: any) => Provider | LanguageModelV2
|
||||
supportsImageGeneration: boolean
|
||||
}
|
||||
|
||||
@ -119,6 +126,12 @@ export const baseProviders = [
|
||||
name: 'DeepSeek',
|
||||
creator: createDeepSeek,
|
||||
supportsImageGeneration: false
|
||||
},
|
||||
{
|
||||
id: 'openrouter',
|
||||
name: 'OpenRouter',
|
||||
creator: createOpenRouter,
|
||||
supportsImageGeneration: true
|
||||
}
|
||||
] as const satisfies BaseProvider[]
|
||||
|
||||
|
||||
@ -216,5 +216,6 @@ export enum codeTools {
|
||||
qwenCode = 'qwen-code',
|
||||
claudeCode = 'claude-code',
|
||||
geminiCli = 'gemini-cli',
|
||||
openaiCodex = 'openai-codex'
|
||||
openaiCodex = 'openai-codex',
|
||||
iFlowCli = 'iflow-cli'
|
||||
}
|
||||
|
||||
241
plan.md
241
plan.md
@ -1,241 +0,0 @@
|
||||
Overview
|
||||
|
||||
Implement comprehensive CRUD APIs for agent, agentSession, and agentSessionLogs management
|
||||
in Cherry Studio's API server using RESTful URL conventions.
|
||||
|
||||
Architecture Overview
|
||||
|
||||
1. Service Layer
|
||||
|
||||
- Create AgentService class in src/main/services/agents/AgentService.ts
|
||||
- Handles database operations using SQL queries from db.ts
|
||||
- Manages SQLite database initialization and connections
|
||||
- Provides business logic for agent operations
|
||||
|
||||
2. API Routes
|
||||
|
||||
- Create route files in src/main/apiServer/routes/:
|
||||
- agents.ts - Agent CRUD endpoints
|
||||
- sessions.ts - Session CRUD endpoints
|
||||
- session-logs.ts - Session logs CRUD endpoints
|
||||
|
||||
3. Database Integration
|
||||
|
||||
- Use SQLite with @libsql/client (following MemoryService pattern)
|
||||
- Database location: userData/agents.db
|
||||
- Leverage existing SQL queries in src/main/services/agents/db.ts
|
||||
|
||||
Implementation Steps
|
||||
|
||||
Phase 1: Database Service Setup
|
||||
|
||||
1. Create AgentService class with database initialization
|
||||
2. Implement database connection management
|
||||
3. Add database initialization to main process startup
|
||||
4. Create helper methods for JSON field serialization/deserialization
|
||||
|
||||
Phase 2: Agent CRUD Operations
|
||||
|
||||
1. Implement service methods:
|
||||
|
||||
- createAgent(agent: Omit<AgentEntity, 'id' | 'created_at' | 'updated_at'>)
|
||||
- getAgent(id: string)
|
||||
- listAgents(options?: { limit?: number, offset?: number })
|
||||
- updateAgent(id: string, updates: Partial<AgentEntity>)
|
||||
- deleteAgent(id: string)
|
||||
|
||||
2. Create API routes:
|
||||
|
||||
- POST /v1/agents - Create agent
|
||||
- GET /v1/agents - List all agents
|
||||
- GET /v1/agents/:agentId - Get agent by ID
|
||||
- PUT /v1/agents/:agentId - Update agent
|
||||
- DELETE /v1/agents/:agentId - Delete agent
|
||||
|
||||
Phase 3: Session CRUD Operations
|
||||
|
||||
1. Implement service methods:
|
||||
|
||||
- createSession(session: Omit<AgentSessionEntity, 'id' | 'created_at' | 'updated_at'>)
|
||||
- getSession(id: string)
|
||||
- listSessions(agentId?: string, options?: { status?: SessionStatus, limit?: number,
|
||||
offset?: number })
|
||||
- updateSession(id: string, updates: Partial<AgentSessionEntity>)
|
||||
- updateSessionStatus(id: string, status: SessionStatus)
|
||||
- deleteSession(id: string)
|
||||
- getSessionWithAgent(id: string) - Get session with merged agent configuration
|
||||
|
||||
2. Create API routes (RESTful nested resources):
|
||||
|
||||
- POST /v1/agents/:agentId/sessions - Create session for specific agent
|
||||
- GET /v1/agents/:agentId/sessions - List sessions for specific agent
|
||||
- GET /v1/agents/:agentId/sessions/:sessionId - Get specific session
|
||||
- PUT /v1/agents/:agentId/sessions/:sessionId - Update session
|
||||
- PATCH /v1/agents/:agentId/sessions/:sessionId/status - Update session status
|
||||
- DELETE /v1/agents/:agentId/sessions/:sessionId - Delete session
|
||||
|
||||
Additional convenience endpoints:
|
||||
|
||||
- GET /v1/sessions - List all sessions (across all agents)
|
||||
- GET /v1/sessions/:sessionId - Get session by ID (without agent context)
|
||||
|
||||
Phase 4: Session Logs CRUD Operations
|
||||
|
||||
1. Implement service methods:
|
||||
|
||||
- createSessionLog(log: Omit<SessionLogEntity, 'id' | 'created_at' | 'updated_at'>)
|
||||
- getSessionLog(id: number)
|
||||
- listSessionLogs(sessionId: string, options?: { limit?: number, offset?: number })
|
||||
- updateSessionLog(id: number, updates: { content?: any, metadata?: any })
|
||||
- deleteSessionLog(id: number)
|
||||
- getSessionLogTree(sessionId: string) - Get logs with parent-child relationships
|
||||
- bulkCreateSessionLogs(logs: Array<...>) - Batch insert logs
|
||||
|
||||
2. Create API routes (RESTful nested resources):
|
||||
|
||||
- POST /v1/agents/:agentId/sessions/:sessionId/logs - Create log entry
|
||||
- GET /v1/agents/:agentId/sessions/:sessionId/logs - List logs for session
|
||||
- GET /v1/agents/:agentId/sessions/:sessionId/logs/:logId - Get specific log
|
||||
- PUT /v1/agents/:agentId/sessions/:sessionId/logs/:logId - Update log
|
||||
- DELETE /v1/agents/:agentId/sessions/:sessionId/logs/:logId - Delete log
|
||||
- POST /v1/agents/:agentId/sessions/:sessionId/logs/bulk - Bulk create logs
|
||||
|
||||
Additional convenience endpoints:
|
||||
|
||||
- GET /v1/sessions/:sessionId/logs - Get logs without agent context
|
||||
- GET /v1/session-logs/:logId - Get specific log by ID
|
||||
|
||||
Phase 5: Route Organization
|
||||
|
||||
1. Mount routes with proper nesting:
|
||||
// In app.ts
|
||||
apiRouter.use('/agents', agentsRoutes)
|
||||
// agentsRoutes will handle:
|
||||
// - /agents/_
|
||||
// - /agents/:agentId/sessions/_
|
||||
// - /agents/:agentId/sessions/:sessionId/logs/\*
|
||||
|
||||
// Convenience routes
|
||||
apiRouter.use('/sessions', sessionsRoutes)
|
||||
apiRouter.use('/session-logs', sessionLogsRoutes)
|
||||
|
||||
2. Use Express Router mergeParams for nested routes:
|
||||
// In agents.ts
|
||||
const sessionsRouter = express.Router({ mergeParams: true })
|
||||
router.use('/:agentId/sessions', sessionsRouter)
|
||||
|
||||
Phase 6: OpenAPI Documentation
|
||||
|
||||
1. Add Swagger schemas for new entities:
|
||||
|
||||
- AgentEntity schema
|
||||
- AgentSessionEntity schema
|
||||
- SessionLogEntity schema
|
||||
- Request/Response schemas
|
||||
|
||||
2. Document all new endpoints with:
|
||||
|
||||
- Clear path parameters (agentId, sessionId, logId)
|
||||
- Request body schemas
|
||||
- Response examples
|
||||
- Error responses
|
||||
- Proper grouping by resource
|
||||
|
||||
Phase 7: Validation & Error Handling
|
||||
|
||||
1. Add path parameter validation:
|
||||
|
||||
- Validate agentId exists before processing session requests
|
||||
- Validate sessionId belongs to agentId
|
||||
- Validate logId belongs to sessionId
|
||||
|
||||
2. Implement middleware for:
|
||||
|
||||
- Request validation using express-validator
|
||||
- Resource existence checks
|
||||
- Permission validation (future consideration)
|
||||
- Transaction support for complex operations
|
||||
|
||||
Phase 8: Testing
|
||||
|
||||
1. Unit tests for service methods
|
||||
2. Integration tests for API endpoints
|
||||
3. Test nested resource validation
|
||||
4. Test cascading deletes
|
||||
5. Test transaction rollbacks
|
||||
|
||||
File Structure
|
||||
|
||||
src/
|
||||
├── main/
|
||||
│ └── services/
|
||||
│ └── agents/
|
||||
│ ├── index.ts (existing)
|
||||
│ ├── db.ts (existing)
|
||||
│ └── AgentService.ts (new)
|
||||
├── main/
|
||||
│ └── apiServer/
|
||||
│ └── routes/
|
||||
│ ├── agents.ts (new - includes nested routes)
|
||||
│ ├── sessions.ts (new - convenience endpoints)
|
||||
│ └── session-logs.ts (new - convenience endpoints)
|
||||
└── renderer/
|
||||
└── src/
|
||||
└── types/
|
||||
└── agent.ts (existing)
|
||||
|
||||
API Endpoint Summary
|
||||
|
||||
Agent Endpoints
|
||||
|
||||
- POST /v1/agents
|
||||
- GET /v1/agents
|
||||
- GET /v1/agents/:agentId
|
||||
- PUT /v1/agents/:agentId
|
||||
- DELETE /v1/agents/:agentId
|
||||
|
||||
Session Endpoints (RESTful)
|
||||
|
||||
- POST /v1/agents/:agentId/sessions
|
||||
- GET /v1/agents/:agentId/sessions
|
||||
- GET /v1/agents/:agentId/sessions/:sessionId
|
||||
- PUT /v1/agents/:agentId/sessions/:sessionId
|
||||
- PATCH /v1/agents/:agentId/sessions/:sessionId/status
|
||||
- DELETE /v1/agents/:agentId/sessions/:sessionId
|
||||
|
||||
Session Convenience Endpoints
|
||||
|
||||
- GET /v1/sessions
|
||||
- GET /v1/sessions/:sessionId
|
||||
|
||||
Session Log Endpoints (RESTful)
|
||||
|
||||
- POST /v1/agents/:agentId/sessions/:sessionId/logs
|
||||
- GET /v1/agents/:agentId/sessions/:sessionId/logs
|
||||
- GET /v1/agents/:agentId/sessions/:sessionId/logs/:logId
|
||||
- PUT /v1/agents/:agentId/sessions/:sessionId/logs/:logId
|
||||
- DELETE /v1/agents/:agentId/sessions/:sessionId/logs/:logId
|
||||
- POST /v1/agents/:agentId/sessions/:sessionId/logs/bulk
|
||||
|
||||
Session Log Convenience Endpoints
|
||||
|
||||
- GET /v1/sessions/:sessionId/logs
|
||||
- GET /v1/session-logs/:logId
|
||||
|
||||
Key Considerations
|
||||
|
||||
- Follow RESTful URL conventions with proper resource nesting
|
||||
- Validate parent-child relationships in nested routes
|
||||
- Use Express Router with mergeParams for nested routing
|
||||
- Implement proper cascading deletes
|
||||
- Add transaction support for data consistency
|
||||
- Follow existing patterns from MemoryService
|
||||
- Ensure backward compatibility
|
||||
- Add rate limiting for write operations
|
||||
|
||||
Dependencies
|
||||
|
||||
- @libsql/client - SQLite database client
|
||||
- express-validator - Request validation
|
||||
- swagger-jsdoc - API documentation
|
||||
- Existing types from @types/agent.ts
|
||||
@ -51,6 +51,8 @@ class CodeToolsService {
|
||||
return '@openai/codex'
|
||||
case codeTools.qwenCode:
|
||||
return '@qwen-code/qwen-code'
|
||||
case codeTools.iFlowCli:
|
||||
return '@iflow-ai/iflow-cli'
|
||||
default:
|
||||
throw new Error(`Unsupported CLI tool: ${cliTool}`)
|
||||
}
|
||||
@ -66,6 +68,8 @@ class CodeToolsService {
|
||||
return 'codex'
|
||||
case codeTools.qwenCode:
|
||||
return 'qwen'
|
||||
case codeTools.iFlowCli:
|
||||
return 'iflow'
|
||||
default:
|
||||
throw new Error(`Unsupported CLI tool: ${cliTool}`)
|
||||
}
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import { WebSearchPluginConfig } from '@cherrystudio/ai-core/built-in/plugins'
|
||||
import { loggerService } from '@logger'
|
||||
import type { MCPTool, Message, Model, Provider } from '@renderer/types'
|
||||
import type { Chunk } from '@renderer/types/chunk'
|
||||
@ -26,6 +27,8 @@ export interface AiSdkMiddlewareConfig {
|
||||
enableUrlContext: boolean
|
||||
mcpTools?: MCPTool[]
|
||||
uiMessages?: Message[]
|
||||
// 内置搜索配置
|
||||
webSearchPluginConfig?: WebSearchPluginConfig
|
||||
}
|
||||
|
||||
/**
|
||||
@ -137,7 +140,7 @@ export function buildAiSdkMiddlewares(config: AiSdkMiddlewareConfig): LanguageMo
|
||||
return builder.build()
|
||||
}
|
||||
|
||||
const tagNameArray = ['think', 'thought']
|
||||
const tagNameArray = ['think', 'thought', 'reasoning']
|
||||
|
||||
/**
|
||||
* 添加provider特定的中间件
|
||||
@ -164,6 +167,16 @@ function addProviderSpecificMiddlewares(builder: AiSdkMiddlewareBuilder, config:
|
||||
case 'gemini':
|
||||
// Gemini特定中间件
|
||||
break
|
||||
case 'aws-bedrock': {
|
||||
if (config.model?.id.includes('gpt-oss')) {
|
||||
const tagName = tagNameArray[2]
|
||||
builder.add({
|
||||
name: 'thinking-tag-extraction',
|
||||
middleware: extractReasoningMiddleware({ tagName })
|
||||
})
|
||||
}
|
||||
break
|
||||
}
|
||||
default:
|
||||
// 其他provider的通用处理
|
||||
break
|
||||
|
||||
@ -30,9 +30,8 @@ export function buildPlugins(
|
||||
}
|
||||
|
||||
// 1. 模型内置搜索
|
||||
if (middlewareConfig.enableWebSearch) {
|
||||
// 内置了默认搜索参数,如果改的话可以传config进去
|
||||
plugins.push(webSearchPlugin())
|
||||
if (middlewareConfig.enableWebSearch && middlewareConfig.webSearchPluginConfig) {
|
||||
plugins.push(webSearchPlugin(middlewareConfig.webSearchPluginConfig))
|
||||
}
|
||||
// 2. 支持工具调用时添加搜索插件
|
||||
if (middlewareConfig.isSupportedToolUse || middlewareConfig.isPromptToolUse) {
|
||||
|
||||
@ -5,6 +5,8 @@
|
||||
|
||||
import { vertexAnthropic } from '@ai-sdk/google-vertex/anthropic/edge'
|
||||
import { vertex } from '@ai-sdk/google-vertex/edge'
|
||||
import { WebSearchPluginConfig } from '@cherrystudio/ai-core/built-in/plugins'
|
||||
import { isBaseProvider } from '@cherrystudio/ai-core/core/providers/schemas'
|
||||
import { loggerService } from '@logger'
|
||||
import {
|
||||
isGenerateImageModel,
|
||||
@ -16,8 +18,11 @@ import {
|
||||
isWebSearchModel
|
||||
} from '@renderer/config/models'
|
||||
import { getAssistantSettings, getDefaultModel } from '@renderer/services/AssistantService'
|
||||
import store from '@renderer/store'
|
||||
import { CherryWebSearchConfig } from '@renderer/store/websearch'
|
||||
import { type Assistant, type MCPTool, type Provider } from '@renderer/types'
|
||||
import type { StreamTextParams } from '@renderer/types/aiCoreTypes'
|
||||
import { mapRegexToPatterns } from '@renderer/utils/blacklistMatchPattern'
|
||||
import type { ModelMessage, Tool } from 'ai'
|
||||
import { stepCountIs } from 'ai'
|
||||
|
||||
@ -25,6 +30,7 @@ import { getAiSdkProviderId } from '../provider/factory'
|
||||
import { setupToolsConfig } from '../utils/mcp'
|
||||
import { buildProviderOptions } from '../utils/options'
|
||||
import { getAnthropicThinkingBudget } from '../utils/reasoning'
|
||||
import { buildProviderBuiltinWebSearchConfig } from '../utils/websearch'
|
||||
import { getTemperature, getTopP } from './modelParameters'
|
||||
|
||||
const logger = loggerService.withContext('parameterBuilder')
|
||||
@ -42,6 +48,7 @@ export async function buildStreamTextParams(
|
||||
options: {
|
||||
mcpTools?: MCPTool[]
|
||||
webSearchProviderId?: string
|
||||
webSearchConfig?: CherryWebSearchConfig
|
||||
requestOptions?: {
|
||||
signal?: AbortSignal
|
||||
timeout?: number
|
||||
@ -57,6 +64,7 @@ export async function buildStreamTextParams(
|
||||
enableGenerateImage: boolean
|
||||
enableUrlContext: boolean
|
||||
}
|
||||
webSearchPluginConfig?: WebSearchPluginConfig
|
||||
}> {
|
||||
const { mcpTools } = options
|
||||
|
||||
@ -93,6 +101,12 @@ export async function buildStreamTextParams(
|
||||
// }
|
||||
|
||||
// 构建真正的 providerOptions
|
||||
const webSearchConfig: CherryWebSearchConfig = {
|
||||
maxResults: store.getState().websearch.maxResults,
|
||||
excludeDomains: store.getState().websearch.excludeDomains,
|
||||
searchWithTime: store.getState().websearch.searchWithTime
|
||||
}
|
||||
|
||||
const providerOptions = buildProviderOptions(assistant, model, provider, {
|
||||
enableReasoning,
|
||||
enableWebSearch,
|
||||
@ -109,15 +123,21 @@ export async function buildStreamTextParams(
|
||||
maxTokens -= getAnthropicThinkingBudget(assistant, model)
|
||||
}
|
||||
|
||||
// google-vertex | google-vertex-anthropic
|
||||
let webSearchPluginConfig: WebSearchPluginConfig | undefined = undefined
|
||||
if (enableWebSearch) {
|
||||
if (isBaseProvider(aiSdkProviderId)) {
|
||||
webSearchPluginConfig = buildProviderBuiltinWebSearchConfig(aiSdkProviderId, webSearchConfig)
|
||||
}
|
||||
if (!tools) {
|
||||
tools = {}
|
||||
}
|
||||
if (aiSdkProviderId === 'google-vertex') {
|
||||
tools.google_search = vertex.tools.googleSearch({}) as ProviderDefinedTool
|
||||
} else if (aiSdkProviderId === 'google-vertex-anthropic') {
|
||||
tools.web_search = vertexAnthropic.tools.webSearch_20250305({}) as ProviderDefinedTool
|
||||
tools.web_search = vertexAnthropic.tools.webSearch_20250305({
|
||||
maxUses: webSearchConfig.maxResults,
|
||||
blockedDomains: mapRegexToPatterns(webSearchConfig.excludeDomains)
|
||||
}) as ProviderDefinedTool
|
||||
}
|
||||
}
|
||||
|
||||
@ -151,7 +171,8 @@ export async function buildStreamTextParams(
|
||||
return {
|
||||
params,
|
||||
modelId: model.id,
|
||||
capabilities: { enableReasoning, enableWebSearch, enableGenerateImage, enableUrlContext }
|
||||
capabilities: { enableReasoning, enableWebSearch, enableGenerateImage, enableUrlContext },
|
||||
webSearchPluginConfig
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -69,6 +69,9 @@ export function getAiSdkProviderId(provider: Provider): ProviderId | 'openai-com
|
||||
return resolvedFromType
|
||||
}
|
||||
}
|
||||
if (provider.apiHost.includes('api.openai.com')) {
|
||||
return 'openai-chat'
|
||||
}
|
||||
// 3. 最后的fallback(通常会成为openai-compatible)
|
||||
return provider.id as ProviderId
|
||||
}
|
||||
|
||||
@ -82,6 +82,7 @@ export function buildProviderOptions(
|
||||
// 应该覆盖所有类型
|
||||
switch (baseProviderId) {
|
||||
case 'openai':
|
||||
case 'openai-chat':
|
||||
case 'azure':
|
||||
providerSpecificOptions = {
|
||||
...buildOpenAIProviderOptions(assistant, model, capabilities),
|
||||
@ -101,13 +102,15 @@ export function buildProviderOptions(
|
||||
providerSpecificOptions = buildXAIProviderOptions(assistant, model, capabilities)
|
||||
break
|
||||
case 'deepseek':
|
||||
case 'openai-compatible':
|
||||
case 'openrouter':
|
||||
case 'openai-compatible': {
|
||||
// 对于其他 provider,使用通用的构建逻辑
|
||||
providerSpecificOptions = {
|
||||
...buildGenericProviderOptions(assistant, model, capabilities),
|
||||
serviceTier: serviceTierSetting
|
||||
}
|
||||
break
|
||||
}
|
||||
default:
|
||||
throw new Error(`Unsupported base provider ${baseProviderId}`)
|
||||
}
|
||||
|
||||
@ -312,7 +312,7 @@ export function getOpenAIReasoningParams(assistant: Assistant, model: Model): Re
|
||||
|
||||
export function getAnthropicThinkingBudget(assistant: Assistant, model: Model): number {
|
||||
const { maxTokens, reasoning_effort: reasoningEffort } = getAssistantSettings(assistant)
|
||||
if (maxTokens === undefined || reasoningEffort === undefined) {
|
||||
if (reasoningEffort === undefined) {
|
||||
return 0
|
||||
}
|
||||
const effortRatio = EFFORT_RATIO[reasoningEffort]
|
||||
|
||||
@ -1,6 +1,13 @@
|
||||
import {
|
||||
AnthropicSearchConfig,
|
||||
OpenAISearchConfig,
|
||||
WebSearchPluginConfig
|
||||
} from '@cherrystudio/ai-core/core/plugins/built-in/webSearchPlugin/helper'
|
||||
import { BaseProviderId } from '@cherrystudio/ai-core/provider'
|
||||
import { isOpenAIWebSearchChatCompletionOnlyModel } from '@renderer/config/models'
|
||||
import { WEB_SEARCH_PROMPT_FOR_OPENROUTER } from '@renderer/config/prompts'
|
||||
import { CherryWebSearchConfig } from '@renderer/store/websearch'
|
||||
import { Model } from '@renderer/types'
|
||||
import { mapRegexToPatterns } from '@renderer/utils/blacklistMatchPattern'
|
||||
|
||||
export function getWebSearchParams(model: Model): Record<string, any> {
|
||||
if (model.provider === 'hunyuan') {
|
||||
@ -21,11 +28,78 @@ export function getWebSearchParams(model: Model): Record<string, any> {
|
||||
web_search_options: {}
|
||||
}
|
||||
}
|
||||
|
||||
if (model.provider === 'openrouter') {
|
||||
return {
|
||||
plugins: [{ id: 'web', search_prompts: WEB_SEARCH_PROMPT_FOR_OPENROUTER }]
|
||||
}
|
||||
}
|
||||
return {}
|
||||
}
|
||||
|
||||
/**
|
||||
* range in [0, 100]
|
||||
* @param maxResults
|
||||
*/
|
||||
function mapMaxResultToOpenAIContextSize(maxResults: number): OpenAISearchConfig['searchContextSize'] {
|
||||
if (maxResults <= 33) return 'low'
|
||||
if (maxResults <= 66) return 'medium'
|
||||
return 'high'
|
||||
}
|
||||
|
||||
export function buildProviderBuiltinWebSearchConfig(
|
||||
providerId: BaseProviderId,
|
||||
webSearchConfig: CherryWebSearchConfig
|
||||
): WebSearchPluginConfig {
|
||||
switch (providerId) {
|
||||
case 'openai': {
|
||||
return {
|
||||
openai: {
|
||||
searchContextSize: mapMaxResultToOpenAIContextSize(webSearchConfig.maxResults)
|
||||
}
|
||||
}
|
||||
}
|
||||
case 'openai-chat': {
|
||||
return {
|
||||
'openai-chat': {
|
||||
searchContextSize: mapMaxResultToOpenAIContextSize(webSearchConfig.maxResults)
|
||||
}
|
||||
}
|
||||
}
|
||||
case 'anthropic': {
|
||||
const anthropicSearchOptions: AnthropicSearchConfig = {
|
||||
maxUses: webSearchConfig.maxResults,
|
||||
blockedDomains: mapRegexToPatterns(webSearchConfig.excludeDomains)
|
||||
}
|
||||
return {
|
||||
anthropic: anthropicSearchOptions
|
||||
}
|
||||
}
|
||||
case 'xai': {
|
||||
return {
|
||||
xai: {
|
||||
maxSearchResults: webSearchConfig.maxResults,
|
||||
returnCitations: true,
|
||||
sources: [
|
||||
{
|
||||
type: 'web',
|
||||
excludedWebsites: mapRegexToPatterns(webSearchConfig.excludeDomains)
|
||||
},
|
||||
{ type: 'news' },
|
||||
{ type: 'x' }
|
||||
],
|
||||
mode: 'on'
|
||||
}
|
||||
}
|
||||
}
|
||||
case 'openrouter': {
|
||||
return {
|
||||
openrouter: {
|
||||
plugins: [
|
||||
{
|
||||
id: 'web',
|
||||
max_results: webSearchConfig.maxResults
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
default: {
|
||||
throw new Error(`Unsupported provider: ${providerId}`)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -229,6 +229,11 @@ export const isGPT5SeriesModel = (model: Model) => {
|
||||
return modelId.includes('gpt-5')
|
||||
}
|
||||
|
||||
export const isGPT5SeriesReasoningModel = (model: Model) => {
|
||||
const modelId = getLowerBaseModelName(model.id)
|
||||
return modelId.includes('gpt-5') && !modelId.includes('chat')
|
||||
}
|
||||
|
||||
export const isGeminiModel = (model: Model) => {
|
||||
const modelId = getLowerBaseModelName(model.id)
|
||||
return modelId.includes('gemini')
|
||||
|
||||
@ -1020,7 +1020,7 @@ export const PROVIDER_URLS: Record<SystemProviderId, ProviderUrls> = {
|
||||
},
|
||||
anthropic: {
|
||||
api: {
|
||||
url: 'https://api.anthropic.com/'
|
||||
url: 'https://api.anthropic.com'
|
||||
},
|
||||
websites: {
|
||||
official: 'https://anthropic.com/',
|
||||
|
||||
@ -38,7 +38,7 @@ export function useAppInit() {
|
||||
customCss,
|
||||
enableDataCollection
|
||||
} = useSettings()
|
||||
const { isTopNavbar } = useNavbarPosition()
|
||||
const { isLeftNavbar } = useNavbarPosition()
|
||||
const { minappShow } = useRuntime()
|
||||
const { setDefaultModel, setQuickModel, setTranslateModel } = useDefaultModel()
|
||||
const avatar = useLiveQuery(() => db.settings.get('image://avatar'))
|
||||
@ -102,16 +102,15 @@ export function useAppInit() {
|
||||
}, [language])
|
||||
|
||||
useEffect(() => {
|
||||
const transparentWindow = windowStyle === 'transparent' && isMac && !minappShow
|
||||
const isMacTransparentWindow = windowStyle === 'transparent' && isMac
|
||||
|
||||
if (minappShow && isTopNavbar) {
|
||||
window.root.style.background =
|
||||
windowStyle === 'transparent' && isMac ? 'var(--color-background)' : 'var(--navbar-background)'
|
||||
if (minappShow && isLeftNavbar) {
|
||||
window.root.style.background = isMacTransparentWindow ? 'var(--color-background)' : 'var(--navbar-background)'
|
||||
return
|
||||
}
|
||||
|
||||
window.root.style.background = transparentWindow ? 'var(--navbar-background-mac)' : 'var(--navbar-background)'
|
||||
}, [windowStyle, minappShow, theme, isTopNavbar])
|
||||
window.root.style.background = isMacTransparentWindow ? 'var(--navbar-background-mac)' : 'var(--navbar-background)'
|
||||
}, [windowStyle, minappShow, theme, isLeftNavbar])
|
||||
|
||||
useEffect(() => {
|
||||
if (isLocalAi) {
|
||||
|
||||
@ -679,7 +679,12 @@
|
||||
"title": "Topics",
|
||||
"unpin": "Unpin Topic"
|
||||
},
|
||||
"translate": "Translate"
|
||||
"translate": "Translate",
|
||||
"web_search": {
|
||||
"warning": {
|
||||
"openai": "The GPT-5 model's minimal reasoning effort does not support web search."
|
||||
}
|
||||
}
|
||||
},
|
||||
"code": {
|
||||
"auto_update_to_latest": "Automatically update to latest version",
|
||||
|
||||
@ -679,7 +679,12 @@
|
||||
"title": "话题",
|
||||
"unpin": "取消固定"
|
||||
},
|
||||
"translate": "翻译"
|
||||
"translate": "翻译",
|
||||
"web_search": {
|
||||
"warning": {
|
||||
"openai": "GPT5 模型 minimal 思考强度不支持网络搜索"
|
||||
}
|
||||
}
|
||||
},
|
||||
"code": {
|
||||
"auto_update_to_latest": "检查更新并安装最新版本",
|
||||
|
||||
@ -679,7 +679,12 @@
|
||||
"title": "話題",
|
||||
"unpin": "取消固定"
|
||||
},
|
||||
"translate": "翻譯"
|
||||
"translate": "翻譯",
|
||||
"web_search": {
|
||||
"warning": {
|
||||
"openai": "GPT-5 模型的最小推理力度不支援網路搜尋。"
|
||||
}
|
||||
}
|
||||
},
|
||||
"code": {
|
||||
"auto_update_to_latest": "檢查更新並安裝最新版本",
|
||||
|
||||
@ -381,8 +381,9 @@
|
||||
"translate": "Μετάφραση στο {{target_language}}",
|
||||
"translating": "Μετάφραση...",
|
||||
"upload": {
|
||||
"attachment": "Μεταφόρτωση συνημμένου",
|
||||
"document": "Φόρτωση έγγραφου (το μοντέλο δεν υποστηρίζει εικόνες)",
|
||||
"label": "Φόρτωση εικόνας ή έγγραφου",
|
||||
"image_or_document": "Μεταφόρτωση εικόνας ή εγγράφου",
|
||||
"upload_from_local": "Μεταφόρτωση αρχείου από τον υπολογιστή..."
|
||||
},
|
||||
"url_context": "Περιεχόμενο ιστοσελίδας",
|
||||
@ -678,7 +679,12 @@
|
||||
"title": "Θέματα",
|
||||
"unpin": "Ξεκαρφίτσωμα"
|
||||
},
|
||||
"translate": "Μετάφραση"
|
||||
"translate": "Μετάφραση",
|
||||
"web_search": {
|
||||
"warning": {
|
||||
"openai": "Το μοντέλο GPT5 με ελάχιστη ένταση σκέψης δεν υποστηρίζει αναζήτηση στο διαδίκτυο"
|
||||
}
|
||||
}
|
||||
},
|
||||
"code": {
|
||||
"auto_update_to_latest": "Έλεγχος για ενημερώσεις και εγκατάσταση της τελευταίας έκδοσης",
|
||||
|
||||
@ -381,8 +381,9 @@
|
||||
"translate": "Traducir a {{target_language}}",
|
||||
"translating": "Traduciendo...",
|
||||
"upload": {
|
||||
"attachment": "Subir archivo adjunto",
|
||||
"document": "Subir documento (el modelo no admite imágenes)",
|
||||
"label": "Subir imagen o documento",
|
||||
"image_or_document": "Subir imagen o documento",
|
||||
"upload_from_local": "Subir archivo local..."
|
||||
},
|
||||
"url_context": "Contexto de la página web",
|
||||
@ -678,7 +679,12 @@
|
||||
"title": "Tema",
|
||||
"unpin": "Quitar fijación"
|
||||
},
|
||||
"translate": "Traducir"
|
||||
"translate": "Traducir",
|
||||
"web_search": {
|
||||
"warning": {
|
||||
"openai": "El modelo GPT5 con intensidad de pensamiento mínima no admite búsqueda en la web."
|
||||
}
|
||||
}
|
||||
},
|
||||
"code": {
|
||||
"auto_update_to_latest": "Comprobar actualizaciones e instalar la versión más reciente",
|
||||
|
||||
@ -381,8 +381,9 @@
|
||||
"translate": "Traduire en {{target_language}}",
|
||||
"translating": "Traduction en cours...",
|
||||
"upload": {
|
||||
"attachment": "Télécharger la pièce jointe",
|
||||
"document": "Télécharger un document (le modèle ne prend pas en charge les images)",
|
||||
"label": "Télécharger une image ou un document",
|
||||
"image_or_document": "Télécharger une image ou un document",
|
||||
"upload_from_local": "Télécharger un fichier local..."
|
||||
},
|
||||
"url_context": "Contexte de la page web",
|
||||
@ -678,7 +679,12 @@
|
||||
"title": "Sujet",
|
||||
"unpin": "Annuler le fixage"
|
||||
},
|
||||
"translate": "Traduire"
|
||||
"translate": "Traduire",
|
||||
"web_search": {
|
||||
"warning": {
|
||||
"openai": "Le modèle GPT5 avec une intensité de réflexion minimale ne prend pas en charge la recherche sur Internet."
|
||||
}
|
||||
}
|
||||
},
|
||||
"code": {
|
||||
"auto_update_to_latest": "Vérifier les mises à jour et installer la dernière version",
|
||||
|
||||
@ -381,8 +381,9 @@
|
||||
"translate": "{{target_language}}に翻訳",
|
||||
"translating": "翻訳中...",
|
||||
"upload": {
|
||||
"attachment": "添付ファイルをアップロード",
|
||||
"document": "ドキュメントをアップロード(モデルは画像をサポートしません)",
|
||||
"label": "画像またはドキュメントをアップロード",
|
||||
"image_or_document": "画像またはドキュメントをアップロード",
|
||||
"upload_from_local": "ローカルファイルをアップロード..."
|
||||
},
|
||||
"url_context": "URLコンテキスト",
|
||||
@ -678,7 +679,12 @@
|
||||
"title": "トピック",
|
||||
"unpin": "固定解除"
|
||||
},
|
||||
"translate": "翻訳"
|
||||
"translate": "翻訳",
|
||||
"web_search": {
|
||||
"warning": {
|
||||
"openai": "GPT5モデルの最小思考強度ではネット検索はサポートされません"
|
||||
}
|
||||
}
|
||||
},
|
||||
"code": {
|
||||
"auto_update_to_latest": "最新バージョンを自動的に更新する",
|
||||
|
||||
@ -381,8 +381,9 @@
|
||||
"translate": "Traduzir para {{target_language}}",
|
||||
"translating": "Traduzindo...",
|
||||
"upload": {
|
||||
"attachment": "Carregar anexo",
|
||||
"document": "Carregar documento (o modelo não suporta imagens)",
|
||||
"label": "Carregar imagem ou documento",
|
||||
"image_or_document": "Carregar imagem ou documento",
|
||||
"upload_from_local": "Fazer upload de arquivo local..."
|
||||
},
|
||||
"url_context": "Contexto da Página da Web",
|
||||
@ -678,7 +679,12 @@
|
||||
"title": "Tópicos",
|
||||
"unpin": "Desfixar"
|
||||
},
|
||||
"translate": "Traduzir"
|
||||
"translate": "Traduzir",
|
||||
"web_search": {
|
||||
"warning": {
|
||||
"openai": "O modelo GPT5 com intensidade mínima de pensamento não suporta pesquisa na web"
|
||||
}
|
||||
}
|
||||
},
|
||||
"code": {
|
||||
"auto_update_to_latest": "Verificar atualizações e instalar a versão mais recente",
|
||||
|
||||
@ -381,8 +381,9 @@
|
||||
"translate": "Перевести на {{target_language}}",
|
||||
"translating": "Перевод...",
|
||||
"upload": {
|
||||
"attachment": "Загрузить вложение",
|
||||
"document": "Загрузить документ (модель не поддерживает изображения)",
|
||||
"label": "Загрузить изображение или документ",
|
||||
"image_or_document": "Загрузить изображение или документ",
|
||||
"upload_from_local": "Загрузить локальный файл..."
|
||||
},
|
||||
"url_context": "Контекст страницы",
|
||||
@ -678,7 +679,12 @@
|
||||
"title": "Топики",
|
||||
"unpin": "Открепленные темы"
|
||||
},
|
||||
"translate": "Перевести"
|
||||
"translate": "Перевести",
|
||||
"web_search": {
|
||||
"warning": {
|
||||
"openai": "Модель GPT5 с минимальной интенсивностью мышления не поддерживает поиск в интернете"
|
||||
}
|
||||
}
|
||||
},
|
||||
"code": {
|
||||
"auto_update_to_latest": "Автоматически обновлять до последней версии",
|
||||
|
||||
@ -19,7 +19,8 @@ export const CLI_TOOLS = [
|
||||
{ value: codeTools.claudeCode, label: 'Claude Code' },
|
||||
{ value: codeTools.qwenCode, label: 'Qwen Code' },
|
||||
{ value: codeTools.geminiCli, label: 'Gemini CLI' },
|
||||
{ value: codeTools.openaiCodex, label: 'OpenAI Codex' }
|
||||
{ value: codeTools.openaiCodex, label: 'OpenAI Codex' },
|
||||
{ value: codeTools.iFlowCli, label: 'iFlow CLI' }
|
||||
]
|
||||
|
||||
export const GEMINI_SUPPORTED_PROVIDERS = ['aihubmix', 'dmxapi', 'new-api']
|
||||
@ -35,7 +36,8 @@ export const CLI_TOOL_PROVIDER_MAP: Record<string, (providers: Provider[]) => Pr
|
||||
providers.filter((p) => p.type === 'gemini' || GEMINI_SUPPORTED_PROVIDERS.includes(p.id)),
|
||||
[codeTools.qwenCode]: (providers) => providers.filter((p) => p.type.includes('openai')),
|
||||
[codeTools.openaiCodex]: (providers) =>
|
||||
providers.filter((p) => p.id === 'openai' || OPENAI_CODEX_SUPPORTED_PROVIDERS.includes(p.id))
|
||||
providers.filter((p) => p.id === 'openai' || OPENAI_CODEX_SUPPORTED_PROVIDERS.includes(p.id)),
|
||||
[codeTools.iFlowCli]: (providers) => providers.filter((p) => p.type.includes('openai'))
|
||||
}
|
||||
|
||||
export const getCodeToolsApiBaseUrl = (model: Model, type: EndpointType) => {
|
||||
@ -144,6 +146,12 @@ export const generateToolEnvironment = ({
|
||||
env.OPENAI_MODEL = model.id
|
||||
env.OPENAI_MODEL_PROVIDER = modelProvider.id
|
||||
break
|
||||
|
||||
case codeTools.iFlowCli:
|
||||
env.IFLOW_API_KEY = apiKey
|
||||
env.IFLOW_BASE_URL = baseUrl
|
||||
env.IFLOW_MODEL_NAME = model.id
|
||||
break
|
||||
}
|
||||
|
||||
return env
|
||||
|
||||
@ -155,23 +155,23 @@ const Chat: FC<Props> = (props) => {
|
||||
flex={1}
|
||||
justify="space-between"
|
||||
style={{ maxWidth: chatMaxWidth, height: mainHeight }}>
|
||||
<Messages
|
||||
key={props.activeTopic.id}
|
||||
assistant={assistant}
|
||||
topic={props.activeTopic}
|
||||
setActiveTopic={props.setActiveTopic}
|
||||
onComponentUpdate={messagesComponentUpdateHandler}
|
||||
onFirstUpdate={messagesComponentFirstUpdateHandler}
|
||||
/>
|
||||
<ContentSearch
|
||||
ref={contentSearchRef}
|
||||
searchTarget={mainRef as React.RefObject<HTMLElement>}
|
||||
filter={contentSearchFilter}
|
||||
includeUser={filterIncludeUser}
|
||||
onIncludeUserChange={userOutlinedItemClickHandler}
|
||||
/>
|
||||
{messageNavigation === 'buttons' && <ChatNavigation containerId="messages" />}
|
||||
<QuickPanelProvider>
|
||||
<Messages
|
||||
key={props.activeTopic.id}
|
||||
assistant={assistant}
|
||||
topic={props.activeTopic}
|
||||
setActiveTopic={props.setActiveTopic}
|
||||
onComponentUpdate={messagesComponentUpdateHandler}
|
||||
onFirstUpdate={messagesComponentFirstUpdateHandler}
|
||||
/>
|
||||
<ContentSearch
|
||||
ref={contentSearchRef}
|
||||
searchTarget={mainRef as React.RefObject<HTMLElement>}
|
||||
filter={contentSearchFilter}
|
||||
includeUser={filterIncludeUser}
|
||||
onIncludeUserChange={userOutlinedItemClickHandler}
|
||||
/>
|
||||
{messageNavigation === 'buttons' && <ChatNavigation containerId="messages" />}
|
||||
<Inputbar assistant={assistant} setActiveTopic={props.setActiveTopic} topic={props.activeTopic} />
|
||||
{isMultiSelectMode && <MultiSelectActionPopup topic={props.activeTopic} />}
|
||||
</QuickPanelProvider>
|
||||
|
||||
@ -71,6 +71,7 @@ interface Props {
|
||||
|
||||
let _text = ''
|
||||
let _files: FileType[] = []
|
||||
let _mentionedModelsCache: Model[] = []
|
||||
|
||||
const Inputbar: FC<Props> = ({ assistant: _assistant, setActiveTopic, topic }) => {
|
||||
const [text, setText] = useState(_text)
|
||||
@ -103,7 +104,8 @@ const Inputbar: FC<Props> = ({ assistant: _assistant, setActiveTopic, topic }) =
|
||||
const spaceClickTimer = useRef<NodeJS.Timeout>(null)
|
||||
const [isTranslating, setIsTranslating] = useState(false)
|
||||
const [selectedKnowledgeBases, setSelectedKnowledgeBases] = useState<KnowledgeBase[]>([])
|
||||
const [mentionedModels, setMentionedModels] = useState<Model[]>([])
|
||||
const [mentionedModels, setMentionedModels] = useState<Model[]>(_mentionedModelsCache)
|
||||
const mentionedModelsRef = useRef(mentionedModels)
|
||||
const [isDragging, setIsDragging] = useState(false)
|
||||
const [isFileDragging, setIsFileDragging] = useState(false)
|
||||
const [textareaHeight, setTextareaHeight] = useState<number>()
|
||||
@ -114,6 +116,10 @@ const Inputbar: FC<Props> = ({ assistant: _assistant, setActiveTopic, topic }) =
|
||||
const isGenerateImageAssistant = useMemo(() => isGenerateImageModel(model), [model])
|
||||
const { setTimeoutTimer } = useTimer()
|
||||
|
||||
useEffect(() => {
|
||||
mentionedModelsRef.current = mentionedModels
|
||||
}, [mentionedModels])
|
||||
|
||||
const isVisionSupported = useMemo(
|
||||
() =>
|
||||
(mentionedModels.length > 0 && isVisionModels(mentionedModels)) ||
|
||||
@ -179,6 +185,13 @@ const Inputbar: FC<Props> = ({ assistant: _assistant, setActiveTopic, topic }) =
|
||||
_text = text
|
||||
_files = files
|
||||
|
||||
useEffect(() => {
|
||||
// 利用useEffect清理函数在卸载组件时更新状态缓存
|
||||
return () => {
|
||||
_mentionedModelsCache = mentionedModelsRef.current
|
||||
}
|
||||
}, [])
|
||||
|
||||
const focusTextarea = useCallback(() => {
|
||||
textareaRef.current?.focus()
|
||||
}, [])
|
||||
|
||||
@ -8,7 +8,13 @@ import {
|
||||
MdiLightbulbOn80
|
||||
} from '@renderer/components/Icons/SVGIcon'
|
||||
import { QuickPanelReservedSymbol, useQuickPanel } from '@renderer/components/QuickPanel'
|
||||
import { getThinkModelType, isDoubaoThinkingAutoModel, MODEL_SUPPORTED_OPTIONS } from '@renderer/config/models'
|
||||
import {
|
||||
getThinkModelType,
|
||||
isDoubaoThinkingAutoModel,
|
||||
isGPT5SeriesReasoningModel,
|
||||
isOpenAIWebSearchModel,
|
||||
MODEL_SUPPORTED_OPTIONS
|
||||
} from '@renderer/config/models'
|
||||
import { useAssistant } from '@renderer/hooks/useAssistant'
|
||||
import { getReasoningEffortOptionsLabel } from '@renderer/i18n/label'
|
||||
import { Model, ThinkingOption } from '@renderer/types'
|
||||
@ -61,6 +67,15 @@ const ThinkingButton: FC<Props> = ({ ref, model, assistantId }): ReactElement =>
|
||||
})
|
||||
return
|
||||
}
|
||||
if (
|
||||
isOpenAIWebSearchModel(model) &&
|
||||
isGPT5SeriesReasoningModel(model) &&
|
||||
assistant.enableWebSearch &&
|
||||
option === 'minimal'
|
||||
) {
|
||||
window.toast.warning(t('chat.web_search.warning.openai'))
|
||||
return
|
||||
}
|
||||
updateAssistantSettings({
|
||||
reasoning_effort: option,
|
||||
reasoning_effort_cache: option,
|
||||
@ -68,7 +83,7 @@ const ThinkingButton: FC<Props> = ({ ref, model, assistantId }): ReactElement =>
|
||||
})
|
||||
return
|
||||
},
|
||||
[updateAssistantSettings]
|
||||
[updateAssistantSettings, assistant.enableWebSearch, model, t]
|
||||
)
|
||||
|
||||
const panelItems = useMemo(() => {
|
||||
|
||||
@ -3,7 +3,12 @@ import { loggerService } from '@logger'
|
||||
import { ActionIconButton } from '@renderer/components/Buttons'
|
||||
import { BingLogo, BochaLogo, ExaLogo, SearXNGLogo, TavilyLogo, ZhipuLogo } from '@renderer/components/Icons'
|
||||
import { QuickPanelListItem, QuickPanelReservedSymbol, useQuickPanel } from '@renderer/components/QuickPanel'
|
||||
import { isGeminiModel, isWebSearchModel } from '@renderer/config/models'
|
||||
import {
|
||||
isGeminiModel,
|
||||
isGPT5SeriesReasoningModel,
|
||||
isOpenAIWebSearchModel,
|
||||
isWebSearchModel
|
||||
} from '@renderer/config/models'
|
||||
import { isGeminiWebSearchProvider } from '@renderer/config/providers'
|
||||
import { useAssistant } from '@renderer/hooks/useAssistant'
|
||||
import { useTimer } from '@renderer/hooks/useTimer'
|
||||
@ -115,6 +120,15 @@ const WebSearchButton: FC<Props> = ({ ref, assistantId }) => {
|
||||
update.enableWebSearch = false
|
||||
window.toast.warning(t('chat.mcp.warning.gemini_web_search'))
|
||||
}
|
||||
if (
|
||||
isOpenAIWebSearchModel(model) &&
|
||||
isGPT5SeriesReasoningModel(model) &&
|
||||
update.enableWebSearch &&
|
||||
assistant.settings?.reasoning_effort === 'minimal'
|
||||
) {
|
||||
update.enableWebSearch = false
|
||||
window.toast.warning(t('chat.web_search.warning.openai'))
|
||||
}
|
||||
setTimeoutTimer('updateSelectedWebSearchBuiltin', () => updateAssistant(update), 200)
|
||||
}, [assistant, setTimeoutTimer, t, updateAssistant])
|
||||
|
||||
|
||||
@ -16,7 +16,13 @@ import { useAppDispatch } from '@renderer/store'
|
||||
import { updateWebSearchProvider } from '@renderer/store/websearch'
|
||||
import { isSystemProvider } from '@renderer/types'
|
||||
import { ApiKeyConnectivity, HealthStatus } from '@renderer/types/healthCheck'
|
||||
import { formatApiHost, formatApiKeys, getFancyProviderName, isOpenAIProvider } from '@renderer/utils'
|
||||
import {
|
||||
formatApiHost,
|
||||
formatApiKeys,
|
||||
getFancyProviderName,
|
||||
isAnthropicProvider,
|
||||
isOpenAIProvider
|
||||
} from '@renderer/utils'
|
||||
import { formatErrorMessage } from '@renderer/utils/error'
|
||||
import { Button, Divider, Flex, Input, Select, Space, Switch, Tooltip } from 'antd'
|
||||
import Link from 'antd/es/typography/Link'
|
||||
@ -212,6 +218,10 @@ const ProviderSetting: FC<Props> = ({ providerId }) => {
|
||||
if (provider.type === 'azure-openai') {
|
||||
return formatApiHost(apiHost) + 'openai/v1'
|
||||
}
|
||||
|
||||
if (provider.type === 'anthropic') {
|
||||
return formatApiHost(apiHost) + 'messages'
|
||||
}
|
||||
return formatApiHost(apiHost) + 'responses'
|
||||
}
|
||||
|
||||
@ -361,7 +371,7 @@ const ProviderSetting: FC<Props> = ({ providerId }) => {
|
||||
</Button>
|
||||
)}
|
||||
</Space.Compact>
|
||||
{isOpenAIProvider(provider) && (
|
||||
{(isOpenAIProvider(provider) || isAnthropicProvider(provider)) && (
|
||||
<SettingHelpTextRow style={{ justifyContent: 'space-between' }}>
|
||||
<SettingHelpText
|
||||
style={{ marginLeft: 6, marginRight: '1em', whiteSpace: 'break-spaces', wordBreak: 'break-all' }}>
|
||||
|
||||
@ -117,7 +117,8 @@ export async function fetchChatCompletion({
|
||||
const {
|
||||
params: aiSdkParams,
|
||||
modelId,
|
||||
capabilities
|
||||
capabilities,
|
||||
webSearchPluginConfig
|
||||
} = await buildStreamTextParams(messages, assistant, provider, {
|
||||
mcpTools: mcpTools,
|
||||
webSearchProviderId: assistant.webSearchProviderId,
|
||||
@ -132,6 +133,7 @@ export async function fetchChatCompletion({
|
||||
isPromptToolUse: isPromptToolUse(assistant),
|
||||
isSupportedToolUse: isSupportedToolUse(assistant),
|
||||
isImageGenerationEndpoint: isDedicatedImageGenerationModel(assistant.model || getDefaultModel()),
|
||||
webSearchPluginConfig: webSearchPluginConfig,
|
||||
enableWebSearch: capabilities.enableWebSearch,
|
||||
enableGenerateImage: capabilities.enableGenerateImage,
|
||||
enableUrlContext: capabilities.enableUrlContext,
|
||||
|
||||
@ -67,7 +67,7 @@ const persistedReducer = persistReducer(
|
||||
{
|
||||
key: 'cherry-studio',
|
||||
storage,
|
||||
version: 155,
|
||||
version: 156,
|
||||
blacklist: ['runtime', 'messages', 'messageBlocks', 'tabs'],
|
||||
migrate
|
||||
},
|
||||
|
||||
@ -2485,6 +2485,21 @@ const migrateConfig = {
|
||||
logger.error('migrate 155 error', error as Error)
|
||||
return state
|
||||
}
|
||||
},
|
||||
'156': (state: RootState) => {
|
||||
try {
|
||||
state.llm.providers.forEach((provider) => {
|
||||
if (provider.id === SystemProviderIds.anthropic) {
|
||||
if (provider.apiHost.endsWith('/')) {
|
||||
provider.apiHost = provider.apiHost.slice(0, -1)
|
||||
}
|
||||
}
|
||||
})
|
||||
return state
|
||||
} catch (error) {
|
||||
logger.error('migrate 156 error', error as Error)
|
||||
return state
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -41,6 +41,8 @@ export interface WebSearchState {
|
||||
providerConfig: Record<string, any>
|
||||
}
|
||||
|
||||
export type CherryWebSearchConfig = Pick<WebSearchState, 'searchWithTime' | 'maxResults' | 'excludeDomains'>
|
||||
|
||||
export const initialState: WebSearchState = {
|
||||
defaultProvider: 'local-bing',
|
||||
providers: WEB_SEARCH_PROVIDERS,
|
||||
|
||||
@ -26,7 +26,7 @@
|
||||
|
||||
import { describe, expect, it } from 'vitest'
|
||||
|
||||
import { MatchPatternMap } from '../blacklistMatchPattern'
|
||||
import { mapRegexToPatterns, MatchPatternMap } from '../blacklistMatchPattern'
|
||||
|
||||
function get(map: MatchPatternMap<number>, url: string) {
|
||||
return map.get(url).sort()
|
||||
@ -161,3 +161,28 @@ describe('blacklistMatchPattern', () => {
|
||||
expect(get(map, 'https://b.mozilla.org/path/')).toEqual([0, 1, 2, 6])
|
||||
})
|
||||
})
|
||||
|
||||
describe('mapRegexToPatterns', () => {
|
||||
it('extracts domains from regex patterns', () => {
|
||||
const result = mapRegexToPatterns([
|
||||
'/example\\.com/',
|
||||
'/(?:www\\.)?sub\\.example\\.co\\.uk/',
|
||||
'/api\\.service\\.io/',
|
||||
'https://baidu.com'
|
||||
])
|
||||
|
||||
expect(result).toEqual(['example.com', 'sub.example.co.uk', 'api.service.io', 'baidu.com'])
|
||||
})
|
||||
|
||||
it('deduplicates domains across multiple patterns', () => {
|
||||
const result = mapRegexToPatterns(['/example\\.com/', '/(example\\.com|test\\.org)/'])
|
||||
|
||||
expect(result).toEqual(['example.com', 'test.org'])
|
||||
})
|
||||
|
||||
it('ignores patterns without domain matches', () => {
|
||||
const result = mapRegexToPatterns(['', 'plain-domain.com', '/^https?:\\/\\/[^/]+$/'])
|
||||
|
||||
expect(result).toEqual(['plain-domain.com'])
|
||||
})
|
||||
})
|
||||
|
||||
@ -202,6 +202,43 @@ export async function parseSubscribeContent(url: string): Promise<string[]> {
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
export function mapRegexToPatterns(patterns: string[]): string[] {
|
||||
const patternSet = new Set<string>()
|
||||
const domainMatcher = /[A-Za-z0-9-]+(?:\.[A-Za-z0-9-]+)+/g
|
||||
|
||||
patterns.forEach((pattern) => {
|
||||
if (!pattern) {
|
||||
return
|
||||
}
|
||||
|
||||
// Handle regex patterns (wrapped in /)
|
||||
if (pattern.startsWith('/') && pattern.endsWith('/')) {
|
||||
const rawPattern = pattern.slice(1, -1)
|
||||
const normalizedPattern = rawPattern.replace(/\\\./g, '.').replace(/\\\//g, '/')
|
||||
const matches = normalizedPattern.match(domainMatcher)
|
||||
|
||||
if (matches) {
|
||||
matches.forEach((match) => {
|
||||
patternSet.add(match.replace(/http(s)?:\/\//g, '').toLowerCase())
|
||||
})
|
||||
}
|
||||
} else if (pattern.includes('://')) {
|
||||
// Handle URLs with protocol (e.g., https://baidu.com)
|
||||
const matches = pattern.match(domainMatcher)
|
||||
if (matches) {
|
||||
matches.forEach((match) => {
|
||||
patternSet.add(match.replace(/http(s)?:\/\//g, '').toLowerCase())
|
||||
})
|
||||
}
|
||||
} else {
|
||||
patternSet.add(pattern.toLowerCase())
|
||||
}
|
||||
})
|
||||
|
||||
return Array.from(patternSet)
|
||||
}
|
||||
|
||||
export async function filterResultWithBlacklist(
|
||||
response: WebSearchProviderResponse,
|
||||
websearch: WebSearchState
|
||||
|
||||
@ -205,6 +205,10 @@ export function isOpenAIProvider(provider: Provider): boolean {
|
||||
return !['anthropic', 'gemini', 'vertexai'].includes(provider.type)
|
||||
}
|
||||
|
||||
export function isAnthropicProvider(provider: Provider): boolean {
|
||||
return provider.type === 'anthropic'
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断模型是否为用户手动选择
|
||||
* @param {Model} model 模型对象
|
||||
|
||||
@ -1,9 +1,9 @@
|
||||
import '@renderer/databases'
|
||||
|
||||
import { HeroUIProvider } from '@heroui/react'
|
||||
import { ErrorBoundary } from '@renderer/components/ErrorBoundary'
|
||||
import { ToastPortal } from '@renderer/components/ToastPortal'
|
||||
import { getToastUtilities } from '@renderer/components/TopView/toast'
|
||||
import { HeroUIProvider } from '@renderer/context/HeroUIProvider'
|
||||
import { useSettings } from '@renderer/hooks/useSettings'
|
||||
import store, { persistor } from '@renderer/store'
|
||||
import { useEffect } from 'react'
|
||||
|
||||
@ -2,13 +2,13 @@ import '@renderer/assets/styles/index.css'
|
||||
import '@renderer/assets/styles/tailwind.css'
|
||||
import '@ant-design/v5-patch-for-react-19'
|
||||
|
||||
import { HeroUIProvider } from '@heroui/react'
|
||||
import KeyvStorage from '@kangfenmao/keyv-storage'
|
||||
import { loggerService } from '@logger'
|
||||
import { ToastPortal } from '@renderer/components/ToastPortal'
|
||||
import { getToastUtilities } from '@renderer/components/TopView/toast'
|
||||
import AntdProvider from '@renderer/context/AntdProvider'
|
||||
import { CodeStyleProvider } from '@renderer/context/CodeStyleProvider'
|
||||
import { HeroUIProvider } from '@renderer/context/HeroUIProvider'
|
||||
import { ThemeProvider } from '@renderer/context/ThemeProvider'
|
||||
import storeSyncService from '@renderer/services/StoreSyncService'
|
||||
import store, { persistor } from '@renderer/store'
|
||||
|
||||
388
yarn.lock
388
yarn.lock
@ -386,8 +386,8 @@ __metadata:
|
||||
linkType: hard
|
||||
|
||||
"@anthropic-ai/claude-code@npm:^1.0.113":
|
||||
version: 1.0.113
|
||||
resolution: "@anthropic-ai/claude-code@npm:1.0.113"
|
||||
version: 1.0.118
|
||||
resolution: "@anthropic-ai/claude-code@npm:1.0.118"
|
||||
dependencies:
|
||||
"@img/sharp-darwin-arm64": "npm:^0.33.5"
|
||||
"@img/sharp-darwin-x64": "npm:^0.33.5"
|
||||
@ -410,7 +410,7 @@ __metadata:
|
||||
optional: true
|
||||
bin:
|
||||
claude: cli.js
|
||||
checksum: 10c0/954303e43916a221b0ea9185844621957ac0d42835be654a0f66f09e01a06ef0d121e98bf156c8a51a30d65ca75d2002e5ece561a960cfcc618467a79fb21cba
|
||||
checksum: 10c0/90e9feac857bfcbdaa3797c70cccfad8588ed45bc9ea6c88664e2f08fa71d069cfb28ce17c20a67ddd841d9a8c7a50e61281687975f7d5ebde9b39f63551cefb
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@ -2338,7 +2338,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@cherrystudio/ai-core@workspace:^1.0.0-alpha.16, @cherrystudio/ai-core@workspace:packages/aiCore":
|
||||
"@cherrystudio/ai-core@workspace:^1.0.0-alpha.18, @cherrystudio/ai-core@workspace:packages/aiCore":
|
||||
version: 0.0.0-use.local
|
||||
resolution: "@cherrystudio/ai-core@workspace:packages/aiCore"
|
||||
dependencies:
|
||||
@ -3449,16 +3449,16 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@esbuild/aix-ppc64@npm:0.25.8":
|
||||
version: 0.25.8
|
||||
resolution: "@esbuild/aix-ppc64@npm:0.25.8"
|
||||
"@esbuild/aix-ppc64@npm:0.25.10":
|
||||
version: 0.25.10
|
||||
resolution: "@esbuild/aix-ppc64@npm:0.25.10"
|
||||
conditions: os=aix & cpu=ppc64
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@esbuild/aix-ppc64@npm:0.25.9":
|
||||
version: 0.25.9
|
||||
resolution: "@esbuild/aix-ppc64@npm:0.25.9"
|
||||
"@esbuild/aix-ppc64@npm:0.25.8":
|
||||
version: 0.25.8
|
||||
resolution: "@esbuild/aix-ppc64@npm:0.25.8"
|
||||
conditions: os=aix & cpu=ppc64
|
||||
languageName: node
|
||||
linkType: hard
|
||||
@ -3470,16 +3470,16 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@esbuild/android-arm64@npm:0.25.8":
|
||||
version: 0.25.8
|
||||
resolution: "@esbuild/android-arm64@npm:0.25.8"
|
||||
"@esbuild/android-arm64@npm:0.25.10":
|
||||
version: 0.25.10
|
||||
resolution: "@esbuild/android-arm64@npm:0.25.10"
|
||||
conditions: os=android & cpu=arm64
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@esbuild/android-arm64@npm:0.25.9":
|
||||
version: 0.25.9
|
||||
resolution: "@esbuild/android-arm64@npm:0.25.9"
|
||||
"@esbuild/android-arm64@npm:0.25.8":
|
||||
version: 0.25.8
|
||||
resolution: "@esbuild/android-arm64@npm:0.25.8"
|
||||
conditions: os=android & cpu=arm64
|
||||
languageName: node
|
||||
linkType: hard
|
||||
@ -3491,16 +3491,16 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@esbuild/android-arm@npm:0.25.8":
|
||||
version: 0.25.8
|
||||
resolution: "@esbuild/android-arm@npm:0.25.8"
|
||||
"@esbuild/android-arm@npm:0.25.10":
|
||||
version: 0.25.10
|
||||
resolution: "@esbuild/android-arm@npm:0.25.10"
|
||||
conditions: os=android & cpu=arm
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@esbuild/android-arm@npm:0.25.9":
|
||||
version: 0.25.9
|
||||
resolution: "@esbuild/android-arm@npm:0.25.9"
|
||||
"@esbuild/android-arm@npm:0.25.8":
|
||||
version: 0.25.8
|
||||
resolution: "@esbuild/android-arm@npm:0.25.8"
|
||||
conditions: os=android & cpu=arm
|
||||
languageName: node
|
||||
linkType: hard
|
||||
@ -3512,16 +3512,16 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@esbuild/android-x64@npm:0.25.8":
|
||||
version: 0.25.8
|
||||
resolution: "@esbuild/android-x64@npm:0.25.8"
|
||||
"@esbuild/android-x64@npm:0.25.10":
|
||||
version: 0.25.10
|
||||
resolution: "@esbuild/android-x64@npm:0.25.10"
|
||||
conditions: os=android & cpu=x64
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@esbuild/android-x64@npm:0.25.9":
|
||||
version: 0.25.9
|
||||
resolution: "@esbuild/android-x64@npm:0.25.9"
|
||||
"@esbuild/android-x64@npm:0.25.8":
|
||||
version: 0.25.8
|
||||
resolution: "@esbuild/android-x64@npm:0.25.8"
|
||||
conditions: os=android & cpu=x64
|
||||
languageName: node
|
||||
linkType: hard
|
||||
@ -3533,16 +3533,16 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@esbuild/darwin-arm64@npm:0.25.8":
|
||||
version: 0.25.8
|
||||
resolution: "@esbuild/darwin-arm64@npm:0.25.8"
|
||||
"@esbuild/darwin-arm64@npm:0.25.10":
|
||||
version: 0.25.10
|
||||
resolution: "@esbuild/darwin-arm64@npm:0.25.10"
|
||||
conditions: os=darwin & cpu=arm64
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@esbuild/darwin-arm64@npm:0.25.9":
|
||||
version: 0.25.9
|
||||
resolution: "@esbuild/darwin-arm64@npm:0.25.9"
|
||||
"@esbuild/darwin-arm64@npm:0.25.8":
|
||||
version: 0.25.8
|
||||
resolution: "@esbuild/darwin-arm64@npm:0.25.8"
|
||||
conditions: os=darwin & cpu=arm64
|
||||
languageName: node
|
||||
linkType: hard
|
||||
@ -3554,16 +3554,16 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@esbuild/darwin-x64@npm:0.25.8":
|
||||
version: 0.25.8
|
||||
resolution: "@esbuild/darwin-x64@npm:0.25.8"
|
||||
"@esbuild/darwin-x64@npm:0.25.10":
|
||||
version: 0.25.10
|
||||
resolution: "@esbuild/darwin-x64@npm:0.25.10"
|
||||
conditions: os=darwin & cpu=x64
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@esbuild/darwin-x64@npm:0.25.9":
|
||||
version: 0.25.9
|
||||
resolution: "@esbuild/darwin-x64@npm:0.25.9"
|
||||
"@esbuild/darwin-x64@npm:0.25.8":
|
||||
version: 0.25.8
|
||||
resolution: "@esbuild/darwin-x64@npm:0.25.8"
|
||||
conditions: os=darwin & cpu=x64
|
||||
languageName: node
|
||||
linkType: hard
|
||||
@ -3575,16 +3575,16 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@esbuild/freebsd-arm64@npm:0.25.8":
|
||||
version: 0.25.8
|
||||
resolution: "@esbuild/freebsd-arm64@npm:0.25.8"
|
||||
"@esbuild/freebsd-arm64@npm:0.25.10":
|
||||
version: 0.25.10
|
||||
resolution: "@esbuild/freebsd-arm64@npm:0.25.10"
|
||||
conditions: os=freebsd & cpu=arm64
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@esbuild/freebsd-arm64@npm:0.25.9":
|
||||
version: 0.25.9
|
||||
resolution: "@esbuild/freebsd-arm64@npm:0.25.9"
|
||||
"@esbuild/freebsd-arm64@npm:0.25.8":
|
||||
version: 0.25.8
|
||||
resolution: "@esbuild/freebsd-arm64@npm:0.25.8"
|
||||
conditions: os=freebsd & cpu=arm64
|
||||
languageName: node
|
||||
linkType: hard
|
||||
@ -3596,16 +3596,16 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@esbuild/freebsd-x64@npm:0.25.8":
|
||||
version: 0.25.8
|
||||
resolution: "@esbuild/freebsd-x64@npm:0.25.8"
|
||||
"@esbuild/freebsd-x64@npm:0.25.10":
|
||||
version: 0.25.10
|
||||
resolution: "@esbuild/freebsd-x64@npm:0.25.10"
|
||||
conditions: os=freebsd & cpu=x64
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@esbuild/freebsd-x64@npm:0.25.9":
|
||||
version: 0.25.9
|
||||
resolution: "@esbuild/freebsd-x64@npm:0.25.9"
|
||||
"@esbuild/freebsd-x64@npm:0.25.8":
|
||||
version: 0.25.8
|
||||
resolution: "@esbuild/freebsd-x64@npm:0.25.8"
|
||||
conditions: os=freebsd & cpu=x64
|
||||
languageName: node
|
||||
linkType: hard
|
||||
@ -3617,16 +3617,16 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@esbuild/linux-arm64@npm:0.25.8":
|
||||
version: 0.25.8
|
||||
resolution: "@esbuild/linux-arm64@npm:0.25.8"
|
||||
"@esbuild/linux-arm64@npm:0.25.10":
|
||||
version: 0.25.10
|
||||
resolution: "@esbuild/linux-arm64@npm:0.25.10"
|
||||
conditions: os=linux & cpu=arm64
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@esbuild/linux-arm64@npm:0.25.9":
|
||||
version: 0.25.9
|
||||
resolution: "@esbuild/linux-arm64@npm:0.25.9"
|
||||
"@esbuild/linux-arm64@npm:0.25.8":
|
||||
version: 0.25.8
|
||||
resolution: "@esbuild/linux-arm64@npm:0.25.8"
|
||||
conditions: os=linux & cpu=arm64
|
||||
languageName: node
|
||||
linkType: hard
|
||||
@ -3638,16 +3638,16 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@esbuild/linux-arm@npm:0.25.8":
|
||||
version: 0.25.8
|
||||
resolution: "@esbuild/linux-arm@npm:0.25.8"
|
||||
"@esbuild/linux-arm@npm:0.25.10":
|
||||
version: 0.25.10
|
||||
resolution: "@esbuild/linux-arm@npm:0.25.10"
|
||||
conditions: os=linux & cpu=arm
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@esbuild/linux-arm@npm:0.25.9":
|
||||
version: 0.25.9
|
||||
resolution: "@esbuild/linux-arm@npm:0.25.9"
|
||||
"@esbuild/linux-arm@npm:0.25.8":
|
||||
version: 0.25.8
|
||||
resolution: "@esbuild/linux-arm@npm:0.25.8"
|
||||
conditions: os=linux & cpu=arm
|
||||
languageName: node
|
||||
linkType: hard
|
||||
@ -3659,16 +3659,16 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@esbuild/linux-ia32@npm:0.25.8":
|
||||
version: 0.25.8
|
||||
resolution: "@esbuild/linux-ia32@npm:0.25.8"
|
||||
"@esbuild/linux-ia32@npm:0.25.10":
|
||||
version: 0.25.10
|
||||
resolution: "@esbuild/linux-ia32@npm:0.25.10"
|
||||
conditions: os=linux & cpu=ia32
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@esbuild/linux-ia32@npm:0.25.9":
|
||||
version: 0.25.9
|
||||
resolution: "@esbuild/linux-ia32@npm:0.25.9"
|
||||
"@esbuild/linux-ia32@npm:0.25.8":
|
||||
version: 0.25.8
|
||||
resolution: "@esbuild/linux-ia32@npm:0.25.8"
|
||||
conditions: os=linux & cpu=ia32
|
||||
languageName: node
|
||||
linkType: hard
|
||||
@ -3680,16 +3680,16 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@esbuild/linux-loong64@npm:0.25.8":
|
||||
version: 0.25.8
|
||||
resolution: "@esbuild/linux-loong64@npm:0.25.8"
|
||||
"@esbuild/linux-loong64@npm:0.25.10":
|
||||
version: 0.25.10
|
||||
resolution: "@esbuild/linux-loong64@npm:0.25.10"
|
||||
conditions: os=linux & cpu=loong64
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@esbuild/linux-loong64@npm:0.25.9":
|
||||
version: 0.25.9
|
||||
resolution: "@esbuild/linux-loong64@npm:0.25.9"
|
||||
"@esbuild/linux-loong64@npm:0.25.8":
|
||||
version: 0.25.8
|
||||
resolution: "@esbuild/linux-loong64@npm:0.25.8"
|
||||
conditions: os=linux & cpu=loong64
|
||||
languageName: node
|
||||
linkType: hard
|
||||
@ -3701,16 +3701,16 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@esbuild/linux-mips64el@npm:0.25.8":
|
||||
version: 0.25.8
|
||||
resolution: "@esbuild/linux-mips64el@npm:0.25.8"
|
||||
"@esbuild/linux-mips64el@npm:0.25.10":
|
||||
version: 0.25.10
|
||||
resolution: "@esbuild/linux-mips64el@npm:0.25.10"
|
||||
conditions: os=linux & cpu=mips64el
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@esbuild/linux-mips64el@npm:0.25.9":
|
||||
version: 0.25.9
|
||||
resolution: "@esbuild/linux-mips64el@npm:0.25.9"
|
||||
"@esbuild/linux-mips64el@npm:0.25.8":
|
||||
version: 0.25.8
|
||||
resolution: "@esbuild/linux-mips64el@npm:0.25.8"
|
||||
conditions: os=linux & cpu=mips64el
|
||||
languageName: node
|
||||
linkType: hard
|
||||
@ -3722,16 +3722,16 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@esbuild/linux-ppc64@npm:0.25.8":
|
||||
version: 0.25.8
|
||||
resolution: "@esbuild/linux-ppc64@npm:0.25.8"
|
||||
"@esbuild/linux-ppc64@npm:0.25.10":
|
||||
version: 0.25.10
|
||||
resolution: "@esbuild/linux-ppc64@npm:0.25.10"
|
||||
conditions: os=linux & cpu=ppc64
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@esbuild/linux-ppc64@npm:0.25.9":
|
||||
version: 0.25.9
|
||||
resolution: "@esbuild/linux-ppc64@npm:0.25.9"
|
||||
"@esbuild/linux-ppc64@npm:0.25.8":
|
||||
version: 0.25.8
|
||||
resolution: "@esbuild/linux-ppc64@npm:0.25.8"
|
||||
conditions: os=linux & cpu=ppc64
|
||||
languageName: node
|
||||
linkType: hard
|
||||
@ -3743,16 +3743,16 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@esbuild/linux-riscv64@npm:0.25.8":
|
||||
version: 0.25.8
|
||||
resolution: "@esbuild/linux-riscv64@npm:0.25.8"
|
||||
"@esbuild/linux-riscv64@npm:0.25.10":
|
||||
version: 0.25.10
|
||||
resolution: "@esbuild/linux-riscv64@npm:0.25.10"
|
||||
conditions: os=linux & cpu=riscv64
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@esbuild/linux-riscv64@npm:0.25.9":
|
||||
version: 0.25.9
|
||||
resolution: "@esbuild/linux-riscv64@npm:0.25.9"
|
||||
"@esbuild/linux-riscv64@npm:0.25.8":
|
||||
version: 0.25.8
|
||||
resolution: "@esbuild/linux-riscv64@npm:0.25.8"
|
||||
conditions: os=linux & cpu=riscv64
|
||||
languageName: node
|
||||
linkType: hard
|
||||
@ -3764,16 +3764,16 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@esbuild/linux-s390x@npm:0.25.8":
|
||||
version: 0.25.8
|
||||
resolution: "@esbuild/linux-s390x@npm:0.25.8"
|
||||
"@esbuild/linux-s390x@npm:0.25.10":
|
||||
version: 0.25.10
|
||||
resolution: "@esbuild/linux-s390x@npm:0.25.10"
|
||||
conditions: os=linux & cpu=s390x
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@esbuild/linux-s390x@npm:0.25.9":
|
||||
version: 0.25.9
|
||||
resolution: "@esbuild/linux-s390x@npm:0.25.9"
|
||||
"@esbuild/linux-s390x@npm:0.25.8":
|
||||
version: 0.25.8
|
||||
resolution: "@esbuild/linux-s390x@npm:0.25.8"
|
||||
conditions: os=linux & cpu=s390x
|
||||
languageName: node
|
||||
linkType: hard
|
||||
@ -3785,6 +3785,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@esbuild/linux-x64@npm:0.25.10":
|
||||
version: 0.25.10
|
||||
resolution: "@esbuild/linux-x64@npm:0.25.10"
|
||||
conditions: os=linux & cpu=x64
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@esbuild/linux-x64@npm:0.25.8":
|
||||
version: 0.25.8
|
||||
resolution: "@esbuild/linux-x64@npm:0.25.8"
|
||||
@ -3792,10 +3799,10 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@esbuild/linux-x64@npm:0.25.9":
|
||||
version: 0.25.9
|
||||
resolution: "@esbuild/linux-x64@npm:0.25.9"
|
||||
conditions: os=linux & cpu=x64
|
||||
"@esbuild/netbsd-arm64@npm:0.25.10":
|
||||
version: 0.25.10
|
||||
resolution: "@esbuild/netbsd-arm64@npm:0.25.10"
|
||||
conditions: os=netbsd & cpu=arm64
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@ -3806,13 +3813,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@esbuild/netbsd-arm64@npm:0.25.9":
|
||||
version: 0.25.9
|
||||
resolution: "@esbuild/netbsd-arm64@npm:0.25.9"
|
||||
conditions: os=netbsd & cpu=arm64
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@esbuild/netbsd-x64@npm:0.18.20":
|
||||
version: 0.18.20
|
||||
resolution: "@esbuild/netbsd-x64@npm:0.18.20"
|
||||
@ -3820,6 +3820,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@esbuild/netbsd-x64@npm:0.25.10":
|
||||
version: 0.25.10
|
||||
resolution: "@esbuild/netbsd-x64@npm:0.25.10"
|
||||
conditions: os=netbsd & cpu=x64
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@esbuild/netbsd-x64@npm:0.25.8":
|
||||
version: 0.25.8
|
||||
resolution: "@esbuild/netbsd-x64@npm:0.25.8"
|
||||
@ -3827,10 +3834,10 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@esbuild/netbsd-x64@npm:0.25.9":
|
||||
version: 0.25.9
|
||||
resolution: "@esbuild/netbsd-x64@npm:0.25.9"
|
||||
conditions: os=netbsd & cpu=x64
|
||||
"@esbuild/openbsd-arm64@npm:0.25.10":
|
||||
version: 0.25.10
|
||||
resolution: "@esbuild/openbsd-arm64@npm:0.25.10"
|
||||
conditions: os=openbsd & cpu=arm64
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@ -3841,13 +3848,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@esbuild/openbsd-arm64@npm:0.25.9":
|
||||
version: 0.25.9
|
||||
resolution: "@esbuild/openbsd-arm64@npm:0.25.9"
|
||||
conditions: os=openbsd & cpu=arm64
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@esbuild/openbsd-x64@npm:0.18.20":
|
||||
version: 0.18.20
|
||||
resolution: "@esbuild/openbsd-x64@npm:0.18.20"
|
||||
@ -3855,6 +3855,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@esbuild/openbsd-x64@npm:0.25.10":
|
||||
version: 0.25.10
|
||||
resolution: "@esbuild/openbsd-x64@npm:0.25.10"
|
||||
conditions: os=openbsd & cpu=x64
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@esbuild/openbsd-x64@npm:0.25.8":
|
||||
version: 0.25.8
|
||||
resolution: "@esbuild/openbsd-x64@npm:0.25.8"
|
||||
@ -3862,10 +3869,10 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@esbuild/openbsd-x64@npm:0.25.9":
|
||||
version: 0.25.9
|
||||
resolution: "@esbuild/openbsd-x64@npm:0.25.9"
|
||||
conditions: os=openbsd & cpu=x64
|
||||
"@esbuild/openharmony-arm64@npm:0.25.10":
|
||||
version: 0.25.10
|
||||
resolution: "@esbuild/openharmony-arm64@npm:0.25.10"
|
||||
conditions: os=openharmony & cpu=arm64
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@ -3876,13 +3883,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@esbuild/openharmony-arm64@npm:0.25.9":
|
||||
version: 0.25.9
|
||||
resolution: "@esbuild/openharmony-arm64@npm:0.25.9"
|
||||
conditions: os=openharmony & cpu=arm64
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@esbuild/sunos-x64@npm:0.18.20":
|
||||
version: 0.18.20
|
||||
resolution: "@esbuild/sunos-x64@npm:0.18.20"
|
||||
@ -3890,16 +3890,16 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@esbuild/sunos-x64@npm:0.25.8":
|
||||
version: 0.25.8
|
||||
resolution: "@esbuild/sunos-x64@npm:0.25.8"
|
||||
"@esbuild/sunos-x64@npm:0.25.10":
|
||||
version: 0.25.10
|
||||
resolution: "@esbuild/sunos-x64@npm:0.25.10"
|
||||
conditions: os=sunos & cpu=x64
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@esbuild/sunos-x64@npm:0.25.9":
|
||||
version: 0.25.9
|
||||
resolution: "@esbuild/sunos-x64@npm:0.25.9"
|
||||
"@esbuild/sunos-x64@npm:0.25.8":
|
||||
version: 0.25.8
|
||||
resolution: "@esbuild/sunos-x64@npm:0.25.8"
|
||||
conditions: os=sunos & cpu=x64
|
||||
languageName: node
|
||||
linkType: hard
|
||||
@ -3911,16 +3911,16 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@esbuild/win32-arm64@npm:0.25.8":
|
||||
version: 0.25.8
|
||||
resolution: "@esbuild/win32-arm64@npm:0.25.8"
|
||||
"@esbuild/win32-arm64@npm:0.25.10":
|
||||
version: 0.25.10
|
||||
resolution: "@esbuild/win32-arm64@npm:0.25.10"
|
||||
conditions: os=win32 & cpu=arm64
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@esbuild/win32-arm64@npm:0.25.9":
|
||||
version: 0.25.9
|
||||
resolution: "@esbuild/win32-arm64@npm:0.25.9"
|
||||
"@esbuild/win32-arm64@npm:0.25.8":
|
||||
version: 0.25.8
|
||||
resolution: "@esbuild/win32-arm64@npm:0.25.8"
|
||||
conditions: os=win32 & cpu=arm64
|
||||
languageName: node
|
||||
linkType: hard
|
||||
@ -3932,16 +3932,16 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@esbuild/win32-ia32@npm:0.25.8":
|
||||
version: 0.25.8
|
||||
resolution: "@esbuild/win32-ia32@npm:0.25.8"
|
||||
"@esbuild/win32-ia32@npm:0.25.10":
|
||||
version: 0.25.10
|
||||
resolution: "@esbuild/win32-ia32@npm:0.25.10"
|
||||
conditions: os=win32 & cpu=ia32
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@esbuild/win32-ia32@npm:0.25.9":
|
||||
version: 0.25.9
|
||||
resolution: "@esbuild/win32-ia32@npm:0.25.9"
|
||||
"@esbuild/win32-ia32@npm:0.25.8":
|
||||
version: 0.25.8
|
||||
resolution: "@esbuild/win32-ia32@npm:0.25.8"
|
||||
conditions: os=win32 & cpu=ia32
|
||||
languageName: node
|
||||
linkType: hard
|
||||
@ -3953,16 +3953,16 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@esbuild/win32-x64@npm:0.25.8":
|
||||
version: 0.25.8
|
||||
resolution: "@esbuild/win32-x64@npm:0.25.8"
|
||||
"@esbuild/win32-x64@npm:0.25.10":
|
||||
version: 0.25.10
|
||||
resolution: "@esbuild/win32-x64@npm:0.25.10"
|
||||
conditions: os=win32 & cpu=x64
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@esbuild/win32-x64@npm:0.25.9":
|
||||
version: 0.25.9
|
||||
resolution: "@esbuild/win32-x64@npm:0.25.9"
|
||||
"@esbuild/win32-x64@npm:0.25.8":
|
||||
version: 0.25.8
|
||||
resolution: "@esbuild/win32-x64@npm:0.25.8"
|
||||
conditions: os=win32 & cpu=x64
|
||||
languageName: node
|
||||
linkType: hard
|
||||
@ -14169,7 +14169,7 @@ __metadata:
|
||||
"@aws-sdk/client-bedrock-runtime": "npm:^3.840.0"
|
||||
"@aws-sdk/client-s3": "npm:^3.840.0"
|
||||
"@biomejs/biome": "npm:2.2.4"
|
||||
"@cherrystudio/ai-core": "workspace:^1.0.0-alpha.16"
|
||||
"@cherrystudio/ai-core": "workspace:^1.0.0-alpha.18"
|
||||
"@cherrystudio/embedjs": "npm:^0.1.31"
|
||||
"@cherrystudio/embedjs-libsql": "npm:^0.1.31"
|
||||
"@cherrystudio/embedjs-loader-csv": "npm:^0.1.31"
|
||||
@ -14304,7 +14304,6 @@ __metadata:
|
||||
diff: "npm:^8.0.2"
|
||||
docx: "npm:^9.0.2"
|
||||
dompurify: "npm:^3.2.6"
|
||||
dotenv: "npm:^17.2.2"
|
||||
dotenv-cli: "npm:^7.4.2"
|
||||
drizzle-kit: "npm:^0.31.4"
|
||||
drizzle-orm: "npm:^0.44.5"
|
||||
@ -14417,7 +14416,7 @@ __metadata:
|
||||
turndown: "npm:7.2.0"
|
||||
turndown-plugin-gfm: "npm:^1.0.2"
|
||||
tw-animate-css: "npm:^1.3.8"
|
||||
typescript: "npm:^5.6.2"
|
||||
typescript: "npm:~5.8.2"
|
||||
undici: "npm:6.21.2"
|
||||
unified: "npm:^11.0.5"
|
||||
uuid: "npm:^13.0.0"
|
||||
@ -17656,13 +17655,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"dotenv@npm:^17.2.2":
|
||||
version: 17.2.2
|
||||
resolution: "dotenv@npm:17.2.2"
|
||||
checksum: 10c0/be66513504590aff6eccb14167625aed9bd42ce80547f4fe5d195860211971a7060949b57108dfaeaf90658f79e40edccd3f233f0a978bff507b5b1565ae162b
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"drizzle-kit@npm:^0.31.4":
|
||||
version: 0.31.4
|
||||
resolution: "drizzle-kit@npm:0.31.4"
|
||||
@ -18193,35 +18185,35 @@ __metadata:
|
||||
linkType: hard
|
||||
|
||||
"esbuild@npm:^0.25.4":
|
||||
version: 0.25.9
|
||||
resolution: "esbuild@npm:0.25.9"
|
||||
version: 0.25.10
|
||||
resolution: "esbuild@npm:0.25.10"
|
||||
dependencies:
|
||||
"@esbuild/aix-ppc64": "npm:0.25.9"
|
||||
"@esbuild/android-arm": "npm:0.25.9"
|
||||
"@esbuild/android-arm64": "npm:0.25.9"
|
||||
"@esbuild/android-x64": "npm:0.25.9"
|
||||
"@esbuild/darwin-arm64": "npm:0.25.9"
|
||||
"@esbuild/darwin-x64": "npm:0.25.9"
|
||||
"@esbuild/freebsd-arm64": "npm:0.25.9"
|
||||
"@esbuild/freebsd-x64": "npm:0.25.9"
|
||||
"@esbuild/linux-arm": "npm:0.25.9"
|
||||
"@esbuild/linux-arm64": "npm:0.25.9"
|
||||
"@esbuild/linux-ia32": "npm:0.25.9"
|
||||
"@esbuild/linux-loong64": "npm:0.25.9"
|
||||
"@esbuild/linux-mips64el": "npm:0.25.9"
|
||||
"@esbuild/linux-ppc64": "npm:0.25.9"
|
||||
"@esbuild/linux-riscv64": "npm:0.25.9"
|
||||
"@esbuild/linux-s390x": "npm:0.25.9"
|
||||
"@esbuild/linux-x64": "npm:0.25.9"
|
||||
"@esbuild/netbsd-arm64": "npm:0.25.9"
|
||||
"@esbuild/netbsd-x64": "npm:0.25.9"
|
||||
"@esbuild/openbsd-arm64": "npm:0.25.9"
|
||||
"@esbuild/openbsd-x64": "npm:0.25.9"
|
||||
"@esbuild/openharmony-arm64": "npm:0.25.9"
|
||||
"@esbuild/sunos-x64": "npm:0.25.9"
|
||||
"@esbuild/win32-arm64": "npm:0.25.9"
|
||||
"@esbuild/win32-ia32": "npm:0.25.9"
|
||||
"@esbuild/win32-x64": "npm:0.25.9"
|
||||
"@esbuild/aix-ppc64": "npm:0.25.10"
|
||||
"@esbuild/android-arm": "npm:0.25.10"
|
||||
"@esbuild/android-arm64": "npm:0.25.10"
|
||||
"@esbuild/android-x64": "npm:0.25.10"
|
||||
"@esbuild/darwin-arm64": "npm:0.25.10"
|
||||
"@esbuild/darwin-x64": "npm:0.25.10"
|
||||
"@esbuild/freebsd-arm64": "npm:0.25.10"
|
||||
"@esbuild/freebsd-x64": "npm:0.25.10"
|
||||
"@esbuild/linux-arm": "npm:0.25.10"
|
||||
"@esbuild/linux-arm64": "npm:0.25.10"
|
||||
"@esbuild/linux-ia32": "npm:0.25.10"
|
||||
"@esbuild/linux-loong64": "npm:0.25.10"
|
||||
"@esbuild/linux-mips64el": "npm:0.25.10"
|
||||
"@esbuild/linux-ppc64": "npm:0.25.10"
|
||||
"@esbuild/linux-riscv64": "npm:0.25.10"
|
||||
"@esbuild/linux-s390x": "npm:0.25.10"
|
||||
"@esbuild/linux-x64": "npm:0.25.10"
|
||||
"@esbuild/netbsd-arm64": "npm:0.25.10"
|
||||
"@esbuild/netbsd-x64": "npm:0.25.10"
|
||||
"@esbuild/openbsd-arm64": "npm:0.25.10"
|
||||
"@esbuild/openbsd-x64": "npm:0.25.10"
|
||||
"@esbuild/openharmony-arm64": "npm:0.25.10"
|
||||
"@esbuild/sunos-x64": "npm:0.25.10"
|
||||
"@esbuild/win32-arm64": "npm:0.25.10"
|
||||
"@esbuild/win32-ia32": "npm:0.25.10"
|
||||
"@esbuild/win32-x64": "npm:0.25.10"
|
||||
dependenciesMeta:
|
||||
"@esbuild/aix-ppc64":
|
||||
optional: true
|
||||
@ -18277,7 +18269,7 @@ __metadata:
|
||||
optional: true
|
||||
bin:
|
||||
esbuild: bin/esbuild
|
||||
checksum: 10c0/aaa1284c75fcf45c82f9a1a117fe8dc5c45628e3386bda7d64916ae27730910b51c5aec7dd45a6ba19256be30ba2935e64a8f011a3f0539833071e06bf76d5b3
|
||||
checksum: 10c0/8ee5fdd43ed0d4092ce7f41577c63147f54049d5617763f0549c638bbe939e8adaa8f1a2728adb63417eb11df51956b7b0d8eb88ee08c27ad1d42960256158fa
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@ -29191,7 +29183,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"typescript@npm:^5.4.3, typescript@npm:^5.6.2":
|
||||
"typescript@npm:^5.4.3, typescript@npm:~5.8.2":
|
||||
version: 5.8.3
|
||||
resolution: "typescript@npm:5.8.3"
|
||||
bin:
|
||||
@ -29211,7 +29203,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"typescript@patch:typescript@npm%3A^5.4.3#optional!builtin<compat/typescript>, typescript@patch:typescript@npm%3A^5.6.2#optional!builtin<compat/typescript>":
|
||||
"typescript@patch:typescript@npm%3A^5.4.3#optional!builtin<compat/typescript>, typescript@patch:typescript@npm%3A~5.8.2#optional!builtin<compat/typescript>":
|
||||
version: 5.8.3
|
||||
resolution: "typescript@patch:typescript@npm%3A5.8.3#optional!builtin<compat/typescript>::version=5.8.3&hash=5786d5"
|
||||
bin:
|
||||
|
||||
Loading…
Reference in New Issue
Block a user