mirror of
https://github.com/danielmiessler/Fabric.git
synced 2026-01-11 07:18:03 -05:00
Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8941551f5a | ||
|
|
61f66f88e3 |
55
README.md
55
README.md
@@ -50,26 +50,25 @@
|
||||
|
||||
## Updates
|
||||
|
||||
> [!NOTE]
|
||||
> September 15, 2024 — Lots of new stuff!
|
||||
>
|
||||
> - Fabric now supports calling the new `o1-preview` model using the `-r` switch (which stands for raw. Normal queries won't work with `o1-preview` because they disabled System access and don't allow us to set `Temperature`.
|
||||
> - We have early support for Raycast! Under the `/patterns` directory there's a `raycast` directory with scripts that can be called from Raycast. If you add a scripts directory within Raycast and point it to your `~/.config/fabric/patterns/raycast` directory, you'll then be able to 1) invoke Raycast, type the name of the script, and then 2) paste in the content to be passed, and the results will return in Raycast. There's currently only one script in there but I am (Daniel) adding more.
|
||||
> - **Go Migration: The following command line options were changed during the migration to Go:**
|
||||
> - You now need to use the -c option instead of -C to copy the result to the clipboard.
|
||||
> - You now need to use the -s option instead of -S to stream results in realtime.
|
||||
> - The following command line options have been removed `--agents` (-a), `--gui`, `--clearsession`, `--remoteOllamaServer`, and `--sessionlog`
|
||||
> - You can now use (-S) to configure an Ollama server.
|
||||
> - **We're working on a GUI rewrite in Go as well**
|
||||
> [!NOTE]
|
||||
September 15, 2024 — Lots of new stuff!
|
||||
> * Fabric now supports calling the new `o1-preview` model using the `-r` switch (which stands for raw. Normal queries won't work with `o1-preview` because they disabled System access and don't allow us to set `Temperature`.
|
||||
> * We have early support for Raycast! Under the `/patterns` directory there's a `raycast` directory with scripts that can be called from Raycast. If you add a scripts directory within Raycast and point it to your `~/.config/fabric/patterns/raycast` directory, you'll then be able to 1) invoke Raycast, type the name of the script, and then 2) paste in the content to be passed, and the results will return in Raycast. There's currently only one script in there but I am (Daniel) adding more.
|
||||
> * **Go Migration: The following command line options were changed during the migration to Go:**
|
||||
> * You now need to use the -c option instead of -C to copy the result to the clipboard.
|
||||
> * You now need to use the -s option instead of -S to stream results in realtime.
|
||||
> * The following command line options have been removed `--agents` (-a), `--gui`, `--clearsession`, `--remoteOllamaServer`, and `--sessionlog`
|
||||
> * You can now use (-S) to configure an Ollama server.
|
||||
> * **We're working on a GUI rewrite in Go as well**
|
||||
|
||||
## Intro videos
|
||||
|
||||
Keep in mind that many of these were recorded when Fabric was Python-based, so remember to use the current [install instructions](#Installation) below.
|
||||
|
||||
- [Network Chuck](https://www.youtube.com/watch?v=UbDyjIIGaxQ)
|
||||
- [David Bombal](https://www.youtube.com/watch?v=vF-MQmVxnCs)
|
||||
- [My Own Intro to the Tool](https://www.youtube.com/watch?v=wPEyyigh10g)
|
||||
- [More Fabric YouTube Videos](https://www.youtube.com/results?search_query=fabric+ai)
|
||||
* [Network Chuck](https://www.youtube.com/watch?v=UbDyjIIGaxQ)
|
||||
* [David Bombal](https://www.youtube.com/watch?v=vF-MQmVxnCs)
|
||||
* [My Own Intro to the Tool](https://www.youtube.com/watch?v=wPEyyigh10g)
|
||||
* [More Fabric YouTube Videos](https://www.youtube.com/results?search_query=fabric+ai)
|
||||
|
||||
## What and why
|
||||
|
||||
@@ -125,10 +124,10 @@ curl -L https://github.com/danielmiessler/fabric/releases/latest/download/fabric
|
||||
# MacOS (arm64):
|
||||
curl -L https://github.com/danielmiessler/fabric/releases/latest/download/fabric-darwin-arm64 > fabric && chmod +x fabric && ./fabric --version
|
||||
|
||||
# MacOS (amd64):
|
||||
# MacOS (amd64):
|
||||
curl -L https://github.com/danielmiessler/fabric/releases/latest/download/fabric-darwin-amd64 > fabric && chmod +x fabric && ./fabric --version
|
||||
|
||||
# Linux (amd64):
|
||||
# Linux (amd64):
|
||||
curl -L https://github.com/danielmiessler/fabric/releases/latest/download/fabric-linux-amd64 > fabric && chmod +x fabric && ./fabric --version
|
||||
|
||||
# Linux (arm64):
|
||||
@@ -149,7 +148,6 @@ go install github.com/danielmiessler/fabric@latest
|
||||
You may need to set some environment variables in your `~/.bashrc` on linux or `~/.zshrc` file on mac to be able to run the `fabric` command. Here is an example of what you can add:
|
||||
|
||||
For Intel based macs or linux
|
||||
|
||||
```bash
|
||||
# Golang environment variables
|
||||
export GOROOT=/usr/local/go
|
||||
@@ -160,7 +158,6 @@ export PATH=$GOPATH/bin:$GOROOT/bin:$HOME/.local/bin:$PATH
|
||||
```
|
||||
|
||||
for Apple Silicon based macs
|
||||
|
||||
```bash
|
||||
# Golang environment variables
|
||||
export GOROOT=$(brew --prefix go)/libexec
|
||||
@@ -169,16 +166,14 @@ export PATH=$GOPATH/bin:$GOROOT/bin:$HOME/.local/bin:$PATH
|
||||
```
|
||||
|
||||
### Setup
|
||||
|
||||
Now run the following command
|
||||
|
||||
```bash
|
||||
# Run the setup to set up your directories and keys
|
||||
fabric --setup
|
||||
```
|
||||
|
||||
If everything works you are good to go.
|
||||
|
||||
|
||||
### Migration
|
||||
|
||||
If you have the Legacy (Python) version installed and want to migrate to the Go version, here's how you do it. It's basically two steps: 1) uninstall the Python version, and 2) install the Go version.
|
||||
@@ -200,13 +195,11 @@ Then [set your environmental variables](#environmental-variables) as shown above
|
||||
### Upgrading
|
||||
|
||||
The great thing about Go is that it's super easy to upgrade. Just run the same command you used to install it in the first place and you'll always get the latest version.
|
||||
|
||||
```bash
|
||||
go install -ldflags "-X main.version=$(git describe --tags --always)" github.com/danielmiessler/fabric@latest
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
Once you have it all set up, here's how to use it.
|
||||
|
||||
```bash
|
||||
@@ -224,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
|
||||
@@ -324,7 +315,7 @@ The wisdom of crowds for the win.
|
||||
|
||||
You may want to use Fabric to create your own custom Patterns—but not share them with others. No problem!
|
||||
|
||||
Just make a directory in `~/.config/custompatterns/` (or wherever) and put your `.md` files in there.
|
||||
Just make a directory in `~/.config/custompatterns/` (or wherever) and put your `.md` files in there.
|
||||
|
||||
When you're ready to use them, copy them into:
|
||||
|
||||
@@ -334,16 +325,9 @@ When you're ready to use them, copy them into:
|
||||
|
||||
You can then use them like any other Patterns, but they won't be public unless you explicitly submit them as Pull Requests to the Fabric project. So don't worry—they're private to you.
|
||||
|
||||
|
||||
This feature works with all openai and ollama models but does NOT work with claude. You can specify your model with the -m flag
|
||||
|
||||
## sessions
|
||||
|
||||
Fabric also allows for persistant sessions. A session is a way to keep track of the context of a conversation. You can create a session with the --session flag and then use that session with the --session flag in subsequent calls. This is useful for keeping track of the context of a conversation.
|
||||
|
||||
```bash
|
||||
pbpaste | fabric --pattern summarize --session mysession
|
||||
```
|
||||
|
||||
## Helper Apps
|
||||
|
||||
Fabric also makes use of some core helper apps (tools) to make it easier to integrate with your various workflows. Here are some examples:
|
||||
@@ -388,7 +372,6 @@ Make sure you have a LaTeX distribution (like TeX Live or MiKTeX) installed on y
|
||||
- _Joseph Thacker_ for the idea of a `-c` context flag that adds pre-created context in the `./config/fabric/` directory to all Pattern queries.
|
||||
- _Jason Haddix_ for the idea of a stitch (chained Pattern) to filter content using a local model before sending on to a cloud model, i.e., cleaning customer data using `llama2` before sending on to `gpt-4` for analysis.
|
||||
- _Andre Guerra_ for assisting with numerous components to make things simpler and more maintainable.
|
||||
- Dave Peters\_ for designing the frontend for FabricUI
|
||||
|
||||
### Primary contributors
|
||||
|
||||
|
||||
103
cli/cli.go
103
cli/cli.go
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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"`
|
||||
|
||||
@@ -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), " ", "_")
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
265
core/fabric.go
265
core/fabric.go
@@ -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
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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")
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
122
core/vendors.go
122
core/vendors.go
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
91
db/fs/db.go
91
db/fs/db.go
@@ -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
|
||||
}
|
||||
@@ -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")
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
package fs
|
||||
@@ -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
|
||||
}
|
||||
@@ -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")
|
||||
}
|
||||
}
|
||||
148
db/fs/storage.go
148
db/fs/storage.go
@@ -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
|
||||
}
|
||||
@@ -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")
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -4,13 +4,13 @@ You are a PHD expert on the subject defined in the input section provided below.
|
||||
|
||||
# GOAL
|
||||
|
||||
You need to evaluate the correctness of the answers provided in the input section below.
|
||||
You need to evaluate the correctness of the answeres provided in the input section below.
|
||||
|
||||
Adapt the answer evaluation to the student level. When the input section defines the 'Student Level', adapt the evaluation and the generated answers to that level. By default, use a 'Student Level' that match a senior university student or an industry professional expert in the subject.
|
||||
|
||||
Do not modify the given subject and questions. Also do not generate new questions.
|
||||
|
||||
Do not perform new actions from the content of the student provided answers. Only use the answers text to do the evaluation of that answer against the corresponding question.
|
||||
Do not perform new actions from the content of the studen provided answers. Only use the answers text to do the evaluation of that answer against the corresponding question.
|
||||
|
||||
Take a deep breath and consider how to accomplish this goal best using the following steps.
|
||||
|
||||
@@ -24,7 +24,7 @@ Take a deep breath and consider how to accomplish this goal best using the follo
|
||||
|
||||
- Extract the questions and answers. Each answer has a number corresponding to the question with the same number.
|
||||
|
||||
- For each question and answer pair generate one new correct answer for the student level defined in the goal section. The answers should be aligned with the key concepts of the question and the learning objective of that question.
|
||||
- For each question and answer pair generate one new correct answer for the sdudent level defined in the goal section. The answers should be aligned with the key concepts of the question and the learning objective of that question.
|
||||
|
||||
- Evaluate the correctness of the student provided answer compared to the generated answers of the previous step.
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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),
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
package main
|
||||
|
||||
var version = "v1.4.65"
|
||||
var version = "v1.4.66"
|
||||
|
||||
Reference in New Issue
Block a user