forked from danielmiessler/fabric
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: plugins arch., new setup procedure
- Loading branch information
Showing
22 changed files
with
1,995 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
package cli | ||
|
||
import ( | ||
"fmt" | ||
"github.com/atotto/clipboard" | ||
"os" | ||
) | ||
|
||
func CopyToClipboard(message string) (err error) { | ||
if err = clipboard.WriteAll(message); err != nil { | ||
err = fmt.Errorf("could not copy to clipboard: %v", err) | ||
} | ||
return | ||
} | ||
|
||
func CreateOutputFile(message string, fileName string) (err error) { | ||
var file *os.File | ||
if file, err = os.Create(fileName); err != nil { | ||
err = fmt.Errorf("error creating file: %v", err) | ||
return | ||
} | ||
defer file.Close() | ||
if _, err = file.WriteString(message); err != nil { | ||
err = fmt.Errorf("error writing to file: %v", err) | ||
} | ||
return | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
package cli | ||
|
||
import ( | ||
"os" | ||
"testing" | ||
) | ||
|
||
func TestCopyToClipboard(t *testing.T) { | ||
t.Skip("skipping test, because of docker env. in ci.") | ||
|
||
message := "test message" | ||
err := CopyToClipboard(message) | ||
if err != nil { | ||
t.Fatalf("CopyToClipboard() error = %v", err) | ||
} | ||
} | ||
|
||
func TestCreateOutputFile(t *testing.T) { | ||
|
||
fileName := "test_output.txt" | ||
message := "test message" | ||
err := CreateOutputFile(message, fileName) | ||
if err != nil { | ||
t.Fatalf("CreateOutputFile() error = %v", err) | ||
} | ||
|
||
defer os.Remove(fileName) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,134 @@ | ||
package common | ||
|
||
import ( | ||
"fmt" | ||
"github.com/samber/lo" | ||
) | ||
|
||
func NewGroupsItemsSelector[I any](selectionLabel string, | ||
getItemLabel func(I) string) *GroupsItemsSelector[I] { | ||
|
||
return &GroupsItemsSelector[I]{SelectionLabel: selectionLabel, | ||
GetItemKey: getItemLabel, | ||
GroupsItems: make([]*GroupItems[I], 0), | ||
} | ||
} | ||
|
||
type GroupItems[I any] struct { | ||
Group string | ||
Items []I | ||
} | ||
|
||
func (o *GroupItems[I]) Count() int { | ||
return len(o.Items) | ||
} | ||
|
||
func (o *GroupItems[I]) ContainsItemBy(predicate func(item I) bool) (ret bool) { | ||
ret = lo.ContainsBy(o.Items, predicate) | ||
return | ||
} | ||
|
||
type GroupsItemsSelector[I any] struct { | ||
SelectionLabel string | ||
GetItemKey func(I) string | ||
|
||
GroupsItems []*GroupItems[I] | ||
} | ||
|
||
func (o *GroupsItemsSelector[I]) AddGroupItems(group string, items ...I) { | ||
o.GroupsItems = append(o.GroupsItems, &GroupItems[I]{group, items}) | ||
} | ||
|
||
func (o *GroupsItemsSelector[I]) GetGroupAndItemByItemNumber(number int) (group string, item I, err error) { | ||
var currentItemNumber int | ||
found := false | ||
|
||
for _, groupItems := range o.GroupsItems { | ||
if currentItemNumber+groupItems.Count() < number { | ||
currentItemNumber += groupItems.Count() | ||
continue | ||
} | ||
|
||
for _, groupItem := range groupItems.Items { | ||
currentItemNumber++ | ||
if currentItemNumber == number { | ||
group = groupItems.Group | ||
item = groupItem | ||
found = true | ||
break | ||
} | ||
} | ||
} | ||
|
||
if !found { | ||
err = fmt.Errorf("number %d is out of range", number) | ||
} | ||
return | ||
} | ||
|
||
func (o *GroupsItemsSelector[I]) Print() { | ||
fmt.Printf("\n%v:\n", o.SelectionLabel) | ||
|
||
var currentItemIndex int | ||
for _, groupItems := range o.GroupsItems { | ||
fmt.Println() | ||
fmt.Printf("%s\n", groupItems.Group) | ||
fmt.Println() | ||
|
||
for _, item := range groupItems.Items { | ||
currentItemIndex++ | ||
fmt.Printf("\t[%d]\t%s\n", currentItemIndex, o.GetItemKey(item)) | ||
|
||
} | ||
} | ||
} | ||
|
||
func (o *GroupsItemsSelector[I]) HasGroup(group string) (ret bool) { | ||
for _, groupItems := range o.GroupsItems { | ||
if ret = groupItems.Group == group; ret { | ||
break | ||
} | ||
} | ||
return | ||
} | ||
|
||
func (o *GroupsItemsSelector[I]) FindGroupsByItemFirst(item I) (ret string) { | ||
itemKey := o.GetItemKey(item) | ||
|
||
for _, groupItems := range o.GroupsItems { | ||
if groupItems.ContainsItemBy(func(groupItem I) bool { | ||
groupItemKey := o.GetItemKey(groupItem) | ||
return groupItemKey == itemKey | ||
}) { | ||
ret = groupItems.Group | ||
break | ||
} | ||
} | ||
return | ||
} | ||
|
||
func (o *GroupsItemsSelector[I]) FindGroupsByItem(item I) (groups []string) { | ||
itemKey := o.GetItemKey(item) | ||
|
||
for _, groupItems := range o.GroupsItems { | ||
if groupItems.ContainsItemBy(func(groupItem I) bool { | ||
groupItemKey := o.GetItemKey(groupItem) | ||
return groupItemKey == itemKey | ||
}) { | ||
groups = append(groups, groupItems.Group) | ||
} | ||
} | ||
return | ||
} | ||
|
||
func ReturnItem(item string) string { | ||
return item | ||
} | ||
|
||
func NewGroupsItemsSelectorString(selectionLabel string) *GroupsItemsSelectorString { | ||
return &GroupsItemsSelectorString{GroupsItemsSelector: NewGroupsItemsSelector(selectionLabel, ReturnItem)} | ||
} | ||
|
||
type GroupsItemsSelectorString struct { | ||
*GroupsItemsSelector[string] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,203 @@ | ||
package core | ||
|
||
import ( | ||
"bytes" | ||
"fmt" | ||
"github.com/danielmiessler/fabric/common" | ||
"github.com/danielmiessler/fabric/plugins/ai/azure" | ||
"github.com/danielmiessler/fabric/plugins/tools" | ||
"github.com/samber/lo" | ||
"strconv" | ||
|
||
"github.com/danielmiessler/fabric/plugins" | ||
"github.com/danielmiessler/fabric/plugins/ai" | ||
"github.com/danielmiessler/fabric/plugins/ai/anthropic" | ||
"github.com/danielmiessler/fabric/plugins/ai/dryrun" | ||
"github.com/danielmiessler/fabric/plugins/ai/gemini" | ||
"github.com/danielmiessler/fabric/plugins/ai/groq" | ||
"github.com/danielmiessler/fabric/plugins/ai/mistral" | ||
"github.com/danielmiessler/fabric/plugins/ai/ollama" | ||
"github.com/danielmiessler/fabric/plugins/ai/openai" | ||
"github.com/danielmiessler/fabric/plugins/ai/openrouter" | ||
"github.com/danielmiessler/fabric/plugins/ai/siliconcloud" | ||
"github.com/danielmiessler/fabric/plugins/db/fsdb" | ||
"github.com/danielmiessler/fabric/plugins/tools/jina" | ||
"github.com/danielmiessler/fabric/plugins/tools/lang" | ||
"github.com/danielmiessler/fabric/plugins/tools/youtube" | ||
) | ||
|
||
func NewPluginRegistry(db *fsdb.Db) (ret *PluginRegistry) { | ||
ret = &PluginRegistry{ | ||
Db: db, | ||
VendorManager: ai.NewVendorsManager(), | ||
VendorsAll: ai.NewVendorsManager(), | ||
PatternsLoader: tools.NewPatternsLoader(db.Patterns), | ||
YouTube: youtube.NewYouTube(), | ||
Language: lang.NewLanguage(), | ||
Jina: jina.NewClient(), | ||
} | ||
|
||
ret.Defaults = tools.NeeDefaults(ret.VendorManager.GetModels) | ||
|
||
ret.VendorsAll.AddVendors(openai.NewClient(), ollama.NewClient(), azure.NewClient(), groq.NewClient(), | ||
gemini.NewClient(), anthropic.NewClient(), siliconcloud.NewClient(), openrouter.NewClient(), mistral.NewClient()) | ||
_ = ret.Configure() | ||
|
||
return | ||
} | ||
|
||
type PluginRegistry struct { | ||
Db *fsdb.Db | ||
|
||
VendorManager *ai.VendorsManager | ||
VendorsAll *ai.VendorsManager | ||
Defaults *tools.Defaults | ||
PatternsLoader *tools.PatternsLoader | ||
YouTube *youtube.YouTube | ||
Language *lang.Language | ||
Jina *jina.Client | ||
} | ||
|
||
func (o *PluginRegistry) SaveEnvFile() (err error) { | ||
// Now create the .env with all configured VendorsController info | ||
var envFileContent bytes.Buffer | ||
|
||
o.Defaults.Settings.FillEnvFileContent(&envFileContent) | ||
o.PatternsLoader.SetupFillEnvFileContent(&envFileContent) | ||
|
||
for _, vendor := range o.VendorManager.Vendors { | ||
vendor.SetupFillEnvFileContent(&envFileContent) | ||
} | ||
|
||
o.YouTube.SetupFillEnvFileContent(&envFileContent) | ||
o.Jina.SetupFillEnvFileContent(&envFileContent) | ||
o.Language.SetupFillEnvFileContent(&envFileContent) | ||
|
||
err = o.Db.SaveEnv(envFileContent.String()) | ||
return | ||
} | ||
|
||
func (o *PluginRegistry) Setup() (err error) { | ||
setupQuestion := plugins.NewSetupQuestion("Enter the number of the plugin to setup") | ||
groupsPlugins := common.NewGroupsItemsSelector[plugins.Plugin]("Available plugins", | ||
func(plugin plugins.Plugin) string { | ||
var configuredLabel string | ||
if plugin.IsConfigured() { | ||
configuredLabel = " (configured)" | ||
} else { | ||
configuredLabel = "" | ||
} | ||
return fmt.Sprintf("%v%v", plugin.GetSetupDescription(), configuredLabel) | ||
}) | ||
|
||
groupsPlugins.AddGroupItems("AI Vendors [at least one, required]", lo.Map(o.VendorsAll.Vendors, | ||
func(vendor ai.Vendor, _ int) plugins.Plugin { | ||
return vendor | ||
})...) | ||
|
||
groupsPlugins.AddGroupItems("Tools", o.Defaults, o.PatternsLoader, o.YouTube, o.Language, o.Jina) | ||
|
||
for { | ||
groupsPlugins.Print() | ||
|
||
if answerErr := setupQuestion.Ask("Plugin Number"); answerErr != nil { | ||
break | ||
} | ||
|
||
if setupQuestion.Value == "" { | ||
break | ||
} | ||
number, parseErr := strconv.Atoi(setupQuestion.Value) | ||
setupQuestion.Value = "" | ||
|
||
if parseErr == nil { | ||
var plugin plugins.Plugin | ||
if _, plugin, err = groupsPlugins.GetGroupAndItemByItemNumber(number); err != nil { | ||
return | ||
} | ||
|
||
if pluginSetupErr := plugin.Setup(); pluginSetupErr != nil { | ||
println(pluginSetupErr.Error()) | ||
} else { | ||
if err = o.SaveEnvFile(); err != nil { | ||
break | ||
} | ||
} | ||
|
||
if _, ok := o.VendorManager.VendorsByName[plugin.GetName()]; !ok { | ||
if vendor, ok := plugin.(ai.Vendor); ok { | ||
o.VendorManager.AddVendors(vendor) | ||
} | ||
} | ||
} else { | ||
break | ||
} | ||
} | ||
|
||
err = o.SaveEnvFile() | ||
|
||
return | ||
} | ||
|
||
func (o *PluginRegistry) SetupVendor(vendorName string) (err error) { | ||
if err = o.VendorsAll.SetupVendor(vendorName, o.VendorManager.VendorsByName); err != nil { | ||
return | ||
} | ||
err = o.SaveEnvFile() | ||
return | ||
} | ||
|
||
// Configure buildClient VendorsController based on the environment variables | ||
func (o *PluginRegistry) Configure() (err error) { | ||
for _, vendor := range o.VendorsAll.Vendors { | ||
if vendorErr := vendor.Configure(); vendorErr == nil { | ||
o.VendorManager.AddVendors(vendor) | ||
} | ||
} | ||
_ = o.Defaults.Configure() | ||
_ = o.PatternsLoader.Configure() | ||
|
||
//YouTube and Jina are not mandatory, so ignore not configured error | ||
_ = o.YouTube.Configure() | ||
_ = o.Jina.Configure() | ||
_ = o.Language.Configure() | ||
return | ||
} | ||
|
||
func (o *PluginRegistry) GetChatter(model string, stream bool, dryRun bool) (ret *Chatter, err error) { | ||
ret = &Chatter{ | ||
db: o.Db, | ||
Stream: stream, | ||
DryRun: dryRun, | ||
} | ||
|
||
defaultModel := o.Defaults.Model.Value | ||
defaultVendor := o.Defaults.Vendor.Value | ||
vendorManager := o.VendorManager | ||
|
||
if dryRun { | ||
ret.vendor = dryrun.NewClient() | ||
ret.model = model | ||
if ret.model == "" { | ||
ret.model = defaultModel | ||
} | ||
} else if model == "" { | ||
ret.vendor = vendorManager.FindByName(defaultVendor) | ||
ret.model = defaultModel | ||
} else { | ||
var models *ai.VendorsModels | ||
if models, err = vendorManager.GetModels(); err != nil { | ||
return | ||
} | ||
ret.vendor = vendorManager.FindByName(models.FindGroupsByItemFirst(model)) | ||
ret.model = model | ||
} | ||
|
||
if ret.vendor == nil { | ||
err = fmt.Errorf( | ||
"could not find vendor.\n Model = %s\n Model = %s\n Vendor = %s", | ||
model, defaultModel, defaultVendor) | ||
return | ||
} | ||
return | ||
} |
Oops, something went wrong.