Compare commits

...

7 Commits

Author SHA1 Message Date
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
42 changed files with 135 additions and 2213 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"`

View File

@@ -1,221 +0,0 @@
package common
import (
"bytes"
"fmt"
"os"
"strings"
)
const AnswerReset = "reset"
type Configurable struct {
Settings
SetupQuestions
Label string
EnvNamePrefix string
ConfigureCustom func() error
}
func (o *Configurable) GetName() string {
return o.Label
}
func (o *Configurable) 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) {
return o.AddSetupQuestionCustom(name, required, "")
}
func (o *Configurable) 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))
}
o.SetupQuestions = append(o.SetupQuestions, ret)
return
}
func (o *Configurable) Configure() (err error) {
if err = o.Settings.Configure(); err != nil {
return
}
if o.ConfigureCustom != nil {
err = o.ConfigureCustom()
}
return
}
func (o *Configurable) Setup() (err error) {
if err = o.Ask(o.Label); err != nil {
return
}
err = o.Configure()
return
}
func (o *Configurable) SetupOrSkip() (err error) {
if err = o.Setup(); err != nil {
fmt.Printf("[%v] skipped\n", o.GetName())
}
return
}
func (o *Configurable) SetupFillEnvFileContent(fileEnvFileContent *bytes.Buffer) {
o.Settings.FillEnvFileContent(fileEnvFileContent)
}
func NewSetting(envVariable string, required bool) *Setting {
return &Setting{
EnvVariable: envVariable,
Required: required,
}
}
type Setting struct {
EnvVariable string
Value string
Required bool
}
func (o *Setting) IsValid() bool {
return o.IsDefined() || !o.Required
}
func (o *Setting) IsValidErr() (err error) {
if !o.IsValid() {
err = fmt.Errorf("%v=%v, is not valid", o.EnvVariable, o.Value)
}
return
}
func (o *Setting) IsDefined() bool {
return o.Value != ""
}
func (o *Setting) Configure() error {
envValue := os.Getenv(o.EnvVariable)
if envValue != "" {
o.Value = envValue
}
return o.IsValidErr()
}
func (o *Setting) FillEnvFileContent(buffer *bytes.Buffer) {
if o.IsDefined() {
buffer.WriteString(o.EnvVariable)
buffer.WriteString("=")
//buffer.WriteString("\"")
buffer.WriteString(o.Value)
//buffer.WriteString("\"")
buffer.WriteString("\n")
}
return
}
func (o *Setting) Print() {
fmt.Printf("%v: %v\n", o.EnvVariable, o.Value)
}
type SetupQuestion struct {
*Setting
Question string
}
func (o *SetupQuestion) Ask(label string) (err error) {
var prefix string
if label != "" {
prefix = fmt.Sprintf("[%v] ", label)
} else {
prefix = ""
}
fmt.Println()
if o.Value != "" {
fmt.Printf("%v%v (leave empty for '%s' or type '%v' to remove the value):\n",
prefix, o.Question, o.Value, AnswerReset)
} else {
fmt.Printf("%v%v (leave empty to skip):\n", prefix, o.Question)
}
var answer string
fmt.Scanln(&answer)
answer = strings.TrimRight(answer, "\n")
if answer == "" {
answer = o.Value
} else if strings.ToLower(answer) == AnswerReset {
answer = ""
}
err = o.OnAnswer(answer)
return
}
func (o *SetupQuestion) OnAnswer(answer string) (err error) {
o.Value = answer
err = o.IsValidErr()
return
}
type Settings []*Setting
func (o Settings) IsConfigured() (ret bool) {
ret = true
for _, setting := range o {
if ret = setting.IsValid(); !ret {
break
}
}
return
}
func (o Settings) Configure() (err error) {
for _, setting := range o {
if err = setting.Configure(); err != nil {
break
}
}
return
}
func (o Settings) FillEnvFileContent(buffer *bytes.Buffer) {
for _, setting := range o {
setting.FillEnvFileContent(buffer)
}
return
}
type SetupQuestions []*SetupQuestion
func (o SetupQuestions) Ask(label string) (err error) {
fmt.Println()
fmt.Printf("[%v]\n", label)
for _, question := range o {
if err = question.Ask(""); err != nil {
break
}
}
return
}
func BuildEnvVariablePrefix(name string) (ret string) {
ret = BuildEnvVariable(name)
if ret != "" {
ret += "_"
}
return
}
func BuildEnvVariable(name string) string {
name = strings.TrimSpace(name)
return strings.ReplaceAll(strings.ToUpper(name), " ", "_")
}

View File

@@ -1,176 +0,0 @@
package common
import (
"bytes"
"os"
"testing"
"github.com/stretchr/testify/assert"
)
func TestConfigurable_AddSetting(t *testing.T) {
conf := &Configurable{
Settings: Settings{},
Label: "TestConfigurable",
EnvNamePrefix: "TEST_",
}
setting := conf.AddSetting("test_setting", true)
assert.Equal(t, "TEST_TEST_SETTING", setting.EnvVariable)
assert.True(t, setting.Required)
assert.Contains(t, conf.Settings, setting)
}
func TestConfigurable_Configure(t *testing.T) {
setting := &Setting{
EnvVariable: "TEST_SETTING",
Required: true,
}
conf := &Configurable{
Settings: Settings{setting},
Label: "TestConfigurable",
}
_ = os.Setenv("TEST_SETTING", "test_value")
err := conf.Configure()
assert.NoError(t, err)
assert.Equal(t, "test_value", setting.Value)
}
func TestConfigurable_Setup(t *testing.T) {
setting := &Setting{
EnvVariable: "TEST_SETTING",
Required: false,
}
conf := &Configurable{
Settings: Settings{setting},
Label: "TestConfigurable",
}
err := conf.Setup()
assert.NoError(t, err)
}
func TestSetting_IsValid(t *testing.T) {
setting := &Setting{
EnvVariable: "TEST_SETTING",
Value: "some_value",
Required: true,
}
assert.True(t, setting.IsValid())
setting.Value = ""
assert.False(t, setting.IsValid())
}
func TestSetting_Configure(t *testing.T) {
_ = os.Setenv("TEST_SETTING", "test_value")
setting := &Setting{
EnvVariable: "TEST_SETTING",
Required: true,
}
err := setting.Configure()
assert.NoError(t, err)
assert.Equal(t, "test_value", setting.Value)
}
func TestSetting_FillEnvFileContent(t *testing.T) {
buffer := &bytes.Buffer{}
setting := &Setting{
EnvVariable: "TEST_SETTING",
Value: "test_value",
}
setting.FillEnvFileContent(buffer)
expected := "TEST_SETTING=test_value\n"
assert.Equal(t, expected, buffer.String())
}
func TestSetting_Print(t *testing.T) {
setting := &Setting{
EnvVariable: "TEST_SETTING",
Value: "test_value",
}
expected := "TEST_SETTING: test_value\n"
fmtOutput := captureOutput(func() {
setting.Print()
})
assert.Equal(t, expected, fmtOutput)
}
func TestSetupQuestion_Ask(t *testing.T) {
setting := &Setting{
EnvVariable: "TEST_SETTING",
Required: true,
}
question := &SetupQuestion{
Setting: setting,
Question: "Enter test setting:",
}
input := "user_value\n"
fmtInput := captureInput(input)
defer fmtInput()
err := question.Ask("TestConfigurable")
assert.NoError(t, err)
assert.Equal(t, "user_value", setting.Value)
}
func TestSettings_IsConfigured(t *testing.T) {
settings := Settings{
{EnvVariable: "TEST_SETTING1", Value: "value1", Required: true},
{EnvVariable: "TEST_SETTING2", Value: "", Required: false},
}
assert.True(t, settings.IsConfigured())
settings[0].Value = ""
assert.False(t, settings.IsConfigured())
}
func TestSettings_Configure(t *testing.T) {
_ = os.Setenv("TEST_SETTING", "test_value")
settings := Settings{
{EnvVariable: "TEST_SETTING", Required: true},
}
err := settings.Configure()
assert.NoError(t, err)
assert.Equal(t, "test_value", settings[0].Value)
}
func TestSettings_FillEnvFileContent(t *testing.T) {
buffer := &bytes.Buffer{}
settings := Settings{
{EnvVariable: "TEST_SETTING", Value: "test_value"},
}
settings.FillEnvFileContent(buffer)
expected := "TEST_SETTING=test_value\n"
assert.Equal(t, expected, buffer.String())
}
// captureOutput captures the output of a function call
func captureOutput(f func()) string {
var buf bytes.Buffer
stdout := os.Stdout
r, w, _ := os.Pipe()
os.Stdout = w
f()
_ = w.Close()
os.Stdout = stdout
_, _ = buf.ReadFrom(r)
return buf.String()
}
// captureInput captures the input for a function call
func captureInput(input string) func() {
r, w, _ := os.Pipe()
_, _ = w.WriteString(input)
_ = w.Close()
stdin := os.Stdin
os.Stdin = r
return func() {
os.Stdin = stdin
}
}

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

View File

@@ -1,282 +0,0 @@
package core
import (
"fmt"
"github.com/danielmiessler/fabric/db/fs"
"io"
"os"
"path/filepath"
"sort"
"strings"
"github.com/danielmiessler/fabric/common"
"github.com/go-git/go-git/v5"
"github.com/go-git/go-git/v5/plumbing"
"github.com/go-git/go-git/v5/plumbing/object"
"github.com/go-git/go-git/v5/storage/memory"
"github.com/otiai10/copy"
)
func NewPatternsLoader(patterns *fs.PatternsEntity) (ret *PatternsLoader) {
label := "Patterns Loader"
ret = &PatternsLoader{
Patterns: patterns,
}
ret.Configurable = &common.Configurable{
Label: label,
EnvNamePrefix: common.BuildEnvVariablePrefix(label),
ConfigureCustom: ret.configure,
}
ret.DefaultGitRepoUrl = ret.AddSetupQuestionCustom("Git Repo Url", true,
"Enter the default Git repository URL for the patterns")
ret.DefaultGitRepoUrl.Value = DefaultPatternsGitRepoUrl
ret.DefaultFolder = ret.AddSetupQuestionCustom("Git Repo Patterns Folder", true,
"Enter the default folder in the Git repository where patterns are stored")
ret.DefaultFolder.Value = DefaultPatternsGitRepoFolder
return
}
type PatternsLoader struct {
*common.Configurable
Patterns *fs.PatternsEntity
DefaultGitRepoUrl *common.SetupQuestion
DefaultFolder *common.SetupQuestion
pathPatternsPrefix string
tempPatternsFolder string
}
func (o *PatternsLoader) configure() (err error) {
o.pathPatternsPrefix = fmt.Sprintf("%v/", o.DefaultFolder.Value)
o.tempPatternsFolder = filepath.Join(os.TempDir(), o.DefaultFolder.Value)
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)
fmt.Println()
if err = o.gitCloneAndCopy(); err != nil {
return
}
if err = o.movePatterns(); err != nil {
return
}
return
}
// PersistPatterns copies custom patterns to the updated patterns directory
func (o *PatternsLoader) PersistPatterns() (err error) {
var currentPatterns []os.DirEntry
if currentPatterns, err = os.ReadDir(o.Patterns.Dir); err != nil {
return
}
newPatternsFolder := o.tempPatternsFolder
var newPatterns []os.DirEntry
if newPatterns, err = os.ReadDir(newPatternsFolder); err != nil {
return
}
for _, currentPattern := range currentPatterns {
for _, newPattern := range newPatterns {
if currentPattern.Name() == newPattern.Name() {
break
}
err = copy.Copy(filepath.Join(o.Patterns.Dir, newPattern.Name()), filepath.Join(newPatternsFolder, newPattern.Name()))
}
}
return
}
// movePatterns copies the new patterns into the config directory
func (o *PatternsLoader) movePatterns() (err error) {
if err = os.MkdirAll(o.Patterns.Dir, os.ModePerm); err != nil {
return
}
patternsDir := o.tempPatternsFolder
if err = o.PersistPatterns(); err != nil {
return
}
if err = copy.Copy(patternsDir, o.Patterns.Dir); err != nil { // copies the patterns to the config directory
return
}
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:
var r *git.Repository
if r, err = git.Clone(memory.NewStorage(), nil, &git.CloneOptions{
URL: o.DefaultGitRepoUrl.Value,
}); err != nil {
fmt.Println(err)
return
}
// ... retrieves the branch pointed by HEAD
var ref *plumbing.Reference
if ref, err = r.Head(); err != nil {
fmt.Println(err)
return
}
// ... retrieves the commit history for /patterns folder
var cIter object.CommitIter
if cIter, err = r.Log(&git.LogOptions{
From: ref.Hash(),
PathFilter: func(path string) bool {
return path == o.DefaultFolder.Value || strings.HasPrefix(path, o.pathPatternsPrefix)
},
}); err != nil {
fmt.Println(err)
return err
}
var changes []fs.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
parentIter := c.Parents()
if err = parentIter.ForEach(func(parent *object.Commit) (err error) {
var patch *object.Patch
if patch, err = parent.Patch(c); err != nil {
fmt.Println(err)
return
}
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})
}
}
return
}); err != nil {
fmt.Println(err)
return
}
return
}); err != nil {
fmt.Println(err)
return
}
// Sort changes by timestamp
sort.Slice(changes, func(i, j int) bool {
return changes[i].Timestamp.Before(changes[j].Timestamp)
})
if err = o.makeUniqueList(changes); err != nil {
return
}
var commit *object.Commit
if commit, err = r.CommitObject(ref.Hash()); err != nil {
fmt.Println(err)
return
}
var tree *object.Tree
if tree, err = commit.Tree(); err != nil {
fmt.Println(err)
return
}
if err = tree.Files().ForEach(func(f *object.File) (err error) {
if strings.HasPrefix(f.Name, o.pathPatternsPrefix) {
// Create the local file path
localPath := filepath.Join(os.TempDir(), f.Name)
// Create the directories if they don't exist
if err = os.MkdirAll(filepath.Dir(localPath), os.ModePerm); err != nil {
fmt.Println(err)
return
}
// Write the file to the local filesystem
var blob *object.Blob
if blob, err = r.BlobObject(f.Hash); err != nil {
fmt.Println(err)
return
}
err = o.writeBlobToFile(blob, localPath)
return
}
return
}); err != nil {
fmt.Println(err)
}
return
}
func (o *PatternsLoader) writeBlobToFile(blob *object.Blob, path string) (err error) {
var reader io.ReadCloser
if reader, err = blob.Reader(); err != nil {
return
}
defer reader.Close()
// Create the file
var file *os.File
if file, err = os.Create(path); err != nil {
return
}
defer file.Close()
// Copy the contents of the blob to the file
if _, err = io.Copy(file, reader); err != nil {
return
}
return
}
func (o *PatternsLoader) makeUniqueList(changes []fs.DirectoryChange) (err error) {
uniqueItems := make(map[string]bool)
for _, change := range changes {
if strings.TrimSpace(change.Dir) != "" && !strings.Contains(change.Dir, "=>") {
pattern := strings.ReplaceAll(change.Dir, o.pathPatternsPrefix, "")
pattern = strings.TrimSpace(pattern)
uniqueItems[pattern] = true
}
}
finalList := make([]string, 0, len(uniqueItems))
for _, change := range changes {
pattern := strings.ReplaceAll(change.Dir, o.pathPatternsPrefix, "")
pattern = strings.TrimSpace(pattern)
if _, exists := uniqueItems[pattern]; exists {
finalList = append(finalList, pattern)
delete(uniqueItems, pattern) // Remove to avoid duplicates in the final list
}
}
joined := strings.Join(finalList, "\n")
err = os.WriteFile(o.Patterns.UniquePatternsFilePath, []byte(joined), 0o644)
return
}

View File

@@ -1,122 +0,0 @@
package core
import (
"context"
"fmt"
"github.com/danielmiessler/fabric/plugins/ai"
"sync"
)
func NewVendorsManager() *VendorsManager {
return &VendorsManager{
Vendors: map[string]ai.Vendor{},
}
}
type VendorsManager struct {
Vendors map[string]ai.Vendor
Models *VendorsModels
}
func (o *VendorsManager) AddVendors(vendors ...ai.Vendor) {
for _, vendor := range vendors {
o.Vendors[vendor.GetName()] = vendor
}
}
func (o *VendorsManager) GetModels() *VendorsModels {
if o.Models == nil {
o.readModels()
}
return o.Models
}
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) readModels() {
o.Models = NewVendorsModels()
var wg sync.WaitGroup
resultsChan := make(chan modelResult, len(o.Vendors))
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
for _, vendor := range o.Vendors {
wg.Add(1)
go o.fetchVendorModels(ctx, &wg, vendor, resultsChan)
}
// Wait for all goroutines to finish
go func() {
wg.Wait()
close(resultsChan)
}()
// Collect results
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)
}
}
}
func (o *VendorsManager) fetchVendorModels(
ctx context.Context, wg *sync.WaitGroup, vendor ai.Vendor, resultsChan chan<- modelResult) {
defer wg.Done()
models, err := vendor.ListModels()
select {
case <-ctx.Done():
// Context canceled, don't send the result
return
case resultsChan <- modelResult{vendorName: vendor.GetName(), models: models, err: err}:
// Result sent
}
}
func (o *VendorsManager) Setup() (ret map[string]ai.Vendor, err error) {
ret = map[string]ai.Vendor{}
for _, vendor := range o.Vendors {
fmt.Println()
o.setupVendorTo(vendor, ret)
}
return
}
func (o *VendorsManager) setupVendorTo(vendor ai.Vendor, configuredVendors map[string]ai.Vendor) {
if vendorErr := vendor.Setup(); vendorErr == nil {
fmt.Printf("[%v] configured\n", vendor.GetName())
configuredVendors[vendor.GetName()] = vendor
} else {
delete(configuredVendors, vendor.GetName())
fmt.Printf("[%v] skipped\n", vendor.GetName())
}
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
err error
}

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,32 +0,0 @@
package fs
import "fmt"
type ContextsEntity struct {
*StorageEntity
}
// Get Load a context from file
func (o *ContextsEntity) Get(name string) (ret *Context, err error) {
var content []byte
if content, err = o.Load(name); err != nil {
return
}
ret = &Context{Name: name, Content: string(content)}
return
}
func (o *ContextsEntity) PrintContext(name string) (err error) {
var context *Context
if context, err = o.Get(name); err != nil {
return
}
fmt.Println(context.Content)
return
}
type Context struct {
Name string
Content string
}

View File

@@ -1,29 +0,0 @@
package fs
import (
"os"
"path/filepath"
"testing"
)
func TestContexts_GetContext(t *testing.T) {
dir := t.TempDir()
contexts := &ContextsEntity{
StorageEntity: &StorageEntity{Dir: dir},
}
contextName := "testContext"
contextPath := filepath.Join(dir, contextName)
contextContent := "test content"
err := os.WriteFile(contextPath, []byte(contextContent), 0644)
if err != nil {
t.Fatalf("failed to write context file: %v", err)
}
context, err := contexts.Get(contextName)
if err != nil {
t.Fatalf("failed to get context: %v", err)
}
expectedContext := &Context{Name: contextName, Content: contextContent}
if *context != *expectedContext {
t.Errorf("expected %v, got %v", expectedContext, context)
}
}

View File

@@ -1,91 +0,0 @@
package fs
import (
"fmt"
"github.com/joho/godotenv"
"os"
"path/filepath"
"time"
)
func NewDb(dir string) (db *Db) {
db = &Db{Dir: dir}
db.EnvFilePath = db.FilePath(".env")
db.Patterns = &PatternsEntity{
StorageEntity: &StorageEntity{Label: "Patterns", Dir: db.FilePath("patterns"), ItemIsDir: true},
SystemPatternFile: "system.md",
UniquePatternsFilePath: db.FilePath("unique_patterns.txt"),
}
db.Sessions = &SessionsEntity{
&StorageEntity{Label: "Sessions", Dir: db.FilePath("sessions"), FileExtension: ".json"}}
db.Contexts = &ContextsEntity{
&StorageEntity{Label: "Contexts", Dir: db.FilePath("contexts")}}
return
}
type Db struct {
Dir string
Patterns *PatternsEntity
Sessions *SessionsEntity
Contexts *ContextsEntity
EnvFilePath string
}
func (o *Db) Configure() (err error) {
if err = os.MkdirAll(o.Dir, os.ModePerm); err != nil {
return
}
if err = o.LoadEnvFile(); err != nil {
return
}
if err = o.Patterns.Configure(); err != nil {
return
}
if err = o.Sessions.Configure(); err != nil {
return
}
if err = o.Contexts.Configure(); err != nil {
return
}
return
}
func (o *Db) LoadEnvFile() (err error) {
if err = godotenv.Load(o.EnvFilePath); err != nil {
err = fmt.Errorf("error loading .env file: %s", err)
}
return
}
func (o *Db) IsEnvFileExists() (ret bool) {
_, err := os.Stat(o.EnvFilePath)
ret = !os.IsNotExist(err)
return
}
func (o *Db) SaveEnv(content string) (err error) {
err = os.WriteFile(o.EnvFilePath, []byte(content), 0644)
return
}
func (o *Db) FilePath(fileName string) (ret string) {
return filepath.Join(o.Dir, fileName)
}
type DirectoryChange struct {
Dir string
Timestamp time.Time
}

View File

@@ -1,55 +0,0 @@
package fs
import (
"os"
"testing"
)
func TestDb_Configure(t *testing.T) {
dir := t.TempDir()
db := NewDb(dir)
err := db.Configure()
if err == nil {
t.Fatalf("db is configured, but must not be at empty dir: %v", dir)
}
if db.IsEnvFileExists() {
t.Fatalf("db file exists, but must not be at empty dir: %v", dir)
}
err = db.SaveEnv("")
if err != nil {
t.Fatalf("db can't save env for empty conf.: %v", err)
}
err = db.Configure()
if err != nil {
t.Fatalf("db is not configured, but shall be after save: %v", err)
}
}
func TestDb_LoadEnvFile(t *testing.T) {
dir := t.TempDir()
db := NewDb(dir)
content := "KEY=VALUE\n"
err := os.WriteFile(db.EnvFilePath, []byte(content), 0644)
if err != nil {
t.Fatalf("failed to write .env file: %v", err)
}
err = db.LoadEnvFile()
if err != nil {
t.Errorf("failed to load .env file: %v", err)
}
}
func TestDb_SaveEnv(t *testing.T) {
dir := t.TempDir()
db := NewDb(dir)
content := "KEY=VALUE\n"
err := db.SaveEnv(content)
if err != nil {
t.Errorf("failed to save .env file: %v", err)
}
if _, err := os.Stat(db.EnvFilePath); os.IsNotExist(err) {
t.Errorf("expected .env file to be saved")
}
}

View File

@@ -1,68 +0,0 @@
package fs
import (
"fmt"
"os"
"path/filepath"
"strings"
)
type PatternsEntity struct {
*StorageEntity
SystemPatternFile string
UniquePatternsFilePath string
}
func (o *PatternsEntity) Get(name string) (ret *Pattern, err error) {
patternPath := filepath.Join(o.Dir, name, o.SystemPatternFile)
var pattern []byte
if pattern, err = os.ReadFile(patternPath); err != nil {
return
}
patternStr := string(pattern)
ret = &Pattern{
Name: name,
Pattern: patternStr,
}
return
}
// GetApplyVariables finds a pattern by name and returns the pattern as an entry or an error
func (o *PatternsEntity) GetApplyVariables(name string, variables map[string]string) (ret *Pattern, err error) {
if ret, err = o.Get(name); err != nil {
return
}
if variables != nil && len(variables) > 0 {
for variableName, value := range variables {
ret.Pattern = strings.ReplaceAll(ret.Pattern, variableName, value)
}
}
return
}
func (o *PatternsEntity) PrintLatestPatterns(latestNumber int) (err error) {
var contents []byte
if contents, err = os.ReadFile(o.UniquePatternsFilePath); err != nil {
err = fmt.Errorf("could not read unique patterns file. Pleas run --updatepatterns (%s)", err)
return
}
uniquePatterns := strings.Split(string(contents), "\n")
if latestNumber > len(uniquePatterns) {
latestNumber = len(uniquePatterns)
}
for i := len(uniquePatterns) - 1; i > len(uniquePatterns)-latestNumber-1; i-- {
fmt.Println(uniquePatterns[i])
}
return
}
type Pattern struct {
Name string
Description string
Pattern string
}

View File

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

View File

@@ -1,88 +0,0 @@
package fs
import (
"fmt"
"github.com/danielmiessler/fabric/common"
)
type SessionsEntity struct {
*StorageEntity
}
func (o *SessionsEntity) Get(name string) (session *Session, err error) {
session = &Session{Name: name}
if o.Exists(name) {
err = o.LoadAsJson(name, &session.Messages)
} else {
fmt.Printf("Creating new session: %s\n", name)
}
return
}
func (o *SessionsEntity) PrintSession(name string) (err error) {
if o.Exists(name) {
var session Session
if err = o.LoadAsJson(name, &session.Messages); err == nil {
fmt.Println(session.String())
}
}
return
}
func (o *SessionsEntity) SaveSession(session *Session) (err error) {
return o.SaveAsJson(session.Name, session.Messages)
}
type Session struct {
Name string
Messages []*common.Message
vendorMessages []*common.Message
}
func (o *Session) IsEmpty() bool {
return len(o.Messages) == 0
}
func (o *Session) Append(messages ...*common.Message) {
if o.vendorMessages != nil {
for _, message := range messages {
o.Messages = append(o.Messages, message)
o.appendVendorMessage(message)
}
} else {
o.Messages = append(o.Messages, messages...)
}
}
func (o *Session) GetVendorMessages() (ret []*common.Message) {
if o.vendorMessages == nil {
o.vendorMessages = []*common.Message{}
for _, message := range o.Messages {
o.appendVendorMessage(message)
}
}
ret = o.vendorMessages
return
}
func (o *Session) appendVendorMessage(message *common.Message) {
if message.Role != common.ChatMessageRoleMeta {
o.vendorMessages = append(o.vendorMessages, message)
}
}
func (o *Session) GetLastMessage() (ret *common.Message) {
if len(o.Messages) > 0 {
ret = o.Messages[len(o.Messages)-1]
}
return
}
func (o *Session) String() (ret string) {
for _, message := range o.Messages {
ret += fmt.Sprintf("\n--- \n[%v]\n\n%v", message.Role, message.Content)
}
return
}

View File

@@ -1,38 +0,0 @@
package fs
import (
"testing"
"github.com/danielmiessler/fabric/common"
)
func TestSessions_GetOrCreateSession(t *testing.T) {
dir := t.TempDir()
sessions := &SessionsEntity{
StorageEntity: &StorageEntity{Dir: dir, FileExtension: ".json"},
}
sessionName := "testSession"
session, err := sessions.Get(sessionName)
if err != nil {
t.Fatalf("failed to get or create session: %v", err)
}
if session.Name != sessionName {
t.Errorf("expected session name %v, got %v", sessionName, session.Name)
}
}
func TestSessions_SaveSession(t *testing.T) {
dir := t.TempDir()
sessions := &SessionsEntity{
StorageEntity: &StorageEntity{Dir: dir, FileExtension: ".json"},
}
sessionName := "testSession"
session := &Session{Name: sessionName, Messages: []*common.Message{{Content: "message1"}}}
err := sessions.SaveSession(session)
if err != nil {
t.Fatalf("failed to save session: %v", err)
}
if !sessions.Exists(sessionName) {
t.Errorf("expected session to be saved")
}
}

View File

@@ -1,148 +0,0 @@
package fs
import (
"encoding/json"
"fmt"
"os"
"path/filepath"
"strings"
"github.com/samber/lo"
)
type StorageEntity struct {
Label string
Dir string
ItemIsDir bool
FileExtension string
}
func (o *StorageEntity) Configure() (err error) {
if err = os.MkdirAll(o.Dir, os.ModePerm); err != nil {
return
}
return
}
// GetNames finds all patterns in the patterns directory and enters the id, name, and pattern into a slice of Entry structs. it returns these entries or an error
func (o *StorageEntity) GetNames() (ret []string, err error) {
var entries []os.DirEntry
if entries, err = os.ReadDir(o.Dir); err != nil {
err = fmt.Errorf("could not read items from directory: %v", err)
return
}
if o.ItemIsDir {
ret = lo.FilterMap(entries, func(item os.DirEntry, index int) (ret string, ok bool) {
if ok = item.IsDir(); ok {
ret = item.Name()
}
return
})
} else {
if o.FileExtension == "" {
ret = lo.FilterMap(entries, func(item os.DirEntry, index int) (ret string, ok bool) {
if ok = !item.IsDir(); ok {
ret = item.Name()
}
return
})
} else {
ret = lo.FilterMap(entries, func(item os.DirEntry, index int) (ret string, ok bool) {
if ok = !item.IsDir() && filepath.Ext(item.Name()) == o.FileExtension; ok {
ret = strings.TrimSuffix(item.Name(), o.FileExtension)
}
return
})
}
}
return
}
func (o *StorageEntity) Delete(name string) (err error) {
if err = os.Remove(o.BuildFilePathByName(name)); err != nil {
err = fmt.Errorf("could not delete %s: %v", name, err)
}
return
}
func (o *StorageEntity) Exists(name string) (ret bool) {
_, err := os.Stat(o.BuildFilePathByName(name))
ret = !os.IsNotExist(err)
return
}
func (o *StorageEntity) Rename(oldName, newName string) (err error) {
if err = os.Rename(o.BuildFilePathByName(oldName), o.BuildFilePathByName(newName)); err != nil {
err = fmt.Errorf("could not rename %s to %s: %v", oldName, newName, err)
}
return
}
func (o *StorageEntity) Save(name string, content []byte) (err error) {
if err = os.WriteFile(o.BuildFilePathByName(name), content, 0644); err != nil {
err = fmt.Errorf("could not save %s: %v", name, err)
}
return
}
func (o *StorageEntity) Load(name string) (ret []byte, err error) {
if ret, err = os.ReadFile(o.BuildFilePathByName(name)); err != nil {
err = fmt.Errorf("could not load %s: %v", name, err)
}
return
}
func (o *StorageEntity) ListNames() (err error) {
var names []string
if names, err = o.GetNames(); err != nil {
return
}
if len(names) == 0 {
fmt.Printf("\nNo %v\n", o.Label)
return
}
for _, item := range names {
fmt.Printf("%s\n", item)
}
return
}
func (o *StorageEntity) BuildFilePathByName(name string) (ret string) {
ret = o.BuildFilePath(o.buildFileName(name))
return
}
func (o *StorageEntity) BuildFilePath(fileName string) (ret string) {
ret = filepath.Join(o.Dir, fileName)
return
}
func (o *StorageEntity) buildFileName(name string) string {
return fmt.Sprintf("%s%v", name, o.FileExtension)
}
func (o *StorageEntity) SaveAsJson(name string, item interface{}) (err error) {
var jsonString []byte
if jsonString, err = json.Marshal(item); err == nil {
err = o.Save(name, jsonString)
} else {
err = fmt.Errorf("could not marshal %s: %s", name, err)
}
return err
}
func (o *StorageEntity) LoadAsJson(name string, item interface{}) (err error) {
var content []byte
if content, err = o.Load(name); err != nil {
return
}
if err = json.Unmarshal(content, &item); err != nil {
err = fmt.Errorf("could not unmarshal %s: %s", name, err)
}
return
}

View File

@@ -1,52 +0,0 @@
package fs
import (
"testing"
)
func TestStorage_SaveAndLoad(t *testing.T) {
dir := t.TempDir()
storage := &StorageEntity{Dir: dir}
name := "test"
content := []byte("test content")
if err := storage.Save(name, content); err != nil {
t.Fatalf("failed to save content: %v", err)
}
loadedContent, err := storage.Load(name)
if err != nil {
t.Fatalf("failed to load content: %v", err)
}
if string(loadedContent) != string(content) {
t.Errorf("expected %v, got %v", string(content), string(loadedContent))
}
}
func TestStorage_Exists(t *testing.T) {
dir := t.TempDir()
storage := &StorageEntity{Dir: dir}
name := "test"
if storage.Exists(name) {
t.Errorf("expected file to not exist")
}
if err := storage.Save(name, []byte("test content")); err != nil {
t.Fatalf("failed to save content: %v", err)
}
if !storage.Exists(name) {
t.Errorf("expected file to exist")
}
}
func TestStorage_Delete(t *testing.T) {
dir := t.TempDir()
storage := &StorageEntity{Dir: dir}
name := "test"
if err := storage.Save(name, []byte("test content")); err != nil {
t.Fatalf("failed to save content: %v", err)
}
if err := storage.Delete(name); err != nil {
t.Fatalf("failed to delete content: %v", err)
}
if storage.Exists(name) {
t.Errorf("expected file to be deleted")
}
}

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) {

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

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

@@ -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.66"