@talex-touch/utils 1.0.32 → 1.0.33
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/common/utils/polling.ts +2 -1
- package/core-box/index.ts +1 -0
- package/core-box/recommendation.ts +77 -0
- package/electron/index.ts +1 -1
- package/package.json +1 -1
- package/plugin/sdk/README.md +216 -0
- package/plugin/sdk/box-sdk.ts +219 -0
- package/plugin/sdk/feature-sdk.ts +235 -0
- package/plugin/sdk/index.ts +2 -0
- package/plugin/sdk/types.ts +21 -8
- package/renderer/storage/intelligence-storage.ts +12 -9
- package/types/intelligence.ts +198 -4
- package/types/update.ts +12 -0
- package/electron/clipboard-helper.ts +0 -207
package/common/utils/polling.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
/* eslint-disable no-console */
|
|
1
2
|
/**
|
|
2
3
|
* @module polling
|
|
3
4
|
* A high-precision, efficient, singleton polling service for scheduling periodic tasks.
|
|
@@ -75,7 +76,7 @@ export class PollingService {
|
|
|
75
76
|
nextRunMs,
|
|
76
77
|
})
|
|
77
78
|
|
|
78
|
-
console.
|
|
79
|
+
console.debug(`[PollingService] Task '${id}' registered to run every ${options.interval} ${options.unit}.`)
|
|
79
80
|
|
|
80
81
|
if (this.isRunning) {
|
|
81
82
|
this._reschedule()
|
package/core-box/index.ts
CHANGED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Time-based usage pattern context for recommendation matching.
|
|
3
|
+
*/
|
|
4
|
+
export interface TimePattern {
|
|
5
|
+
/** Hour of day (0-23) */
|
|
6
|
+
hourOfDay: number
|
|
7
|
+
/** Day of week (0-6, 0=Sunday) */
|
|
8
|
+
dayOfWeek: number
|
|
9
|
+
/** Whether current time falls within working hours (9-18, weekdays) */
|
|
10
|
+
isWorkingHours: boolean
|
|
11
|
+
/** Broad time categorization */
|
|
12
|
+
timeSlot: 'morning' | 'afternoon' | 'evening' | 'night'
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Complete contextual signal for recommendation matching.
|
|
17
|
+
* Gathered from system state, clipboard, and active applications.
|
|
18
|
+
*/
|
|
19
|
+
export interface ContextSignal {
|
|
20
|
+
time: TimePattern
|
|
21
|
+
clipboard?: {
|
|
22
|
+
type: string
|
|
23
|
+
/** Hashed content for privacy (not original text) */
|
|
24
|
+
content: string
|
|
25
|
+
timestamp: number
|
|
26
|
+
contentType?: 'url' | 'text' | 'code' | 'file'
|
|
27
|
+
meta?: {
|
|
28
|
+
isUrl?: boolean
|
|
29
|
+
urlDomain?: string
|
|
30
|
+
textLength?: number
|
|
31
|
+
fileExtension?: string
|
|
32
|
+
fileType?: 'code' | 'text' | 'image' | 'document' | 'other'
|
|
33
|
+
language?: string
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
foregroundApp?: {
|
|
37
|
+
bundleId: string
|
|
38
|
+
name: string
|
|
39
|
+
}
|
|
40
|
+
systemState?: {
|
|
41
|
+
isOnline: boolean
|
|
42
|
+
batteryLevel: number
|
|
43
|
+
isDNDEnabled: boolean
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Scored recommendation item from recommendation engine.
|
|
49
|
+
*/
|
|
50
|
+
export interface ScoredItem {
|
|
51
|
+
sourceId: string
|
|
52
|
+
itemId: string
|
|
53
|
+
score: number
|
|
54
|
+
source: 'frequent' | 'time-based' | 'recent' | 'trending' | 'context'
|
|
55
|
+
reason?: string
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Recommendation badge display configuration for UI rendering.
|
|
60
|
+
*/
|
|
61
|
+
export interface RecommendationBadge {
|
|
62
|
+
text: string
|
|
63
|
+
icon: string
|
|
64
|
+
variant: 'frequent' | 'intelligent' | 'recent' | 'trending'
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Enhanced item metadata for intelligent recommendations.
|
|
69
|
+
* Attached to TuffItem.meta for rendering and filtering.
|
|
70
|
+
*/
|
|
71
|
+
export interface RecommendationMetadata {
|
|
72
|
+
score: number
|
|
73
|
+
source: ScoredItem['source']
|
|
74
|
+
reason: string
|
|
75
|
+
isIntelligent: boolean
|
|
76
|
+
badge: RecommendationBadge
|
|
77
|
+
}
|
package/electron/index.ts
CHANGED
package/package.json
CHANGED
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
# Plugin SDK 重构说明
|
|
2
|
+
|
|
3
|
+
## 概述
|
|
4
|
+
|
|
5
|
+
Plugin SDK 已重构为统一的工厂函数模式,参考 `DivisionBoxSDK` 的设计。旧版本 API 已废弃,调用时会抛出错误。
|
|
6
|
+
|
|
7
|
+
## 新的 SDK 结构
|
|
8
|
+
|
|
9
|
+
### 1. BoxSDK - CoreBox 窗口控制
|
|
10
|
+
|
|
11
|
+
控制 CoreBox 窗口的显示、大小和输入框状态。
|
|
12
|
+
|
|
13
|
+
```typescript
|
|
14
|
+
// 隐藏/显示 CoreBox
|
|
15
|
+
plugin.box.hide()
|
|
16
|
+
plugin.box.show()
|
|
17
|
+
|
|
18
|
+
// 扩展窗口(显示更多结果)
|
|
19
|
+
plugin.box.expand({ length: 10 })
|
|
20
|
+
plugin.box.expand({ forceMax: true })
|
|
21
|
+
|
|
22
|
+
// 收缩窗口
|
|
23
|
+
plugin.box.shrink()
|
|
24
|
+
|
|
25
|
+
// 控制输入框
|
|
26
|
+
plugin.box.hideInput()
|
|
27
|
+
plugin.box.showInput()
|
|
28
|
+
|
|
29
|
+
// 获取当前输入
|
|
30
|
+
const input = await plugin.box.getInput()
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
### 2. FeatureSDK - 搜索结果管理
|
|
34
|
+
|
|
35
|
+
管理插件推送的搜索结果项(TuffItems)。
|
|
36
|
+
|
|
37
|
+
```typescript
|
|
38
|
+
// 推送多个结果
|
|
39
|
+
plugin.feature.pushItems([
|
|
40
|
+
{ id: 'item-1', title: { text: 'Result 1' }, ... },
|
|
41
|
+
{ id: 'item-2', title: { text: 'Result 2' }, ... }
|
|
42
|
+
])
|
|
43
|
+
|
|
44
|
+
// 更新单个结果
|
|
45
|
+
plugin.feature.updateItem('item-1', {
|
|
46
|
+
title: { text: 'Updated Title' }
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
// 删除单个结果
|
|
50
|
+
plugin.feature.removeItem('item-1')
|
|
51
|
+
|
|
52
|
+
// 清空所有结果
|
|
53
|
+
plugin.feature.clearItems()
|
|
54
|
+
|
|
55
|
+
// 获取所有结果
|
|
56
|
+
const items = plugin.feature.getItems()
|
|
57
|
+
|
|
58
|
+
// 监听输入变化(实时搜索)
|
|
59
|
+
const unsubscribe = plugin.feature.onInputChange((input) => {
|
|
60
|
+
console.log('User typed:', input)
|
|
61
|
+
// 执行实时搜索
|
|
62
|
+
performSearch(input)
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
// 取消监听
|
|
66
|
+
unsubscribe()
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## 废弃的 API
|
|
70
|
+
|
|
71
|
+
以下 API 已废弃,调用时会抛出错误:
|
|
72
|
+
|
|
73
|
+
### 旧的 Box API
|
|
74
|
+
```typescript
|
|
75
|
+
// ❌ 废弃
|
|
76
|
+
plugin.$box.hide()
|
|
77
|
+
plugin.$box.show()
|
|
78
|
+
|
|
79
|
+
// ✅ 使用新 API
|
|
80
|
+
plugin.box.hide()
|
|
81
|
+
plugin.box.show()
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
### 旧的 Feature API
|
|
85
|
+
```typescript
|
|
86
|
+
// ❌ 废弃
|
|
87
|
+
plugin.pushItems(items)
|
|
88
|
+
plugin.clearItems()
|
|
89
|
+
plugin.getItems()
|
|
90
|
+
|
|
91
|
+
// ✅ 使用新 API
|
|
92
|
+
plugin.feature.pushItems(items)
|
|
93
|
+
plugin.feature.clearItems()
|
|
94
|
+
plugin.feature.getItems()
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
## 迁移指南
|
|
98
|
+
|
|
99
|
+
### 1. 更新 Box 控制代码
|
|
100
|
+
|
|
101
|
+
**旧代码:**
|
|
102
|
+
```typescript
|
|
103
|
+
plugin.$box.hide()
|
|
104
|
+
plugin.$box.show()
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
**新代码:**
|
|
108
|
+
```typescript
|
|
109
|
+
plugin.box.hide()
|
|
110
|
+
plugin.box.show()
|
|
111
|
+
plugin.box.expand({ length: 10 })
|
|
112
|
+
plugin.box.shrink()
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
### 2. 更新搜索结果管理
|
|
116
|
+
|
|
117
|
+
**旧代码:**
|
|
118
|
+
```typescript
|
|
119
|
+
plugin.pushItems([...])
|
|
120
|
+
plugin.clearItems()
|
|
121
|
+
const items = plugin.getItems()
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
**新代码:**
|
|
125
|
+
```typescript
|
|
126
|
+
plugin.feature.pushItems([...])
|
|
127
|
+
plugin.feature.updateItem('id', { ... })
|
|
128
|
+
plugin.feature.removeItem('id')
|
|
129
|
+
plugin.feature.clearItems()
|
|
130
|
+
const items = plugin.feature.getItems()
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
### 3. 添加实时搜索支持
|
|
134
|
+
|
|
135
|
+
**新功能:**
|
|
136
|
+
```typescript
|
|
137
|
+
// 在插件初始化时注册监听器
|
|
138
|
+
onInit(context) {
|
|
139
|
+
context.utils.feature.onInputChange((input) => {
|
|
140
|
+
// 用户输入变化时触发
|
|
141
|
+
this.performRealTimeSearch(input)
|
|
142
|
+
})
|
|
143
|
+
}
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
## 完整示例
|
|
147
|
+
|
|
148
|
+
```typescript
|
|
149
|
+
export default {
|
|
150
|
+
onInit(context) {
|
|
151
|
+
const { feature, box } = context.utils
|
|
152
|
+
|
|
153
|
+
// 监听输入变化
|
|
154
|
+
feature.onInputChange((input) => {
|
|
155
|
+
if (input.length > 2) {
|
|
156
|
+
// 执行搜索
|
|
157
|
+
const results = performSearch(input)
|
|
158
|
+
feature.pushItems(results)
|
|
159
|
+
} else {
|
|
160
|
+
feature.clearItems()
|
|
161
|
+
}
|
|
162
|
+
})
|
|
163
|
+
},
|
|
164
|
+
|
|
165
|
+
onFeatureTriggered(featureId, query, feature) {
|
|
166
|
+
const { feature: featureSDK, box } = this.context.utils
|
|
167
|
+
|
|
168
|
+
// 推送结果
|
|
169
|
+
featureSDK.pushItems([
|
|
170
|
+
{
|
|
171
|
+
id: 'result-1',
|
|
172
|
+
title: { text: 'Search Result' },
|
|
173
|
+
subtitle: { text: 'Description' },
|
|
174
|
+
source: { id: this.pluginName, name: this.pluginName }
|
|
175
|
+
}
|
|
176
|
+
])
|
|
177
|
+
|
|
178
|
+
// 扩展窗口显示结果
|
|
179
|
+
box.expand({ length: 5 })
|
|
180
|
+
|
|
181
|
+
// 3秒后隐藏
|
|
182
|
+
setTimeout(() => {
|
|
183
|
+
box.hide()
|
|
184
|
+
}, 3000)
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
## 技术细节
|
|
190
|
+
|
|
191
|
+
### SDK 工厂函数
|
|
192
|
+
|
|
193
|
+
所有 SDK 都通过工厂函数创建:
|
|
194
|
+
|
|
195
|
+
- `createBoxSDK(channel)` - 创建 Box SDK 实例
|
|
196
|
+
- `createFeatureSDK(boxItems, channel)` - 创建 Feature SDK 实例
|
|
197
|
+
- `createDivisionBoxSDK(channel)` - 创建 DivisionBox SDK 实例
|
|
198
|
+
|
|
199
|
+
### IPC 通道
|
|
200
|
+
|
|
201
|
+
新增的 IPC 通道:
|
|
202
|
+
|
|
203
|
+
- `core-box:hide-input` - 隐藏输入框
|
|
204
|
+
- `core-box:show-input` - 显示输入框
|
|
205
|
+
- `core-box:get-input` - 获取当前输入值
|
|
206
|
+
- `core-box:input-changed` - 输入变化广播(主进程 → 插件)
|
|
207
|
+
- `core-box:set-input-visibility` - 设置输入框可见性(主进程 → 渲染进程)
|
|
208
|
+
- `core-box:request-input-value` - 请求输入值(主进程 → 渲染进程)
|
|
209
|
+
|
|
210
|
+
### 架构优势
|
|
211
|
+
|
|
212
|
+
1. **统一的 API 风格** - 所有 SDK 使用相同的工厂函数模式
|
|
213
|
+
2. **更好的类型安全** - TypeScript 类型定义完整
|
|
214
|
+
3. **功能分离** - Box 控制和 Feature 管理职责清晰
|
|
215
|
+
4. **扩展性强** - 易于添加新功能
|
|
216
|
+
5. **向后不兼容** - 强制迁移到新 API,避免技术债务
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Box SDK for Plugin Development
|
|
3
|
+
*
|
|
4
|
+
* Provides a unified API for plugins to control the CoreBox window behavior,
|
|
5
|
+
* including visibility, size, input field control, and input value access.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Expand options for CoreBox window
|
|
10
|
+
*/
|
|
11
|
+
export interface BoxExpandOptions {
|
|
12
|
+
/** Number of items to show (affects window height) */
|
|
13
|
+
length?: number
|
|
14
|
+
/** Force maximum expansion */
|
|
15
|
+
forceMax?: boolean
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Box SDK interface for plugins
|
|
20
|
+
*
|
|
21
|
+
* @example
|
|
22
|
+
* ```typescript
|
|
23
|
+
* // Hide CoreBox
|
|
24
|
+
* plugin.box.hide()
|
|
25
|
+
*
|
|
26
|
+
* // Show CoreBox
|
|
27
|
+
* plugin.box.show()
|
|
28
|
+
*
|
|
29
|
+
* // Expand to show 10 items
|
|
30
|
+
* plugin.box.expand({ length: 10 })
|
|
31
|
+
*
|
|
32
|
+
* // Get current input
|
|
33
|
+
* const input = plugin.box.getInput()
|
|
34
|
+
* ```
|
|
35
|
+
*/
|
|
36
|
+
export interface BoxSDK {
|
|
37
|
+
/**
|
|
38
|
+
* Hides the CoreBox window
|
|
39
|
+
*
|
|
40
|
+
* @example
|
|
41
|
+
* ```typescript
|
|
42
|
+
* plugin.box.hide()
|
|
43
|
+
* ```
|
|
44
|
+
*/
|
|
45
|
+
hide(): void
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Shows the CoreBox window
|
|
49
|
+
*
|
|
50
|
+
* @example
|
|
51
|
+
* ```typescript
|
|
52
|
+
* plugin.box.show()
|
|
53
|
+
* ```
|
|
54
|
+
*/
|
|
55
|
+
show(): void
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Expands the CoreBox window
|
|
59
|
+
*
|
|
60
|
+
* @param options - Optional expansion configuration
|
|
61
|
+
*
|
|
62
|
+
* @example
|
|
63
|
+
* ```typescript
|
|
64
|
+
* // Expand to show 10 items
|
|
65
|
+
* plugin.box.expand({ length: 10 })
|
|
66
|
+
*
|
|
67
|
+
* // Force maximum expansion
|
|
68
|
+
* plugin.box.expand({ forceMax: true })
|
|
69
|
+
*
|
|
70
|
+
* // Default expansion
|
|
71
|
+
* plugin.box.expand()
|
|
72
|
+
* ```
|
|
73
|
+
*/
|
|
74
|
+
expand(options?: BoxExpandOptions): Promise<void>
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Shrinks the CoreBox window to compact size
|
|
78
|
+
*
|
|
79
|
+
* @example
|
|
80
|
+
* ```typescript
|
|
81
|
+
* plugin.box.shrink()
|
|
82
|
+
* ```
|
|
83
|
+
*/
|
|
84
|
+
shrink(): Promise<void>
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Hides the input field in CoreBox
|
|
88
|
+
*
|
|
89
|
+
* @example
|
|
90
|
+
* ```typescript
|
|
91
|
+
* plugin.box.hideInput()
|
|
92
|
+
* ```
|
|
93
|
+
*/
|
|
94
|
+
hideInput(): Promise<void>
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Shows the input field in CoreBox
|
|
98
|
+
*
|
|
99
|
+
* @example
|
|
100
|
+
* ```typescript
|
|
101
|
+
* plugin.box.showInput()
|
|
102
|
+
* ```
|
|
103
|
+
*/
|
|
104
|
+
showInput(): Promise<void>
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Gets the current input value from CoreBox search field
|
|
108
|
+
*
|
|
109
|
+
* @returns Promise resolving to the current input string
|
|
110
|
+
*
|
|
111
|
+
* @example
|
|
112
|
+
* ```typescript
|
|
113
|
+
* const input = await plugin.box.getInput()
|
|
114
|
+
* console.log('Current input:', input)
|
|
115
|
+
* ```
|
|
116
|
+
*/
|
|
117
|
+
getInput(): Promise<string>
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Creates a Box SDK instance for plugin use
|
|
122
|
+
*
|
|
123
|
+
* @param channel - The plugin channel bridge for IPC communication
|
|
124
|
+
* @returns Configured Box SDK instance
|
|
125
|
+
*
|
|
126
|
+
* @internal
|
|
127
|
+
*/
|
|
128
|
+
export function createBoxSDK(channel: any): BoxSDK {
|
|
129
|
+
const sendFn = channel.sendToMain || channel.send
|
|
130
|
+
|
|
131
|
+
if (!sendFn) {
|
|
132
|
+
throw new Error('[Box SDK] Channel send function not available')
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
return {
|
|
136
|
+
hide(): void {
|
|
137
|
+
sendFn('core-box:hide').catch((error: any) => {
|
|
138
|
+
console.error('[Box SDK] Failed to hide CoreBox:', error)
|
|
139
|
+
})
|
|
140
|
+
},
|
|
141
|
+
|
|
142
|
+
show(): void {
|
|
143
|
+
sendFn('core-box:show').catch((error: any) => {
|
|
144
|
+
console.error('[Box SDK] Failed to show CoreBox:', error)
|
|
145
|
+
})
|
|
146
|
+
},
|
|
147
|
+
|
|
148
|
+
async expand(options?: BoxExpandOptions): Promise<void> {
|
|
149
|
+
try {
|
|
150
|
+
await sendFn('core-box:expand', options || {})
|
|
151
|
+
} catch (error) {
|
|
152
|
+
console.error('[Box SDK] Failed to expand CoreBox:', error)
|
|
153
|
+
throw error
|
|
154
|
+
}
|
|
155
|
+
},
|
|
156
|
+
|
|
157
|
+
async shrink(): Promise<void> {
|
|
158
|
+
try {
|
|
159
|
+
await sendFn('core-box:expand', { mode: 'collapse' })
|
|
160
|
+
} catch (error) {
|
|
161
|
+
console.error('[Box SDK] Failed to shrink CoreBox:', error)
|
|
162
|
+
throw error
|
|
163
|
+
}
|
|
164
|
+
},
|
|
165
|
+
|
|
166
|
+
async hideInput(): Promise<void> {
|
|
167
|
+
try {
|
|
168
|
+
await sendFn('core-box:hide-input')
|
|
169
|
+
} catch (error) {
|
|
170
|
+
console.error('[Box SDK] Failed to hide input:', error)
|
|
171
|
+
throw error
|
|
172
|
+
}
|
|
173
|
+
},
|
|
174
|
+
|
|
175
|
+
async showInput(): Promise<void> {
|
|
176
|
+
try {
|
|
177
|
+
await sendFn('core-box:show-input')
|
|
178
|
+
} catch (error) {
|
|
179
|
+
console.error('[Box SDK] Failed to show input:', error)
|
|
180
|
+
throw error
|
|
181
|
+
}
|
|
182
|
+
},
|
|
183
|
+
|
|
184
|
+
async getInput(): Promise<string> {
|
|
185
|
+
try {
|
|
186
|
+
const result = await sendFn('core-box:get-input')
|
|
187
|
+
return result?.data?.input || result?.input || ''
|
|
188
|
+
} catch (error) {
|
|
189
|
+
console.error('[Box SDK] Failed to get input:', error)
|
|
190
|
+
throw error
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Hook for using Box SDK in plugin context
|
|
198
|
+
*
|
|
199
|
+
* @returns Box SDK instance
|
|
200
|
+
*
|
|
201
|
+
* @example
|
|
202
|
+
* ```typescript
|
|
203
|
+
* const box = useBox()
|
|
204
|
+
*
|
|
205
|
+
* box.hide()
|
|
206
|
+
* box.expand({ length: 10 })
|
|
207
|
+
* const input = await box.getInput()
|
|
208
|
+
* ```
|
|
209
|
+
*/
|
|
210
|
+
export function useBox(): BoxSDK {
|
|
211
|
+
// @ts-ignore - window.$channel is injected by the plugin system
|
|
212
|
+
const channel = window.$channel
|
|
213
|
+
|
|
214
|
+
if (!channel) {
|
|
215
|
+
throw new Error('[Box SDK] Channel not available. Make sure this is called in a plugin context.')
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
return createBoxSDK(channel)
|
|
219
|
+
}
|
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Feature SDK for Plugin Development
|
|
3
|
+
*
|
|
4
|
+
* Provides a unified API for plugins to manage search result items (TuffItems)
|
|
5
|
+
* in the CoreBox interface. This SDK handles item lifecycle, updates, and
|
|
6
|
+
* input change notifications.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import type { TuffItem } from '../../core-box/tuff'
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Input change event handler
|
|
13
|
+
*/
|
|
14
|
+
export type InputChangeHandler = (input: string) => void
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Feature SDK interface for plugins
|
|
18
|
+
*
|
|
19
|
+
* @example
|
|
20
|
+
* ```typescript
|
|
21
|
+
* // Push items to CoreBox
|
|
22
|
+
* plugin.feature.pushItems([
|
|
23
|
+
* { id: 'item-1', title: 'Result 1', ... },
|
|
24
|
+
* { id: 'item-2', title: 'Result 2', ... }
|
|
25
|
+
* ])
|
|
26
|
+
*
|
|
27
|
+
* // Update a specific item
|
|
28
|
+
* plugin.feature.updateItem('item-1', { title: 'Updated Title' })
|
|
29
|
+
*
|
|
30
|
+
* // Listen for input changes
|
|
31
|
+
* plugin.feature.onInputChange((input) => {
|
|
32
|
+
* console.log('User typed:', input)
|
|
33
|
+
* })
|
|
34
|
+
* ```
|
|
35
|
+
*/
|
|
36
|
+
export interface FeatureSDK {
|
|
37
|
+
/**
|
|
38
|
+
* Pushes multiple items to the CoreBox search results
|
|
39
|
+
*
|
|
40
|
+
* @param items - Array of TuffItem objects to display
|
|
41
|
+
*
|
|
42
|
+
* @example
|
|
43
|
+
* ```typescript
|
|
44
|
+
* plugin.feature.pushItems([
|
|
45
|
+
* {
|
|
46
|
+
* id: 'calc-result',
|
|
47
|
+
* title: { text: '42' },
|
|
48
|
+
* subtitle: { text: 'Calculation result' },
|
|
49
|
+
* source: { id: 'calculator', name: 'Calculator' }
|
|
50
|
+
* }
|
|
51
|
+
* ])
|
|
52
|
+
* ```
|
|
53
|
+
*/
|
|
54
|
+
pushItems(items: TuffItem[]): void
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Updates a specific item by ID
|
|
58
|
+
*
|
|
59
|
+
* @param id - The unique ID of the item to update
|
|
60
|
+
* @param updates - Partial TuffItem with fields to update
|
|
61
|
+
*
|
|
62
|
+
* @example
|
|
63
|
+
* ```typescript
|
|
64
|
+
* // Update title and subtitle
|
|
65
|
+
* plugin.feature.updateItem('item-1', {
|
|
66
|
+
* title: { text: 'New Title' },
|
|
67
|
+
* subtitle: { text: 'Updated subtitle' }
|
|
68
|
+
* })
|
|
69
|
+
* ```
|
|
70
|
+
*/
|
|
71
|
+
updateItem(id: string, updates: Partial<TuffItem>): void
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Removes a specific item by ID
|
|
75
|
+
*
|
|
76
|
+
* @param id - The unique ID of the item to remove
|
|
77
|
+
*
|
|
78
|
+
* @example
|
|
79
|
+
* ```typescript
|
|
80
|
+
* plugin.feature.removeItem('item-1')
|
|
81
|
+
* ```
|
|
82
|
+
*/
|
|
83
|
+
removeItem(id: string): void
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Clears all items pushed by this plugin
|
|
87
|
+
*
|
|
88
|
+
* @example
|
|
89
|
+
* ```typescript
|
|
90
|
+
* plugin.feature.clearItems()
|
|
91
|
+
* ```
|
|
92
|
+
*/
|
|
93
|
+
clearItems(): void
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Gets all items currently pushed by this plugin
|
|
97
|
+
*
|
|
98
|
+
* @returns Array of TuffItem objects
|
|
99
|
+
*
|
|
100
|
+
* @example
|
|
101
|
+
* ```typescript
|
|
102
|
+
* const items = plugin.feature.getItems()
|
|
103
|
+
* console.log(`Currently showing ${items.length} items`)
|
|
104
|
+
* ```
|
|
105
|
+
*/
|
|
106
|
+
getItems(): TuffItem[]
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Registers a listener for input changes in the CoreBox search field
|
|
110
|
+
*
|
|
111
|
+
* @param handler - Callback function invoked when input changes
|
|
112
|
+
* @returns Unsubscribe function
|
|
113
|
+
*
|
|
114
|
+
* @example
|
|
115
|
+
* ```typescript
|
|
116
|
+
* const unsubscribe = plugin.feature.onInputChange((input) => {
|
|
117
|
+
* console.log('User typed:', input)
|
|
118
|
+
* // Perform real-time search
|
|
119
|
+
* performSearch(input)
|
|
120
|
+
* })
|
|
121
|
+
*
|
|
122
|
+
* // Later, unsubscribe
|
|
123
|
+
* unsubscribe()
|
|
124
|
+
* ```
|
|
125
|
+
*/
|
|
126
|
+
onInputChange(handler: InputChangeHandler): () => void
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Creates a Feature SDK instance for plugin use
|
|
131
|
+
*
|
|
132
|
+
* @param boxItemsAPI - The boxItems API object from plugin context
|
|
133
|
+
* @param channel - The plugin channel bridge for IPC communication
|
|
134
|
+
* @returns Configured Feature SDK instance
|
|
135
|
+
*
|
|
136
|
+
* @internal
|
|
137
|
+
*/
|
|
138
|
+
export function createFeatureSDK(boxItemsAPI: any, channel: any): FeatureSDK {
|
|
139
|
+
const inputChangeHandlers: Set<InputChangeHandler> = new Set()
|
|
140
|
+
|
|
141
|
+
// Register listener for input change events from main process
|
|
142
|
+
const registerListener = () => {
|
|
143
|
+
if (channel.onMain) {
|
|
144
|
+
// Main process plugin context
|
|
145
|
+
channel.onMain('core-box:input-changed', (event: any) => {
|
|
146
|
+
const input = event.data?.input || event.input || ''
|
|
147
|
+
inputChangeHandlers.forEach(handler => handler(input))
|
|
148
|
+
})
|
|
149
|
+
} else if (channel.on) {
|
|
150
|
+
// Renderer process context
|
|
151
|
+
channel.on('core-box:input-changed', (data: any) => {
|
|
152
|
+
const input = data?.input || data || ''
|
|
153
|
+
inputChangeHandlers.forEach(handler => handler(input))
|
|
154
|
+
})
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
registerListener()
|
|
159
|
+
|
|
160
|
+
return {
|
|
161
|
+
pushItems(items: TuffItem[]): void {
|
|
162
|
+
if (!boxItemsAPI || !boxItemsAPI.pushItems) {
|
|
163
|
+
throw new Error('[Feature SDK] boxItems API not available')
|
|
164
|
+
}
|
|
165
|
+
boxItemsAPI.pushItems(items)
|
|
166
|
+
},
|
|
167
|
+
|
|
168
|
+
updateItem(id: string, updates: Partial<TuffItem>): void {
|
|
169
|
+
if (!boxItemsAPI || !boxItemsAPI.update) {
|
|
170
|
+
throw new Error('[Feature SDK] boxItems API not available')
|
|
171
|
+
}
|
|
172
|
+
boxItemsAPI.update(id, updates)
|
|
173
|
+
},
|
|
174
|
+
|
|
175
|
+
removeItem(id: string): void {
|
|
176
|
+
if (!boxItemsAPI || !boxItemsAPI.remove) {
|
|
177
|
+
throw new Error('[Feature SDK] boxItems API not available')
|
|
178
|
+
}
|
|
179
|
+
boxItemsAPI.remove(id)
|
|
180
|
+
},
|
|
181
|
+
|
|
182
|
+
clearItems(): void {
|
|
183
|
+
if (!boxItemsAPI || !boxItemsAPI.clear) {
|
|
184
|
+
throw new Error('[Feature SDK] boxItems API not available')
|
|
185
|
+
}
|
|
186
|
+
boxItemsAPI.clear()
|
|
187
|
+
},
|
|
188
|
+
|
|
189
|
+
getItems(): TuffItem[] {
|
|
190
|
+
if (!boxItemsAPI || !boxItemsAPI.getItems) {
|
|
191
|
+
throw new Error('[Feature SDK] boxItems API not available')
|
|
192
|
+
}
|
|
193
|
+
return boxItemsAPI.getItems()
|
|
194
|
+
},
|
|
195
|
+
|
|
196
|
+
onInputChange(handler: InputChangeHandler): () => void {
|
|
197
|
+
inputChangeHandlers.add(handler)
|
|
198
|
+
|
|
199
|
+
return () => {
|
|
200
|
+
inputChangeHandlers.delete(handler)
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Hook for using Feature SDK in plugin context
|
|
208
|
+
*
|
|
209
|
+
* @returns Feature SDK instance
|
|
210
|
+
*
|
|
211
|
+
* @example
|
|
212
|
+
* ```typescript
|
|
213
|
+
* const feature = useFeature()
|
|
214
|
+
*
|
|
215
|
+
* feature.pushItems([
|
|
216
|
+
* { id: '1', title: { text: 'Item 1' }, ... }
|
|
217
|
+
* ])
|
|
218
|
+
* ```
|
|
219
|
+
*/
|
|
220
|
+
export function useFeature(): FeatureSDK {
|
|
221
|
+
// @ts-ignore - window.$boxItems and window.$channel are injected by the plugin system
|
|
222
|
+
const boxItemsAPI = window.$boxItems
|
|
223
|
+
// @ts-ignore
|
|
224
|
+
const channel = window.$channel
|
|
225
|
+
|
|
226
|
+
if (!boxItemsAPI) {
|
|
227
|
+
throw new Error('[Feature SDK] boxItems API not available. Make sure this is called in a plugin context.')
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
if (!channel) {
|
|
231
|
+
throw new Error('[Feature SDK] Channel not available. Make sure this is called in a plugin context.')
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
return createFeatureSDK(boxItemsAPI, channel)
|
|
235
|
+
}
|
package/plugin/sdk/index.ts
CHANGED
|
@@ -7,10 +7,12 @@ export interface ITouchSDK {
|
|
|
7
7
|
|
|
8
8
|
// Note: Window.$touchSDK is declared in ../preload.ts to avoid duplicate declarations
|
|
9
9
|
|
|
10
|
+
export * from './box-sdk'
|
|
10
11
|
export * from './channel'
|
|
11
12
|
export * from './clipboard'
|
|
12
13
|
export * from './core-box'
|
|
13
14
|
export * from './division-box'
|
|
15
|
+
export * from './feature-sdk'
|
|
14
16
|
export { createFeaturesManager, useFeatures } from './features'
|
|
15
17
|
|
|
16
18
|
export * from './hooks/index'
|
package/plugin/sdk/types.ts
CHANGED
|
@@ -183,6 +183,18 @@ export interface IPluginUtils {
|
|
|
183
183
|
*/
|
|
184
184
|
divisionBox: import('./division-box').DivisionBoxSDK
|
|
185
185
|
|
|
186
|
+
/**
|
|
187
|
+
* Box SDK for controlling CoreBox window behavior
|
|
188
|
+
* @see {@link BoxSDK}
|
|
189
|
+
*/
|
|
190
|
+
box: import('./box-sdk').BoxSDK
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Feature SDK for managing search result items
|
|
194
|
+
* @see {@link FeatureSDK}
|
|
195
|
+
*/
|
|
196
|
+
feature: import('./feature-sdk').FeatureSDK
|
|
197
|
+
|
|
186
198
|
/**
|
|
187
199
|
* Opens a URL in the default browser
|
|
188
200
|
* @param url - The URL to open
|
|
@@ -190,21 +202,22 @@ export interface IPluginUtils {
|
|
|
190
202
|
openUrl: (url: string) => void
|
|
191
203
|
|
|
192
204
|
/**
|
|
193
|
-
*
|
|
194
|
-
* @
|
|
205
|
+
* @deprecated Use plugin.feature.pushItems() instead
|
|
206
|
+
* @throws Error indicating the API is deprecated
|
|
195
207
|
*/
|
|
196
|
-
pushItems: (items: any[]) =>
|
|
208
|
+
pushItems: (items: any[]) => never
|
|
197
209
|
|
|
198
210
|
/**
|
|
199
|
-
*
|
|
211
|
+
* @deprecated Use plugin.feature.clearItems() instead
|
|
212
|
+
* @throws Error indicating the API is deprecated
|
|
200
213
|
*/
|
|
201
|
-
clearItems: () =>
|
|
214
|
+
clearItems: () => never
|
|
202
215
|
|
|
203
216
|
/**
|
|
204
|
-
*
|
|
205
|
-
* @
|
|
217
|
+
* @deprecated Use plugin.feature.getItems() instead
|
|
218
|
+
* @throws Error indicating the API is deprecated
|
|
206
219
|
*/
|
|
207
|
-
getItems: () =>
|
|
220
|
+
getItems: () => never
|
|
208
221
|
|
|
209
222
|
/**
|
|
210
223
|
* Features manager for dynamic feature management
|
|
@@ -18,7 +18,7 @@ const defaultIntelligenceData: AISDKStorageData = {
|
|
|
18
18
|
providers: [...DEFAULT_PROVIDERS],
|
|
19
19
|
globalConfig: { ...DEFAULT_GLOBAL_CONFIG },
|
|
20
20
|
capabilities: { ...DEFAULT_CAPABILITIES },
|
|
21
|
-
version:
|
|
21
|
+
version: 2,
|
|
22
22
|
}
|
|
23
23
|
|
|
24
24
|
const INTELLIGENCE_STORAGE_KEY = `storage:${StorageList.IntelligenceConfig}`
|
|
@@ -145,8 +145,9 @@ export async function migrateIntelligenceSettings(): Promise<void> {
|
|
|
145
145
|
console.log('[Intelligence Storage] Starting migration check...')
|
|
146
146
|
const currentData = intelligenceStorage.data
|
|
147
147
|
|
|
148
|
-
|
|
149
|
-
|
|
148
|
+
// Version 2: Force update capabilities to include all new ones
|
|
149
|
+
if (!currentData.version || currentData.version < 2) {
|
|
150
|
+
console.log('[Intelligence Storage] Migrating settings to version 2')
|
|
150
151
|
|
|
151
152
|
const migratedProviders = currentData.providers.map(provider => ({
|
|
152
153
|
...provider,
|
|
@@ -169,24 +170,26 @@ export async function migrateIntelligenceSettings(): Promise<void> {
|
|
|
169
170
|
cacheExpiration: currentData.globalConfig?.cacheExpiration ?? 3600,
|
|
170
171
|
}
|
|
171
172
|
|
|
172
|
-
|
|
173
|
-
|
|
173
|
+
// Force update to latest DEFAULT_CAPABILITIES (version 2)
|
|
174
|
+
console.log('[Intelligence Storage] Updating capabilities to latest defaults')
|
|
175
|
+
|
|
174
176
|
intelligenceStorage.applyData({
|
|
175
177
|
providers: migratedProviders,
|
|
176
178
|
globalConfig: migratedGlobalConfig,
|
|
177
|
-
capabilities:
|
|
178
|
-
version:
|
|
179
|
+
capabilities: { ...DEFAULT_CAPABILITIES },
|
|
180
|
+
version: 2,
|
|
179
181
|
})
|
|
180
182
|
|
|
181
183
|
await intelligenceStorage.saveToRemote({ force: true })
|
|
182
184
|
|
|
183
|
-
console.log('[Intelligence Storage] Migration complete')
|
|
185
|
+
console.log('[Intelligence Storage] Migration to v2 complete, capabilities count:', Object.keys(DEFAULT_CAPABILITIES).length)
|
|
184
186
|
}
|
|
185
187
|
else {
|
|
186
188
|
console.log('[Intelligence Storage] No migration needed, current version:', currentData.version)
|
|
187
189
|
}
|
|
188
190
|
|
|
189
191
|
console.log('[Intelligence Storage] Final providers count:', intelligenceStorage.data.providers.length)
|
|
192
|
+
console.log('[Intelligence Storage] Final capabilities count:', Object.keys(intelligenceStorage.data.capabilities).length)
|
|
190
193
|
}
|
|
191
194
|
|
|
192
195
|
/**
|
|
@@ -201,7 +204,7 @@ export async function resetIntelligenceConfig(): Promise<void> {
|
|
|
201
204
|
providers: [...DEFAULT_PROVIDERS],
|
|
202
205
|
globalConfig: { ...DEFAULT_GLOBAL_CONFIG },
|
|
203
206
|
capabilities: { ...DEFAULT_CAPABILITIES },
|
|
204
|
-
version:
|
|
207
|
+
version: 2,
|
|
205
208
|
})
|
|
206
209
|
|
|
207
210
|
await intelligenceStorage.saveToRemote({ force: true })
|
package/types/intelligence.ts
CHANGED
|
@@ -382,7 +382,7 @@ export const DEFAULT_CAPABILITIES: Record<string, AISDKCapabilityConfig> = {
|
|
|
382
382
|
},
|
|
383
383
|
'embedding.generate': {
|
|
384
384
|
id: 'embedding.generate',
|
|
385
|
-
label: 'Embedding',
|
|
385
|
+
label: 'Embedding / 向量生成',
|
|
386
386
|
description: '为向量检索/摘要生成 embedding',
|
|
387
387
|
providers: [
|
|
388
388
|
{
|
|
@@ -391,11 +391,17 @@ export const DEFAULT_CAPABILITIES: Record<string, AISDKCapabilityConfig> = {
|
|
|
391
391
|
priority: 1,
|
|
392
392
|
enabled: true,
|
|
393
393
|
},
|
|
394
|
+
{
|
|
395
|
+
providerId: 'openai-default',
|
|
396
|
+
models: ['text-embedding-3-small', 'text-embedding-3-large'],
|
|
397
|
+
priority: 2,
|
|
398
|
+
enabled: false,
|
|
399
|
+
},
|
|
394
400
|
],
|
|
395
401
|
},
|
|
396
402
|
'vision.ocr': {
|
|
397
403
|
id: 'vision.ocr',
|
|
398
|
-
label: '图像 OCR',
|
|
404
|
+
label: '图像 OCR / Image Recognition',
|
|
399
405
|
description: '识别截图、图片中的文字并生成关键词',
|
|
400
406
|
promptTemplate:
|
|
401
407
|
'你是 OCR 助手,识别图片所有文本,生成 keywords 数组(5 个以内)辅助搜索。',
|
|
@@ -403,10 +409,198 @@ export const DEFAULT_CAPABILITIES: Record<string, AISDKCapabilityConfig> = {
|
|
|
403
409
|
providers: [
|
|
404
410
|
{
|
|
405
411
|
providerId: 'siliconflow-default',
|
|
406
|
-
models: ['deepseek-ai/DeepSeek-OCR', '
|
|
412
|
+
models: ['deepseek-ai/DeepSeek-OCR', 'THUDM/GLM-4.1V-9B-Thinking'],
|
|
407
413
|
priority: 1,
|
|
408
414
|
enabled: true,
|
|
409
|
-
|
|
415
|
+
},
|
|
416
|
+
{
|
|
417
|
+
providerId: 'openai-default',
|
|
418
|
+
models: ['gpt-4o', 'gpt-4o-mini'],
|
|
419
|
+
priority: 2,
|
|
420
|
+
enabled: false,
|
|
421
|
+
},
|
|
422
|
+
{
|
|
423
|
+
providerId: 'anthropic-default',
|
|
424
|
+
models: ['claude-3-5-sonnet-20241022'],
|
|
425
|
+
priority: 3,
|
|
426
|
+
enabled: false,
|
|
427
|
+
},
|
|
428
|
+
],
|
|
429
|
+
},
|
|
430
|
+
'text.translate': {
|
|
431
|
+
id: 'text.translate',
|
|
432
|
+
label: '翻译 / Translation',
|
|
433
|
+
description: '多语言文本翻译',
|
|
434
|
+
promptTemplate: '你是专业翻译助手。请将以下文本翻译成 {{targetLang}},只返回译文,不要解释。',
|
|
435
|
+
providers: [
|
|
436
|
+
{ providerId: 'deepseek-default', priority: 1, enabled: true },
|
|
437
|
+
{ providerId: 'openai-default', priority: 2, enabled: false },
|
|
438
|
+
{ providerId: 'anthropic-default', priority: 3, enabled: false },
|
|
439
|
+
],
|
|
440
|
+
},
|
|
441
|
+
'text.summarize': {
|
|
442
|
+
id: 'text.summarize',
|
|
443
|
+
label: '摘要 / Summarization',
|
|
444
|
+
description: '生成文本内容的简洁摘要',
|
|
445
|
+
promptTemplate: '请用简洁的语言总结以下内容的核心要点,不超过 {{maxLength}} 字。',
|
|
446
|
+
providers: [
|
|
447
|
+
{ providerId: 'deepseek-default', priority: 1, enabled: true },
|
|
448
|
+
{ providerId: 'openai-default', priority: 2, enabled: false },
|
|
449
|
+
{ providerId: 'anthropic-default', priority: 3, enabled: false },
|
|
450
|
+
],
|
|
451
|
+
},
|
|
452
|
+
'intent.detect': {
|
|
453
|
+
id: 'intent.detect',
|
|
454
|
+
label: '意图识别 / Intent Detection',
|
|
455
|
+
description: '识别用户查询的意图类型(搜索、打开、计算等)',
|
|
456
|
+
promptTemplate: '分析用户输入的意图,返回 JSON 格式:{intent: string, confidence: number, entities: string[]}',
|
|
457
|
+
providers: [
|
|
458
|
+
{ providerId: 'deepseek-default', priority: 1, enabled: true },
|
|
459
|
+
{ providerId: 'openai-default', priority: 2, enabled: false },
|
|
460
|
+
],
|
|
461
|
+
},
|
|
462
|
+
'code.generate': {
|
|
463
|
+
id: 'code.generate',
|
|
464
|
+
label: '代码生成 / Code Generation',
|
|
465
|
+
description: '根据需求生成代码片段',
|
|
466
|
+
promptTemplate: '你是编程助手。根据需求生成 {{language}} 代码,包含注释说明。',
|
|
467
|
+
providers: [
|
|
468
|
+
{ providerId: 'deepseek-default', models: ['deepseek-coder'], priority: 1, enabled: true },
|
|
469
|
+
{ providerId: 'openai-default', models: ['gpt-4o'], priority: 2, enabled: false },
|
|
470
|
+
],
|
|
471
|
+
},
|
|
472
|
+
'code.explain': {
|
|
473
|
+
id: 'code.explain',
|
|
474
|
+
label: '代码解释 / Code Explanation',
|
|
475
|
+
description: '解释代码的功能和逻辑',
|
|
476
|
+
promptTemplate: '你是编程导师。用通俗易懂的语言解释这段代码的功能、逻辑和关键点。',
|
|
477
|
+
providers: [
|
|
478
|
+
{ providerId: 'deepseek-default', models: ['deepseek-coder'], priority: 1, enabled: true },
|
|
479
|
+
{ providerId: 'anthropic-default', priority: 2, enabled: false },
|
|
480
|
+
],
|
|
481
|
+
},
|
|
482
|
+
'content.extract': {
|
|
483
|
+
id: 'content.extract',
|
|
484
|
+
label: '内容提取 / Content Extraction',
|
|
485
|
+
description: '从文本中提取关键信息(日期、人名、地点等)',
|
|
486
|
+
promptTemplate: '从文本中提取关键信息,返回 JSON 格式:{dates: [], people: [], locations: [], keywords: []}',
|
|
487
|
+
providers: [
|
|
488
|
+
{ providerId: 'deepseek-default', priority: 1, enabled: true },
|
|
489
|
+
{ providerId: 'openai-default', priority: 2, enabled: false },
|
|
490
|
+
],
|
|
491
|
+
},
|
|
492
|
+
'sentiment.analyze': {
|
|
493
|
+
id: 'sentiment.analyze',
|
|
494
|
+
label: '情感分析 / Sentiment Analysis',
|
|
495
|
+
description: '分析文本的情感倾向(积极/消极/中性)',
|
|
496
|
+
promptTemplate: '分析文本情感倾向,返回 JSON:{sentiment: "positive"|"negative"|"neutral", score: 0-1, keywords: []}',
|
|
497
|
+
providers: [
|
|
498
|
+
{ providerId: 'deepseek-default', priority: 1, enabled: true },
|
|
499
|
+
{ providerId: 'openai-default', priority: 2, enabled: false },
|
|
500
|
+
],
|
|
501
|
+
},
|
|
502
|
+
'code.review': {
|
|
503
|
+
id: 'code.review',
|
|
504
|
+
label: '代码审查 / Code Review',
|
|
505
|
+
description: '审查代码,发现潜在问题、最佳实践和改进建议',
|
|
506
|
+
promptTemplate: '作为资深代码审查员,审查以下代码。关注:1) 潜在bug 2) 性能问题 3) 安全隐患 4) 最佳实践 5) 可读性',
|
|
507
|
+
providers: [
|
|
508
|
+
{ providerId: 'deepseek-default', models: ['deepseek-coder'], priority: 1, enabled: true },
|
|
509
|
+
{ providerId: 'anthropic-default', priority: 2, enabled: false },
|
|
510
|
+
{ providerId: 'openai-default', models: ['gpt-4o'], priority: 3, enabled: false },
|
|
511
|
+
],
|
|
512
|
+
},
|
|
513
|
+
'keywords.extract': {
|
|
514
|
+
id: 'keywords.extract',
|
|
515
|
+
label: '关键词提取 / Keyword Extraction',
|
|
516
|
+
description: '从文本中提取关键词和短语',
|
|
517
|
+
promptTemplate: '从文本中提取最重要的关键词,返回 JSON 数组:{keywords: [{term: string, relevance: number}]}',
|
|
518
|
+
providers: [
|
|
519
|
+
{ providerId: 'deepseek-default', priority: 1, enabled: true },
|
|
520
|
+
{ providerId: 'openai-default', priority: 2, enabled: false },
|
|
521
|
+
],
|
|
522
|
+
},
|
|
523
|
+
'audio.transcribe': {
|
|
524
|
+
id: 'audio.transcribe',
|
|
525
|
+
label: '音频转录 / Audio Transcription',
|
|
526
|
+
description: '将语音转换为文字(支持多语言)',
|
|
527
|
+
providers: [
|
|
528
|
+
{
|
|
529
|
+
providerId: 'openai-default',
|
|
530
|
+
models: ['whisper-1'],
|
|
531
|
+
priority: 1,
|
|
532
|
+
enabled: false,
|
|
533
|
+
},
|
|
534
|
+
{
|
|
535
|
+
providerId: 'siliconflow-default',
|
|
536
|
+
models: ['TeleAI/TeleSpeechASR'],
|
|
537
|
+
priority: 2,
|
|
538
|
+
enabled: true,
|
|
539
|
+
},
|
|
540
|
+
],
|
|
541
|
+
},
|
|
542
|
+
'audio.tts': {
|
|
543
|
+
id: 'audio.tts',
|
|
544
|
+
label: '语音合成 / Text-to-Speech',
|
|
545
|
+
description: '将文字转换为自然语音',
|
|
546
|
+
providers: [
|
|
547
|
+
{
|
|
548
|
+
providerId: 'openai-default',
|
|
549
|
+
models: ['tts-1', 'tts-1-hd'],
|
|
550
|
+
priority: 1,
|
|
551
|
+
enabled: false,
|
|
552
|
+
},
|
|
553
|
+
],
|
|
554
|
+
},
|
|
555
|
+
'image.caption': {
|
|
556
|
+
id: 'image.caption',
|
|
557
|
+
label: '图像标题 / Image Captioning',
|
|
558
|
+
description: '为图片生成描述性标题',
|
|
559
|
+
promptTemplate: '生成简洁准确的图片描述(中英文),捕捉主要内容和氛围。',
|
|
560
|
+
providers: [
|
|
561
|
+
{
|
|
562
|
+
providerId: 'siliconflow-default',
|
|
563
|
+
models: ['THUDM/GLM-4.1V-9B-Thinking'],
|
|
564
|
+
priority: 1,
|
|
565
|
+
enabled: true,
|
|
566
|
+
},
|
|
567
|
+
{
|
|
568
|
+
providerId: 'openai-default',
|
|
569
|
+
models: ['gpt-4o', 'gpt-4o-mini'],
|
|
570
|
+
priority: 2,
|
|
571
|
+
enabled: false,
|
|
572
|
+
},
|
|
573
|
+
{
|
|
574
|
+
providerId: 'anthropic-default',
|
|
575
|
+
models: ['claude-3-5-sonnet-20241022'],
|
|
576
|
+
priority: 3,
|
|
577
|
+
enabled: false,
|
|
578
|
+
},
|
|
579
|
+
],
|
|
580
|
+
},
|
|
581
|
+
'image.analyze': {
|
|
582
|
+
id: 'image.analyze',
|
|
583
|
+
label: '图像分析 / Image Analysis',
|
|
584
|
+
description: '深度分析图像内容、物体、场景和上下文',
|
|
585
|
+
promptTemplate: '详细分析图片,包括:物体识别、场景理解、颜色分析、构图评估。返回结构化结果。',
|
|
586
|
+
providers: [
|
|
587
|
+
{
|
|
588
|
+
providerId: 'siliconflow-default',
|
|
589
|
+
models: ['THUDM/GLM-4.1V-9B-Thinking'],
|
|
590
|
+
priority: 1,
|
|
591
|
+
enabled: true,
|
|
592
|
+
},
|
|
593
|
+
{
|
|
594
|
+
providerId: 'openai-default',
|
|
595
|
+
models: ['gpt-4o'],
|
|
596
|
+
priority: 2,
|
|
597
|
+
enabled: false,
|
|
598
|
+
},
|
|
599
|
+
{
|
|
600
|
+
providerId: 'anthropic-default',
|
|
601
|
+
models: ['claude-3-5-sonnet-20241022'],
|
|
602
|
+
priority: 3,
|
|
603
|
+
enabled: false,
|
|
410
604
|
},
|
|
411
605
|
],
|
|
412
606
|
},
|
package/types/update.ts
CHANGED
|
@@ -51,6 +51,18 @@ export interface UpdateCheckResult {
|
|
|
51
51
|
source: string
|
|
52
52
|
}
|
|
53
53
|
|
|
54
|
+
export type UpdateUserAction = 'update-now' | 'skip' | 'remind-later'
|
|
55
|
+
|
|
56
|
+
export interface CachedUpdateRecord {
|
|
57
|
+
release: GitHubRelease
|
|
58
|
+
channel: AppPreviewChannel
|
|
59
|
+
status: 'pending' | 'skipped' | 'snoozed' | 'acknowledged'
|
|
60
|
+
fetchedAt: number
|
|
61
|
+
snoozeUntil?: number | null
|
|
62
|
+
tag: string
|
|
63
|
+
source: string
|
|
64
|
+
}
|
|
65
|
+
|
|
54
66
|
/**
|
|
55
67
|
* Custom provider definition used to extend the updater beyond the built-ins.
|
|
56
68
|
*/
|
|
@@ -1,207 +0,0 @@
|
|
|
1
|
-
import { clipboard } from 'electron'
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Represents a file object from the clipboard
|
|
5
|
-
*/
|
|
6
|
-
interface ClipboardFileInfo {
|
|
7
|
-
/** File data as a buffer */
|
|
8
|
-
buffer: Buffer
|
|
9
|
-
/** MIME type of the file */
|
|
10
|
-
mimetype: string
|
|
11
|
-
/** Original filename */
|
|
12
|
-
originalname: string
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* Possible return types from clipboard file operations
|
|
17
|
-
*/
|
|
18
|
-
export type ClipboardFileResult = string | ClipboardFileInfo
|
|
19
|
-
|
|
20
|
-
/**
|
|
21
|
-
* Utility class for handling clipboard file operations across different platforms
|
|
22
|
-
*/
|
|
23
|
-
export class ClipboardHelper {
|
|
24
|
-
/**
|
|
25
|
-
* Check if the current platform is macOS
|
|
26
|
-
* @private
|
|
27
|
-
*/
|
|
28
|
-
private get isMac(): boolean {
|
|
29
|
-
return process.platform === 'darwin'
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
/**
|
|
33
|
-
* Generate a unique identifier with specified length and radix
|
|
34
|
-
* @param length - Length of the generated string
|
|
35
|
-
* @param radix - Radix for number conversion (default: 16)
|
|
36
|
-
* @returns Generated unique identifier
|
|
37
|
-
* @private
|
|
38
|
-
*/
|
|
39
|
-
private generateUuid(length: number, radix: number = 16): string {
|
|
40
|
-
const chars = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'.split('')
|
|
41
|
-
const uuid: string[] = []
|
|
42
|
-
let i: number
|
|
43
|
-
|
|
44
|
-
radix = radix || chars.length
|
|
45
|
-
|
|
46
|
-
if (length) {
|
|
47
|
-
for (i = 0; i < length; i++) {
|
|
48
|
-
uuid[i] = chars[0 | (Math.random() * radix)]
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
else {
|
|
52
|
-
let r: number
|
|
53
|
-
uuid[8] = uuid[13] = uuid[18] = uuid[23] = '-'
|
|
54
|
-
uuid[14] = '4'
|
|
55
|
-
|
|
56
|
-
for (i = 0; i < 36; i++) {
|
|
57
|
-
if (!uuid[i]) {
|
|
58
|
-
r = 0 | (Math.random() * 16)
|
|
59
|
-
uuid[i] = chars[i === 19 ? (r & 0x3) | 0x8 : r]
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
return uuid.join('')
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
/**
|
|
68
|
-
* Process clipboard files on macOS platform
|
|
69
|
-
* @returns Array of file paths or file info objects
|
|
70
|
-
* @private
|
|
71
|
-
*/
|
|
72
|
-
private getClipboardFilesMac(): ClipboardFileResult[] {
|
|
73
|
-
let filePath: ClipboardFileResult[] = []
|
|
74
|
-
|
|
75
|
-
if (clipboard.has('NSFilenamesPboardType')) {
|
|
76
|
-
// Handle multiple files
|
|
77
|
-
const tagContent = clipboard.read('NSFilenamesPboardType').match(/<string>.*<\/string>/g)
|
|
78
|
-
filePath = tagContent ? tagContent.map(item => item.replace(/<string>|<\/string>/g, '')) : []
|
|
79
|
-
}
|
|
80
|
-
else {
|
|
81
|
-
// Handle single file
|
|
82
|
-
const clipboardImage = clipboard.readImage('clipboard')
|
|
83
|
-
if (!clipboardImage.isEmpty()) {
|
|
84
|
-
// Handle image from clipboard
|
|
85
|
-
const png = clipboardImage.toPNG()
|
|
86
|
-
const fileInfo: ClipboardFileInfo = {
|
|
87
|
-
buffer: png,
|
|
88
|
-
mimetype: 'image/png',
|
|
89
|
-
originalname: `${this.generateUuid(8, 16)}.png`,
|
|
90
|
-
}
|
|
91
|
-
filePath = [fileInfo]
|
|
92
|
-
}
|
|
93
|
-
else {
|
|
94
|
-
// Handle single file path
|
|
95
|
-
const fileUrl = clipboard.read('public.file-url')
|
|
96
|
-
if (fileUrl) {
|
|
97
|
-
filePath = [fileUrl.replace('file://', '')].filter(item => item)
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
return filePath
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
/**
|
|
106
|
-
* Process clipboard files on Windows platform
|
|
107
|
-
* @returns Array of file paths or file info objects
|
|
108
|
-
* @private
|
|
109
|
-
*/
|
|
110
|
-
private getClipboardFilesWindows(): ClipboardFileResult[] {
|
|
111
|
-
let filePath: ClipboardFileResult[] = []
|
|
112
|
-
|
|
113
|
-
if (clipboard.has('CF_HDROP')) {
|
|
114
|
-
// Handle multiple files
|
|
115
|
-
const rawFilePathStr = clipboard.readBuffer('CF_HDROP').toString('ucs2') || ''
|
|
116
|
-
let formatFilePathStr = [...rawFilePathStr]
|
|
117
|
-
.filter((_, index) => rawFilePathStr.charCodeAt(index) !== 0)
|
|
118
|
-
.join('')
|
|
119
|
-
.replace(/\\/g, '\\\\')
|
|
120
|
-
|
|
121
|
-
const drivePrefix = formatFilePathStr.match(/[a-z]:\\/i)
|
|
122
|
-
|
|
123
|
-
if (drivePrefix) {
|
|
124
|
-
const drivePrefixIndex = formatFilePathStr.indexOf(drivePrefix[0])
|
|
125
|
-
if (drivePrefixIndex !== 0) {
|
|
126
|
-
formatFilePathStr = formatFilePathStr.substring(drivePrefixIndex)
|
|
127
|
-
}
|
|
128
|
-
filePath = formatFilePathStr
|
|
129
|
-
.split(drivePrefix[0])
|
|
130
|
-
.filter(item => item)
|
|
131
|
-
.map(item => drivePrefix[0] + item)
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
else {
|
|
135
|
-
// Handle single file
|
|
136
|
-
const clipboardImage = clipboard.readImage('clipboard')
|
|
137
|
-
if (!clipboardImage.isEmpty()) {
|
|
138
|
-
// Handle image from clipboard
|
|
139
|
-
const png = clipboardImage.toPNG()
|
|
140
|
-
const fileInfo: ClipboardFileInfo = {
|
|
141
|
-
buffer: png,
|
|
142
|
-
mimetype: 'image/png',
|
|
143
|
-
originalname: `${this.generateUuid(8, 16)}.png`,
|
|
144
|
-
}
|
|
145
|
-
filePath = [fileInfo]
|
|
146
|
-
}
|
|
147
|
-
else {
|
|
148
|
-
// Handle single file path
|
|
149
|
-
try {
|
|
150
|
-
const fileName = clipboard.readBuffer('FileNameW').toString('ucs2').replace(new RegExp(String.fromCharCode(0), 'g'), '')
|
|
151
|
-
if (fileName) {
|
|
152
|
-
filePath = [fileName]
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
catch (error) {
|
|
156
|
-
// Ignore read errors for non-file clipboard content
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
return filePath
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
/**
|
|
165
|
-
* Retrieves file paths or file information from the system clipboard
|
|
166
|
-
*
|
|
167
|
-
* This method handles both file paths and images from the clipboard across different platforms.
|
|
168
|
-
* On macOS, it uses NSFilenamesPboardType for multiple files and public.file-url for single files.
|
|
169
|
-
* On Windows, it uses CF_HDROP for multiple files and FileNameW for single files.
|
|
170
|
-
*
|
|
171
|
-
* @returns Array of file paths (strings) or file info objects containing buffer data for images
|
|
172
|
-
* @throws {Error} When clipboard access fails or platform is unsupported
|
|
173
|
-
*
|
|
174
|
-
* @example
|
|
175
|
-
* ```typescript
|
|
176
|
-
* const clipboardHelper = new ClipboardHelper()
|
|
177
|
-
* const files = clipboardHelper.getClipboardFiles()
|
|
178
|
-
*
|
|
179
|
-
* files.forEach(file => {
|
|
180
|
-
* if (typeof file === 'string') {
|
|
181
|
-
* console.log('File path:', file)
|
|
182
|
-
* } else {
|
|
183
|
-
* console.log('Image file:', file.originalname, 'Size:', file.buffer.length)
|
|
184
|
-
* }
|
|
185
|
-
* })
|
|
186
|
-
* ```
|
|
187
|
-
*/
|
|
188
|
-
public getClipboardFiles(): ClipboardFileResult[] {
|
|
189
|
-
try {
|
|
190
|
-
if (this.isMac) {
|
|
191
|
-
return this.getClipboardFilesMac()
|
|
192
|
-
}
|
|
193
|
-
else {
|
|
194
|
-
return this.getClipboardFilesWindows()
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
catch (error) {
|
|
198
|
-
console.error('Failed to read clipboard files:', error)
|
|
199
|
-
return []
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
/**
|
|
205
|
-
* Default export of ClipboardHelper class for convenience
|
|
206
|
-
*/
|
|
207
|
-
export default ClipboardHelper
|