Compare commits

...

10 Commits

Author SHA1 Message Date
github-actions[bot]
1b7a1fa652 Update version to v1.4.67 and commit 2024-10-19 11:10:23 +00:00
Eugen Eisler
8a3d63ef48 Merge remote-tracking branch 'origin/main' 2024-10-19 13:10:09 +02:00
Eugen Eisler
609df943dd feat: plugins arch., new setup procedure 2024-10-19 13:09:58 +02:00
github-actions[bot]
8941551f5a Update version to v1.4.66 and commit 2024-10-19 11:09:55 +00:00
Eugen Eisler
61f66f88e3 feat: plugins arch., new setup procedure 2024-10-19 13:09:37 +02:00
github-actions[bot]
15de33107b Update version to v1.4.65 and commit 2024-10-16 13:27:28 +00:00
Eugen Eisler
81f9b1dabb Merge pull request #1045 from Fenicio/patch-1
Update patterns/analyze_answers/system.md - Fixed a bunch of typos
2024-10-16 16:27:14 +03:00
Guillermo G C
888342c119 Update patterns/analyze_answers/system.md - Fixed a bunch of typos 2024-10-15 08:53:26 +02:00
github-actions[bot]
12d83dad6d Update version to v1.4.64 and commit 2024-10-14 18:17:37 +00:00
Jonathan Dunn
14ef9fd41c updated readme 2024-10-14 14:17:19 -04:00
51 changed files with 817 additions and 900 deletions

View File

@@ -217,8 +217,6 @@ Application Options:
-C, --context= Choose a context from the available contexts
--session= Choose a session from the available sessions
-S, --setup Run setup for all reconfigurable parts of fabric
--setup-skip-patterns Run Setup for all reconfigurable parts of fabric except patterns update
--setup-vendor= Run Setup for specific vendor, one of Ollama, OpenAI, Anthropic, Azure, Gemini, Groq, Mistral, OpenRouter, SiliconCloud. E.g. fabric --setup-vendor=OpenAI
-t, --temperature= Set temperature (default: 0.7)
-T, --topp= Set top P (default: 0.9)
-s, --stream Stream

View File

@@ -2,15 +2,15 @@ package cli
import (
"fmt"
"github.com/danielmiessler/fabric/db/fs"
"github.com/danielmiessler/fabric/core"
"github.com/danielmiessler/fabric/plugins/ai"
"github.com/danielmiessler/fabric/plugins/db/fsdb"
"github.com/danielmiessler/fabric/plugins/tools/converter"
"github.com/danielmiessler/fabric/restapi"
"os"
"path/filepath"
"strconv"
"strings"
"github.com/danielmiessler/fabric/core"
)
// Cli Controls the cli. It takes in the flags and runs the appropriate functions
@@ -30,44 +30,35 @@ func Cli(version string) (err error) {
return
}
fabricDb := fs.NewDb(filepath.Join(homedir, ".config/fabric"))
fabricDb := fsdb.NewDb(filepath.Join(homedir, ".config/fabric"))
if err = fabricDb.Configure(); err != nil {
if !currentFlags.Setup {
println(err.Error())
currentFlags.Setup = true
}
}
registry := core.NewPluginRegistry(fabricDb)
// if the setup flag is set, run the setup function
if currentFlags.Setup || currentFlags.SetupSkipPatterns || currentFlags.SetupVendor != "" {
_ = fabricDb.Configure()
if currentFlags.SetupVendor != "" {
_, err = SetupVendor(fabricDb, currentFlags.SetupVendor)
} else {
_, err = Setup(fabricDb, currentFlags.SetupSkipPatterns)
}
if currentFlags.Setup {
err = registry.Setup()
return
}
var fabric *core.Fabric
if err = fabricDb.Configure(); err != nil {
fmt.Println("init is failed, run start the setup procedure", err)
if fabric, err = Setup(fabricDb, currentFlags.SetupSkipPatterns); err != nil {
return
}
} else {
if fabric, err = core.NewFabric(fabricDb); err != nil {
fmt.Println("fabric can't initialize, please run the --setup procedure", err)
return
}
}
if currentFlags.Serve {
err = restapi.Serve(fabricDb, currentFlags.ServeAddress)
err = restapi.Serve(registry, currentFlags.ServeAddress)
return
}
if currentFlags.UpdatePatterns {
err = fabric.PopulateDB()
err = registry.PatternsLoader.PopulateDB()
return
}
if currentFlags.ChangeDefaultModel {
err = fabric.SetupDefaultModel()
err = registry.Defaults.Setup()
return
}
@@ -89,7 +80,11 @@ func Cli(version string) (err error) {
}
if currentFlags.ListAllModels {
fabric.GetModels().Print()
var models *ai.VendorsModels
if models, err = registry.VendorManager.GetModels(); err != nil {
return
}
models.Print()
return
}
@@ -139,27 +134,27 @@ func Cli(version string) (err error) {
// if none of the above currentFlags are set, run the initiate chat function
if currentFlags.YouTube != "" {
if fabric.YouTube.IsConfigured() == false {
if registry.YouTube.IsConfigured() == false {
err = fmt.Errorf("YouTube is not configured, please run the setup procedure")
return
}
var videoId string
if videoId, err = fabric.YouTube.GetVideoId(currentFlags.YouTube); err != nil {
if videoId, err = registry.YouTube.GetVideoId(currentFlags.YouTube); err != nil {
return
}
if !currentFlags.YouTubeComments || currentFlags.YouTubeTranscript {
var transcript string
var language = "en"
if currentFlags.Language != "" || fabric.DefaultLanguage.Value != "" {
if currentFlags.Language != "" || registry.Language.DefaultLanguage.Value != "" {
if currentFlags.Language != "" {
language = currentFlags.Language
} else {
language = fabric.DefaultLanguage.Value
language = registry.Language.DefaultLanguage.Value
}
}
if transcript, err = fabric.YouTube.GrabTranscript(videoId, language); err != nil {
if transcript, err = registry.YouTube.GrabTranscript(videoId, language); err != nil {
return
}
@@ -168,7 +163,7 @@ func Cli(version string) (err error) {
if currentFlags.YouTubeComments {
var comments []string
if comments, err = fabric.YouTube.GrabComments(videoId); err != nil {
if comments, err = registry.YouTube.GrabComments(videoId); err != nil {
return
}
@@ -184,11 +179,11 @@ func Cli(version string) (err error) {
}
}
if (currentFlags.ScrapeURL != "" || currentFlags.ScrapeQuestion != "") && fabric.Jina.IsConfigured() {
if (currentFlags.ScrapeURL != "" || currentFlags.ScrapeQuestion != "") && registry.Jina.IsConfigured() {
// Check if the scrape_url flag is set and call ScrapeURL
if currentFlags.ScrapeURL != "" {
var website string
if website, err = fabric.Jina.ScrapeURL(currentFlags.ScrapeURL); err != nil {
if website, err = registry.Jina.ScrapeURL(currentFlags.ScrapeURL); err != nil {
return
}
@@ -198,7 +193,7 @@ func Cli(version string) (err error) {
// Check if the scrape_question flag is set and call ScrapeQuestion
if currentFlags.ScrapeQuestion != "" {
var website string
if website, err = fabric.Jina.ScrapeQuestion(currentFlags.ScrapeQuestion); err != nil {
if website, err = registry.Jina.ScrapeQuestion(currentFlags.ScrapeQuestion); err != nil {
return
}
@@ -213,14 +208,14 @@ func Cli(version string) (err error) {
}
var chatter *core.Chatter
if chatter, err = fabric.GetChatter(currentFlags.Model, currentFlags.Stream, currentFlags.DryRun); err != nil {
if chatter, err = registry.GetChatter(currentFlags.Model, currentFlags.Stream, currentFlags.DryRun); err != nil {
return
}
var session *fs.Session
var session *fsdb.Session
chatReq := currentFlags.BuildChatRequest(strings.Join(os.Args[1:], " "))
if chatReq.Language == "" {
chatReq.Language = fabric.DefaultLanguage.Value
chatReq.Language = registry.Language.DefaultLanguage.Value
}
if session, err = chatter.Send(chatReq, currentFlags.BuildChatOptions()); err != nil {
return
@@ -235,7 +230,7 @@ func Cli(version string) (err error) {
// if the copy flag is set, copy the message to the clipboard
if currentFlags.Copy {
if err = fabric.CopyToClipboard(result); err != nil {
if err = CopyToClipboard(result); err != nil {
return
}
}
@@ -244,32 +239,10 @@ func Cli(version string) (err error) {
if currentFlags.Output != "" {
if currentFlags.OutputSession {
sessionAsString := session.String()
err = fabric.CreateOutputFile(sessionAsString, currentFlags.Output)
err = CreateOutputFile(sessionAsString, currentFlags.Output)
} else {
err = fabric.CreateOutputFile(result, currentFlags.Output)
err = CreateOutputFile(result, currentFlags.Output)
}
}
return
}
func Setup(db *fs.Db, skipUpdatePatterns bool) (ret *core.Fabric, err error) {
instance := core.NewFabricForSetup(db)
if err = instance.Setup(); err != nil {
return
}
if !skipUpdatePatterns {
if err = instance.PopulateDB(); err != nil {
return
}
}
ret = instance
return
}
func SetupVendor(db *fs.Db, vendorName string) (ret *core.Fabric, err error) {
ret = core.NewFabricForSetup(db)
err = ret.SetupVendor(vendorName)
return
}

View File

@@ -2,7 +2,6 @@ package cli
import (
"github.com/danielmiessler/fabric/core"
"github.com/danielmiessler/fabric/db/fs"
"os"
"testing"
@@ -19,11 +18,3 @@ func TestCli(t *testing.T) {
assert.Error(t, err)
assert.Equal(t, core.NoSessionPatternUserMessages, err.Error())
}
func TestSetup(t *testing.T) {
mockDB := fs.NewDb(os.TempDir())
fabric, err := Setup(mockDB, false)
assert.Error(t, err)
assert.Nil(t, fabric)
}

View File

@@ -19,8 +19,6 @@ type Flags struct {
Context string `short:"C" long:"context" description:"Choose a context from the available contexts" default:""`
Session string `long:"session" description:"Choose a session from the available sessions"`
Setup bool `short:"S" long:"setup" description:"Run setup for all reconfigurable parts of fabric"`
SetupSkipPatterns bool `long:"setup-skip-patterns" description:"Run Setup for all reconfigurable parts of fabric except patterns update"`
SetupVendor string `long:"setup-vendor" description:"Run Setup for specific vendor, one of Ollama, OpenAI, Anthropic, Azure, Gemini, Groq, Mistral, OpenRouter, SiliconCloud. E.g. fabric --setup-vendor=OpenAI"`
Temperature float64 `short:"t" long:"temperature" description:"Set temperature" default:"0.7"`
TopP float64 `short:"T" long:"topp" description:"Set top P" default:"0.9"`
Stream bool `short:"s" long:"stream" description:"Stream"`

27
cli/output.go Normal file
View File

@@ -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
}

28
cli/output_test.go Normal file
View File

@@ -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)
}

134
common/groups_items.go Normal file
View File

@@ -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]
}

View File

@@ -4,14 +4,16 @@ import (
"context"
"fmt"
"github.com/danielmiessler/fabric/common"
"github.com/danielmiessler/fabric/db/fs"
"github.com/danielmiessler/fabric/plugins/ai"
"github.com/danielmiessler/fabric/plugins/db/fsdb"
goopenai "github.com/sashabaranov/go-openai"
"strings"
)
const NoSessionPatternUserMessages = "no session, pattern or user messages provided"
type Chatter struct {
db *fs.Db
db *fsdb.Db
Stream bool
DryRun bool
@@ -20,7 +22,7 @@ type Chatter struct {
vendor ai.Vendor
}
func (o *Chatter) Send(request *common.ChatRequest, opts *common.ChatOptions) (session *fs.Session, err error) {
func (o *Chatter) Send(request *common.ChatRequest, opts *common.ChatOptions) (session *fsdb.Session, err error) {
if session, err = o.BuildSession(request, opts.Raw); err != nil {
return
}
@@ -63,16 +65,16 @@ func (o *Chatter) Send(request *common.ChatRequest, opts *common.ChatOptions) (s
return
}
func (o *Chatter) BuildSession(request *common.ChatRequest, raw bool) (session *fs.Session, err error) {
func (o *Chatter) BuildSession(request *common.ChatRequest, raw bool) (session *fsdb.Session, err error) {
if request.SessionName != "" {
var sess *fs.Session
var sess *fsdb.Session
if sess, err = o.db.Sessions.Get(request.SessionName); err != nil {
err = fmt.Errorf("could not find session %s: %v", request.SessionName, err)
return
}
session = sess
} else {
session = &fs.Session{}
session = &fsdb.Session{}
}
if request.Meta != "" {
@@ -81,7 +83,7 @@ func (o *Chatter) BuildSession(request *common.ChatRequest, raw bool) (session *
var contextContent string
if request.ContextName != "" {
var ctx *fs.Context
var ctx *fsdb.Context
if ctx, err = o.db.Contexts.Get(request.ContextName); err != nil {
err = fmt.Errorf("could not find context %s: %v", request.ContextName, err)
return
@@ -91,7 +93,7 @@ func (o *Chatter) BuildSession(request *common.ChatRequest, raw bool) (session *
var patternContent string
if request.PatternName != "" {
var pattern *fs.Pattern
var pattern *fsdb.Pattern
if pattern, err = o.db.Patterns.GetApplyVariables(request.PatternName, request.PatternVariables); err != nil {
err = fmt.Errorf("could not find pattern %s: %v", request.PatternName, err)
return

View File

@@ -1,265 +0,0 @@
package core
import (
"bytes"
"fmt"
"os"
"strconv"
"github.com/atotto/clipboard"
"github.com/danielmiessler/fabric/common"
"github.com/danielmiessler/fabric/db/fs"
"github.com/danielmiessler/fabric/plugins/ai/anthropic"
"github.com/danielmiessler/fabric/plugins/ai/azure"
"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/tools/jina"
"github.com/danielmiessler/fabric/plugins/tools/lang"
"github.com/danielmiessler/fabric/plugins/tools/youtube"
"github.com/pkg/errors"
)
const DefaultPatternsGitRepoUrl = "https://github.com/danielmiessler/fabric.git"
const DefaultPatternsGitRepoFolder = "patterns"
const NoSessionPatternUserMessages = "no session, pattern or user messages provided"
func NewFabric(db *fs.Db) (ret *Fabric, err error) {
ret = NewFabricBase(db)
err = ret.Configure()
return
}
func NewFabricForSetup(db *fs.Db) (ret *Fabric) {
ret = NewFabricBase(db)
_ = ret.Configure()
return
}
// NewFabricBase Create a new Fabric from a list of already configured VendorsController
func NewFabricBase(db *fs.Db) (ret *Fabric) {
ret = &Fabric{
VendorsManager: NewVendorsManager(),
Db: db,
VendorsAll: NewVendorsManager(),
PatternsLoader: NewPatternsLoader(db.Patterns),
YouTube: youtube.NewYouTube(),
Language: lang.NewLanguage(),
Jina: jina.NewClient(),
}
label := "Default"
ret.Configurable = &common.Configurable{
Label: label,
EnvNamePrefix: common.BuildEnvVariablePrefix(label),
ConfigureCustom: ret.configure,
}
ret.DefaultVendor = ret.AddSetting("Vendor", true)
ret.DefaultModel = ret.AddSetupQuestionCustom("Model", true,
"Enter the index the name of your default model")
ret.VendorsAll.AddVendors(openai.NewClient(), azure.NewClient(), ollama.NewClient(), groq.NewClient(),
gemini.NewClient(), anthropic.NewClient(), siliconcloud.NewClient(), openrouter.NewClient(), mistral.NewClient())
return
}
type Fabric struct {
*common.Configurable
*VendorsManager
VendorsAll *VendorsManager
*PatternsLoader
*youtube.YouTube
*lang.Language
Jina *jina.Client
Db *fs.Db
DefaultVendor *common.Setting
DefaultModel *common.SetupQuestion
}
type ChannelName struct {
channel chan []string
name string
}
func (o *Fabric) SaveEnvFile() (err error) {
// Now create the .env with all configured VendorsController info
var envFileContent bytes.Buffer
o.Settings.FillEnvFileContent(&envFileContent)
o.PatternsLoader.SetupFillEnvFileContent(&envFileContent)
for _, vendor := range o.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 *Fabric) Setup() (err error) {
if err = o.SetupVendors(); err != nil {
return
}
if err = o.SetupDefaultModel(); err != nil {
return
}
_ = o.YouTube.SetupOrSkip()
if err = o.Jina.SetupOrSkip(); err != nil {
return
}
if err = o.PatternsLoader.Setup(); err != nil {
return
}
if err = o.Language.SetupOrSkip(); err != nil {
return
}
err = o.SaveEnvFile()
return
}
func (o *Fabric) SetupDefaultModel() (err error) {
vendorsModels := o.GetModels()
vendorsModels.Print()
if err = o.Ask(o.Label); err != nil {
return
}
index, parseErr := strconv.Atoi(o.DefaultModel.Value)
if parseErr == nil {
o.DefaultVendor.Value, o.DefaultModel.Value = vendorsModels.GetVendorAndModelByModelIndex(index)
} else {
o.DefaultVendor.Value = vendorsModels.FindVendorsByModelFirst(o.DefaultModel.Value)
}
//verify
vendorNames := vendorsModels.FindVendorsByModel(o.DefaultModel.Value)
if len(vendorNames) == 0 {
err = errors.Errorf("You need to chose an available default model.")
return
}
fmt.Println()
o.DefaultVendor.Print()
o.DefaultModel.Print()
err = o.SaveEnvFile()
return
}
func (o *Fabric) SetupVendors() (err error) {
o.Models = nil
if o.Vendors, err = o.VendorsAll.Setup(); err != nil {
return
}
if !o.HasVendors() {
err = errors.New("No vendors configured")
return
}
err = o.SaveEnvFile()
return
}
func (o *Fabric) SetupVendor(vendorName string) (err error) {
if err = o.VendorsAll.SetupVendor(vendorName, o.Vendors); err != nil {
return
}
err = o.SaveEnvFile()
return
}
// Configure buildClient VendorsController based on the environment variables
func (o *Fabric) configure() (err error) {
for _, vendor := range o.VendorsAll.Vendors {
if vendorErr := vendor.Configure(); vendorErr == nil {
o.AddVendors(vendor)
}
}
if err = o.PatternsLoader.Configure(); err != nil {
return
}
//YouTube and Jina are not mandatory, so ignore not configured error
_ = o.YouTube.Configure()
_ = o.Jina.Configure()
_ = o.Language.Configure()
return
}
func (o *Fabric) GetChatter(model string, stream bool, dryRun bool) (ret *Chatter, err error) {
ret = &Chatter{
db: o.Db,
Stream: stream,
DryRun: dryRun,
}
if dryRun {
ret.vendor = dryrun.NewClient()
ret.model = model
if ret.model == "" {
ret.model = o.DefaultModel.Value
}
} else if model == "" {
ret.vendor = o.FindByName(o.DefaultVendor.Value)
ret.model = o.DefaultModel.Value
} else {
ret.vendor = o.FindByName(o.GetModels().FindVendorsByModelFirst(model))
ret.model = model
}
if ret.vendor == nil {
err = fmt.Errorf(
"could not find vendor.\n Model = %s\n DefaultModel = %s\n DefaultVendor = %s",
model, o.DefaultModel.Value, o.DefaultVendor.Value)
return
}
return
}
func (o *Fabric) CopyToClipboard(message string) (err error) {
if err = clipboard.WriteAll(message); err != nil {
err = fmt.Errorf("could not copy to clipboard: %v", err)
}
return
}
func (o *Fabric) 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
}

View File

@@ -1,48 +0,0 @@
package core
import (
"github.com/danielmiessler/fabric/db/fs"
"os"
"testing"
)
func TestNewFabric(t *testing.T) {
_, err := NewFabric(fs.NewDb(os.TempDir()))
if err == nil {
t.Fatal("without setup error expected")
}
}
func TestSaveEnvFile(t *testing.T) {
fabric := NewFabricBase(fs.NewDb(os.TempDir()))
err := fabric.SaveEnvFile()
if err != nil {
t.Fatalf("SaveEnvFile() error = %v", err)
}
}
func TestCopyToClipboard(t *testing.T) {
t.Skip("skipping test, because of docker env. in ci.")
fabric := NewFabricBase(fs.NewDb(os.TempDir()))
message := "test message"
err := fabric.CopyToClipboard(message)
if err != nil {
t.Fatalf("CopyToClipboard() error = %v", err)
}
}
func TestCreateOutputFile(t *testing.T) {
mockDb := &fs.Db{}
fabric := NewFabricBase(mockDb)
fileName := "test_output.txt"
message := "test message"
err := fabric.CreateOutputFile(message, fileName)
if err != nil {
t.Fatalf("CreateOutputFile() error = %v", err)
}
defer os.Remove(fileName)
}

View File

@@ -1,97 +0,0 @@
package core
import (
"fmt"
"sort"
)
func NewVendorsModels() *VendorsModels {
return &VendorsModels{VendorsModels: make(map[string][]string)}
}
type VendorsModels struct {
Vendors []string
VendorsModels map[string][]string
Errs []error
}
func (o *VendorsModels) AddVendorModels(vendor string, models []string) {
o.Vendors = append(o.Vendors, vendor)
o.VendorsModels[vendor] = models
}
func (o *VendorsModels) GetVendorAndModelByModelIndex(modelIndex int) (vendor string, model string) {
vendorModelIndexFrom := 0
vendorModelIndexTo := 0
for _, currenVendor := range o.Vendors {
vendorModelIndexFrom = vendorModelIndexTo + 1
vendorModelIndexTo = vendorModelIndexFrom + len(o.VendorsModels[currenVendor]) - 1
if modelIndex >= vendorModelIndexFrom && modelIndex <= vendorModelIndexTo {
vendor = currenVendor
model = o.VendorsModels[currenVendor][modelIndex-vendorModelIndexFrom]
break
}
}
return
}
func (o *VendorsModels) AddError(err error) {
o.Errs = append(o.Errs, err)
}
func (o *VendorsModels) Print() {
fmt.Printf("\nAvailable vendor models:\n")
sort.Strings(o.Vendors)
var currentModelIndex int
for _, vendor := range o.Vendors {
fmt.Println()
fmt.Printf("%s\n", vendor)
fmt.Println()
currentModelIndex = o.PrintVendor(vendor, currentModelIndex)
}
return
}
func (o *VendorsModels) PrintVendor(vendor string, modelIndex int) (currentModelIndex int) {
currentModelIndex = modelIndex
models := o.VendorsModels[vendor]
for _, model := range models {
currentModelIndex++
fmt.Printf("\t[%d]\t%s\n", currentModelIndex, model)
}
fmt.Println()
return
}
func (o *VendorsModels) GetVendorModels(vendor string) (models []string) {
models = o.VendorsModels[vendor]
return
}
func (o *VendorsModels) HasVendor(vendor string) (ret bool) {
ret = o.VendorsModels[vendor] != nil
return
}
func (o *VendorsModels) FindVendorsByModelFirst(model string) (ret string) {
vendors := o.FindVendorsByModel(model)
if len(vendors) > 0 {
ret = vendors[0]
}
return
}
func (o *VendorsModels) FindVendorsByModel(model string) (vendors []string) {
for vendor, models := range o.VendorsModels {
for _, m := range models {
if m == model {
vendors = append(vendors, vendor)
continue
}
}
}
return
}

View File

@@ -1,52 +0,0 @@
package core
import (
"errors"
"testing"
)
func TestNewVendorsModels(t *testing.T) {
vendors := NewVendorsModels()
if vendors == nil {
t.Fatalf("NewVendorsModels() returned nil")
}
if len(vendors.VendorsModels) != 0 {
t.Fatalf("NewVendorsModels() returned non-empty VendorsModels map")
}
}
func TestFindVendorsByModelFirst(t *testing.T) {
vendors := NewVendorsModels()
vendors.AddVendorModels("vendor1", []string{"model1", "model2"})
vendor := vendors.FindVendorsByModelFirst("model1")
if vendor != "vendor1" {
t.Fatalf("FindVendorsByModelFirst() = %v, want %v", vendor, "vendor1")
}
}
func TestFindVendorsByModel(t *testing.T) {
vendors := NewVendorsModels()
vendors.AddVendorModels("vendor1", []string{"model1", "model2"})
foundVendors := vendors.FindVendorsByModel("model1")
if len(foundVendors) != 1 || foundVendors[0] != "vendor1" {
t.Fatalf("FindVendorsByModel() = %v, want %v", foundVendors, []string{"vendor1"})
}
}
func TestAddVendorModels(t *testing.T) {
vendors := NewVendorsModels()
vendors.AddVendorModels("vendor1", []string{"model1", "model2"})
models := vendors.GetVendorModels("vendor1")
if len(models) != 2 {
t.Fatalf("AddVendorModels() failed to add models")
}
}
func TestAddError(t *testing.T) {
vendors := NewVendorsModels()
err := errors.New("sample error")
vendors.AddError(err)
if len(vendors.Errs) != 1 {
t.Fatalf("AddError() failed to add error")
}
}

203
core/plugin_registry.go Normal file
View File

@@ -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
}

View File

@@ -0,0 +1,16 @@
package core
import (
"github.com/danielmiessler/fabric/plugins/db/fsdb"
"os"
"testing"
)
func TestSaveEnvFile(t *testing.T) {
registry := NewPluginRegistry(fsdb.NewDb(os.TempDir()))
err := registry.SaveEnvFile()
if err != nil {
t.Fatalf("SaveEnvFile() error = %v", err)
}
}

View File

@@ -1,131 +0,0 @@
package core
import (
"bytes"
"context"
"testing"
"github.com/danielmiessler/fabric/common"
)
func TestNewVendorsManager(t *testing.T) {
vendorsManager := NewVendorsManager()
if vendorsManager == nil {
t.Fatalf("NewVendorsManager() returned nil")
}
}
func TestAddVendors(t *testing.T) {
vendorsManager := NewVendorsManager()
mockVendor := &MockVendor{name: "testVendor"}
vendorsManager.AddVendors(mockVendor)
if _, exists := vendorsManager.Vendors[mockVendor.GetName()]; !exists {
t.Fatalf("AddVendors() did not add vendor")
}
}
func TestGetModels(t *testing.T) {
vendorsManager := NewVendorsManager()
mockVendor := &MockVendor{name: "testVendor"}
vendorsManager.AddVendors(mockVendor)
models := vendorsManager.GetModels()
if models == nil {
t.Fatalf("GetModels() returned nil")
}
}
func TestHasVendors(t *testing.T) {
vendorsManager := NewVendorsManager()
if vendorsManager.HasVendors() {
t.Fatalf("HasVendors() should return false for an empty manager")
}
mockVendor := &MockVendor{name: "testVendor"}
vendorsManager.AddVendors(mockVendor)
if !vendorsManager.HasVendors() {
t.Fatalf("HasVendors() should return true after adding a vendor")
}
}
func TestFindByName(t *testing.T) {
vendorsManager := NewVendorsManager()
mockVendor := &MockVendor{name: "testVendor"}
vendorsManager.AddVendors(mockVendor)
foundVendor := vendorsManager.FindByName("testVendor")
if foundVendor == nil {
t.Fatalf("FindByName() did not find added vendor")
}
}
func TestReadModels(t *testing.T) {
vendorsManager := NewVendorsManager()
mockVendor := &MockVendor{name: "testVendor"}
vendorsManager.AddVendors(mockVendor)
vendorsManager.readModels()
if vendorsManager.Models == nil || len(vendorsManager.Models.Vendors) == 0 {
t.Fatalf("readModels() did not read models correctly")
}
}
func TestSetup(t *testing.T) {
vendorsManager := NewVendorsManager()
mockVendor := &MockVendor{name: "testVendor"}
vendorsManager.AddVendors(mockVendor)
vendors, err := vendorsManager.Setup()
if err != nil {
t.Fatalf("Setup() error = %v", err)
}
if len(vendors) == 0 {
t.Fatalf("Setup() did not setup any vendors")
}
}
// MockVendor is a mock implementation of the Vendor interface for testing purposes.
type MockVendor struct {
*common.Settings
name string
}
func (o *MockVendor) SendStream(messages []*common.Message, options *common.ChatOptions, strings chan string) error {
// TODO implement me
panic("implement me")
}
func (o *MockVendor) Send(ctx context.Context, messages []*common.Message, options *common.ChatOptions) (string, error) {
// TODO implement me
panic("implement me")
}
func (o *MockVendor) SetupFillEnvFileContent(buffer *bytes.Buffer) {
// TODO implement me
panic("implement me")
}
func (o *MockVendor) IsConfigured() bool {
return false
}
func (o *MockVendor) GetSettings() *common.Settings {
return o.Settings
}
func (o *MockVendor) GetName() string {
return o.name
}
func (o *MockVendor) Configure() error {
return nil
}
func (o *MockVendor) Setup() error {
return nil
}
func (o *MockVendor) ListModels() ([]string, error) {
return []string{"model1", "model2"}, nil
}

View File

@@ -1 +0,0 @@
package fs

View File

@@ -1,41 +0,0 @@
package lang
import (
"github.com/danielmiessler/fabric/common"
"golang.org/x/text/language"
)
func NewLanguage() (ret *Language) {
label := "Language"
ret = &Language{}
ret.Configurable = &common.Configurable{
Label: label,
EnvNamePrefix: common.BuildEnvVariablePrefix(label),
ConfigureCustom: ret.configure,
}
ret.DefaultLanguage = ret.Configurable.AddSetupQuestionCustom("Output", false,
"Enter your default want output lang (for example: zh_CN)")
return
}
type Language struct {
*common.Configurable
DefaultLanguage *common.SetupQuestion
}
func (o *Language) configure() error {
if o.DefaultLanguage.Value != "" {
langTag, err := language.Parse(o.DefaultLanguage.Value)
if err == nil {
o.DefaultLanguage.Value = langTag.String()
} else {
o.DefaultLanguage.Value = ""
}
}
return nil
}

View File

@@ -4,6 +4,8 @@ import (
"context"
"errors"
"fmt"
"github.com/danielmiessler/fabric/plugins"
goopenai "github.com/sashabaranov/go-openai"
"github.com/danielmiessler/fabric/common"
@@ -16,15 +18,15 @@ func NewClient() (ret *Client) {
vendorName := "Anthropic"
ret = &Client{}
ret.Configurable = &common.Configurable{
Label: vendorName,
EnvNamePrefix: common.BuildEnvVariablePrefix(vendorName),
ret.PluginBase = &plugins.PluginBase{
Name: vendorName,
EnvNamePrefix: plugins.BuildEnvVariablePrefix(vendorName),
ConfigureCustom: ret.configure,
}
ret.ApiBaseURL = ret.AddSetupQuestion("API Base URL", false)
ret.ApiBaseURL.Value = baseUrl
ret.ApiKey = ret.Configurable.AddSetupQuestion("API key", true)
ret.ApiKey = ret.PluginBase.AddSetupQuestion("API key", true)
// we could provide a setup question for the following settings
ret.maxTokens = 4096
@@ -39,9 +41,9 @@ func NewClient() (ret *Client) {
}
type Client struct {
*common.Configurable
ApiBaseURL *common.SetupQuestion
ApiKey *common.SetupQuestion
*plugins.PluginBase
ApiBaseURL *plugins.SetupQuestion
ApiKey *plugins.SetupQuestion
maxTokens int
defaultRequiredUserMessage string

View File

@@ -1,10 +1,10 @@
package azure
import (
"github.com/danielmiessler/fabric/plugins"
"github.com/danielmiessler/fabric/plugins/ai/openai"
"strings"
"github.com/danielmiessler/fabric/common"
goopenai "github.com/sashabaranov/go-openai"
)
@@ -19,7 +19,7 @@ func NewClient() (ret *Client) {
type Client struct {
*openai.Client
ApiDeployments *common.SetupQuestion
ApiDeployments *plugins.SetupQuestion
apiDeployments []string
}

View File

@@ -4,27 +4,18 @@ import (
"bytes"
"context"
"fmt"
"github.com/danielmiessler/fabric/plugins"
goopenai "github.com/sashabaranov/go-openai"
"github.com/danielmiessler/fabric/common"
)
type Client struct{}
type Client struct {
*plugins.PluginBase
}
func NewClient() *Client {
return &Client{}
}
func (c *Client) GetName() string {
return "DryRun"
}
func (c *Client) IsConfigured() bool {
return true
}
func (c *Client) Configure() error {
return nil
return &Client{PluginBase: &plugins.PluginBase{Name: "DryRun"}}
}
func (c *Client) ListModels() ([]string, error) {

View File

@@ -4,6 +4,7 @@ import (
"context"
"errors"
"fmt"
"github.com/danielmiessler/fabric/plugins"
"strings"
"github.com/danielmiessler/fabric/common"
@@ -18,19 +19,19 @@ func NewClient() (ret *Client) {
vendorName := "Gemini"
ret = &Client{}
ret.Configurable = &common.Configurable{
Label: vendorName,
EnvNamePrefix: common.BuildEnvVariablePrefix(vendorName),
ret.PluginBase = &plugins.PluginBase{
Name: vendorName,
EnvNamePrefix: plugins.BuildEnvVariablePrefix(vendorName),
}
ret.ApiKey = ret.Configurable.AddSetupQuestion("API key", true)
ret.ApiKey = ret.PluginBase.AddSetupQuestion("API key", true)
return
}
type Client struct {
*common.Configurable
ApiKey *common.SetupQuestion
*plugins.PluginBase
ApiKey *plugins.SetupQuestion
}
func (o *Client) ListModels() (ret []string, err error) {

13
plugins/ai/models.go Normal file
View File

@@ -0,0 +1,13 @@
package ai
import (
"github.com/danielmiessler/fabric/common"
)
func NewVendorsModels() *VendorsModels {
return &VendorsModels{GroupsItemsSelectorString: common.NewGroupsItemsSelectorString("Available models")}
}
type VendorsModels struct {
*common.GroupsItemsSelectorString
}

33
plugins/ai/models_test.go Normal file
View File

@@ -0,0 +1,33 @@
package ai
import (
"testing"
)
func TestNewVendorsModels(t *testing.T) {
vendors := NewVendorsModels()
if vendors == nil {
t.Fatalf("NewVendorsModels() returned nil")
}
if len(vendors.GroupsItems) != 0 {
t.Fatalf("NewVendorsModels() returned non-empty VendorsModels map")
}
}
func TestFindVendorsByModelFirst(t *testing.T) {
vendors := NewVendorsModels()
vendors.AddGroupItems("vendor1", []string{"model1", "model2"}...)
vendor := vendors.FindGroupsByItemFirst("model1")
if vendor != "vendor1" {
t.Fatalf("FindVendorsByModelFirst() = %v, want %v", vendor, "vendor1")
}
}
func TestFindVendorsByModel(t *testing.T) {
vendors := NewVendorsModels()
vendors.AddGroupItems("vendor1", []string{"model1", "model2"}...)
foundVendors := vendors.FindGroupsByItem("model1")
if len(foundVendors) != 1 || foundVendors[0] != "vendor1" {
t.Fatalf("FindVendorsByModel() = %v, want %v", foundVendors, []string{"vendor1"})
}
}

View File

@@ -3,6 +3,7 @@ package ollama
import (
"context"
"fmt"
"github.com/danielmiessler/fabric/plugins"
"net/http"
"net/url"
"time"
@@ -17,21 +18,21 @@ func NewClient() (ret *Client) {
vendorName := "Ollama"
ret = &Client{}
ret.Configurable = &common.Configurable{
Label: vendorName,
EnvNamePrefix: common.BuildEnvVariablePrefix(vendorName),
ret.PluginBase = &plugins.PluginBase{
Name: vendorName,
EnvNamePrefix: plugins.BuildEnvVariablePrefix(vendorName),
ConfigureCustom: ret.configure,
}
ret.ApiUrl = ret.Configurable.AddSetupQuestionCustom("API URL", true,
ret.ApiUrl = ret.PluginBase.AddSetupQuestionCustom("API URL", true,
"Enter your Ollama URL (as a reminder, it is usually http://localhost:11434)")
return
}
type Client struct {
*common.Configurable
ApiUrl *common.SetupQuestion
*plugins.PluginBase
ApiUrl *plugins.SetupQuestion
apiUrl *url.URL
client *ollamaapi.Client

View File

@@ -4,6 +4,7 @@ import (
"context"
"errors"
"fmt"
"github.com/danielmiessler/fabric/plugins"
"io"
"log/slog"
@@ -24,9 +25,9 @@ func NewClientCompatible(vendorName string, defaultBaseUrl string, configureCust
configureCustom = ret.configure
}
ret.Configurable = &common.Configurable{
Label: vendorName,
EnvNamePrefix: common.BuildEnvVariablePrefix(vendorName),
ret.PluginBase = &plugins.PluginBase{
Name: vendorName,
EnvNamePrefix: plugins.BuildEnvVariablePrefix(vendorName),
ConfigureCustom: configureCustom,
}
@@ -38,9 +39,9 @@ func NewClientCompatible(vendorName string, defaultBaseUrl string, configureCust
}
type Client struct {
*common.Configurable
ApiKey *common.SetupQuestion
ApiBaseURL *common.SetupQuestion
*plugins.PluginBase
ApiKey *plugins.SetupQuestion
ApiBaseURL *plugins.SetupQuestion
ApiClient *openai.Client
}

View File

@@ -1,19 +1,15 @@
package ai
import (
"bytes"
"context"
"github.com/danielmiessler/fabric/plugins"
"github.com/danielmiessler/fabric/common"
)
type Vendor interface {
GetName() string
IsConfigured() bool
Configure() error
plugins.Plugin
ListModels() ([]string, error)
SendStream([]*common.Message, *common.ChatOptions, chan string) error
Send(context.Context, []*common.Message, *common.ChatOptions) (string, error)
Setup() error
SetupFillEnvFileContent(*bytes.Buffer)
}

View File

@@ -1,45 +1,70 @@
package core
package ai
import (
"bytes"
"context"
"fmt"
"github.com/danielmiessler/fabric/plugins/ai"
"github.com/danielmiessler/fabric/plugins"
"sync"
)
func NewVendorsManager() *VendorsManager {
return &VendorsManager{
Vendors: map[string]ai.Vendor{},
Vendors: []Vendor{},
VendorsByName: map[string]Vendor{},
}
}
type VendorsManager struct {
Vendors map[string]ai.Vendor
Models *VendorsModels
*plugins.PluginBase
Vendors []Vendor
VendorsByName map[string]Vendor
Models *VendorsModels
}
func (o *VendorsManager) AddVendors(vendors ...ai.Vendor) {
func (o *VendorsManager) AddVendors(vendors ...Vendor) {
for _, vendor := range vendors {
o.Vendors[vendor.GetName()] = vendor
o.VendorsByName[vendor.GetName()] = vendor
o.Vendors = append(o.Vendors, vendor)
}
}
func (o *VendorsManager) GetModels() *VendorsModels {
if o.Models == nil {
o.readModels()
func (o *VendorsManager) SetupFillEnvFileContent(envFileContent *bytes.Buffer) {
for _, vendor := range o.Vendors {
vendor.SetupFillEnvFileContent(envFileContent)
}
return o.Models
}
func (o *VendorsManager) GetModels() (ret *VendorsModels, err error) {
if o.Models == nil {
err = o.readModels()
}
ret = o.Models
return
}
func (o *VendorsManager) Configure() (err error) {
for _, vendor := range o.Vendors {
_ = vendor.Configure()
}
return
}
func (o *VendorsManager) HasVendors() bool {
return len(o.Vendors) > 0
}
func (o *VendorsManager) FindByName(name string) ai.Vendor {
return o.Vendors[name]
func (o *VendorsManager) FindByName(name string) Vendor {
return o.VendorsByName[name]
}
func (o *VendorsManager) readModels() {
func (o *VendorsManager) readModels() (err error) {
if len(o.Vendors) == 0 {
err = fmt.Errorf("no AI vendors configured to read models from. Please configure at least one AI vendor")
return
}
o.Models = NewVendorsModels()
var wg sync.WaitGroup
@@ -62,16 +87,16 @@ func (o *VendorsManager) readModels() {
for result := range resultsChan {
if result.err != nil {
fmt.Println(result.vendorName, result.err)
o.Models.AddError(result.err)
cancel() // Cancel remaining goroutines if needed
} else {
o.Models.AddVendorModels(result.vendorName, result.models)
o.Models.AddGroupItems(result.vendorName, result.models...)
}
}
return
}
func (o *VendorsManager) fetchVendorModels(
ctx context.Context, wg *sync.WaitGroup, vendor ai.Vendor, resultsChan chan<- modelResult) {
ctx context.Context, wg *sync.WaitGroup, vendor Vendor, resultsChan chan<- modelResult) {
defer wg.Done()
@@ -85,8 +110,8 @@ func (o *VendorsManager) fetchVendorModels(
}
}
func (o *VendorsManager) Setup() (ret map[string]ai.Vendor, err error) {
ret = map[string]ai.Vendor{}
func (o *VendorsManager) Setup() (ret map[string]Vendor, err error) {
ret = map[string]Vendor{}
for _, vendor := range o.Vendors {
fmt.Println()
o.setupVendorTo(vendor, ret)
@@ -94,7 +119,17 @@ func (o *VendorsManager) Setup() (ret map[string]ai.Vendor, err error) {
return
}
func (o *VendorsManager) setupVendorTo(vendor ai.Vendor, configuredVendors map[string]ai.Vendor) {
func (o *VendorsManager) SetupVendor(vendorName string, configuredVendors map[string]Vendor) (err error) {
vendor := o.FindByName(vendorName)
if vendor == nil {
err = fmt.Errorf("vendor %s not found", vendorName)
return
}
o.setupVendorTo(vendor, configuredVendors)
return
}
func (o *VendorsManager) setupVendorTo(vendor Vendor, configuredVendors map[string]Vendor) {
if vendorErr := vendor.Setup(); vendorErr == nil {
fmt.Printf("[%v] configured\n", vendor.GetName())
configuredVendors[vendor.GetName()] = vendor
@@ -105,16 +140,6 @@ func (o *VendorsManager) setupVendorTo(vendor ai.Vendor, configuredVendors map[s
return
}
func (o *VendorsManager) SetupVendor(vendorName string, configuredVendors map[string]ai.Vendor) (err error) {
vendor := o.FindByName(vendorName)
if vendor == nil {
err = fmt.Errorf("vendor %s not found", vendorName)
return
}
o.setupVendorTo(vendor, configuredVendors)
return
}
type modelResult struct {
vendorName string
models []string

View File

@@ -1,4 +1,4 @@
package fs
package fsdb
import "fmt"

View File

@@ -1,4 +1,4 @@
package fs
package fsdb
import (
"os"

View File

@@ -1,4 +1,4 @@
package fs
package fsdb
import (
"fmt"

View File

@@ -1,4 +1,4 @@
package fs
package fsdb
import (
"os"

View File

@@ -1,4 +1,4 @@
package fs
package fsdb
import (
"fmt"

View File

@@ -0,0 +1 @@
package fsdb

View File

@@ -1,4 +1,4 @@
package fs
package fsdb
import (
"fmt"

View File

@@ -1,4 +1,4 @@
package fs
package fsdb
import (
"testing"

View File

@@ -1,4 +1,4 @@
package fs
package fsdb
import (
"encoding/json"

View File

@@ -1,4 +1,4 @@
package fs
package fsdb
import (
"testing"

View File

@@ -1,4 +1,4 @@
package common
package plugins
import (
"bytes"
@@ -9,41 +9,58 @@ import (
const AnswerReset = "reset"
type Configurable struct {
type Plugin interface {
GetName() string
GetSetupDescription() string
IsConfigured() bool
Configure() error
Setup() error
SetupFillEnvFileContent(*bytes.Buffer)
}
type PluginBase struct {
Settings
SetupQuestions
Label string
EnvNamePrefix string
Name string
SetupDescription string
EnvNamePrefix string
ConfigureCustom func() error
}
func (o *Configurable) GetName() string {
return o.Label
func (o *PluginBase) GetName() string {
return o.Name
}
func (o *Configurable) AddSetting(name string, required bool) (ret *Setting) {
func (o *PluginBase) GetSetupDescription() (ret string) {
if ret = o.SetupDescription; ret == "" {
ret = o.GetName()
}
return
}
func (o *PluginBase) AddSetting(name string, required bool) (ret *Setting) {
ret = NewSetting(fmt.Sprintf("%v%v", o.EnvNamePrefix, BuildEnvVariable(name)), required)
o.Settings = append(o.Settings, ret)
return
}
func (o *Configurable) AddSetupQuestion(name string, required bool) (ret *SetupQuestion) {
func (o *PluginBase) AddSetupQuestion(name string, required bool) (ret *SetupQuestion) {
return o.AddSetupQuestionCustom(name, required, "")
}
func (o *Configurable) AddSetupQuestionCustom(name string, required bool, question string) (ret *SetupQuestion) {
func (o *PluginBase) AddSetupQuestionCustom(name string, required bool, question string) (ret *SetupQuestion) {
setting := o.AddSetting(name, required)
ret = &SetupQuestion{Setting: setting, Question: question}
if ret.Question == "" {
ret.Question = fmt.Sprintf("Enter your %v %v", o.Label, strings.ToUpper(name))
ret.Question = fmt.Sprintf("Enter your %v %v", o.Name, strings.ToUpper(name))
}
o.SetupQuestions = append(o.SetupQuestions, ret)
return
}
func (o *Configurable) Configure() (err error) {
func (o *PluginBase) Configure() (err error) {
if err = o.Settings.Configure(); err != nil {
return
}
@@ -54,8 +71,8 @@ func (o *Configurable) Configure() (err error) {
return
}
func (o *Configurable) Setup() (err error) {
if err = o.Ask(o.Label); err != nil {
func (o *PluginBase) Setup() (err error) {
if err = o.Ask(o.Name); err != nil {
return
}
@@ -63,14 +80,14 @@ func (o *Configurable) Setup() (err error) {
return
}
func (o *Configurable) SetupOrSkip() (err error) {
func (o *PluginBase) SetupOrSkip() (err error) {
if err = o.Setup(); err != nil {
fmt.Printf("[%v] skipped\n", o.GetName())
}
return
}
func (o *Configurable) SetupFillEnvFileContent(fileEnvFileContent *bytes.Buffer) {
func (o *PluginBase) SetupFillEnvFileContent(fileEnvFileContent *bytes.Buffer) {
o.Settings.FillEnvFileContent(fileEnvFileContent)
}
@@ -126,6 +143,10 @@ func (o *Setting) Print() {
fmt.Printf("%v: %v\n", o.EnvVariable, o.Value)
}
func NewSetupQuestion(question string) *SetupQuestion {
return &SetupQuestion{Setting: &Setting{}, Question: question}
}
type SetupQuestion struct {
*Setting
Question string

View File

@@ -1,4 +1,4 @@
package common
package plugins
import (
"bytes"
@@ -9,9 +9,9 @@ import (
)
func TestConfigurable_AddSetting(t *testing.T) {
conf := &Configurable{
conf := &PluginBase{
Settings: Settings{},
Label: "TestConfigurable",
Name: "TestConfigurable",
EnvNamePrefix: "TEST_",
}
@@ -26,9 +26,9 @@ func TestConfigurable_Configure(t *testing.T) {
EnvVariable: "TEST_SETTING",
Required: true,
}
conf := &Configurable{
conf := &PluginBase{
Settings: Settings{setting},
Label: "TestConfigurable",
Name: "TestConfigurable",
}
_ = os.Setenv("TEST_SETTING", "test_value")
@@ -42,9 +42,9 @@ func TestConfigurable_Setup(t *testing.T) {
EnvVariable: "TEST_SETTING",
Required: false,
}
conf := &Configurable{
conf := &PluginBase{
Settings: Settings{setting},
Label: "TestConfigurable",
Name: "TestConfigurable",
}
err := conf.Setup()

71
plugins/tools/defaults.go Normal file
View File

@@ -0,0 +1,71 @@
package tools
import (
"fmt"
"strconv"
"github.com/danielmiessler/fabric/plugins"
"github.com/danielmiessler/fabric/plugins/ai"
"github.com/pkg/errors"
)
func NeeDefaults(getVendorsModels func() (*ai.VendorsModels, error)) (ret *Defaults) {
vendorName := "Default"
ret = &Defaults{
PluginBase: &plugins.PluginBase{
Name: vendorName,
SetupDescription: "Default AI Vendor and Model [required]",
EnvNamePrefix: plugins.BuildEnvVariablePrefix(vendorName),
},
GetVendorsModels: getVendorsModels,
}
ret.Vendor = ret.AddSetting("Vendor", true)
ret.Model = ret.AddSetupQuestionCustom("Model", true,
"Enter the index the name of your default model")
return
}
type Defaults struct {
*plugins.PluginBase
Vendor *plugins.Setting
Model *plugins.SetupQuestion
GetVendorsModels func() (*ai.VendorsModels, error)
}
func (o *Defaults) Setup() (err error) {
var vendorsModels *ai.VendorsModels
if vendorsModels, err = o.GetVendorsModels(); err != nil {
return
}
vendorsModels.Print()
if err = o.Ask(o.Name); err != nil {
return
}
index, parseErr := strconv.Atoi(o.Model.Value)
if parseErr == nil {
if o.Vendor.Value, o.Model.Value, err = vendorsModels.GetGroupAndItemByItemNumber(index); err != nil {
return
}
} else {
o.Vendor.Value = vendorsModels.FindGroupsByItemFirst(o.Model.Value)
}
//verify
vendorNames := vendorsModels.FindGroupsByItem(o.Model.Value)
if len(vendorNames) == 0 {
err = errors.Errorf("You need to chose an available default model.")
return
}
fmt.Println()
o.Vendor.Print()
o.Model.Print()
return
}

View File

@@ -7,12 +7,12 @@ import (
"io"
"net/http"
"github.com/danielmiessler/fabric/common"
"github.com/danielmiessler/fabric/plugins"
)
type Client struct {
*common.Configurable
ApiKey *common.SetupQuestion
*plugins.PluginBase
ApiKey *plugins.SetupQuestion
}
func NewClient() (ret *Client) {
@@ -20,9 +20,10 @@ func NewClient() (ret *Client) {
label := "Jina AI"
ret = &Client{
Configurable: &common.Configurable{
Label: label,
EnvNamePrefix: common.BuildEnvVariablePrefix(label),
PluginBase: &plugins.PluginBase{
Name: label,
SetupDescription: "Jina AI Service - to grab a webpage as clean, LLM-friendly text",
EnvNamePrefix: plugins.BuildEnvVariablePrefix(label),
},
}

View File

@@ -1,7 +1,7 @@
package lang
import (
"github.com/danielmiessler/fabric/common"
"github.com/danielmiessler/fabric/plugins"
"golang.org/x/text/language"
)
@@ -10,21 +10,22 @@ func NewLanguage() (ret *Language) {
label := "Language"
ret = &Language{}
ret.Configurable = &common.Configurable{
Label: label,
EnvNamePrefix: common.BuildEnvVariablePrefix(label),
ConfigureCustom: ret.configure,
ret.PluginBase = &plugins.PluginBase{
Name: label,
SetupDescription: "Language - Default AI Vendor Output Language",
EnvNamePrefix: plugins.BuildEnvVariablePrefix(label),
ConfigureCustom: ret.configure,
}
ret.DefaultLanguage = ret.Configurable.AddSetupQuestionCustom("Output", false,
"Enter your default want output lang (for example: zh_CN)")
ret.DefaultLanguage = ret.PluginBase.AddSetupQuestionCustom("Output", false,
"Enter your default output language (for example: zh_CN)")
return
}
type Language struct {
*common.Configurable
DefaultLanguage *common.SetupQuestion
*plugins.PluginBase
DefaultLanguage *plugins.SetupQuestion
}
func (o *Language) configure() error {

View File

@@ -1,15 +1,16 @@
package core
package tools
import (
"fmt"
"github.com/danielmiessler/fabric/db/fs"
"io"
"os"
"path/filepath"
"sort"
"strings"
"github.com/danielmiessler/fabric/common"
"github.com/danielmiessler/fabric/plugins"
"github.com/danielmiessler/fabric/plugins/db/fsdb"
"github.com/go-git/go-git/v5"
"github.com/go-git/go-git/v5/plumbing"
"github.com/go-git/go-git/v5/plumbing/object"
@@ -17,16 +18,21 @@ import (
"github.com/otiai10/copy"
)
func NewPatternsLoader(patterns *fs.PatternsEntity) (ret *PatternsLoader) {
const DefaultPatternsGitRepoUrl = "https://github.com/danielmiessler/fabric.git"
const DefaultPatternsGitRepoFolder = "patterns"
func NewPatternsLoader(patterns *fsdb.PatternsEntity) (ret *PatternsLoader) {
label := "Patterns Loader"
ret = &PatternsLoader{
Patterns: patterns,
Patterns: patterns,
loadedFilePath: patterns.BuildFilePath("loaded"),
}
ret.Configurable = &common.Configurable{
Label: label,
EnvNamePrefix: common.BuildEnvVariablePrefix(label),
ConfigureCustom: ret.configure,
ret.PluginBase = &plugins.PluginBase{
Name: label,
SetupDescription: "Patterns - Downloads patterns [required]",
EnvNamePrefix: plugins.BuildEnvVariablePrefix(label),
ConfigureCustom: ret.configure,
}
ret.DefaultGitRepoUrl = ret.AddSetupQuestionCustom("Git Repo Url", true,
@@ -41,11 +47,13 @@ func NewPatternsLoader(patterns *fs.PatternsEntity) (ret *PatternsLoader) {
}
type PatternsLoader struct {
*common.Configurable
Patterns *fs.PatternsEntity
*plugins.PluginBase
Patterns *fsdb.PatternsEntity
DefaultGitRepoUrl *common.SetupQuestion
DefaultFolder *common.SetupQuestion
DefaultGitRepoUrl *plugins.SetupQuestion
DefaultFolder *plugins.SetupQuestion
loadedFilePath string
pathPatternsPrefix string
tempPatternsFolder string
@@ -58,6 +66,27 @@ func (o *PatternsLoader) configure() (err error) {
return
}
func (o *PatternsLoader) IsConfigured() (ret bool) {
ret = o.PluginBase.IsConfigured()
if ret {
if _, err := os.Stat(o.loadedFilePath); os.IsNotExist(err) {
ret = false
}
}
return
}
func (o *PatternsLoader) Setup() (err error) {
if err = o.PluginBase.Setup(); err != nil {
return
}
if err = o.PopulateDB(); err != nil {
return
}
return
}
// PopulateDB downloads patterns from the internet and populates the patterns folder
func (o *PatternsLoader) PopulateDB() (err error) {
fmt.Printf("Downloading patterns and Populating %s..\n", o.Patterns.Dir)
@@ -110,22 +139,14 @@ func (o *PatternsLoader) movePatterns() (err error) {
if err = copy.Copy(patternsDir, o.Patterns.Dir); err != nil { // copies the patterns to the config directory
return
}
//create an empty file to indicate that the patterns have been updated if not exists
_, _ = os.Create(o.loadedFilePath)
err = os.RemoveAll(patternsDir)
return
}
// checks if a pattern already exists in the directory
// func DoesPatternExistAlready(name string) (bool, error) {
// entry := db.Entry{
// Label: name,
// }
// _, err := entry.GetByName()
// if err != nil {
// return false, err
// }
// return true, nil
// }
func (o *PatternsLoader) gitCloneAndCopy() (err error) {
// Clones the given repository, creating the remote, the local branches
// and fetching the objects, everything in memory:
@@ -156,7 +177,7 @@ func (o *PatternsLoader) gitCloneAndCopy() (err error) {
return err
}
var changes []fs.DirectoryChange
var changes []fsdb.DirectoryChange
// ... iterates over the commits
if err = cIter.ForEach(func(c *object.Commit) (err error) {
// GetApplyVariables the files changed in this commit by comparing with its parents
@@ -171,7 +192,7 @@ func (o *PatternsLoader) gitCloneAndCopy() (err error) {
for _, fileStat := range patch.Stats() {
if strings.HasPrefix(fileStat.Name, o.pathPatternsPrefix) {
dir := filepath.Dir(fileStat.Name)
changes = append(changes, fs.DirectoryChange{Dir: dir, Timestamp: c.Committer.When})
changes = append(changes, fsdb.DirectoryChange{Dir: dir, Timestamp: c.Committer.When})
}
}
return
@@ -256,7 +277,7 @@ func (o *PatternsLoader) writeBlobToFile(blob *object.Blob, path string) (err er
return
}
func (o *PatternsLoader) makeUniqueList(changes []fs.DirectoryChange) (err error) {
func (o *PatternsLoader) makeUniqueList(changes []fsdb.DirectoryChange) (err error) {
uniqueItems := make(map[string]bool)
for _, change := range changes {
if strings.TrimSpace(change.Dir) != "" && !strings.Contains(change.Dir, "=>") {

View File

@@ -5,15 +5,16 @@ import (
"encoding/json"
"flag"
"fmt"
"github.com/anaskhan96/soup"
"github.com/danielmiessler/fabric/common"
"google.golang.org/api/option"
"google.golang.org/api/youtube/v3"
"log"
"net/url"
"regexp"
"strconv"
"strings"
"github.com/anaskhan96/soup"
"github.com/danielmiessler/fabric/plugins"
"google.golang.org/api/option"
"google.golang.org/api/youtube/v3"
)
func NewYouTube() (ret *YouTube) {
@@ -21,9 +22,10 @@ func NewYouTube() (ret *YouTube) {
label := "YouTube"
ret = &YouTube{}
ret.Configurable = &common.Configurable{
Label: label,
EnvNamePrefix: common.BuildEnvVariablePrefix(label),
ret.PluginBase = &plugins.PluginBase{
Name: label,
SetupDescription: label + " - to grab video transcripts and comments",
EnvNamePrefix: plugins.BuildEnvVariablePrefix(label),
}
ret.ApiKey = ret.AddSetupQuestion("API key", true)
@@ -32,8 +34,8 @@ func NewYouTube() (ret *YouTube) {
}
type YouTube struct {
*common.Configurable
ApiKey *common.SetupQuestion
*plugins.PluginBase
ApiKey *plugins.SetupQuestion
service *youtube.Service
}

View File

@@ -1,19 +1,19 @@
package restapi
import (
"github.com/danielmiessler/fabric/db/fs"
"github.com/danielmiessler/fabric/plugins/db/fsdb"
"github.com/gin-gonic/gin"
)
// ContextsHandler defines the handler for contexts-related operations
type ContextsHandler struct {
*StorageHandler[fs.Context]
contexts *fs.ContextsEntity
*StorageHandler[fsdb.Context]
contexts *fsdb.ContextsEntity
}
// NewContextsHandler creates a new ContextsHandler
func NewContextsHandler(r *gin.Engine, contexts *fs.ContextsEntity) (ret *ContextsHandler) {
func NewContextsHandler(r *gin.Engine, contexts *fsdb.ContextsEntity) (ret *ContextsHandler) {
ret = &ContextsHandler{
StorageHandler: NewStorageHandler[fs.Context](r, "contexts", contexts), contexts: contexts}
StorageHandler: NewStorageHandler[fsdb.Context](r, "contexts", contexts), contexts: contexts}
return
}

View File

@@ -1,21 +1,21 @@
package restapi
import (
"github.com/danielmiessler/fabric/db/fs"
"github.com/danielmiessler/fabric/plugins/db/fsdb"
"github.com/gin-gonic/gin"
"net/http"
)
// PatternsHandler defines the handler for patterns-related operations
type PatternsHandler struct {
*StorageHandler[fs.Pattern]
patterns *fs.PatternsEntity
*StorageHandler[fsdb.Pattern]
patterns *fsdb.PatternsEntity
}
// NewPatternsHandler creates a new PatternsHandler
func NewPatternsHandler(r *gin.Engine, patterns *fs.PatternsEntity) (ret *PatternsHandler) {
func NewPatternsHandler(r *gin.Engine, patterns *fsdb.PatternsEntity) (ret *PatternsHandler) {
ret = &PatternsHandler{
StorageHandler: NewStorageHandler[fs.Pattern](r, "patterns", patterns), patterns: patterns}
StorageHandler: NewStorageHandler[fsdb.Pattern](r, "patterns", patterns), patterns: patterns}
// TODO: Add custom, replacement routes here
//r.GET("/patterns/:name", ret.Get)

View File

@@ -1,11 +1,11 @@
package restapi
import (
"github.com/danielmiessler/fabric/db/fs"
"github.com/danielmiessler/fabric/core"
"github.com/gin-gonic/gin"
)
func Serve(fabricDb *fs.Db, address string) (err error) {
func Serve(registry *core.PluginRegistry, address string) (err error) {
r := gin.Default()
// Middleware
@@ -13,6 +13,7 @@ func Serve(fabricDb *fs.Db, address string) (err error) {
r.Use(gin.Recovery())
// Register routes
fabricDb := registry.Db
NewPatternsHandler(r, fabricDb.Patterns)
NewContextsHandler(r, fabricDb.Contexts)
NewSessionsHandler(r, fabricDb.Sessions)

View File

@@ -1,19 +1,19 @@
package restapi
import (
"github.com/danielmiessler/fabric/db/fs"
"github.com/danielmiessler/fabric/plugins/db/fsdb"
"github.com/gin-gonic/gin"
)
// SessionsHandler defines the handler for sessions-related operations
type SessionsHandler struct {
*StorageHandler[fs.Session]
sessions *fs.SessionsEntity
*StorageHandler[fsdb.Session]
sessions *fsdb.SessionsEntity
}
// NewSessionsHandler creates a new SessionsHandler
func NewSessionsHandler(r *gin.Engine, sessions *fs.SessionsEntity) (ret *SessionsHandler) {
func NewSessionsHandler(r *gin.Engine, sessions *fsdb.SessionsEntity) (ret *SessionsHandler) {
ret = &SessionsHandler{
StorageHandler: NewStorageHandler[fs.Session](r, "sessions", sessions), sessions: sessions}
StorageHandler: NewStorageHandler[fsdb.Session](r, "sessions", sessions), sessions: sessions}
return ret
}

View File

@@ -2,7 +2,7 @@ package restapi
import (
"fmt"
"github.com/danielmiessler/fabric/db"
"github.com/danielmiessler/fabric/plugins/db"
"github.com/gin-gonic/gin"
"io"
"net/http"

View File

@@ -1,3 +1,3 @@
package main
var version = "v1.4.63"
var version = "v1.4.67"