mirror of https://github.com/Wox-launcher/Wox
237 lines
7.0 KiB
Go
237 lines
7.0 KiB
Go
package plugin
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"path"
|
|
"wox/ai"
|
|
"wox/i18n"
|
|
"wox/setting"
|
|
"wox/share"
|
|
"wox/util"
|
|
|
|
"github.com/disintegration/imaging"
|
|
"github.com/samber/lo"
|
|
)
|
|
|
|
type LogLevel = string
|
|
|
|
const (
|
|
LogLevelInfo LogLevel = "Info"
|
|
LogLevelError LogLevel = "Error"
|
|
LogLevelDebug LogLevel = "Debug"
|
|
LogLevelWarning LogLevel = "Warning"
|
|
)
|
|
|
|
type API interface {
|
|
ChangeQuery(ctx context.Context, query share.PlainQuery)
|
|
HideApp(ctx context.Context)
|
|
ShowApp(ctx context.Context)
|
|
Notify(ctx context.Context, description string)
|
|
Log(ctx context.Context, level LogLevel, msg string)
|
|
GetTranslation(ctx context.Context, key string) string
|
|
GetSetting(ctx context.Context, key string) string
|
|
SaveSetting(ctx context.Context, key string, value string, isPlatformSpecific bool)
|
|
OnSettingChanged(ctx context.Context, callback func(key string, value string))
|
|
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())
|
|
RegisterQueryCommands(ctx context.Context, commands []MetadataCommand)
|
|
AIChatStream(ctx context.Context, model ai.Model, conversations []ai.Conversation, callback ai.ChatStreamFunc) error
|
|
}
|
|
|
|
type APIImpl struct {
|
|
pluginInstance *Instance
|
|
logger *util.Log
|
|
}
|
|
|
|
func (a *APIImpl) ChangeQuery(ctx context.Context, query share.PlainQuery) {
|
|
GetPluginManager().GetUI().ChangeQuery(ctx, query)
|
|
}
|
|
|
|
func (a *APIImpl) HideApp(ctx context.Context) {
|
|
GetPluginManager().GetUI().HideApp(ctx)
|
|
}
|
|
|
|
func (a *APIImpl) ShowApp(ctx context.Context) {
|
|
GetPluginManager().GetUI().ShowApp(ctx, share.ShowContext{
|
|
SelectAll: true,
|
|
})
|
|
}
|
|
|
|
func (a *APIImpl) Notify(ctx context.Context, message string) {
|
|
GetPluginManager().GetUI().Notify(ctx, share.NotifyMsg{
|
|
PluginId: a.pluginInstance.Metadata.Id,
|
|
Text: a.GetTranslation(ctx, message),
|
|
DisplaySeconds: 3,
|
|
})
|
|
}
|
|
|
|
func (a *APIImpl) Log(ctx context.Context, level LogLevel, msg string) {
|
|
logCtx := util.NewComponentContext(ctx, a.pluginInstance.Metadata.Name)
|
|
if level == LogLevelError {
|
|
a.logger.Error(logCtx, msg)
|
|
logger.Error(logCtx, msg)
|
|
return
|
|
}
|
|
|
|
if level == LogLevelInfo {
|
|
a.logger.Info(logCtx, msg)
|
|
logger.Info(logCtx, msg)
|
|
return
|
|
}
|
|
|
|
if level == LogLevelDebug {
|
|
a.logger.Debug(logCtx, msg)
|
|
logger.Debug(logCtx, msg)
|
|
return
|
|
}
|
|
|
|
if level == LogLevelWarning {
|
|
a.logger.Warn(logCtx, msg)
|
|
logger.Warn(logCtx, msg)
|
|
return
|
|
}
|
|
}
|
|
|
|
func (a *APIImpl) GetTranslation(ctx context.Context, key string) string {
|
|
if a.pluginInstance.IsSystemPlugin {
|
|
return i18n.GetI18nManager().TranslateWox(ctx, key)
|
|
} else {
|
|
return i18n.GetI18nManager().TranslatePlugin(ctx, key, a.pluginInstance.PluginDirectory)
|
|
}
|
|
}
|
|
|
|
func (a *APIImpl) GetSetting(ctx context.Context, key string) string {
|
|
// try to get platform specific setting first
|
|
platformSpecificKey := key + "@" + util.GetCurrentPlatform()
|
|
v, exist := a.pluginInstance.Setting.GetSetting(platformSpecificKey)
|
|
if exist {
|
|
return v
|
|
}
|
|
|
|
v, exist = a.pluginInstance.Setting.GetSetting(key)
|
|
if exist {
|
|
return v
|
|
}
|
|
return ""
|
|
}
|
|
|
|
func (a *APIImpl) SaveSetting(ctx context.Context, key string, value string, isPlatformSpecific bool) {
|
|
finalKey := key
|
|
if isPlatformSpecific {
|
|
finalKey = key + "@" + util.GetCurrentPlatform()
|
|
} else {
|
|
// if not platform specific, remove platform specific setting, otherwise it will be loaded first
|
|
a.pluginInstance.Setting.Settings.Delete(key + "@" + util.GetCurrentPlatform())
|
|
}
|
|
|
|
existValue, exist := a.pluginInstance.Setting.Settings.Load(finalKey)
|
|
a.pluginInstance.Setting.Settings.Store(finalKey, value)
|
|
saveErr := a.pluginInstance.SaveSetting(ctx)
|
|
if saveErr != nil {
|
|
a.logger.Error(ctx, fmt.Sprintf("failed to save setting: %s", saveErr.Error()))
|
|
return
|
|
}
|
|
|
|
if !exist || (existValue != value) {
|
|
for _, callback := range a.pluginInstance.SettingChangeCallbacks {
|
|
callback(key, value)
|
|
}
|
|
}
|
|
}
|
|
|
|
func (a *APIImpl) OnSettingChanged(ctx context.Context, callback func(key string, value string)) {
|
|
a.pluginInstance.SettingChangeCallbacks = append(a.pluginInstance.SettingChangeCallbacks, callback)
|
|
}
|
|
|
|
func (a *APIImpl) OnGetDynamicSetting(ctx context.Context, callback func(key string) string) {
|
|
a.pluginInstance.DynamicSettingCallbacks = append(a.pluginInstance.DynamicSettingCallbacks, callback)
|
|
}
|
|
|
|
func (a *APIImpl) OnDeepLink(ctx context.Context, callback func(arguments map[string]string)) {
|
|
if !a.pluginInstance.Metadata.IsSupportFeature(MetadataFeatureDeepLink) {
|
|
a.Log(ctx, LogLevelError, "plugin has no access to deep link feature")
|
|
return
|
|
}
|
|
|
|
a.pluginInstance.DeepLinkCallbacks = append(a.pluginInstance.DeepLinkCallbacks, callback)
|
|
}
|
|
|
|
func (a *APIImpl) OnUnload(ctx context.Context, callback func()) {
|
|
a.pluginInstance.UnloadCallbacks = append(a.pluginInstance.UnloadCallbacks, callback)
|
|
}
|
|
|
|
func (a *APIImpl) RegisterQueryCommands(ctx context.Context, commands []MetadataCommand) {
|
|
a.pluginInstance.Setting.QueryCommands = lo.Map(commands, func(command MetadataCommand, _ int) setting.PluginQueryCommand {
|
|
return setting.PluginQueryCommand{
|
|
Command: command.Command,
|
|
Description: command.Description,
|
|
}
|
|
})
|
|
a.pluginInstance.SaveSetting(ctx)
|
|
}
|
|
|
|
func (a *APIImpl) AIChatStream(ctx context.Context, model ai.Model, conversations []ai.Conversation, callback ai.ChatStreamFunc) error {
|
|
//check if plugin has the feature permission
|
|
if !a.pluginInstance.Metadata.IsSupportFeature(MetadataFeatureAI) {
|
|
return fmt.Errorf("plugin has no access to ai feature")
|
|
}
|
|
|
|
provider, providerErr := GetPluginManager().GetAIProvider(ctx, model.Provider)
|
|
if providerErr != nil {
|
|
return providerErr
|
|
}
|
|
|
|
// resize images in the conversation
|
|
for i, conversation := range conversations {
|
|
for j, image := range conversation.Images {
|
|
// resize image if it's too large
|
|
maxWidth := 600
|
|
if image.Bounds().Dx() > maxWidth {
|
|
start := util.GetSystemTimestamp()
|
|
conversations[i].Images[j] = imaging.Resize(image, maxWidth, 0, imaging.Lanczos)
|
|
a.Log(ctx, LogLevelDebug, fmt.Sprintf("resizing image (%d -> %d) in ai chat, cost %d ms", image.Bounds().Dx(), maxWidth, util.GetSystemTimestamp()-start))
|
|
}
|
|
}
|
|
}
|
|
|
|
stream, err := provider.ChatStream(ctx, model, conversations)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if callback != nil {
|
|
util.Go(ctx, "ai chat stream", func() {
|
|
for {
|
|
util.GetLogger().Info(ctx, fmt.Sprintf("reading chat stream"))
|
|
response, streamErr := stream.Receive(ctx)
|
|
if errors.Is(streamErr, io.EOF) {
|
|
util.GetLogger().Info(ctx, "read stream completed")
|
|
callback(ai.ChatStreamTypeFinished, "")
|
|
return
|
|
}
|
|
|
|
if streamErr != nil {
|
|
util.GetLogger().Info(ctx, fmt.Sprintf("failed to read stream: %s", streamErr.Error()))
|
|
callback(ai.ChatStreamTypeError, streamErr.Error())
|
|
return
|
|
}
|
|
|
|
callback(ai.ChatStreamTypeStreaming, response)
|
|
}
|
|
})
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func NewAPI(instance *Instance) API {
|
|
apiImpl := &APIImpl{pluginInstance: instance}
|
|
logFolder := path.Join(util.GetLocation().GetLogPluginDirectory(), instance.Metadata.Name)
|
|
apiImpl.logger = util.CreateLogger(logFolder)
|
|
return apiImpl
|
|
}
|