mirror of https://github.com/Wox-launcher/Wox
feat(mru): Implement Most Recently Used (MRU) functionality across plugins
- Added MRU support to SysPlugin and UrlPlugin, allowing users to access recently used commands and URLs. - Introduced new data structures for MRU items and context data. - Updated the query methods to include MRU results based on user settings. - Modified the settings management to accommodate the new query mode for MRU. - Enhanced the UI to reflect the changes in query modes and added relevant translations. - Created a dedicated MRU manager for handling MRU items in the database. - Updated Flutter UI to support MRU queries and settings.
This commit is contained in:
parent
9f9cf84ea8
commit
4b2018934d
|
@ -36,6 +36,19 @@ type Oplog struct {
|
|||
SyncedToCloud bool `gorm:"default:false"`
|
||||
}
|
||||
|
||||
type MRURecord struct {
|
||||
Hash string `gorm:"primaryKey"` // MD5 hash of pluginId+title+subTitle
|
||||
PluginID string `gorm:"not null"`
|
||||
Title string `gorm:"not null"`
|
||||
SubTitle string
|
||||
Icon string // JSON serialized WoxImage
|
||||
ContextData string // Plugin context data for restoration
|
||||
LastUsed int64 `gorm:"not null"`
|
||||
UseCount int `gorm:"default:1"`
|
||||
CreatedAt time.Time
|
||||
UpdatedAt time.Time
|
||||
}
|
||||
|
||||
func Init(ctx context.Context) error {
|
||||
dbPath := filepath.Join(util.GetLocation().GetUserDataDirectory(), "wox.db")
|
||||
|
||||
|
@ -86,6 +99,7 @@ func Init(ctx context.Context) error {
|
|||
&WoxSetting{},
|
||||
&PluginSetting{},
|
||||
&Oplog{},
|
||||
&MRURecord{},
|
||||
)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to migrate database schema: %w", err)
|
||||
|
|
|
@ -163,6 +163,9 @@ func main() {
|
|||
// Start auto backup if enabled
|
||||
setting.GetSettingManager().StartAutoBackup(ctx)
|
||||
|
||||
// Start MRU cleanup
|
||||
setting.GetSettingManager().StartMRUCleanup(ctx)
|
||||
|
||||
// Start auto update checker if enabled
|
||||
updater.StartAutoUpdateChecker(ctx)
|
||||
|
||||
|
|
|
@ -224,7 +224,7 @@ func Run(ctx context.Context) error {
|
|||
"HideOnLostFocus": oldSettings.HideOnLostFocus,
|
||||
"ShowTray": oldSettings.ShowTray,
|
||||
"LangCode": oldSettings.LangCode,
|
||||
"LastQueryMode": oldSettings.LastQueryMode,
|
||||
"QueryMode": oldSettings.LastQueryMode, // Migrate LastQueryMode to QueryMode
|
||||
"ShowPosition": oldSettings.ShowPosition,
|
||||
"EnableAutoBackup": oldSettings.EnableAutoBackup,
|
||||
"EnableAutoUpdate": oldSettings.EnableAutoUpdate,
|
||||
|
|
|
@ -37,6 +37,7 @@ type API interface {
|
|||
OnGetDynamicSetting(ctx context.Context, callback func(key string) string)
|
||||
OnDeepLink(ctx context.Context, callback func(arguments map[string]string))
|
||||
OnUnload(ctx context.Context, callback func())
|
||||
OnMRURestore(ctx context.Context, callback func(mruData MRUData) (*QueryResult, error))
|
||||
RegisterQueryCommands(ctx context.Context, commands []MetadataCommand)
|
||||
AIChatStream(ctx context.Context, model common.Model, conversations []common.Conversation, options common.ChatOptions, callback common.ChatStreamFunc) error
|
||||
}
|
||||
|
@ -305,6 +306,15 @@ func (a *APIImpl) applyStartTimeIfAbsent(streamResult *common.ChatStreamData) {
|
|||
}
|
||||
}
|
||||
|
||||
func (a *APIImpl) OnMRURestore(ctx context.Context, callback func(mruData MRUData) (*QueryResult, error)) {
|
||||
if !a.pluginInstance.Metadata.IsSupportFeature(MetadataFeatureMRU) {
|
||||
a.Log(ctx, LogLevelError, "plugin has no access to MRU feature")
|
||||
return
|
||||
}
|
||||
|
||||
a.pluginInstance.MRURestoreCallbacks = append(a.pluginInstance.MRURestoreCallbacks, callback)
|
||||
}
|
||||
|
||||
func NewAPI(instance *Instance) API {
|
||||
apiImpl := &APIImpl{pluginInstance: instance}
|
||||
logFolder := path.Join(util.GetLocation().GetLogPluginDirectory(), instance.Metadata.Name)
|
||||
|
|
|
@ -19,6 +19,7 @@ type Instance struct {
|
|||
SettingChangeCallbacks []func(key string, value string)
|
||||
DeepLinkCallbacks []func(arguments map[string]string)
|
||||
UnloadCallbacks []func()
|
||||
MRURestoreCallbacks []func(mruData MRUData) (*QueryResult, error) // MRU restore callbacks
|
||||
|
||||
// for measure performance
|
||||
LoadStartTimestamp int64
|
||||
|
|
|
@ -820,6 +820,8 @@ func (m *Manager) PolishResult(ctx context.Context, pluginInstance *Instance, qu
|
|||
}
|
||||
}
|
||||
|
||||
originalIcon := result.Icon
|
||||
|
||||
// convert icon
|
||||
result.Icon = common.ConvertIcon(ctx, result.Icon, pluginInstance.PluginDirectory)
|
||||
for i := range result.Tails {
|
||||
|
@ -883,6 +885,7 @@ func (m *Manager) PolishResult(ctx context.Context, pluginInstance *Instance, qu
|
|||
ResultTitle: result.Title,
|
||||
ResultSubTitle: result.SubTitle,
|
||||
ContextData: result.ContextData,
|
||||
Icon: originalIcon,
|
||||
PluginInstance: pluginInstance,
|
||||
Query: query,
|
||||
Actions: util.NewHashMap[string, func(ctx context.Context, actionContext ActionContext)](),
|
||||
|
@ -1385,13 +1388,41 @@ func (m *Manager) ExecuteAction(ctx context.Context, resultId string, actionId s
|
|||
ContextData: resultCache.ContextData,
|
||||
})
|
||||
|
||||
util.Go(ctx, fmt.Sprintf("[%s] add actioned result", resultCache.PluginInstance.Metadata.Name), func() {
|
||||
setting.GetSettingManager().AddActionedResult(ctx, resultCache.PluginInstance.Metadata.Id, resultCache.ResultTitle, resultCache.ResultSubTitle, resultCache.Query.RawQuery)
|
||||
util.Go(ctx, fmt.Sprintf("[%s] post execute action", resultCache.PluginInstance.Metadata.Name), func() {
|
||||
m.postExecuteAction(ctx, resultCache)
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Manager) postExecuteAction(ctx context.Context, resultCache *QueryResultCache) {
|
||||
// Add actioned result for statistics
|
||||
setting.GetSettingManager().AddActionedResult(ctx, resultCache.PluginInstance.Metadata.Id, resultCache.ResultTitle, resultCache.ResultSubTitle, resultCache.Query.RawQuery)
|
||||
|
||||
// Add to MRU if plugin supports it
|
||||
if resultCache.PluginInstance.Metadata.IsSupportFeature(MetadataFeatureMRU) {
|
||||
mruItem := setting.MRUItem{
|
||||
PluginID: resultCache.PluginInstance.Metadata.Id,
|
||||
Title: resultCache.ResultTitle,
|
||||
SubTitle: resultCache.ResultSubTitle,
|
||||
Icon: resultCache.Icon,
|
||||
ContextData: resultCache.ContextData,
|
||||
}
|
||||
if err := setting.GetSettingManager().AddMRUItem(ctx, mruItem); err != nil {
|
||||
util.GetLogger().Error(ctx, fmt.Sprintf("failed to add MRU item: %s", err.Error()))
|
||||
}
|
||||
}
|
||||
|
||||
// Add to query history only if query is not empty (skip empty queries like MRU)
|
||||
if resultCache.Query.RawQuery != "" {
|
||||
plainQuery := common.PlainQuery{
|
||||
QueryType: resultCache.Query.Type,
|
||||
QueryText: resultCache.Query.RawQuery,
|
||||
}
|
||||
setting.GetSettingManager().AddQueryHistory(ctx, plainQuery)
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Manager) ExecuteRefresh(ctx context.Context, refreshableResultWithId RefreshableResultWithResultId) (RefreshableResultWithResultId, error) {
|
||||
var refreshableResult RefreshableResult
|
||||
copyErr := copier.Copy(&refreshableResult, &refreshableResultWithId)
|
||||
|
@ -1606,3 +1637,71 @@ func (m *Manager) ExecutePluginDeeplink(ctx context.Context, pluginId string, ar
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Manager) QueryMRU(ctx context.Context) []QueryResultUI {
|
||||
mruItems, err := setting.GetSettingManager().GetMRUItems(ctx, 10)
|
||||
if err != nil {
|
||||
util.GetLogger().Error(ctx, fmt.Sprintf("failed to get MRU items: %s", err.Error()))
|
||||
return []QueryResultUI{}
|
||||
}
|
||||
|
||||
var results []QueryResultUI
|
||||
for _, item := range mruItems {
|
||||
pluginInstance := m.getPluginInstance(item.PluginID)
|
||||
if pluginInstance == nil {
|
||||
continue
|
||||
}
|
||||
if !pluginInstance.Metadata.IsSupportFeature(MetadataFeatureMRU) {
|
||||
continue
|
||||
}
|
||||
|
||||
if restored := m.restoreFromMRU(ctx, pluginInstance, item); restored != nil {
|
||||
polishedResult := m.PolishResult(ctx, pluginInstance, Query{}, *restored)
|
||||
results = append(results, polishedResult.ToUI())
|
||||
}
|
||||
}
|
||||
|
||||
return results
|
||||
}
|
||||
|
||||
// getPluginInstance finds a plugin instance by ID
|
||||
func (m *Manager) getPluginInstance(pluginID string) *Instance {
|
||||
pluginInstance, found := lo.Find(m.instances, func(item *Instance) bool {
|
||||
return item.Metadata.Id == pluginID
|
||||
})
|
||||
if found {
|
||||
return pluginInstance
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// restoreFromMRU attempts to restore a QueryResult from MRU data
|
||||
func (m *Manager) restoreFromMRU(ctx context.Context, pluginInstance *Instance, item setting.MRUItem) *QueryResult {
|
||||
// For Go plugins, call MRU restore callbacks directly
|
||||
if len(pluginInstance.MRURestoreCallbacks) > 0 {
|
||||
mruData := MRUData{
|
||||
PluginID: item.PluginID,
|
||||
Title: item.Title,
|
||||
SubTitle: item.SubTitle,
|
||||
Icon: item.Icon,
|
||||
ContextData: item.ContextData,
|
||||
LastUsed: item.LastUsed,
|
||||
UseCount: item.UseCount,
|
||||
}
|
||||
|
||||
// Call the first (and typically only) MRU restore callback
|
||||
if restored, err := pluginInstance.MRURestoreCallbacks[0](mruData); err == nil {
|
||||
return restored
|
||||
} else {
|
||||
util.GetLogger().Debug(ctx, fmt.Sprintf("MRU restore failed for plugin %s: %s", pluginInstance.Metadata.Name, err.Error()))
|
||||
}
|
||||
}
|
||||
|
||||
// For external plugins (Python/Node.js), MRU support will be implemented later
|
||||
// Currently only Go plugins support MRU functionality
|
||||
if pluginInstance.Host != nil {
|
||||
util.GetLogger().Debug(ctx, fmt.Sprintf("External plugin MRU restore not yet implemented for plugin %s", pluginInstance.Metadata.Name))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -37,6 +37,10 @@ const (
|
|||
// by default, the width ratio is 0.5, which means the result list and preview panel have the same width
|
||||
// if the width ratio is 0.3, which means the result list takes 30% of the width and the preview panel takes 70% of the width
|
||||
MetadataFeatureResultPreviewWidthRatio MetadataFeatureName = "resultPreviewWidthRatio"
|
||||
|
||||
// enable this feature to support MRU (Most Recently Used) functionality
|
||||
// plugin must implement OnMRURestore callback to restore results from MRU data
|
||||
MetadataFeatureMRU MetadataFeatureName = "mru"
|
||||
)
|
||||
|
||||
// Metadata parsed from plugin.json, see `Plugin.json.md` for more detail
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
package plugin
|
||||
|
||||
import "wox/common"
|
||||
|
||||
type MRUData struct {
|
||||
PluginID string `json:"pluginId"`
|
||||
Title string `json:"title"`
|
||||
SubTitle string `json:"subTitle"`
|
||||
Icon common.WoxImage `json:"icon"`
|
||||
ContextData string `json:"contextData"`
|
||||
LastUsed int64 `json:"lastUsed"`
|
||||
UseCount int `json:"useCount"`
|
||||
}
|
|
@ -210,6 +210,7 @@ type QueryResultCache struct {
|
|||
ResultTitle string
|
||||
ResultSubTitle string
|
||||
ContextData string
|
||||
Icon common.WoxImage
|
||||
Refresh func(context.Context, RefreshableResult) RefreshableResult
|
||||
PluginInstance *Instance
|
||||
Query Query
|
||||
|
|
|
@ -43,6 +43,12 @@ type appInfo struct {
|
|||
Pid int `json:"-"`
|
||||
}
|
||||
|
||||
type appContextData struct {
|
||||
Name string `json:"name"`
|
||||
Path string `json:"path"`
|
||||
Type string `json:"type"`
|
||||
}
|
||||
|
||||
func (a *appInfo) GetDisplayPath() string {
|
||||
if a.Type == AppTypeUWP {
|
||||
return ""
|
||||
|
@ -92,6 +98,11 @@ func (a *ApplicationPlugin) GetMetadata() plugin.Metadata {
|
|||
"Macos",
|
||||
"Linux",
|
||||
},
|
||||
Features: []plugin.MetadataFeature{
|
||||
{
|
||||
Name: plugin.MetadataFeatureMRU,
|
||||
},
|
||||
},
|
||||
SettingDefinitions: []definition.PluginSettingDefinitionItem{
|
||||
{
|
||||
Type: definition.PluginSettingDefinitionTypeTable,
|
||||
|
@ -140,6 +151,8 @@ func (a *ApplicationPlugin) Init(ctx context.Context, initParams plugin.InitPara
|
|||
a.indexApps(ctx)
|
||||
}
|
||||
})
|
||||
|
||||
a.api.OnMRURestore(ctx, a.handleMRURestore)
|
||||
}
|
||||
|
||||
func (a *ApplicationPlugin) Query(ctx context.Context, query plugin.Query) []plugin.QueryResult {
|
||||
|
@ -149,12 +162,21 @@ func (a *ApplicationPlugin) Query(ctx context.Context, query plugin.Query) []plu
|
|||
isPathNameMatch, pathNameScore := system.IsStringMatchScore(ctx, filepath.Base(info.Path), query.Search)
|
||||
if isNameMatch || isPathNameMatch {
|
||||
displayPath := info.GetDisplayPath()
|
||||
|
||||
contextData := appContextData{
|
||||
Name: info.Name,
|
||||
Path: info.Path,
|
||||
Type: info.Type,
|
||||
}
|
||||
contextDataJson, _ := json.Marshal(contextData)
|
||||
|
||||
result := plugin.QueryResult{
|
||||
Id: uuid.NewString(),
|
||||
Title: info.Name,
|
||||
SubTitle: displayPath,
|
||||
Icon: info.Icon,
|
||||
Score: util.MaxInt64(nameScore, pathNameScore),
|
||||
ContextData: string(contextDataJson),
|
||||
Actions: []plugin.QueryResultAction{
|
||||
{
|
||||
Name: "i18n:plugin_app_open",
|
||||
|
@ -525,3 +547,87 @@ func (a *ApplicationPlugin) removeDuplicateApps(ctx context.Context, apps []appI
|
|||
a.api.Log(ctx, plugin.LogLevelInfo, fmt.Sprintf("removed %d duplicate apps, %d apps remaining", len(apps)-len(result), len(result)))
|
||||
return result
|
||||
}
|
||||
|
||||
func (a *ApplicationPlugin) handleMRURestore(mruData plugin.MRUData) (*plugin.QueryResult, error) {
|
||||
var contextData appContextData
|
||||
if err := json.Unmarshal([]byte(mruData.ContextData), &contextData); err != nil {
|
||||
return nil, fmt.Errorf("failed to parse context data: %w", err)
|
||||
}
|
||||
|
||||
var appInfo *appInfo
|
||||
for _, info := range a.apps {
|
||||
if info.Name == contextData.Name && info.Path == contextData.Path {
|
||||
appInfo = &info
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if appInfo == nil {
|
||||
return nil, fmt.Errorf("app not found: %s", contextData.Name)
|
||||
}
|
||||
|
||||
displayPath := appInfo.GetDisplayPath()
|
||||
result := &plugin.QueryResult{
|
||||
Id: uuid.NewString(),
|
||||
Title: appInfo.Name,
|
||||
SubTitle: displayPath,
|
||||
Icon: mruData.Icon,
|
||||
ContextData: mruData.ContextData,
|
||||
Actions: []plugin.QueryResultAction{
|
||||
{
|
||||
Name: "i18n:plugin_app_open",
|
||||
Icon: plugin.OpenIcon,
|
||||
Action: func(ctx context.Context, actionContext plugin.ActionContext) {
|
||||
runErr := shell.Open(appInfo.Path)
|
||||
if runErr != nil {
|
||||
a.api.Log(ctx, plugin.LogLevelError, fmt.Sprintf("error opening app %s: %s", appInfo.Path, runErr.Error()))
|
||||
a.api.Notify(ctx, fmt.Sprintf("i18n:plugin_app_open_failed_description: %s", runErr.Error()))
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "i18n:plugin_app_open_containing_folder",
|
||||
Icon: plugin.OpenContainingFolderIcon,
|
||||
Action: func(ctx context.Context, actionContext plugin.ActionContext) {
|
||||
if err := a.retriever.OpenAppFolder(ctx, *appInfo); err != nil {
|
||||
a.api.Log(ctx, plugin.LogLevelError, fmt.Sprintf("error opening folder: %s", err.Error()))
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "i18n:plugin_app_copy_path",
|
||||
Icon: plugin.CopyIcon,
|
||||
Action: func(ctx context.Context, actionContext plugin.ActionContext) {
|
||||
clipboard.WriteText(appInfo.Path)
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
if appInfo.IsRunning() {
|
||||
result.Actions = append(result.Actions, plugin.QueryResultAction{
|
||||
Name: "i18n:plugin_app_terminate",
|
||||
Icon: plugin.TerminateAppIcon,
|
||||
Action: func(ctx context.Context, actionContext plugin.ActionContext) {
|
||||
p, getErr := os.FindProcess(appInfo.Pid)
|
||||
if getErr != nil {
|
||||
a.api.Log(ctx, plugin.LogLevelError, fmt.Sprintf("error finding process %d: %s", appInfo.Pid, getErr.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
killErr := p.Kill()
|
||||
if killErr != nil {
|
||||
a.api.Log(ctx, plugin.LogLevelError, fmt.Sprintf("error killing process %d: %s", appInfo.Pid, killErr.Error()))
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
result.RefreshInterval = 1000
|
||||
result.OnRefresh = func(ctx context.Context, result plugin.RefreshableResult) plugin.RefreshableResult {
|
||||
result.Tails = a.getRunningProcessResult(appInfo.Pid)
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
|
|
@ -58,6 +58,9 @@ func (e emptyAPIImpl) AIChatStream(ctx context.Context, model common.Model, conv
|
|||
return nil
|
||||
}
|
||||
|
||||
func (e emptyAPIImpl) OnMRURestore(ctx context.Context, callback func(mruData plugin.MRUData) (*plugin.QueryResult, error)) {
|
||||
}
|
||||
|
||||
func TestMacRetriever_ParseAppInfo(t *testing.T) {
|
||||
if util.IsMacOS() {
|
||||
util.GetLocation().Init()
|
||||
|
|
|
@ -2,6 +2,7 @@ package system
|
|||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
@ -23,6 +24,11 @@ type Bookmark struct {
|
|||
Url string
|
||||
}
|
||||
|
||||
type bookmarkContextData struct {
|
||||
Name string `json:"name"`
|
||||
Url string `json:"url"`
|
||||
}
|
||||
|
||||
type BrowserBookmarkPlugin struct {
|
||||
api plugin.API
|
||||
bookmarks []Bookmark
|
||||
|
@ -49,6 +55,11 @@ func (c *BrowserBookmarkPlugin) GetMetadata() plugin.Metadata {
|
|||
"Macos",
|
||||
"Linux",
|
||||
},
|
||||
Features: []plugin.MetadataFeature{
|
||||
{
|
||||
Name: plugin.MetadataFeatureMRU,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -97,6 +108,8 @@ func (c *BrowserBookmarkPlugin) Init(ctx context.Context, initParams plugin.Init
|
|||
|
||||
// Remove duplicate bookmarks (same name and url)
|
||||
c.bookmarks = c.removeDuplicateBookmarks(c.bookmarks)
|
||||
|
||||
c.api.OnMRURestore(ctx, c.handleMRURestore)
|
||||
}
|
||||
|
||||
func (c *BrowserBookmarkPlugin) Query(ctx context.Context, query plugin.Query) (results []plugin.QueryResult) {
|
||||
|
@ -122,11 +135,18 @@ func (c *BrowserBookmarkPlugin) Query(ctx context.Context, query plugin.Query) (
|
|||
}
|
||||
|
||||
if isMatch {
|
||||
contextData := bookmarkContextData{
|
||||
Name: bookmark.Name,
|
||||
Url: bookmark.Url,
|
||||
}
|
||||
contextDataJson, _ := json.Marshal(contextData)
|
||||
|
||||
results = append(results, plugin.QueryResult{
|
||||
Title: bookmark.Name,
|
||||
SubTitle: bookmark.Url,
|
||||
Score: matchScore,
|
||||
Icon: browserBookmarkIcon,
|
||||
ContextData: string(contextDataJson),
|
||||
Actions: []plugin.QueryResultAction{
|
||||
{
|
||||
Name: "i18n:plugin_browser_bookmark_open_in_browser",
|
||||
|
@ -235,3 +255,40 @@ func (c *BrowserBookmarkPlugin) removeDuplicateBookmarks(bookmarks []Bookmark) [
|
|||
|
||||
return result
|
||||
}
|
||||
|
||||
func (c *BrowserBookmarkPlugin) handleMRURestore(mruData plugin.MRUData) (*plugin.QueryResult, error) {
|
||||
var contextData bookmarkContextData
|
||||
if err := json.Unmarshal([]byte(mruData.ContextData), &contextData); err != nil {
|
||||
return nil, fmt.Errorf("failed to parse context data: %w", err)
|
||||
}
|
||||
|
||||
// Check if bookmark still exists in current bookmarks
|
||||
found := false
|
||||
for _, bookmark := range c.bookmarks {
|
||||
if bookmark.Name == contextData.Name && bookmark.Url == contextData.Url {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !found {
|
||||
return nil, fmt.Errorf("bookmark no longer exists: %s", contextData.Name)
|
||||
}
|
||||
|
||||
result := &plugin.QueryResult{
|
||||
Title: contextData.Name,
|
||||
SubTitle: contextData.Url,
|
||||
Icon: mruData.Icon,
|
||||
ContextData: mruData.ContextData,
|
||||
Actions: []plugin.QueryResultAction{
|
||||
{
|
||||
Name: "i18n:plugin_browser_bookmark_open_in_browser",
|
||||
Action: func(ctx context.Context, actionContext plugin.ActionContext) {
|
||||
shell.Open(contextData.Url)
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
|
|
@ -270,3 +270,5 @@ func (m *mockAPI) RegisterQueryCommands(ctx context.Context, commands []plugin.M
|
|||
func (m *mockAPI) AIChatStream(ctx context.Context, model common.Model, conversations []common.Conversation, options common.ChatOptions, callback common.ChatStreamFunc) error {
|
||||
return nil
|
||||
}
|
||||
func (m *mockAPI) OnMRURestore(ctx context.Context, callback func(mruData plugin.MRUData) (*plugin.QueryResult, error)) {
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ package system
|
|||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"wox/common"
|
||||
"wox/i18n"
|
||||
|
@ -21,6 +22,12 @@ type IndicatorPlugin struct {
|
|||
api plugin.API
|
||||
}
|
||||
|
||||
type indicatorContextData struct {
|
||||
TriggerKeyword string `json:"triggerKeyword"`
|
||||
PluginID string `json:"pluginId"`
|
||||
Command string `json:"command,omitempty"`
|
||||
}
|
||||
|
||||
func (i *IndicatorPlugin) GetMetadata() plugin.Metadata {
|
||||
return plugin.Metadata{
|
||||
Id: "38564bf0-75ad-4b3e-8afe-a0e0a287c42e",
|
||||
|
@ -41,11 +48,17 @@ func (i *IndicatorPlugin) GetMetadata() plugin.Metadata {
|
|||
"Macos",
|
||||
"Linux",
|
||||
},
|
||||
Features: []plugin.MetadataFeature{
|
||||
{
|
||||
Name: plugin.MetadataFeatureMRU,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (i *IndicatorPlugin) Init(ctx context.Context, initParams plugin.InitParams) {
|
||||
i.api = initParams.API
|
||||
i.api.OnMRURestore(ctx, i.handleMRURestore)
|
||||
}
|
||||
|
||||
func (i *IndicatorPlugin) Query(ctx context.Context, query plugin.Query) []plugin.QueryResult {
|
||||
|
@ -55,12 +68,19 @@ func (i *IndicatorPlugin) Query(ctx context.Context, query plugin.Query) []plugi
|
|||
return triggerKeyword != "*" && IsStringMatchNoPinYin(ctx, triggerKeyword, query.Search)
|
||||
})
|
||||
if found {
|
||||
contextData := indicatorContextData{
|
||||
TriggerKeyword: triggerKeyword,
|
||||
PluginID: pluginInstance.Metadata.Id,
|
||||
}
|
||||
contextDataJson, _ := json.Marshal(contextData)
|
||||
|
||||
results = append(results, plugin.QueryResult{
|
||||
Id: uuid.NewString(),
|
||||
Title: triggerKeyword,
|
||||
SubTitle: fmt.Sprintf(i18n.GetI18nManager().TranslateWox(ctx, "plugin_indicator_activate_plugin"), pluginInstance.Metadata.Name),
|
||||
Score: 10,
|
||||
Icon: pluginInstance.Metadata.GetIconOrDefault(pluginInstance.PluginDirectory, indicatorIcon),
|
||||
ContextData: string(contextDataJson),
|
||||
Actions: []plugin.QueryResultAction{
|
||||
{
|
||||
Name: "i18n:plugin_indicator_activate",
|
||||
|
@ -101,3 +121,59 @@ func (i *IndicatorPlugin) Query(ctx context.Context, query plugin.Query) []plugi
|
|||
}
|
||||
return results
|
||||
}
|
||||
|
||||
func (i *IndicatorPlugin) handleMRURestore(mruData plugin.MRUData) (*plugin.QueryResult, error) {
|
||||
var contextData indicatorContextData
|
||||
if err := json.Unmarshal([]byte(mruData.ContextData), &contextData); err != nil {
|
||||
return nil, fmt.Errorf("failed to parse context data: %w", err)
|
||||
}
|
||||
|
||||
// Find the plugin instance by ID
|
||||
var pluginInstance *plugin.Instance
|
||||
for _, instance := range plugin.GetPluginManager().GetPluginInstances() {
|
||||
if instance.Metadata.Id == contextData.PluginID {
|
||||
pluginInstance = instance
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if pluginInstance == nil {
|
||||
return nil, fmt.Errorf("plugin no longer exists: %s", contextData.PluginID)
|
||||
}
|
||||
|
||||
// Check if trigger keyword still exists
|
||||
triggerKeywords := pluginInstance.GetTriggerKeywords()
|
||||
found := false
|
||||
for _, keyword := range triggerKeywords {
|
||||
if keyword == contextData.TriggerKeyword {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !found {
|
||||
return nil, fmt.Errorf("trigger keyword no longer exists: %s", contextData.TriggerKeyword)
|
||||
}
|
||||
|
||||
result := &plugin.QueryResult{
|
||||
Id: uuid.NewString(),
|
||||
Title: contextData.TriggerKeyword,
|
||||
SubTitle: fmt.Sprintf(i18n.GetI18nManager().TranslateWox(context.Background(), "plugin_indicator_activate_plugin"), pluginInstance.Metadata.Name),
|
||||
Icon: mruData.Icon,
|
||||
ContextData: mruData.ContextData,
|
||||
Actions: []plugin.QueryResultAction{
|
||||
{
|
||||
Name: "i18n:plugin_indicator_activate",
|
||||
PreventHideAfterAction: true,
|
||||
Action: func(ctx context.Context, actionContext plugin.ActionContext) {
|
||||
i.api.ChangeQuery(ctx, common.PlainQuery{
|
||||
QueryType: plugin.QueryTypeInput,
|
||||
QueryText: fmt.Sprintf("%s ", contextData.TriggerKeyword),
|
||||
})
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ package system
|
|||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
|
@ -34,6 +35,10 @@ type SysCommand struct {
|
|||
Action func(ctx context.Context, actionContext plugin.ActionContext)
|
||||
}
|
||||
|
||||
type sysContextData struct {
|
||||
CommandTitle string `json:"commandTitle"`
|
||||
}
|
||||
|
||||
func (r *SysPlugin) GetMetadata() plugin.Metadata {
|
||||
return plugin.Metadata{
|
||||
Id: "227f7d64-df08-4e35-ad05-98a26d540d06",
|
||||
|
@ -55,6 +60,11 @@ func (r *SysPlugin) GetMetadata() plugin.Metadata {
|
|||
"Macos",
|
||||
"Linux",
|
||||
},
|
||||
Features: []plugin.MetadataFeature{
|
||||
{
|
||||
Name: plugin.MetadataFeatureMRU,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -186,11 +196,17 @@ func (r *SysPlugin) Query(ctx context.Context, query plugin.Query) (results []pl
|
|||
}
|
||||
|
||||
if isTitleMatch {
|
||||
contextData := sysContextData{
|
||||
CommandTitle: command.Title,
|
||||
}
|
||||
contextDataJson, _ := json.Marshal(contextData)
|
||||
|
||||
results = append(results, plugin.QueryResult{
|
||||
Title: command.Title,
|
||||
SubTitle: command.SubTitle,
|
||||
Score: titleScore,
|
||||
Icon: command.Icon,
|
||||
ContextData: string(contextDataJson),
|
||||
Actions: []plugin.QueryResultAction{
|
||||
{
|
||||
Name: "i18n:plugin_sys_execute",
|
||||
|
@ -200,6 +216,8 @@ func (r *SysPlugin) Query(ctx context.Context, query plugin.Query) (results []pl
|
|||
},
|
||||
})
|
||||
}
|
||||
|
||||
r.api.OnMRURestore(ctx, r.handleMRURestore)
|
||||
}
|
||||
|
||||
for _, instance := range plugin.GetPluginManager().GetPluginInstances() {
|
||||
|
@ -236,3 +254,39 @@ func (r *SysPlugin) Query(ctx context.Context, query plugin.Query) (results []pl
|
|||
|
||||
return
|
||||
}
|
||||
|
||||
func (r *SysPlugin) handleMRURestore(mruData plugin.MRUData) (*plugin.QueryResult, error) {
|
||||
var contextData sysContextData
|
||||
if err := json.Unmarshal([]byte(mruData.ContextData), &contextData); err != nil {
|
||||
return nil, fmt.Errorf("failed to parse context data: %w", err)
|
||||
}
|
||||
|
||||
// Find the command by title
|
||||
var foundCommand *SysCommand
|
||||
for _, command := range r.commands {
|
||||
if command.Title == contextData.CommandTitle {
|
||||
foundCommand = &command
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if foundCommand == nil {
|
||||
return nil, fmt.Errorf("system command no longer exists: %s", contextData.CommandTitle)
|
||||
}
|
||||
|
||||
result := &plugin.QueryResult{
|
||||
Title: foundCommand.Title,
|
||||
SubTitle: foundCommand.SubTitle,
|
||||
Icon: mruData.Icon,
|
||||
ContextData: mruData.ContextData,
|
||||
Actions: []plugin.QueryResultAction{
|
||||
{
|
||||
Name: "i18n:plugin_sys_execute",
|
||||
Action: foundCommand.Action,
|
||||
PreventHideAfterAction: foundCommand.PreventHideAfterAction,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
|
|
@ -26,6 +26,12 @@ type UrlHistory struct {
|
|||
Title string
|
||||
}
|
||||
|
||||
type urlContextData struct {
|
||||
Url string `json:"url"`
|
||||
Title string `json:"title"`
|
||||
Type string `json:"type"` // "history" or "direct"
|
||||
}
|
||||
|
||||
type UrlPlugin struct {
|
||||
api plugin.API
|
||||
reg *regexp.Regexp
|
||||
|
@ -53,6 +59,11 @@ func (r *UrlPlugin) GetMetadata() plugin.Metadata {
|
|||
"Macos",
|
||||
"Linux",
|
||||
},
|
||||
Features: []plugin.MetadataFeature{
|
||||
{
|
||||
Name: plugin.MetadataFeatureMRU,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -60,6 +71,8 @@ func (r *UrlPlugin) Init(ctx context.Context, initParams plugin.InitParams) {
|
|||
r.api = initParams.API
|
||||
r.reg = r.getReg()
|
||||
r.recentUrls = r.loadRecentUrls(ctx)
|
||||
|
||||
r.api.OnMRURestore(ctx, r.handleMRURestore)
|
||||
}
|
||||
|
||||
func (r *UrlPlugin) loadRecentUrls(ctx context.Context) []UrlHistory {
|
||||
|
@ -90,11 +103,19 @@ func (r *UrlPlugin) Query(ctx context.Context, query plugin.Query) (results []pl
|
|||
})
|
||||
|
||||
for _, history := range existingUrlHistory {
|
||||
contextData := urlContextData{
|
||||
Url: history.Url,
|
||||
Title: history.Title,
|
||||
Type: "history",
|
||||
}
|
||||
contextDataJson, _ := json.Marshal(contextData)
|
||||
|
||||
results = append(results, plugin.QueryResult{
|
||||
Title: history.Url,
|
||||
SubTitle: history.Title,
|
||||
Score: 100,
|
||||
Icon: history.Icon.Overlay(urlIcon, 0.4, 0.6, 0.6),
|
||||
ContextData: string(contextDataJson),
|
||||
Actions: []plugin.QueryResultAction{
|
||||
{
|
||||
Name: "i18n:plugin_url_open",
|
||||
|
@ -119,11 +140,19 @@ func (r *UrlPlugin) Query(ctx context.Context, query plugin.Query) (results []pl
|
|||
}
|
||||
|
||||
if len(r.reg.FindStringIndex(query.Search)) > 0 {
|
||||
contextData := urlContextData{
|
||||
Url: query.Search,
|
||||
Title: "",
|
||||
Type: "direct",
|
||||
}
|
||||
contextDataJson, _ := json.Marshal(contextData)
|
||||
|
||||
results = append(results, plugin.QueryResult{
|
||||
Title: query.Search,
|
||||
SubTitle: "i18n:plugin_url_open_in_browser",
|
||||
Score: 100,
|
||||
Icon: urlIcon,
|
||||
ContextData: string(contextDataJson),
|
||||
Actions: []plugin.QueryResultAction{
|
||||
{
|
||||
Name: "i18n:plugin_url_open",
|
||||
|
@ -202,3 +231,77 @@ func (r *UrlPlugin) removeRecentUrl(ctx context.Context, url string) {
|
|||
|
||||
r.api.SaveSetting(ctx, "recentUrls", string(urlsJson), false)
|
||||
}
|
||||
|
||||
func (r *UrlPlugin) handleMRURestore(mruData plugin.MRUData) (*plugin.QueryResult, error) {
|
||||
var contextData urlContextData
|
||||
if err := json.Unmarshal([]byte(mruData.ContextData), &contextData); err != nil {
|
||||
return nil, fmt.Errorf("failed to parse context data: %w", err)
|
||||
}
|
||||
|
||||
url := contextData.Url
|
||||
if !strings.HasPrefix(url, "http") {
|
||||
url = "https://" + url
|
||||
}
|
||||
|
||||
result := &plugin.QueryResult{
|
||||
Title: contextData.Url,
|
||||
SubTitle: contextData.Title,
|
||||
Icon: mruData.Icon,
|
||||
ContextData: mruData.ContextData,
|
||||
}
|
||||
|
||||
if contextData.Type == "history" {
|
||||
found := false
|
||||
for _, history := range r.recentUrls {
|
||||
if history.Url == contextData.Url {
|
||||
found = true
|
||||
result.SubTitle = history.Title
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !found {
|
||||
return nil, fmt.Errorf("URL no longer in history: %s", contextData.Url)
|
||||
}
|
||||
|
||||
result.Actions = []plugin.QueryResultAction{
|
||||
{
|
||||
Name: "i18n:plugin_url_open",
|
||||
Icon: plugin.OpenIcon,
|
||||
Action: func(ctx context.Context, actionContext plugin.ActionContext) {
|
||||
openErr := shell.Open(url)
|
||||
if openErr != nil {
|
||||
r.api.Log(ctx, "Error opening URL", openErr.Error())
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "i18n:plugin_url_remove",
|
||||
Icon: plugin.TrashIcon,
|
||||
Action: func(ctx context.Context, actionContext plugin.ActionContext) {
|
||||
r.removeRecentUrl(ctx, contextData.Url)
|
||||
},
|
||||
},
|
||||
}
|
||||
} else {
|
||||
result.SubTitle = "i18n:plugin_url_open_in_browser"
|
||||
result.Actions = []plugin.QueryResultAction{
|
||||
{
|
||||
Name: "i18n:plugin_url_open",
|
||||
Icon: urlIcon,
|
||||
Action: func(ctx context.Context, actionContext plugin.ActionContext) {
|
||||
openErr := shell.Open(url)
|
||||
if openErr != nil {
|
||||
r.api.Log(ctx, "Error opening URL", openErr.Error())
|
||||
} else {
|
||||
util.Go(ctx, "saveRecentUrl", func() {
|
||||
r.saveRecentUrl(ctx, url)
|
||||
})
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
|
|
@ -9,10 +9,11 @@
|
|||
"ui_selection_hotkey_tips": "Hotkeys to do actions on selected text or files",
|
||||
"ui_use_pinyin": "Use Pinyin",
|
||||
"ui_use_pinyin_tips": "When selected, Wox will convert Chinese into Pinyin",
|
||||
"ui_last_query_mode": "Last Query Mode",
|
||||
"ui_last_query_mode_tips": "Choose the default behavior when opening Wox",
|
||||
"ui_last_query_mode_preserve": "Preserve last query",
|
||||
"ui_last_query_mode_empty": "Always start with empty query",
|
||||
"ui_query_mode": "Query Mode",
|
||||
"ui_query_mode_tips": "Choose the default behavior when opening Wox",
|
||||
"ui_query_mode_preserve": "Preserve last query",
|
||||
"ui_query_mode_empty": "Always start with empty query",
|
||||
"ui_query_mode_mru": "Show most recently used items",
|
||||
"ui_hide_on_lost_focus": "Hide on lost focus",
|
||||
"ui_hide_on_lost_focus_tips": "When selected, Wox will hide when it loses focus",
|
||||
"ui_hide_on_start": "Hide on start",
|
||||
|
@ -386,6 +387,7 @@
|
|||
"plugin_ai_chat_default_model_tooltip": "The default model to use for this command",
|
||||
"plugin_ai_chat_enable_fallback_search": "Fallback Search",
|
||||
"plugin_ai_chat_enable_fallback_search_tooltip": "When enabled, if the query result is empty, Wox will return a result of 'use AI Chat for %s'",
|
||||
"plugin_app_open_failed_description": "Failed to open: %s",
|
||||
"plugin_ai_chat_fallback_search_chat_for": "Use AI Chat for %s",
|
||||
"plugin_ai_chat_start_chat": "Start Chat",
|
||||
"plugin_ai_chat_enable_auto_focus_to_chat_input": "Auto focus to chat input when open with query hotkey",
|
||||
|
|
|
@ -13,6 +13,9 @@
|
|||
"ui_last_query_mode_tips": "Escolha o comportamento padrão ao abrir o Wox",
|
||||
"ui_last_query_mode_preserve": "Preservar última consulta",
|
||||
"ui_last_query_mode_empty": "Sempre começar com consulta vazia",
|
||||
"ui_last_query_mode_mru": "Mostrar itens usados recentemente",
|
||||
"ui_last_query_mode_preserve": "Preservar última consulta",
|
||||
"ui_last_query_mode_empty": "Sempre começar com consulta vazia",
|
||||
"ui_hide_on_lost_focus": "Ocultar na perda do foco",
|
||||
"ui_hide_on_lost_focus_tips": "Quando selecionado, o Wox será ocultado na perda do foco",
|
||||
"ui_hide_on_start": "Ocultar ao iniciar",
|
||||
|
|
|
@ -13,6 +13,9 @@
|
|||
"ui_last_query_mode_tips": "Выберите поведение по умолчанию при открытии Wox",
|
||||
"ui_last_query_mode_preserve": "Сохранить последний запрос",
|
||||
"ui_last_query_mode_empty": "Всегда начинать с пустого запроса",
|
||||
"ui_last_query_mode_mru": "Показать недавно использованные элементы",
|
||||
"ui_last_query_mode_preserve": "Сохранить последний запрос",
|
||||
"ui_last_query_mode_empty": "Всегда начинать с пустого запроса",
|
||||
"ui_hide_on_lost_focus": "Скрывать при потере фокуса",
|
||||
"ui_hide_on_lost_focus_tips": "При выборе Wox будет скрываться при потере фокуса",
|
||||
"ui_hide_on_start": "Скрывать при запуске",
|
||||
|
|
|
@ -9,10 +9,11 @@
|
|||
"ui_selection_hotkey_tips": "用于在选定的文本或文件上执行操作的快捷键",
|
||||
"ui_use_pinyin": "使用拼音",
|
||||
"ui_use_pinyin_tips": "搜索时,把中文转换为拼音",
|
||||
"ui_last_query_mode": "上次查询模式",
|
||||
"ui_last_query_mode_tips": "选择打开Wox时的默认行为",
|
||||
"ui_last_query_mode_preserve": "保留上次查询",
|
||||
"ui_last_query_mode_empty": "总是从空查询开始",
|
||||
"ui_query_mode": "查询模式",
|
||||
"ui_query_mode_tips": "选择打开Wox时的默认行为",
|
||||
"ui_query_mode_preserve": "保留上次查询",
|
||||
"ui_query_mode_empty": "总是从空查询开始",
|
||||
"ui_query_mode_mru": "显示最近使用的项目",
|
||||
"ui_hide_on_lost_focus": "失去焦点时隐藏",
|
||||
"ui_hide_on_lost_focus_tips": "选中后,Wox失去焦点时将隐藏",
|
||||
"ui_hide_on_start": "启动时隐藏",
|
||||
|
@ -386,6 +387,11 @@
|
|||
"plugin_ai_chat_default_model_tooltip": "用于对话的默认模型",
|
||||
"plugin_ai_chat_enable_fallback_search": "回退搜索",
|
||||
"plugin_ai_chat_enable_fallback_search_tooltip": "当查询结果为空时,Wox 将返回一个 '使用 AI Chat 查询 %s' 的结果",
|
||||
"plugin_app_open": "打开",
|
||||
"plugin_app_open_containing_folder": "打开所在文件夹",
|
||||
"plugin_app_copy_path": "复制路径",
|
||||
"plugin_app_terminate": "终止进程",
|
||||
"plugin_app_open_failed_description": "打开失败:%s",
|
||||
"plugin_ai_chat_fallback_search_chat_for": "使用 AI Chat 查询 %s",
|
||||
"plugin_ai_chat_start_chat": "开始对话",
|
||||
"plugin_ai_chat_enable_auto_focus_to_chat_input": "使用查询快捷键打开时自动聚焦到对话输入框",
|
||||
|
|
|
@ -4,10 +4,13 @@ import (
|
|||
"context"
|
||||
"fmt"
|
||||
"sync"
|
||||
"time"
|
||||
"wox/common"
|
||||
"wox/database"
|
||||
"wox/util"
|
||||
"wox/util/autostart"
|
||||
|
||||
"github.com/samber/lo"
|
||||
)
|
||||
|
||||
var managerInstance *Manager
|
||||
|
@ -16,6 +19,7 @@ var logger *util.Log
|
|||
|
||||
type Manager struct {
|
||||
woxSetting *WoxSetting
|
||||
mruManager *MRUManager
|
||||
}
|
||||
|
||||
func GetSettingManager() *Manager {
|
||||
|
@ -30,6 +34,7 @@ func GetSettingManager() *Manager {
|
|||
store := NewWoxSettingStore(db)
|
||||
managerInstance = &Manager{}
|
||||
managerInstance.woxSetting = NewWoxSetting(store)
|
||||
managerInstance.mruManager = NewMRUManager(db)
|
||||
})
|
||||
return managerInstance
|
||||
}
|
||||
|
@ -77,14 +82,14 @@ func (m *Manager) GetWoxSetting(ctx context.Context) *WoxSetting {
|
|||
return m.woxSetting
|
||||
}
|
||||
|
||||
func (m *Manager) GetLatestQueryHistory(ctx context.Context, limit int) []common.PlainQuery {
|
||||
func (m *Manager) GetLatestQueryHistory(ctx context.Context, limit int) []QueryHistory {
|
||||
histories := m.woxSetting.QueryHistories.Get()
|
||||
|
||||
// Sort by timestamp descending and limit results
|
||||
var result []common.PlainQuery
|
||||
var result []QueryHistory
|
||||
count := 0
|
||||
for i := len(histories) - 1; i >= 0 && count < limit; i-- {
|
||||
result = append(result, histories[i].Query)
|
||||
result = append(result, histories[i])
|
||||
count++
|
||||
}
|
||||
|
||||
|
@ -137,3 +142,64 @@ func (m *Manager) RemoveFavoriteResult(ctx context.Context, pluginId string, res
|
|||
favoriteResults.Delete(resultHash)
|
||||
m.woxSetting.FavoriteResults.Set(favoriteResults)
|
||||
}
|
||||
|
||||
func (m *Manager) AddQueryHistory(ctx context.Context, query common.PlainQuery) {
|
||||
histories := m.woxSetting.QueryHistories.Get()
|
||||
newHistory := QueryHistory{
|
||||
Query: query,
|
||||
Timestamp: util.GetSystemTimestamp(),
|
||||
}
|
||||
|
||||
// Remove duplicate if exists (same query text)
|
||||
histories = lo.Filter(histories, func(item QueryHistory, index int) bool {
|
||||
return !item.Query.IsEmpty() && item.Query.QueryText != query.QueryText
|
||||
})
|
||||
|
||||
// Add new history at the end
|
||||
histories = append(histories, newHistory)
|
||||
|
||||
// Keep only the most recent 1000 entries
|
||||
if len(histories) > 1000 {
|
||||
histories = histories[len(histories)-1000:]
|
||||
}
|
||||
|
||||
m.woxSetting.QueryHistories.Set(histories)
|
||||
}
|
||||
|
||||
// MRU related methods
|
||||
|
||||
func (m *Manager) AddMRUItem(ctx context.Context, item MRUItem) error {
|
||||
return m.mruManager.AddMRUItem(ctx, item)
|
||||
}
|
||||
|
||||
func (m *Manager) GetMRUItems(ctx context.Context, limit int) ([]MRUItem, error) {
|
||||
return m.mruManager.GetMRUItems(ctx, limit)
|
||||
}
|
||||
|
||||
func (m *Manager) RemoveMRUItem(ctx context.Context, pluginID, title, subTitle string) error {
|
||||
return m.mruManager.RemoveMRUItem(ctx, pluginID, title, subTitle)
|
||||
}
|
||||
|
||||
func (m *Manager) CleanupOldMRUItems(ctx context.Context, keepCount int) error {
|
||||
return m.mruManager.CleanupOldMRUItems(ctx, keepCount)
|
||||
}
|
||||
|
||||
// StartMRUCleanup starts a background goroutine to periodically clean up old MRU items
|
||||
func (m *Manager) StartMRUCleanup(ctx context.Context) {
|
||||
util.Go(ctx, "MRU cleanup", func() {
|
||||
ticker := time.NewTicker(24 * time.Hour) // Clean up once per day
|
||||
defer ticker.Stop()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-ticker.C:
|
||||
// Keep only the most recent 100 MRU items
|
||||
if err := m.CleanupOldMRUItems(ctx, 100); err != nil {
|
||||
util.GetLogger().Error(ctx, fmt.Sprintf("failed to cleanup old MRU items: %s", err.Error()))
|
||||
}
|
||||
case <-ctx.Done():
|
||||
return
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
@ -0,0 +1,153 @@
|
|||
package setting
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"time"
|
||||
"wox/common"
|
||||
"wox/database"
|
||||
"wox/util"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// MRUItem represents a Most Recently Used item
|
||||
type MRUItem struct {
|
||||
PluginID string `json:"pluginId"`
|
||||
Title string `json:"title"`
|
||||
SubTitle string `json:"subTitle"`
|
||||
Icon common.WoxImage `json:"icon"`
|
||||
ContextData string `json:"contextData"`
|
||||
LastUsed int64 `json:"lastUsed"`
|
||||
UseCount int `json:"useCount"`
|
||||
}
|
||||
|
||||
// MRUManager manages Most Recently Used items
|
||||
type MRUManager struct {
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
// NewMRUManager creates a new MRU manager
|
||||
func NewMRUManager(db *gorm.DB) *MRUManager {
|
||||
return &MRUManager{db: db}
|
||||
}
|
||||
|
||||
// AddMRUItem adds or updates an MRU item
|
||||
func (m *MRUManager) AddMRUItem(ctx context.Context, item MRUItem) error {
|
||||
hash := NewResultHash(item.PluginID, item.Title, item.SubTitle)
|
||||
|
||||
// Serialize icon to JSON
|
||||
iconData, err := json.Marshal(item.Icon)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to serialize icon: %w", err)
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
timestamp := util.GetSystemTimestamp()
|
||||
|
||||
// Check if record exists
|
||||
var existingRecord database.MRURecord
|
||||
err = m.db.Where("hash = ?", string(hash)).First(&existingRecord).Error
|
||||
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
// Create new record
|
||||
record := database.MRURecord{
|
||||
Hash: string(hash),
|
||||
PluginID: item.PluginID,
|
||||
Title: item.Title,
|
||||
SubTitle: item.SubTitle,
|
||||
Icon: string(iconData),
|
||||
ContextData: item.ContextData,
|
||||
LastUsed: timestamp,
|
||||
UseCount: 1,
|
||||
CreatedAt: now,
|
||||
UpdatedAt: now,
|
||||
}
|
||||
return m.db.Create(&record).Error
|
||||
} else if err != nil {
|
||||
return fmt.Errorf("failed to query MRU record: %w", err)
|
||||
} else {
|
||||
// Update existing record
|
||||
updates := map[string]interface{}{
|
||||
"last_used": timestamp,
|
||||
"use_count": existingRecord.UseCount + 1,
|
||||
"context_data": item.ContextData, // Update context data in case it changed
|
||||
"icon": string(iconData), // Update icon in case it changed
|
||||
"updated_at": now,
|
||||
}
|
||||
return m.db.Model(&existingRecord).Updates(updates).Error
|
||||
}
|
||||
}
|
||||
|
||||
// GetMRUItems retrieves MRU items sorted by usage
|
||||
func (m *MRUManager) GetMRUItems(ctx context.Context, limit int) ([]MRUItem, error) {
|
||||
var records []database.MRURecord
|
||||
|
||||
// Order by last_used DESC, then by use_count DESC for items with same last_used time
|
||||
err := m.db.Order("last_used DESC, use_count DESC").Limit(limit).Find(&records).Error
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to query MRU records: %w", err)
|
||||
}
|
||||
|
||||
items := make([]MRUItem, 0, len(records))
|
||||
for _, record := range records {
|
||||
// Deserialize icon
|
||||
var icon common.WoxImage
|
||||
if err := json.Unmarshal([]byte(record.Icon), &icon); err != nil {
|
||||
util.GetLogger().Warn(ctx, fmt.Sprintf("failed to deserialize icon for MRU item %s: %s", record.Hash, err.Error()))
|
||||
icon = common.WoxImage{} // Use empty icon as fallback
|
||||
}
|
||||
|
||||
items = append(items, MRUItem{
|
||||
PluginID: record.PluginID,
|
||||
Title: record.Title,
|
||||
SubTitle: record.SubTitle,
|
||||
Icon: icon,
|
||||
ContextData: record.ContextData,
|
||||
LastUsed: record.LastUsed,
|
||||
UseCount: record.UseCount,
|
||||
})
|
||||
}
|
||||
|
||||
return items, nil
|
||||
}
|
||||
|
||||
// RemoveMRUItem removes an MRU item by hash
|
||||
func (m *MRUManager) RemoveMRUItem(ctx context.Context, pluginID, title, subTitle string) error {
|
||||
hash := NewResultHash(pluginID, title, subTitle)
|
||||
result := m.db.Where("hash = ?", string(hash)).Delete(&database.MRURecord{})
|
||||
if result.Error != nil {
|
||||
return fmt.Errorf("failed to remove MRU item: %w", result.Error)
|
||||
}
|
||||
|
||||
util.GetLogger().Debug(ctx, fmt.Sprintf("removed MRU item: %s", hash))
|
||||
return nil
|
||||
}
|
||||
|
||||
// CleanupOldMRUItems removes old MRU items to keep the database size manageable
|
||||
func (m *MRUManager) CleanupOldMRUItems(ctx context.Context, keepCount int) error {
|
||||
// Keep only the most recent keepCount items
|
||||
subQuery := m.db.Model(&database.MRURecord{}).
|
||||
Select("hash").
|
||||
Order("last_used DESC, use_count DESC").
|
||||
Limit(keepCount)
|
||||
|
||||
result := m.db.Where("hash NOT IN (?)", subQuery).Delete(&database.MRURecord{})
|
||||
if result.Error != nil {
|
||||
return fmt.Errorf("failed to cleanup old MRU items: %w", result.Error)
|
||||
}
|
||||
|
||||
if result.RowsAffected > 0 {
|
||||
util.GetLogger().Info(ctx, fmt.Sprintf("cleaned up %d old MRU items", result.RowsAffected))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetMRUCount returns the total number of MRU items
|
||||
func (m *MRUManager) GetMRUCount(ctx context.Context) (int64, error) {
|
||||
var count int64
|
||||
err := m.db.Model(&database.MRURecord{}).Count(&count).Error
|
||||
return count, err
|
||||
}
|
|
@ -22,7 +22,7 @@ type WoxSetting struct {
|
|||
LangCode *WoxSettingValue[i18n.LangCode]
|
||||
QueryHotkeys *PlatformValue[[]QueryHotkey]
|
||||
QueryShortcuts *WoxSettingValue[[]QueryShortcut]
|
||||
LastQueryMode *WoxSettingValue[LastQueryMode]
|
||||
QueryMode *WoxSettingValue[QueryMode]
|
||||
ShowPosition *WoxSettingValue[PositionType]
|
||||
AIProviders *WoxSettingValue[[]AIProvider]
|
||||
EnableAutoBackup *WoxSettingValue[bool]
|
||||
|
@ -49,7 +49,7 @@ type WoxSetting struct {
|
|||
ActionedResults *WoxSettingValue[*util.HashMap[ResultHash, []ActionedResult]]
|
||||
}
|
||||
|
||||
type LastQueryMode = string
|
||||
type QueryMode = string
|
||||
|
||||
type PositionType string
|
||||
|
||||
|
@ -60,8 +60,9 @@ const (
|
|||
)
|
||||
|
||||
const (
|
||||
LastQueryModePreserve LastQueryMode = "preserve" // preserve last query and select all for quick modify
|
||||
LastQueryModeEmpty LastQueryMode = "empty" // empty last query
|
||||
QueryModePreserve QueryMode = "preserve" // preserve last query and select all for quick modify
|
||||
QueryModeEmpty QueryMode = "empty" // empty last query
|
||||
QueryModeMRU QueryMode = "mru" // show MRU (Most Recently Used) list
|
||||
)
|
||||
|
||||
const (
|
||||
|
@ -134,7 +135,7 @@ func NewWoxSetting(store *WoxSettingStore) *WoxSetting {
|
|||
LangCode: NewWoxSettingValueWithValidator(store, "LangCode", defaultLangCode, func(code i18n.LangCode) bool {
|
||||
return i18n.IsSupportedLangCode(string(code))
|
||||
}),
|
||||
LastQueryMode: NewWoxSettingValue(store, "LastQueryMode", LastQueryModeEmpty),
|
||||
QueryMode: NewWoxSettingValue(store, "QueryMode", QueryModeEmpty),
|
||||
ShowPosition: NewWoxSettingValue(store, "ShowPosition", PositionTypeMouseScreen),
|
||||
AppWidth: NewWoxSettingValue(store, "AppWidth", 800),
|
||||
MaxResultCount: NewWoxSettingValue(store, "MaxResultCount", 10),
|
||||
|
|
|
@ -17,7 +17,7 @@ type WoxSettingDto struct {
|
|||
LangCode i18n.LangCode
|
||||
QueryHotkeys []setting.QueryHotkey
|
||||
QueryShortcuts []setting.QueryShortcut
|
||||
LastQueryMode setting.LastQueryMode
|
||||
QueryMode setting.QueryMode
|
||||
AIProviders []setting.AIProvider
|
||||
HttpProxyEnabled bool
|
||||
HttpProxyUrl string
|
||||
|
|
|
@ -88,6 +88,7 @@ var routers = map[string]func(w http.ResponseWriter, r *http.Request){
|
|||
"/hotkey/available": handleHotkeyAvailable,
|
||||
"/query/icon": handleQueryIcon,
|
||||
"/query/ratio": handleQueryRatio,
|
||||
"/query/mru": handleQueryMRU,
|
||||
"/deeplink": handleDeeplink,
|
||||
"/version": handleVersion,
|
||||
}
|
||||
|
@ -484,7 +485,7 @@ func handleSettingWox(w http.ResponseWriter, r *http.Request) {
|
|||
settingDto.LangCode = woxSetting.LangCode.Get()
|
||||
settingDto.QueryHotkeys = woxSetting.QueryHotkeys.Get()
|
||||
settingDto.QueryShortcuts = woxSetting.QueryShortcuts.Get()
|
||||
settingDto.LastQueryMode = woxSetting.LastQueryMode.Get()
|
||||
settingDto.QueryMode = woxSetting.QueryMode.Get()
|
||||
settingDto.AIProviders = woxSetting.AIProviders.Get()
|
||||
settingDto.HttpProxyEnabled = woxSetting.HttpProxyEnabled.Get()
|
||||
settingDto.HttpProxyUrl = woxSetting.HttpProxyUrl.Get()
|
||||
|
@ -537,8 +538,8 @@ func handleSettingWoxUpdate(w http.ResponseWriter, r *http.Request) {
|
|||
woxSetting.ShowTray.Set(kv.Value.(bool))
|
||||
case "LangCode":
|
||||
woxSetting.LangCode.Set(i18n.LangCode(kv.Value.(string)))
|
||||
case "LastQueryMode":
|
||||
woxSetting.LastQueryMode.Set(setting.LastQueryMode(kv.Value.(string)))
|
||||
case "QueryMode":
|
||||
woxSetting.QueryMode.Set(setting.QueryMode(kv.Value.(string)))
|
||||
case "ShowPosition":
|
||||
woxSetting.ShowPosition.Set(setting.PositionType(kv.Value.(string)))
|
||||
case "EnableAutoBackup":
|
||||
|
@ -1155,3 +1156,23 @@ func handlePluginDetail(w http.ResponseWriter, r *http.Request) {
|
|||
func handleVersion(w http.ResponseWriter, r *http.Request) {
|
||||
writeSuccessResponse(w, updater.CURRENT_VERSION)
|
||||
}
|
||||
|
||||
func handleQueryMRU(w http.ResponseWriter, r *http.Request) {
|
||||
body, readErr := io.ReadAll(r.Body)
|
||||
if readErr != nil {
|
||||
writeErrorResponse(w, fmt.Sprintf("failed to read request body: %s", readErr.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
var ctx context.Context
|
||||
traceIdResult := gjson.GetBytes(body, "traceId")
|
||||
if traceIdResult.Exists() && traceIdResult.String() != "" {
|
||||
ctx = util.NewTraceContextWith(traceIdResult.String())
|
||||
} else {
|
||||
ctx = util.NewTraceContext()
|
||||
}
|
||||
|
||||
mruResults := plugin.GetPluginManager().QueryMRU(ctx)
|
||||
logger.Info(ctx, fmt.Sprintf("found %d MRU results", len(mruResults)))
|
||||
writeSuccessResponse(w, mruResults)
|
||||
}
|
||||
|
|
|
@ -203,7 +203,7 @@ func getShowAppParams(ctx context.Context, showContext common.ShowContext) map[s
|
|||
"AutoFocusToChatInput": showContext.AutoFocusToChatInput,
|
||||
"Position": position,
|
||||
"QueryHistories": setting.GetSettingManager().GetLatestQueryHistory(ctx, 10),
|
||||
"LastQueryMode": woxSetting.LastQueryMode.Get(),
|
||||
"QueryMode": woxSetting.QueryMode.Get(),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -183,6 +183,14 @@ class WoxApi {
|
|||
return await WoxHttpUtil.instance.postData<List<DoctorCheckResult>>("/doctor/check", null);
|
||||
}
|
||||
|
||||
Future<List<WoxQueryResult>> queryMRU(String traceId) async {
|
||||
final response = await WoxHttpUtil.instance.postData("/query/mru", {"traceId": traceId});
|
||||
if (response is List) {
|
||||
return response.map((item) => WoxQueryResult.fromJson(item)).toList();
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
Future<String> getUserDataLocation() async {
|
||||
return await WoxHttpUtil.instance.postData("/setting/userdata/location", null);
|
||||
}
|
||||
|
|
|
@ -19,13 +19,14 @@ import 'package:wox/entity/wox_hotkey.dart';
|
|||
import 'package:wox/entity/wox_image.dart';
|
||||
import 'package:wox/entity/wox_preview.dart';
|
||||
import 'package:wox/entity/wox_query.dart';
|
||||
import 'package:wox/enums/wox_query_mode_enum.dart';
|
||||
import 'package:wox/entity/wox_setting.dart';
|
||||
import 'package:wox/entity/wox_theme.dart';
|
||||
import 'package:wox/entity/wox_toolbar.dart';
|
||||
import 'package:wox/entity/wox_websocket_msg.dart';
|
||||
import 'package:wox/enums/wox_direction_enum.dart';
|
||||
import 'package:wox/enums/wox_image_type_enum.dart';
|
||||
import 'package:wox/enums/wox_last_query_mode_enum.dart';
|
||||
|
||||
import 'package:wox/enums/wox_msg_method_enum.dart';
|
||||
import 'package:wox/enums/wox_msg_type_enum.dart';
|
||||
import 'package:wox/enums/wox_position_type_enum.dart';
|
||||
|
@ -78,7 +79,7 @@ class WoxLauncherController extends GetxController {
|
|||
var currentQueryHistoryIndex = 0; // query history index, used to navigate query history
|
||||
|
||||
var refreshCounter = 0;
|
||||
var lastQueryMode = WoxLastQueryModeEnum.WOX_LAST_QUERY_MODE_PRESERVE.code;
|
||||
var lastQueryMode = WoxQueryModeEnum.WOX_QUERY_MODE_PRESERVE.code;
|
||||
final isInSettingView = false.obs;
|
||||
var positionBeforeOpenSetting = const Offset(0, 0);
|
||||
|
||||
|
@ -148,7 +149,6 @@ class WoxLauncherController extends GetxController {
|
|||
return;
|
||||
}
|
||||
|
||||
//ignore the results if the query id is not matched
|
||||
if (currentQuery.value.queryId != receivedResults.first.queryId) {
|
||||
Logger.instance.error(traceId, "query id is not matched, ignore the results");
|
||||
return;
|
||||
|
@ -215,7 +215,7 @@ class WoxLauncherController extends GetxController {
|
|||
Future<void> showApp(String traceId, ShowAppParams params) async {
|
||||
if (currentQuery.value.queryType == WoxQueryTypeEnum.WOX_QUERY_TYPE_INPUT.code) {
|
||||
canArrowUpHistory = true;
|
||||
if (lastQueryMode == WoxLastQueryModeEnum.WOX_LAST_QUERY_MODE_PRESERVE.code) {
|
||||
if (lastQueryMode == WoxQueryModeEnum.WOX_QUERY_MODE_PRESERVE.code) {
|
||||
//skip the first one, because it's the current query
|
||||
currentQueryHistoryIndex = 0;
|
||||
} else {
|
||||
|
@ -225,7 +225,15 @@ class WoxLauncherController extends GetxController {
|
|||
|
||||
// update some properties to latest for later use
|
||||
latestQueryHistories.assignAll(params.queryHistories);
|
||||
lastQueryMode = params.lastQueryMode;
|
||||
lastQueryMode = params.queryMode;
|
||||
|
||||
// Handle MRU mode
|
||||
if (lastQueryMode == WoxQueryModeEnum.WOX_QUERY_MODE_MRU.code) {
|
||||
// Clear current query and show MRU results
|
||||
currentQuery.value = PlainQuery.emptyInput();
|
||||
queryBoxTextFieldController.clear();
|
||||
queryMRU(traceId);
|
||||
}
|
||||
|
||||
// Handle different position types
|
||||
// on linux, we need to show first and then set position or center it
|
||||
|
@ -247,8 +255,10 @@ class WoxLauncherController extends GetxController {
|
|||
}
|
||||
|
||||
Future<void> hideApp(String traceId) async {
|
||||
//clear query box text if query type is selection or last query mode is empty
|
||||
if (currentQuery.value.queryType == WoxQueryTypeEnum.WOX_QUERY_TYPE_SELECTION.code || lastQueryMode == WoxLastQueryModeEnum.WOX_LAST_QUERY_MODE_EMPTY.code) {
|
||||
//clear query box text if query type is selection or last query mode is empty or MRU
|
||||
if (currentQuery.value.queryType == WoxQueryTypeEnum.WOX_QUERY_TYPE_SELECTION.code ||
|
||||
lastQueryMode == WoxQueryModeEnum.WOX_QUERY_MODE_EMPTY.code ||
|
||||
lastQueryMode == WoxQueryModeEnum.WOX_QUERY_MODE_MRU.code) {
|
||||
currentQuery.value = PlainQuery.emptyInput();
|
||||
queryBoxTextFieldController.clear();
|
||||
hideActionPanel(traceId);
|
||||
|
@ -439,6 +449,24 @@ class WoxLauncherController extends GetxController {
|
|||
}
|
||||
}
|
||||
|
||||
Future<void> queryMRU(String traceId) async {
|
||||
clearQueryResults(traceId);
|
||||
|
||||
var queryId = const UuidV4().generate();
|
||||
currentQuery.value = PlainQuery.emptyInput();
|
||||
currentQuery.value.queryId = queryId;
|
||||
|
||||
try {
|
||||
final results = await WoxApi.instance.queryMRU(traceId);
|
||||
for (var result in results) {
|
||||
result.queryId = queryId;
|
||||
}
|
||||
onReceivedQueryResults(traceId, results);
|
||||
} catch (e) {
|
||||
Logger.instance.error(traceId, "Failed to query MRU: $e");
|
||||
}
|
||||
}
|
||||
|
||||
void onQueryChanged(String traceId, PlainQuery query, String changeReason, {bool moveCursorToEnd = false}) {
|
||||
Logger.instance.debug(traceId, "query changed: ${query.queryText}, reason: $changeReason");
|
||||
|
||||
|
@ -469,7 +497,12 @@ class WoxLauncherController extends GetxController {
|
|||
updateResultPreviewWidthRatioOnQueryChanged(traceId, query);
|
||||
updateToolbarOnQueryChanged(traceId, query);
|
||||
if (query.isEmpty) {
|
||||
// Check if we should show MRU results when query is empty
|
||||
if (lastQueryMode == WoxQueryModeEnum.WOX_QUERY_MODE_MRU.code) {
|
||||
queryMRU(traceId);
|
||||
} else {
|
||||
clearQueryResults(traceId);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -854,7 +887,7 @@ class WoxLauncherController extends GetxController {
|
|||
traceId,
|
||||
ShowAppParams(
|
||||
queryHistories: latestQueryHistories,
|
||||
lastQueryMode: lastQueryMode,
|
||||
queryMode: lastQueryMode,
|
||||
selectAll: true,
|
||||
position: Position(
|
||||
type: WoxPositionTypeEnum.POSITION_TYPE_LAST_LOCATION.code,
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import 'package:wox/entity/wox_image.dart';
|
||||
import 'package:wox/entity/wox_list_item.dart';
|
||||
import 'package:wox/entity/wox_preview.dart';
|
||||
import 'package:wox/enums/wox_last_query_mode_enum.dart';
|
||||
import 'package:wox/enums/wox_query_mode_enum.dart';
|
||||
import 'package:wox/enums/wox_position_type_enum.dart';
|
||||
import 'package:wox/enums/wox_query_type_enum.dart';
|
||||
import 'package:wox/enums/wox_selection_type_enum.dart';
|
||||
|
@ -262,10 +262,10 @@ class ShowAppParams {
|
|||
late bool selectAll;
|
||||
late Position position;
|
||||
late List<QueryHistory> queryHistories;
|
||||
late WoxLastQueryMode lastQueryMode;
|
||||
late WoxQueryMode queryMode;
|
||||
late bool autoFocusToChatInput;
|
||||
|
||||
ShowAppParams({required this.selectAll, required this.position, required this.queryHistories, required this.lastQueryMode, this.autoFocusToChatInput = false});
|
||||
ShowAppParams({required this.selectAll, required this.position, required this.queryHistories, required this.queryMode, this.autoFocusToChatInput = false});
|
||||
|
||||
ShowAppParams.fromJson(Map<String, dynamic> json) {
|
||||
selectAll = json['SelectAll'];
|
||||
|
@ -274,13 +274,10 @@ class ShowAppParams {
|
|||
}
|
||||
queryHistories = <QueryHistory>[];
|
||||
if (json['QueryHistories'] != null) {
|
||||
json['QueryHistories'].forEach((v) {
|
||||
queryHistories.add(QueryHistory.fromJson(v));
|
||||
});
|
||||
} else {
|
||||
queryHistories = <QueryHistory>[];
|
||||
final List<dynamic> histories = json['QueryHistories'];
|
||||
queryHistories = histories.map((v) => QueryHistory.fromJson(v)).toList();
|
||||
}
|
||||
lastQueryMode = json['LastQueryMode'];
|
||||
queryMode = json['QueryMode'] ?? 'empty';
|
||||
autoFocusToChatInput = json['AutoFocusToChatInput'] ?? false;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,7 +12,7 @@ class WoxSetting {
|
|||
late String langCode;
|
||||
late List<QueryHotkey> queryHotkeys;
|
||||
late List<QueryShortcut> queryShortcuts;
|
||||
late String lastQueryMode;
|
||||
late String queryMode;
|
||||
late String showPosition;
|
||||
late List<AIProvider> aiProviders;
|
||||
late int appWidth;
|
||||
|
@ -37,7 +37,7 @@ class WoxSetting {
|
|||
required this.langCode,
|
||||
required this.queryHotkeys,
|
||||
required this.queryShortcuts,
|
||||
required this.lastQueryMode,
|
||||
required this.queryMode,
|
||||
required this.showPosition,
|
||||
required this.aiProviders,
|
||||
required this.appWidth,
|
||||
|
@ -81,7 +81,7 @@ class WoxSetting {
|
|||
queryShortcuts = <QueryShortcut>[];
|
||||
}
|
||||
|
||||
lastQueryMode = json['LastQueryMode'];
|
||||
queryMode = json['QueryMode'] ?? 'empty';
|
||||
|
||||
if (json['AIProviders'] != null) {
|
||||
aiProviders = <AIProvider>[];
|
||||
|
@ -116,7 +116,7 @@ class WoxSetting {
|
|||
data['LangCode'] = langCode;
|
||||
data['QueryHotkeys'] = queryHotkeys;
|
||||
data['QueryShortcuts'] = queryShortcuts;
|
||||
data['LastQueryMode'] = lastQueryMode;
|
||||
data['QueryMode'] = queryMode;
|
||||
data['ShowPosition'] = showPosition;
|
||||
data['AIProviders'] = aiProviders;
|
||||
data['AppWidth'] = appWidth;
|
||||
|
|
|
@ -1,13 +0,0 @@
|
|||
typedef WoxLastQueryMode = String;
|
||||
|
||||
enum WoxLastQueryModeEnum {
|
||||
WOX_LAST_QUERY_MODE_PRESERVE("preserve", "preserve"),
|
||||
WOX_LAST_QUERY_MODE_EMPTY("empty", "empty");
|
||||
|
||||
final String code;
|
||||
final String value;
|
||||
|
||||
const WoxLastQueryModeEnum(this.code, this.value);
|
||||
|
||||
static String getValue(String code) => WoxLastQueryModeEnum.values.firstWhere((activity) => activity.code == code).value;
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
typedef WoxQueryMode = String;
|
||||
|
||||
enum WoxQueryModeEnum {
|
||||
WOX_QUERY_MODE_PRESERVE("preserve", "preserve"),
|
||||
WOX_QUERY_MODE_EMPTY("empty", "empty"),
|
||||
WOX_QUERY_MODE_MRU("mru", "mru");
|
||||
|
||||
final String code;
|
||||
final String value;
|
||||
|
||||
const WoxQueryModeEnum(this.code, this.value);
|
||||
|
||||
static String getValue(String code) => WoxQueryModeEnum.values.firstWhere((activity) => activity.code == code).value;
|
||||
}
|
|
@ -9,7 +9,7 @@ import 'package:wox/controllers/wox_launcher_controller.dart';
|
|||
import 'package:wox/entity/setting/wox_plugin_setting_table.dart';
|
||||
import 'package:wox/entity/wox_hotkey.dart';
|
||||
import 'package:wox/entity/wox_lang.dart';
|
||||
import 'package:wox/enums/wox_last_query_mode_enum.dart';
|
||||
import 'package:wox/enums/wox_query_mode_enum.dart';
|
||||
import 'package:wox/modules/setting/views/wox_setting_base.dart';
|
||||
|
||||
class WoxSettingGeneralView extends WoxSettingBaseView {
|
||||
|
@ -115,26 +115,30 @@ class WoxSettingGeneralView extends WoxSettingBaseView {
|
|||
}),
|
||||
),
|
||||
formField(
|
||||
label: controller.tr("ui_last_query_mode"),
|
||||
tips: controller.tr("ui_last_query_mode_tips"),
|
||||
label: controller.tr("ui_query_mode"),
|
||||
tips: controller.tr("ui_query_mode_tips"),
|
||||
child: Obx(() {
|
||||
return SizedBox(
|
||||
width: 250,
|
||||
child: ComboBox<String>(
|
||||
items: [
|
||||
ComboBoxItem(
|
||||
value: WoxLastQueryModeEnum.WOX_LAST_QUERY_MODE_PRESERVE.code,
|
||||
child: Text(controller.tr("ui_last_query_mode_preserve")),
|
||||
value: WoxQueryModeEnum.WOX_QUERY_MODE_PRESERVE.code,
|
||||
child: Text(controller.tr("ui_query_mode_preserve")),
|
||||
),
|
||||
ComboBoxItem(
|
||||
value: WoxLastQueryModeEnum.WOX_LAST_QUERY_MODE_EMPTY.code,
|
||||
child: Text(controller.tr("ui_last_query_mode_empty")),
|
||||
value: WoxQueryModeEnum.WOX_QUERY_MODE_EMPTY.code,
|
||||
child: Text(controller.tr("ui_query_mode_empty")),
|
||||
),
|
||||
ComboBoxItem(
|
||||
value: WoxQueryModeEnum.WOX_QUERY_MODE_MRU.code,
|
||||
child: Text(controller.tr("ui_query_mode_mru")),
|
||||
),
|
||||
],
|
||||
value: controller.woxSetting.value.lastQueryMode,
|
||||
value: controller.woxSetting.value.queryMode,
|
||||
onChanged: (v) {
|
||||
if (v != null) {
|
||||
controller.updateConfig("LastQueryMode", v);
|
||||
controller.updateConfig("QueryMode", v);
|
||||
}
|
||||
},
|
||||
),
|
||||
|
|
Loading…
Reference in New Issue