Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
144 changes: 133 additions & 11 deletions src/main/api/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,14 @@ import pluginToolsAPI from './plugin/tools'
import pluginUIAPI from './plugin/ui'
import pluginWindowAPI from './plugin/window'
import { setupImageAnalysisAPI } from './shared/imageAnalysis'
import {
matchesFilesInput,
matchesOverText,
matchesRegexText,
type FilesCmdLike,
type OverCmdLike,
type RegexCmdLike
} from '@shared/commandContextShared'
import zbrowserAPI from './plugin/zbrowser'
import pluginFFmpegAPI from './plugin/ffmpeg'

Expand Down Expand Up @@ -314,35 +322,149 @@ class APIManager {
return cmdType === 'text' || cmdType === 'over' || cmdType === 'regex'
}

/**
* 获取快捷键上下文中的有效文本输入。
*/
private getShortcutTextInput(context?: ShortcutLaunchContext): string {
return context?.pastedText ?? context?.searchQuery ?? ''
}

/**
* 判断当前快捷键上下文是否包含可用于细粒度匹配的输入。
*/
private hasContextualShortcutInput(context?: ShortcutLaunchContext): boolean {
if (!context) {
return false
}

if (context.pastedImage) {
return true
}

if (Array.isArray(context.pastedFiles) && context.pastedFiles.length > 0) {
return true
}

return this.getShortcutTextInput(context).trim().length > 0
}

/**
* 判断文本类匹配指令是否满足当前快捷键上下文。
*/
private matchesTextCommandContext(cmd: any, text: string): boolean {
if (cmd?.type === 'regex') {
return matchesRegexText(text, cmd as RegexCmdLike, { preserveFlags: true })
}

if (cmd?.type === 'over') {
return matchesOverText(text, cmd as OverCmdLike, {
useExclude: true,
preserveExcludeFlags: true,
allowPlainExclude: true
})
}

return false
}

/**
* 判断文件类匹配指令是否满足当前快捷键上下文。
*/
private matchesFilesCommandContext(cmd: any, files: ShortcutInputFile[]): boolean {
return matchesFilesInput(files, cmd as FilesCmdLike, {
preserveFlags: true,
allowPlainString: true
})
}

/**
* 计算候选命令在当前快捷键上下文下的优先级。
*/
private getCommandContextPriority(
cmd: any,
cmdType: string,
context?: ShortcutLaunchContext
): number {
if (!this.hasContextualShortcutInput(context)) {
return 0
}

if (cmdType === 'files') {
return this.matchesFilesCommandContext(cmd, context?.pastedFiles || []) ? 500 : -1
}

if (cmdType === 'img') {
return context?.pastedImage ? 450 : -1
}

const textInput = this.getShortcutTextInput(context)
if (cmdType === 'regex') {
return this.matchesTextCommandContext(cmd, textInput) ? 400 : -1
}

if (cmdType === 'over') {
return this.matchesTextCommandContext(cmd, textInput) ? 350 : -1
}

return 0
}

/**
* 在指定插件中查找匹配的命令
*/
private async findCommandInPlugin(
plugin: any,
cmdName: string
cmdName: string,
context?: ShortcutLaunchContext
): Promise<{ feature: any; cmdLabel: string; cmdType: string } | null> {
const dynamicFeatures = pluginFeatureAPI.loadDynamicFeatures(plugin.name)
const allFeatures = [...(plugin.features || []), ...dynamicFeatures]
const matches: Array<{ feature: any; cmdLabel: string; cmdType: string; cmd: any }> = []

for (const feature of allFeatures) {
if (feature.cmds && Array.isArray(feature.cmds)) {
for (const cmd of feature.cmds) {
// 处理字符串类型的命令
if (typeof cmd === 'string') {
if (cmd === cmdName) {
return { feature, cmdLabel: cmd, cmdType: 'text' }
matches.push({ feature, cmdLabel: cmd, cmdType: 'text', cmd })
}
continue
}
// 处理 object 类型的命令(regex 和 over 类型)
else if (typeof cmd === 'object' && cmd.label) {
if (cmd.label === cmdName) {
return { feature, cmdLabel: cmd.label, cmdType: cmd.type || 'text' }
}

if (typeof cmd === 'object' && cmd?.label === cmdName) {
matches.push({ feature, cmdLabel: cmd.label, cmdType: cmd.type || 'text', cmd })
}
}
}
}
return null

if (matches.length === 0) {
return null
}

if (!this.hasContextualShortcutInput(context)) {
const [firstMatch] = matches
return {
feature: firstMatch.feature,
cmdLabel: firstMatch.cmdLabel,
cmdType: firstMatch.cmdType
}
}

const prioritizedMatch = matches
.map((match, index) => ({
...match,
index,
priority: this.getCommandContextPriority(match.cmd, match.cmdType, context)
}))
.sort((a, b) => b.priority - a.priority || a.index - b.index)[0]

const selectedMatch = prioritizedMatch.priority > 0 ? prioritizedMatch : matches[0]
return {
feature: selectedMatch.feature,
cmdLabel: selectedMatch.cmdLabel,
cmdType: selectedMatch.cmdType
}
}

/**
Expand Down Expand Up @@ -429,7 +551,7 @@ class APIManager {
return
}

const result = await this.findCommandInPlugin(plugin, cmdName)
const result = await this.findCommandInPlugin(plugin, cmdName, context)
if (!result) {
const msg = `[API] 未找到命令: ${pluginDescription}/${cmdName}`
console.error(msg)
Expand All @@ -452,7 +574,7 @@ class APIManager {
const pluginMatches: { plugin: any; feature: any; cmdLabel: string; cmdType: string }[] = []

for (const plugin of pluginList) {
const result = await this.findCommandInPlugin(plugin, cmdName)
const result = await this.findCommandInPlugin(plugin, cmdName, context)
if (result) {
pluginMatches.push({
plugin,
Expand Down
Loading