mirror of
https://github.com/danielmiessler/Fabric.git
synced 2026-01-10 14:58:02 -05:00
Compare commits
16 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2d8b46b878 | ||
|
|
fbd6083079 | ||
|
|
0320e17652 | ||
|
|
09fb913279 | ||
|
|
ec5ed689bb | ||
|
|
43ca0dccf7 | ||
|
|
fcfcf55610 | ||
|
|
188235efc5 | ||
|
|
fdd1d614b2 | ||
|
|
fc67dea243 | ||
|
|
efd363d5fb | ||
|
|
a7d6de1661 | ||
|
|
c0ade48648 | ||
|
|
7fd4fa4742 | ||
|
|
41b2e66c5c | ||
|
|
ed657383fb |
73
common/utils.go
Normal file
73
common/utils.go
Normal file
@@ -0,0 +1,73 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// GetAbsolutePath resolves a given path to its absolute form, handling ~, ./, ../, UNC paths, and symlinks.
|
||||
func GetAbsolutePath(path string) (string, error) {
|
||||
if path == "" {
|
||||
return "", errors.New("path is empty")
|
||||
}
|
||||
|
||||
// Handle UNC paths on Windows
|
||||
if runtime.GOOS == "windows" && strings.HasPrefix(path, `\\`) {
|
||||
return path, nil
|
||||
}
|
||||
|
||||
// Handle ~ for home directory expansion
|
||||
if strings.HasPrefix(path, "~") {
|
||||
home, err := os.UserHomeDir()
|
||||
if err != nil {
|
||||
return "", errors.New("could not resolve home directory")
|
||||
}
|
||||
path = filepath.Join(home, path[1:])
|
||||
}
|
||||
|
||||
// Convert to absolute path
|
||||
absPath, err := filepath.Abs(path)
|
||||
if err != nil {
|
||||
return "", errors.New("could not get absolute path")
|
||||
}
|
||||
|
||||
// Resolve symlinks, but allow non-existent paths
|
||||
resolvedPath, err := filepath.EvalSymlinks(absPath)
|
||||
if err == nil {
|
||||
return resolvedPath, nil
|
||||
}
|
||||
if os.IsNotExist(err) {
|
||||
// Return the absolute path for non-existent paths
|
||||
return absPath, nil
|
||||
}
|
||||
|
||||
return "", fmt.Errorf("could not resolve symlinks: %w", err)
|
||||
}
|
||||
|
||||
// Helper function to check if a symlink points to a directory
|
||||
func IsSymlinkToDir(path string) bool {
|
||||
fileInfo, err := os.Lstat(path)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
if fileInfo.Mode()&os.ModeSymlink != 0 {
|
||||
resolvedPath, err := filepath.EvalSymlinks(path)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
fileInfo, err = os.Stat(resolvedPath)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
return fileInfo.IsDir()
|
||||
}
|
||||
|
||||
return false // Regular directories should not be treated as symlinks
|
||||
}
|
||||
@@ -31,6 +31,15 @@ func (o *Chatter) Send(request *common.ChatRequest, opts *common.ChatOptions) (s
|
||||
return
|
||||
}
|
||||
|
||||
vendorMessages := session.GetVendorMessages()
|
||||
if len(vendorMessages) == 0 {
|
||||
if session.Name != "" {
|
||||
err = o.db.Sessions.SaveSession(session)
|
||||
}
|
||||
err = fmt.Errorf("no messages provided")
|
||||
return
|
||||
}
|
||||
|
||||
if opts.Model == "" {
|
||||
opts.Model = o.model
|
||||
}
|
||||
@@ -101,24 +110,28 @@ func (o *Chatter) BuildSession(request *common.ChatRequest, raw bool) (session *
|
||||
contextContent = ctx.Content
|
||||
}
|
||||
|
||||
var messageContent string
|
||||
// Process any template variables in the message content (user input)
|
||||
// Double curly braces {{variable}} indicate template substitution
|
||||
// should occur, whether in patterns or direct input
|
||||
if request.Message != nil {
|
||||
if request.Message.Content, err =
|
||||
template.ApplyTemplate(request.Message.Content, request.PatternVariables, ""); err != nil {
|
||||
return
|
||||
// Ensure we have a message before processing, other wise we'll get an error when we pass to pattern.go
|
||||
if request.Message == nil {
|
||||
request.Message = &goopenai.ChatCompletionMessage{
|
||||
Role: goopenai.ChatMessageRoleUser,
|
||||
Content: " ",
|
||||
}
|
||||
messageContent = request.Message.Content
|
||||
}
|
||||
|
||||
// Now we know request.Message is not nil, process template variables
|
||||
request.Message.Content, err = template.ApplyTemplate(request.Message.Content, request.PatternVariables, "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var patternContent string
|
||||
if request.PatternName != "" {
|
||||
var pattern *fsdb.Pattern
|
||||
if pattern, err = o.db.Patterns.GetApplyVariables(
|
||||
request.PatternName, request.PatternVariables, messageContent); err != nil {
|
||||
pattern, err := o.db.Patterns.GetApplyVariables(request.PatternName, request.PatternVariables, request.Message.Content)
|
||||
// pattrn will now contain user input, and all variables will be resolved, or errored
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not get pattern %s: %v", request.PatternName, err)
|
||||
}
|
||||
patternContent = pattern.Pattern
|
||||
|
||||
20
patterns/create_newsletter_entry/system.md
Normal file
20
patterns/create_newsletter_entry/system.md
Normal file
@@ -0,0 +1,20 @@
|
||||
# Identity and Purpose
|
||||
You are a custom GPT designed to create newsletter sections in the style of Frontend Weekly.
|
||||
|
||||
# Step-by-Step Process:
|
||||
1. The user will provide article text.
|
||||
2. Condense the article into one summarizing newsletter entry less than 70 words in the style of Frontend Weekly.
|
||||
3. Generate a concise title for the entry, focus on the main idea or most important fact of the article
|
||||
|
||||
# Tone and Style Guidelines:
|
||||
* Third-Party Narration: The newsletter should sound like it’s being narrated by an outside observer, someone who is both knowledgeable, unbiased and calm. Focus on the facts or main opinions in the original article. Creates a sense of objectivity and adds a layer of professionalism.
|
||||
|
||||
* Concise: Maintain brevity and clarity. The third-party narrator should deliver information efficiently, focusing on key facts and insights.
|
||||
|
||||
# Output Instructions:
|
||||
Your final output should be a polished, newsletter-ready paragraph with a title line in bold followed by the summary paragraph.
|
||||
|
||||
# INPUT:
|
||||
|
||||
INPUT:
|
||||
|
||||
0
patterns/create_newsletter_entry/user.md
Normal file
0
patterns/create_newsletter_entry/user.md
Normal file
@@ -1 +1 @@
|
||||
"1.4.115"
|
||||
"1.4.119"
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/danielmiessler/fabric/common"
|
||||
"github.com/danielmiessler/fabric/plugins/template"
|
||||
)
|
||||
|
||||
@@ -33,8 +34,16 @@ func (o *PatternsEntity) GetApplyVariables(
|
||||
strings.HasPrefix(source, ".")
|
||||
|
||||
if isFilePath {
|
||||
pattern, err = o.getFromFile(source)
|
||||
// Resolve the file path using GetAbsolutePath
|
||||
absPath, err := common.GetAbsolutePath(source)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not resolve file path: %v", err)
|
||||
}
|
||||
|
||||
// Use the resolved absolute path to get the pattern
|
||||
pattern, err = o.getFromFile(absPath)
|
||||
} else {
|
||||
// Otherwise, get the pattern from the database
|
||||
pattern, err = o.getFromDB(source)
|
||||
}
|
||||
|
||||
@@ -42,6 +51,7 @@ func (o *PatternsEntity) GetApplyVariables(
|
||||
return
|
||||
}
|
||||
|
||||
// Apply variables to the pattern
|
||||
err = o.applyVariables(pattern, variables, input)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -79,7 +79,7 @@ func TestApplyVariables(t *testing.T) {
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
result, err := entity.applyVariables(tt.pattern, tt.variables, tt.input)
|
||||
err := entity.applyVariables(tt.pattern, tt.variables, tt.input)
|
||||
|
||||
if tt.wantErr {
|
||||
assert.Error(t, err)
|
||||
@@ -87,7 +87,7 @@ func TestApplyVariables(t *testing.T) {
|
||||
}
|
||||
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, tt.want, result.Pattern)
|
||||
assert.Equal(t, tt.want, tt.pattern.Pattern)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -58,8 +58,7 @@ func (o *Session) Append(messages ...*goopenai.ChatCompletionMessage) {
|
||||
}
|
||||
|
||||
func (o *Session) GetVendorMessages() (ret []*goopenai.ChatCompletionMessage) {
|
||||
if o.vendorMessages == nil {
|
||||
o.vendorMessages = []*goopenai.ChatCompletionMessage{}
|
||||
if len(o.vendorMessages) == 0 {
|
||||
for _, message := range o.Messages {
|
||||
o.appendVendorMessage(message)
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ import (
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/samber/lo"
|
||||
"github.com/danielmiessler/fabric/common"
|
||||
)
|
||||
|
||||
type StorageEntity struct {
|
||||
@@ -26,37 +26,44 @@ func (o *StorageEntity) Configure() (err error) {
|
||||
|
||||
// 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
|
||||
// Resolve the directory path to an absolute path
|
||||
absDir, err := common.GetAbsolutePath(o.Dir)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not resolve directory path: %v", err)
|
||||
}
|
||||
|
||||
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()
|
||||
// Read the directory entries
|
||||
var entries []os.DirEntry
|
||||
if entries, err = os.ReadDir(absDir); err != nil {
|
||||
return nil, fmt.Errorf("could not read items from directory: %v", err)
|
||||
}
|
||||
|
||||
for _, entry := range entries {
|
||||
entryPath := filepath.Join(absDir, entry.Name())
|
||||
|
||||
// Get metadata for the entry, including symlink info
|
||||
fileInfo, err := os.Lstat(entryPath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not stat entry %s: %v", entryPath, err)
|
||||
}
|
||||
|
||||
// Determine if the entry should be included
|
||||
if o.ItemIsDir {
|
||||
// Include directories or symlinks to directories
|
||||
if fileInfo.IsDir() || (fileInfo.Mode()&os.ModeSymlink != 0 && common.IsSymlinkToDir(entryPath)) {
|
||||
ret = append(ret, entry.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)
|
||||
// Include files, optionally filtering by extension
|
||||
if !fileInfo.IsDir() {
|
||||
if o.FileExtension == "" || filepath.Ext(entry.Name()) == o.FileExtension {
|
||||
ret = append(ret, strings.TrimSuffix(entry.Name(), o.FileExtension))
|
||||
}
|
||||
return
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
func (o *StorageEntity) Delete(name string) (err error) {
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
package main
|
||||
|
||||
var version = "v1.4.115"
|
||||
var version = "v1.4.119"
|
||||
|
||||
Reference in New Issue
Block a user