@talex-touch/utils 1.0.39 → 1.0.42

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.
@@ -803,6 +803,65 @@ export interface TuffContext {
803
803
  tags?: string[]
804
804
  }
805
805
 
806
+ // ==================== Footer Hints 配置 ====================
807
+
808
+ /**
809
+ * Footer hint 单项配置
810
+ */
811
+ export interface TuffFooterHintItem {
812
+ /** 快捷键显示文本 */
813
+ key: string
814
+ /** 提示标签 */
815
+ label: string
816
+ /** 是否显示此提示 */
817
+ visible?: boolean
818
+ /** 触发的事件名称 */
819
+ event?: string
820
+ /** 事件携带的数据 */
821
+ eventData?: Record<string, unknown>
822
+ }
823
+
824
+ /**
825
+ * Footer hints 配置
826
+ * @description 控制 CoreBox 底部快捷键提示的显示和行为
827
+ */
828
+ export interface TuffFooterHints {
829
+ /**
830
+ * 主操作提示(回车键)
831
+ * @description 自定义回车键的文案,如 "发送"、"执行"、"打开" 等
832
+ */
833
+ primary?: {
834
+ /** 自定义标签文案 */
835
+ label?: string
836
+ /** 是否显示 */
837
+ visible?: boolean
838
+ }
839
+
840
+ /**
841
+ * 辅助操作提示(Meta+K)
842
+ * @description 控制 Meta+K 快捷键的显示和行为
843
+ */
844
+ secondary?: {
845
+ /** 自定义标签文案 */
846
+ label?: string
847
+ /** 是否显示,默认 false(隐藏) */
848
+ visible?: boolean
849
+ /** 触发的事件名称 */
850
+ event?: string
851
+ /** 事件携带的数据 */
852
+ eventData?: Record<string, unknown>
853
+ }
854
+
855
+ /**
856
+ * 快速选择提示(Meta+1-0)
857
+ * @description 控制快速选择快捷键的显示
858
+ */
859
+ quickSelect?: {
860
+ /** 是否显示,默认根据场景自动判断 */
861
+ visible?: boolean
862
+ }
863
+ }
864
+
806
865
  // ==================== 扩展元数据 ====================
807
866
 
808
867
  /**
@@ -1005,15 +1064,37 @@ export interface TuffMeta {
1005
1064
  permissions?: string[]
1006
1065
  }
1007
1066
 
1067
+ /** TuffIntelligence */
1068
+ intelligence?: any
1069
+
1070
+ /** Keep CoreBox open after action execution */
1071
+ keepCoreBoxOpen?: boolean
1072
+
1073
+ /** 固定配置 */
1074
+ pinned?: {
1075
+ isPinned: boolean
1076
+ pinnedAt?: number
1077
+ order?: number
1078
+ }
1079
+
1080
+ /** 推荐来源标记 */
1081
+ recommendation?: {
1082
+ source: 'frequent' | 'recent' | 'time-based' | 'trending' | 'pinned' | 'context'
1083
+ score?: number
1084
+ }
1085
+
1008
1086
  /**
1009
- * TuffIntelligence
1087
+ * 额外关键词
1088
+ * @description 用于搜索索引的额外关键词,会参与关键字索引匹配
1089
+ * @warning 不建议添加太多关键词(建议 <= 10),过多会影响搜索性能
1010
1090
  */
1011
- intelligence?: any
1091
+ keywords?: string[]
1012
1092
 
1013
1093
  /**
1014
- * Keep CoreBox open after action execution
1094
+ * Footer hints 配置
1095
+ * @description 控制 CoreBox 底部快捷键提示的显示和行为
1015
1096
  */
1016
- keepCoreBoxOpen?: boolean
1097
+ footerHints?: TuffFooterHints
1017
1098
  }
1018
1099
 
1019
1100
  // ==================== 前端展示结构 ====================
@@ -1267,71 +1348,69 @@ export interface SortStat {
1267
1348
  duration: number
1268
1349
  }
1269
1350
 
1270
- /**
1271
- * 搜索结果结构
1272
- *
1273
- * @description
1274
- * 定义搜索返回的结果集合和元数据。
1275
- * 包含匹配项、统计信息和分页数据等。
1276
- */
1351
+ /** 容器布局配置 */
1352
+ export interface TuffContainerLayout {
1353
+ /** 布局模式 */
1354
+ mode: 'list' | 'grid'
1355
+ /** 宫格配置 */
1356
+ grid?: {
1357
+ /** 列数 */
1358
+ columns: number
1359
+ /** 间距(px) */
1360
+ gap?: number
1361
+ /** 单项尺寸 */
1362
+ itemSize?: 'small' | 'medium' | 'large'
1363
+ }
1364
+ /** 分组配置 */
1365
+ sections?: TuffSection[]
1366
+ }
1367
+
1368
+ /** 分组元数据 */
1369
+ export interface TuffSectionMeta {
1370
+ /** 是否为智能推荐分组 */
1371
+ intelligence?: boolean
1372
+ /** 扩展字段 */
1373
+ [key: string]: unknown
1374
+ }
1375
+
1376
+ /** 分组定义 */
1377
+ export interface TuffSection {
1378
+ id: string
1379
+ title?: string
1380
+ layout: 'list' | 'grid'
1381
+ /** 该分组的 item ids */
1382
+ itemIds: string[]
1383
+ collapsed?: boolean
1384
+ /** 分组元数据 */
1385
+ meta?: TuffSectionMeta
1386
+ }
1387
+
1388
+ /** 搜索结果结构 */
1277
1389
  export interface TuffSearchResult {
1278
- /**
1279
- * A unique identifier for this specific search operation.
1280
- * This is crucial for the streaming model to associate updates with the correct search instance.
1281
- */
1390
+ /** 搜索会话 ID */
1282
1391
  sessionId?: string
1283
-
1284
- /**
1285
- * 结果项目
1286
- * @description 匹配的TuffItem列表
1287
- * @required
1288
- */
1392
+ /** 结果项目 */
1289
1393
  items: TuffItem[]
1290
1394
 
1291
- /**
1292
- * 查询信息
1293
- * @description 原始查询参数
1294
- * @required
1295
- */
1395
+ /** 原始查询参数 */
1296
1396
  query: TuffQuery
1297
-
1298
- /**
1299
- * 搜索耗时
1300
- * @description 搜索执行的毫秒数
1301
- * @required
1302
- */
1397
+ /** 搜索耗时(ms) */
1303
1398
  duration: number
1304
-
1305
- /**
1306
- * 来源统计
1307
- * @description 各数据来源的结果统计
1308
- * @required
1309
- */
1399
+ /** 来源统计 */
1310
1400
  sources: Array<{
1311
- /** Provider's unique ID. */
1312
1401
  providerId: string
1313
- /** Provider's display name. */
1314
1402
  providerName: string
1315
- /** Search duration in milliseconds. */
1316
1403
  duration: number
1317
- /** Number of results returned. */
1318
1404
  resultCount: number
1319
- /** Status of the search operation. */
1320
1405
  status: 'success' | 'timeout' | 'error'
1321
1406
  }>
1322
-
1323
- /**
1324
- * AI 推荐
1325
- * @description AI生成的搜索建议
1326
- */
1407
+ /** 容器布局配置 */
1408
+ containerLayout?: TuffContainerLayout
1409
+ /** AI 推荐建议 */
1327
1410
  suggestions?: string[]
1328
-
1329
- /**
1330
- * The provider(s) to activate after this search result.
1331
- */
1411
+ /** 激活的 provider */
1332
1412
  activate?: IProviderActivate[]
1333
-
1334
- /** Optional statistics about the sorting process. */
1413
+ /** 排序统计 */
1335
1414
  sort_stats?: SortStat[]
1336
1415
  }
1337
1416
 
@@ -1,4 +1,4 @@
1
- import type { AiInvokeOptions, AiInvokeResult, AiProviderConfig } from '../types/intelligence'
1
+ import type { IntelligenceInvokeOptions, IntelligenceInvokeResult, IntelligenceProviderConfig } from '../types/intelligence'
2
2
 
3
3
  export interface IntelligenceClientChannel {
4
4
  send: (eventName: string, payload: unknown) => Promise<any>
@@ -50,10 +50,10 @@ async function assertResponse<T>(promise: Promise<ChannelResponse<T>>): Promise<
50
50
  }
51
51
 
52
52
  export interface IntelligenceClient {
53
- invoke: <T = any>(capabilityId: string, payload: any, options?: AiInvokeOptions) => Promise<AiInvokeResult<T>>
54
- testProvider: (config: AiProviderConfig) => Promise<unknown>
53
+ invoke: <T = any>(capabilityId: string, payload: any, options?: IntelligenceInvokeOptions) => Promise<IntelligenceInvokeResult<T>>
54
+ testProvider: (config: IntelligenceProviderConfig) => Promise<unknown>
55
55
  testCapability: (params: Record<string, any>) => Promise<unknown>
56
- fetchModels: (config: AiProviderConfig) => Promise<{ success: boolean, models?: string[], message?: string }>
56
+ fetchModels: (config: IntelligenceProviderConfig) => Promise<{ success: boolean, models?: string[], message?: string }>
57
57
  }
58
58
 
59
59
  export function createIntelligenceClient(channel?: IntelligenceClientChannel, resolvers?: IntelligenceChannelResolver[]): IntelligenceClient {
@@ -63,12 +63,12 @@ export function createIntelligenceClient(channel?: IntelligenceClientChannel, re
63
63
  }
64
64
 
65
65
  return {
66
- invoke<T = any>(capabilityId: string, payload: any, options?: AiInvokeOptions) {
67
- return assertResponse<AiInvokeResult<T>>(
66
+ invoke<T = any>(capabilityId: string, payload: any, options?: IntelligenceInvokeOptions) {
67
+ return assertResponse<IntelligenceInvokeResult<T>>(
68
68
  resolvedChannel.send('intelligence:invoke', { capabilityId, payload, options }),
69
69
  )
70
70
  },
71
- testProvider(config: AiProviderConfig) {
71
+ testProvider(config: IntelligenceProviderConfig) {
72
72
  return assertResponse(
73
73
  resolvedChannel.send('intelligence:test-provider', { provider: config }),
74
74
  )
@@ -78,7 +78,7 @@ export function createIntelligenceClient(channel?: IntelligenceClientChannel, re
78
78
  resolvedChannel.send('intelligence:test-capability', params),
79
79
  )
80
80
  },
81
- fetchModels(config: AiProviderConfig) {
81
+ fetchModels(config: IntelligenceProviderConfig) {
82
82
  return assertResponse<{ success: boolean, models?: string[], message?: string }>(
83
83
  resolvedChannel.send('intelligence:fetch-models', { provider: config }),
84
84
  )
@@ -21,6 +21,20 @@ function defineProvider(
21
21
  }
22
22
 
23
23
  export const DEFAULT_MARKET_PROVIDERS: MarketProviderDefinition[] = [
24
+ defineProvider({
25
+ id: 'tuff-nexus',
26
+ name: 'Tuff Nexus',
27
+ type: 'tpexApi',
28
+ url: 'https://tuff.tagzxia.com',
29
+ description: 'Tuff 官方插件市场,提供经过审核的插件。',
30
+ enabled: true,
31
+ priority: 110,
32
+ trustLevel: 'official',
33
+ readOnly: true,
34
+ config: {
35
+ apiUrl: 'https://tuff.tagzxia.com/api/market/plugins',
36
+ },
37
+ }),
24
38
  defineProvider({
25
39
  id: 'talex-official',
26
40
  name: 'Talex Official',
package/market/types.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import type { StorageList } from '../common/storage/constants'
2
2
 
3
- export type MarketProviderType = 'repository' | 'nexusStore' | 'npmPackage'
3
+ export type MarketProviderType = 'repository' | 'nexusStore' | 'npmPackage' | 'tpexApi'
4
4
 
5
5
  export type MarketProviderTrustLevel = 'official' | 'verified' | 'unverified'
6
6
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@talex-touch/utils",
3
- "version": "1.0.39",
3
+ "version": "1.0.42",
4
4
  "private": false,
5
5
  "description": "Tuff series utils",
6
6
  "author": "TalexDreamSoul",
package/plugin/index.ts CHANGED
@@ -134,7 +134,8 @@ export interface ITouchPlugin extends IPluginBaseInfo {
134
134
  export interface IFeatureCommand {
135
135
  type: 'match' | 'contain' | 'regex' | 'function' | 'over' | 'image' | 'files' | 'directory' | 'window'
136
136
  value: string | string[] | RegExp | Function
137
- onTrigger: () => void
137
+ /** Optional trigger callback - not serialized over IPC */
138
+ onTrigger?: () => void
138
139
  }
139
140
 
140
141
  export interface IPluginFeature {
@@ -142,10 +143,15 @@ export interface IPluginFeature {
142
143
  name: string
143
144
  desc: string
144
145
  icon: ITuffIcon
146
+ keywords?: string[]
145
147
  push: boolean
146
148
  platform: IPlatform
147
149
  commands: IFeatureCommand[]
148
150
  interaction?: IFeatureInteraction
151
+ /**
152
+ * Internal search tokens generated at runtime for better matching
153
+ */
154
+ searchTokens?: string[]
149
155
  /**
150
156
  * Priority of the feature for sorting in search results
151
157
  * Higher numbers have higher priority (displayed first)
@@ -1,2 +1,6 @@
1
1
  export * from './registry'
2
2
  export * from './types'
3
+ export * from './tpex-types'
4
+ export * from './tpex-provider'
5
+ export * from './npm-provider'
6
+ export * from './market-client'
@@ -0,0 +1,215 @@
1
+ import type { NpmPackageInfo } from './npm-provider'
2
+ import type { TpexPluginInfo } from './tpex-provider'
3
+ import { NpmProvider } from './npm-provider'
4
+ import { TpexProvider } from './tpex-provider'
5
+ import { PluginProviderType } from './types'
6
+
7
+ export type PluginSourceType = 'tpex' | 'npm' | 'all'
8
+
9
+ export interface MarketPluginInfo {
10
+ id: string
11
+ name: string
12
+ slug: string
13
+ version: string
14
+ description: string
15
+ author: string
16
+ icon?: string
17
+ source: PluginSourceType
18
+ isOfficial: boolean
19
+ downloads?: number
20
+ category?: string
21
+ keywords?: string[]
22
+ homepage?: string
23
+ packageUrl?: string
24
+ raw: TpexPluginInfo | NpmPackageInfo
25
+ }
26
+
27
+ export interface MarketSearchOptions {
28
+ keyword?: string
29
+ source?: PluginSourceType
30
+ category?: string
31
+ limit?: number
32
+ offset?: number
33
+ }
34
+
35
+ export interface MarketSearchResult {
36
+ plugins: MarketPluginInfo[]
37
+ total: number
38
+ sources: {
39
+ tpex: number
40
+ npm: number
41
+ }
42
+ }
43
+
44
+ function normalizeTpexPlugin(plugin: TpexPluginInfo): MarketPluginInfo {
45
+ return {
46
+ id: plugin.id,
47
+ name: plugin.name,
48
+ slug: plugin.slug,
49
+ version: plugin.latestVersion?.version ?? '0.0.0',
50
+ description: plugin.summary,
51
+ author: plugin.author?.name ?? 'Unknown',
52
+ icon: plugin.iconUrl ?? undefined,
53
+ source: 'tpex',
54
+ isOfficial: plugin.isOfficial,
55
+ downloads: plugin.installs,
56
+ category: plugin.category,
57
+ homepage: plugin.homepage ?? undefined,
58
+ packageUrl: plugin.latestVersion?.packageUrl,
59
+ raw: plugin,
60
+ }
61
+ }
62
+
63
+ function normalizeNpmPlugin(pkg: NpmPackageInfo): MarketPluginInfo {
64
+ const authorName = typeof pkg.author === 'string'
65
+ ? pkg.author
66
+ : pkg.author?.name ?? 'Unknown'
67
+
68
+ return {
69
+ id: pkg.name,
70
+ name: pkg.name.replace(/^tuff-plugin-/, '').replace(/^@tuff\//, ''),
71
+ slug: pkg.name,
72
+ version: pkg.version,
73
+ description: pkg.description ?? '',
74
+ author: authorName,
75
+ icon: pkg.tuff?.icon,
76
+ source: 'npm',
77
+ isOfficial: pkg.name.startsWith('@tuff/'),
78
+ keywords: pkg.keywords,
79
+ packageUrl: pkg.dist.tarball,
80
+ raw: pkg,
81
+ }
82
+ }
83
+
84
+ /**
85
+ * Unified plugin market client supporting multiple sources
86
+ */
87
+ export class PluginMarketClient {
88
+ private tpexProvider: TpexProvider
89
+ private npmProvider: NpmProvider
90
+
91
+ constructor(options?: {
92
+ tpexApiBase?: string
93
+ npmRegistry?: string
94
+ }) {
95
+ this.tpexProvider = new TpexProvider(options?.tpexApiBase)
96
+ this.npmProvider = new NpmProvider(options?.npmRegistry)
97
+ }
98
+
99
+ /**
100
+ * Search plugins from all sources
101
+ */
102
+ async search(options: MarketSearchOptions = {}): Promise<MarketSearchResult> {
103
+ const { keyword, source = 'all', limit = 50, offset = 0 } = options
104
+ const results: MarketPluginInfo[] = []
105
+ let tpexCount = 0
106
+ let npmCount = 0
107
+
108
+ if (source === 'all' || source === 'tpex') {
109
+ try {
110
+ const tpexPlugins = keyword
111
+ ? await this.tpexProvider.searchPlugins(keyword)
112
+ : await this.tpexProvider.listPlugins()
113
+
114
+ const normalized = tpexPlugins.map(normalizeTpexPlugin)
115
+ results.push(...normalized)
116
+ tpexCount = normalized.length
117
+ }
118
+ catch (error) {
119
+ console.warn('[MarketClient] TPEX search failed:', error)
120
+ }
121
+ }
122
+
123
+ if (source === 'all' || source === 'npm') {
124
+ try {
125
+ const npmPlugins = await this.npmProvider.searchPlugins(keyword)
126
+ const normalized = npmPlugins.map(normalizeNpmPlugin)
127
+ results.push(...normalized)
128
+ npmCount = normalized.length
129
+ }
130
+ catch (error) {
131
+ console.warn('[MarketClient] NPM search failed:', error)
132
+ }
133
+ }
134
+
135
+ const sorted = results.sort((a, b) => {
136
+ if (a.isOfficial !== b.isOfficial) return a.isOfficial ? -1 : 1
137
+ return (b.downloads ?? 0) - (a.downloads ?? 0)
138
+ })
139
+
140
+ const paginated = sorted.slice(offset, offset + limit)
141
+
142
+ return {
143
+ plugins: paginated,
144
+ total: results.length,
145
+ sources: {
146
+ tpex: tpexCount,
147
+ npm: npmCount,
148
+ },
149
+ }
150
+ }
151
+
152
+ /**
153
+ * Get plugin details by identifier
154
+ */
155
+ async getPlugin(identifier: string, source?: PluginSourceType): Promise<MarketPluginInfo | null> {
156
+ if (source === 'tpex' || (!source && !identifier.includes('/'))) {
157
+ try {
158
+ const plugin = await this.tpexProvider.getPlugin(identifier)
159
+ if (plugin) return normalizeTpexPlugin(plugin)
160
+ }
161
+ catch {
162
+ // Fall through to npm
163
+ }
164
+ }
165
+
166
+ if (source === 'npm' || !source) {
167
+ try {
168
+ const pkg = await this.npmProvider.getPackageInfo(identifier)
169
+ if (pkg) return normalizeNpmPlugin(pkg)
170
+ }
171
+ catch {
172
+ // Not found
173
+ }
174
+ }
175
+
176
+ return null
177
+ }
178
+
179
+ /**
180
+ * Get install source string for a plugin
181
+ */
182
+ getInstallSource(plugin: MarketPluginInfo): string {
183
+ if (plugin.source === 'tpex') {
184
+ return `tpex:${plugin.slug}`
185
+ }
186
+ return `npm:${plugin.id}`
187
+ }
188
+
189
+ /**
190
+ * Get provider type for a plugin
191
+ */
192
+ getProviderType(plugin: MarketPluginInfo): PluginProviderType {
193
+ return plugin.source === 'tpex'
194
+ ? PluginProviderType.TPEX
195
+ : PluginProviderType.NPM
196
+ }
197
+
198
+ /**
199
+ * List all plugins from official source (TPEX)
200
+ */
201
+ async listOfficialPlugins(): Promise<MarketPluginInfo[]> {
202
+ const plugins = await this.tpexProvider.listPlugins()
203
+ return plugins.map(normalizeTpexPlugin)
204
+ }
205
+
206
+ /**
207
+ * List all plugins from npm
208
+ */
209
+ async listNpmPlugins(): Promise<MarketPluginInfo[]> {
210
+ const plugins = await this.npmProvider.listPlugins()
211
+ return plugins.map(normalizeNpmPlugin)
212
+ }
213
+ }
214
+
215
+ export const defaultMarketClient = new PluginMarketClient()