mirror of
https://github.com/danielmiessler/Fabric.git
synced 2026-01-09 22:38:10 -05:00
Compare commits
64 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
44a6c03bc8 | ||
|
|
d794afe405 | ||
|
|
e4ac322227 | ||
|
|
1fc19da19f | ||
|
|
b213068680 | ||
|
|
bf3af207b9 | ||
|
|
e28ba224b5 | ||
|
|
5b7697c5ab | ||
|
|
0701b7d263 | ||
|
|
aac29025fb | ||
|
|
6928f9a312 | ||
|
|
ef2e985d3f | ||
|
|
1df945556d | ||
|
|
b6f313db8f | ||
|
|
cb00f2026e | ||
|
|
bd39d98ffc | ||
|
|
36b0afa692 | ||
|
|
53d09d8a5a | ||
|
|
703144edad | ||
|
|
717e50e570 | ||
|
|
4af6d5eeed | ||
|
|
de356ddeb5 | ||
|
|
59c50def2a | ||
|
|
1dafb09e07 | ||
|
|
e8caf9fc10 | ||
|
|
59537c4bf5 | ||
|
|
231516917d | ||
|
|
58d17fd0ec | ||
|
|
8bd4aa6d1a | ||
|
|
629c1b3e11 | ||
|
|
2f4569177d | ||
|
|
f2b85af0ea | ||
|
|
36be4c747c | ||
|
|
7ac9b862f5 | ||
|
|
1515139dd6 | ||
|
|
be6049e577 | ||
|
|
a8ae09d4d6 | ||
|
|
30059c46a0 | ||
|
|
2d10c71e39 | ||
|
|
02e12b028c | ||
|
|
6686b83fc8 | ||
|
|
d53b0b678f | ||
|
|
0d5454372e | ||
|
|
2f040f94c3 | ||
|
|
0b29ebd14b | ||
|
|
1dad903199 | ||
|
|
0bec53360e | ||
|
|
cf637e4137 | ||
|
|
9507c2cca1 | ||
|
|
fa575638d1 | ||
|
|
51220c40d9 | ||
|
|
d1d62fcc4c | ||
|
|
96e6a56e5f | ||
|
|
0d7514ea0e | ||
|
|
a74da4acff | ||
|
|
6d8c3eb6e2 | ||
|
|
adbfa2f6ba | ||
|
|
f5776637d9 | ||
|
|
34db384265 | ||
|
|
1f765c5b53 | ||
|
|
4725a94f00 | ||
|
|
15ac5351cf | ||
|
|
f69cda8fab | ||
|
|
a0e1f7204d |
6
.github/workflows/ci.yml
vendored
6
.github/workflows/ci.yml
vendored
@@ -22,6 +22,9 @@ jobs:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install Nix
|
||||
uses: DeterminateSystems/nix-installer-action@main
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v4
|
||||
with:
|
||||
@@ -29,3 +32,6 @@ jobs:
|
||||
|
||||
- name: Run tests
|
||||
run: go test -v ./...
|
||||
|
||||
- name: Check Formatting
|
||||
run: nix flake check
|
||||
|
||||
@@ -25,9 +25,6 @@ jobs:
|
||||
- name: Install Nix
|
||||
uses: DeterminateSystems/nix-installer-action@main
|
||||
|
||||
- name: Setup Nix Cache
|
||||
uses: DeterminateSystems/magic-nix-cache-action@main
|
||||
|
||||
- name: Set up Git
|
||||
run: |
|
||||
git config user.name "github-actions[bot]"
|
||||
@@ -63,21 +60,21 @@ jobs:
|
||||
|
||||
- name: Update version.nix file
|
||||
run: |
|
||||
echo "\"${{ env.new_version }}\"" > pkgs/fabric/version.nix
|
||||
echo "\"${{ env.new_version }}\"" > nix/pkgs/fabric/version.nix
|
||||
|
||||
- name: Format source codes
|
||||
- name: Format source code
|
||||
run: |
|
||||
go fmt ./...
|
||||
nix fmt
|
||||
|
||||
- name: Update gomod2nix.toml file
|
||||
run: |
|
||||
nix run .#gomod2nix
|
||||
nix run .#gomod2nix -- --outdir nix/pkgs/fabric
|
||||
|
||||
- name: Commit changes
|
||||
run: |
|
||||
git add version.go
|
||||
git add pkgs/fabric/version.nix
|
||||
git add gomod2nix.toml
|
||||
git add nix/pkgs/fabric/version.nix
|
||||
git add nix/pkgs/fabric/gomod2nix.toml
|
||||
git add .
|
||||
if ! git diff --staged --quiet; then
|
||||
git commit -m "Update version to ${{ env.new_tag }} and commit $commit_hash"
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
1700
Pattern_Descriptions/pattern_descriptions.json
Normal file
1700
Pattern_Descriptions/pattern_descriptions.json
Normal file
File diff suppressed because it is too large
Load Diff
41
README.md
41
README.md
@@ -204,8 +204,19 @@ for pattern_file in $HOME/.config/fabric/patterns/*; do
|
||||
done
|
||||
|
||||
yt() {
|
||||
if [ "$#" -eq 0 ] || [ "$#" -gt 2 ]; then
|
||||
echo "Usage: yt [-t | --timestamps] youtube-link"
|
||||
echo "Use the '-t' flag to get the transcript with timestamps."
|
||||
return 1
|
||||
fi
|
||||
|
||||
transcript_flag="--transcript"
|
||||
if [ "$1" = "-t" ] || [ "$1" = "--timestamps" ]; then
|
||||
transcript_flag="--transcript-with-timestamps"
|
||||
shift
|
||||
fi
|
||||
local video_link="$1"
|
||||
fabric -y "$video_link" --transcript
|
||||
fabric -y "$video_link" $transcript_flag
|
||||
}
|
||||
```
|
||||
|
||||
@@ -263,10 +274,34 @@ function $patternName {
|
||||
function yt {
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[Parameter(Mandatory = $true)]
|
||||
[Parameter()]
|
||||
[Alias("timestamps")]
|
||||
[switch]$t,
|
||||
|
||||
[Parameter(Position = 0, ValueFromPipeline = $true)]
|
||||
[string]$videoLink
|
||||
)
|
||||
fabric -y $videoLink --transcript
|
||||
|
||||
begin {
|
||||
$transcriptFlag = "--transcript"
|
||||
if ($t) {
|
||||
$transcriptFlag = "--transcript-with-timestamps"
|
||||
}
|
||||
}
|
||||
|
||||
process {
|
||||
if (-not $videoLink) {
|
||||
Write-Error "Usage: yt [-t | --timestamps] youtube-link"
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
end {
|
||||
if ($videoLink) {
|
||||
# Execute and allow output to flow through the pipeline
|
||||
fabric -y $videoLink $transcriptFlag
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
203
Web Interface MOD Readme Files/WEB V2 Install Guide.md
Normal file
203
Web Interface MOD Readme Files/WEB V2 Install Guide.md
Normal file
@@ -0,0 +1,203 @@
|
||||
# How to Install the Web Interface and PDF-to-Markdown
|
||||
|
||||
If Fabric is already installed and you see fabric/web, go to step 3
|
||||
|
||||
If fabric is not installed, ensure Go is installed https://go.dev/doc/install and node / npm for web https://nodejs.org/en/download.
|
||||
|
||||
There are many ways to install fabric. Here's one approach that usually works well:
|
||||
|
||||
## Step 1: clone the repo
|
||||
In terminal, from the parent directory where you want to install fabric:
|
||||
git clone https://github.com/danielmiessler/fabric.git
|
||||
|
||||
## Step 2 : Install Fabric
|
||||
cd fabric
|
||||
go install github.com/danielmiessler/fabric@latest
|
||||
|
||||
## Step 3: Install GUI
|
||||
Navigate to the web directory and install dependencies:
|
||||
|
||||
cd web
|
||||
|
||||
npm install
|
||||
|
||||
npx svelte-kit sync
|
||||
|
||||
## Step 4: Install PDF-to-Markdown
|
||||
Install the PDF conversion components in the correct order:
|
||||
cd web
|
||||
# Install dependencies in this specific order
|
||||
|
||||
npm install -D patch-package
|
||||
|
||||
npm install -D pdfjs-dist@2.5.207
|
||||
|
||||
npm install -D github:jzillmann/pdf-to-markdown#modularize
|
||||
|
||||
|
||||
No build step is required after installation.
|
||||
|
||||
## Step 5: Update Shell Configuration if not already done from your fabric installation
|
||||
For Mac/Linux users:
|
||||
|
||||
Add environment variables to your ~/.bashrc (Linux) or ~/.zshrc (Mac) file:
|
||||
|
||||
# For Intel-based Macs or Linux
|
||||
export GOROOT=/usr/local/go
|
||||
export GOPATH=$HOME/go
|
||||
export PATH=$GOPATH/bin:$GOROOT/bin:$HOME/.local/bin:$PATH
|
||||
|
||||
# For Apple Silicon Macs
|
||||
export GOROOT=$(brew --prefix go)/libexec
|
||||
export GOPATH=$HOME/go
|
||||
export PATH=$GOPATH/bin:$GOROOT/bin:$HOME/.local/bin:$PATH
|
||||
|
||||
REFER TO OFFICIAL FABRIC README.MD FILE FOR OTHER OPERATING SYSTEMS
|
||||
|
||||
Step 5: Create Aliases for Patterns
|
||||
Add the following to your .zshrc or .bashrc file to create shorter commands:
|
||||
|
||||
```bash
|
||||
# Define the base directory for Obsidian notes,
|
||||
obsidian_base="/path/to/obsidian"
|
||||
|
||||
# Loop through all files in the ~/.config/fabric/patterns directory
|
||||
|
||||
for pattern_file in ~/.config/fabric/patterns/*; do
|
||||
# Get the base name of the file
|
||||
pattern_name=$(basename "$pattern_file")
|
||||
|
||||
# Unalias any existing alias with the same name
|
||||
unalias "$pattern_name" 2>/dev/null
|
||||
|
||||
# Define a function dynamically for each pattern
|
||||
eval "
|
||||
$pattern_name() {
|
||||
local title=\$1
|
||||
local date_stamp=\$(date +'%Y-%m-%d')
|
||||
local output_path=\"\$obsidian_base/\${date_stamp}-\${title}.md\"
|
||||
|
||||
# Check if a title was provided
|
||||
if [ -n \"\$title\" ]; then
|
||||
# If a title is provided, use the output path
|
||||
fabric --pattern \"$pattern_name\" -o \"\$output_path\"
|
||||
else
|
||||
# If no title is provided, use --stream
|
||||
fabric --pattern \"$pattern_name\" --stream
|
||||
fi
|
||||
}
|
||||
"
|
||||
done
|
||||
|
||||
# YouTube shortcut function
|
||||
yt() {
|
||||
local video_link="$1"
|
||||
fabric -y "$video_link" --transcript
|
||||
}
|
||||
|
||||
|
||||
After modifying your shell configuration file, apply the changes:
|
||||
|
||||
source ~/.zshrc # or source ~/.bashrc for Linux
|
||||
|
||||
Step 6: Run Fabric Setup
|
||||
Initialize fabric configuration:
|
||||
|
||||
fabric --setup
|
||||
|
||||
Step 7: Launch the Web Interface
|
||||
Open two terminal windows and navigate to the web folder:
|
||||
|
||||
Terminal 1: Start the Fabric API Server
|
||||
fabric --serve
|
||||
|
||||
Terminal 2: Start the Development Server
|
||||
npm run dev
|
||||
|
||||
|
||||
If you get an ** ERROR **.
|
||||
It would be much appreciated that you copy /paste your error in your favorite LLM before opening a ticket, 90% of the time your llm will point you to the solution.
|
||||
|
||||
_____ ______ ______
|
||||
|
||||
OPTIONAL: Create Start/Stop Scripts
|
||||
You can create scripts to start/stop both servers at once.
|
||||
|
||||
### For Mac Users
|
||||
When creating scripts on Mac using TextEdit:
|
||||
|
||||
1. Open TextEdit
|
||||
2. **IMPORTANT:** Select "Format > Make Plain Text" from the menu BEFORE pasting any code
|
||||
3. Paste the script content, follow instructions below ((Mac example)).
|
||||
|
||||
|
||||
### For Windows Users
|
||||
When creating scripts on Windows:
|
||||
|
||||
1. Use Notepad or a code editor like VS Code
|
||||
2. Paste the script content
|
||||
3. Save the file with the appropriate extension
|
||||
4. Ensure line endings are set to LF (not CRLF) for bash scripts
|
||||
|
||||
ACTUAL SCRIPTS (Mac example)
|
||||
|
||||
Start Script
|
||||
1. Create a new file named start-fabric.command on your Desktop:
|
||||
|
||||
#!/bin/bash
|
||||
|
||||
# Change to the fabric web directory
|
||||
cd "$HOME/Documents/Github/fabric/web"
|
||||
|
||||
# Start fabric serve in the background
|
||||
osascript -e 'tell application "Terminal" to do script "cd '$HOME'/Documents/Github/fabric/web && fabric --serve; exit"'
|
||||
|
||||
# Wait a moment to ensure the fabric server starts
|
||||
sleep 2
|
||||
|
||||
# Start npm development server in a new terminal
|
||||
osascript -e 'tell application "Terminal" to do script "cd '$HOME'/Documents/Github/fabric/web && npm run dev; exit"'
|
||||
|
||||
# Close this script's terminal window after starting servers
|
||||
echo "Fabric servers started!"
|
||||
sleep 1
|
||||
osascript -e 'tell application "Terminal" to close (every window whose name contains ".command")' &
|
||||
exit
|
||||
|
||||
Stop Script
|
||||
|
||||
2. Create a new file named stop-fabric.command on your Desktop:
|
||||
|
||||
#!/bin/bash
|
||||
|
||||
# Kill the npm dev server
|
||||
pkill -f "node.*dev"
|
||||
|
||||
# Kill the fabric server
|
||||
pkill -f "fabric --serve"
|
||||
|
||||
# Force quit Terminal entirely and restart it
|
||||
osascript <<EOD
|
||||
tell application "Terminal" to quit
|
||||
delay 1
|
||||
tell application "Terminal" to activate
|
||||
EOD
|
||||
|
||||
echo "Fabric servers stopped!"
|
||||
sleep 1
|
||||
|
||||
# This script's terminal will already be closed by the quit command above
|
||||
exit
|
||||
|
||||
3. Make both scripts executable:
|
||||
chmod +x ~/Desktop/start-fabric.command
|
||||
chmod +x ~/Desktop/stop-fabric.command
|
||||
|
||||
You can customize with icons by finding suitable .icns files, right-clicking each .command file, selecting "Get Info", and dragging your icon file onto the small icon in the top-left corner.
|
||||
|
||||
Note: You might need to allow the scripts to execute in your security settings by going to System Preferences → Security & Privacy after trying to run them the first time.
|
||||
|
||||
|
||||
|
||||
## 🎥 Demo Video
|
||||
https://youtu.be/XMzjgqvdltM
|
||||
@@ -1,21 +1,20 @@
|
||||
https://youtu.be/bhwtWXoMASA# Enhanced Pattern Selection,
|
||||
Pattern Descriptions, New
|
||||
Pattern TAG System, Language Support and other WEB UI Improvements V3
|
||||
|
||||
This Cummulative PR adds several Web UI and functionality improvements to make pattern selection more intuitive (pattern descriptions), ability to save favorite patterns, powerful multilingual capabilities, a Pattern TAG system, a help reference section, more robust Youtube processing and a variety of ui improvements.
|
||||
This Cummulative PR adds several Web UI and functionality improvements to make pattern selection more intuitive with the addition of pattern descriptions, ability to save favorite patterns, a Pattern TAG system, powerful multilingual capabilities, PDF-to-markdown functionnalities, a help reference section, more robust Youtube processing and a variety of other ui improvements.
|
||||
|
||||
## 🎥 Demo Video
|
||||
https://youtu.be/IhE8Iey8hSU
|
||||
https://youtu.be/XMzjgqvdltM
|
||||
|
||||
|
||||
|
||||
## 🌟 Key Features
|
||||
|
||||
### 1. Web UI and Pattern Selection Improvements
|
||||
- Enhanced pattern selection interface for better user experience
|
||||
- New pattern descriptions section accessible via modal
|
||||
- New pattern favorite list and pattern search functionnality
|
||||
- New Tag system for better pattern organization and filtering
|
||||
- Pattern Descriptions
|
||||
- Pattern Tags
|
||||
- Pattern Favourites
|
||||
- Pattern Search bar
|
||||
- PDF to markdown (pdf as pattern input)
|
||||
- Better handling of Youtube url
|
||||
- Multilingual Support
|
||||
- Web UI refinements for clearer interaction
|
||||
- Help section via modal
|
||||
|
||||
@@ -57,6 +56,31 @@ The tag filtering system has been deeply integrated into the Pattern Selection i
|
||||
- Maintained accessibility standards
|
||||
- Responsive design considerations
|
||||
|
||||
|
||||
5. **PDF to Markdown conversion functionality for the web interface**
|
||||
- Automatic detection and processing of PDF files in chat
|
||||
- Conversion to markdown format for LLM processing
|
||||
- Installation instructions from the pdf-to-markdown repository
|
||||
|
||||
The PDF conversion module has been integrated in the svelte web browser interface. Once installed, it will automatically detect pdf files in the chat interface and convert them to markdown
|
||||
|
||||
|
||||
## HOW TO INSTALL PDF-TO-MARKDOWN
|
||||
If you need to update the web component follow the instructions in "Web Interface MOD Readme Files/WEB V2 Install Guide.md".
|
||||
|
||||
Assuming your web install is up to date and web svelte config complete, you can simply follow these steps to add Pdf-to-mardown.
|
||||
|
||||
# FROM FABRIC ROOT DIRECTORY
|
||||
cd .. web
|
||||
|
||||
# Install in this sequence:
|
||||
# Step 1
|
||||
npm install -D patch-package
|
||||
# Step 2
|
||||
npm install -D pdfjs-dist@2.5.207
|
||||
# Step 3
|
||||
npm install -D github:jzillmann/pdf-to-markdown#modularize
|
||||
|
||||
These enhancements create a more intuitive and efficient pattern discovery experience, allowing users to quickly filter and find relevant patterns while maintaining a clean, modern interface.
|
||||
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"github.com/danielmiessler/fabric/core"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/danielmiessler/fabric/core"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
|
||||
29
cli/flags.go
29
cli/flags.go
@@ -10,12 +10,11 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/danielmiessler/fabric/common"
|
||||
"github.com/jessevdk/go-flags"
|
||||
goopenai "github.com/sashabaranov/go-openai"
|
||||
"golang.org/x/text/language"
|
||||
"gopkg.in/yaml.v2"
|
||||
|
||||
"github.com/danielmiessler/fabric/common"
|
||||
)
|
||||
|
||||
// Flags create flags struct. the users flags go into this, this will be passed to the chat struct in cli
|
||||
@@ -115,14 +114,14 @@ func Init() (ret *Flags, err error) {
|
||||
parser := flags.NewParser(ret, flags.Default)
|
||||
var args []string
|
||||
if args, err = parser.Parse(); err != nil {
|
||||
return nil, err
|
||||
return
|
||||
}
|
||||
|
||||
// If config specified, load and apply YAML for unused flags
|
||||
if ret.Config != "" {
|
||||
yamlFlags, err := loadYAMLConfig(ret.Config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
var yamlFlags *Flags
|
||||
if yamlFlags, err = loadYAMLConfig(ret.Config); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Apply YAML values where CLI flags weren't used
|
||||
@@ -152,6 +151,7 @@ func Init() (ret *Flags, err error) {
|
||||
}
|
||||
}
|
||||
|
||||
// Handle stdin and messages
|
||||
// Handle stdin and messages
|
||||
info, _ := os.Stdin.Stat()
|
||||
pipedToStdin := (info.Mode() & os.ModeCharDevice) == 0
|
||||
@@ -168,8 +168,7 @@ func Init() (ret *Flags, err error) {
|
||||
}
|
||||
ret.Message = AppendMessage(ret.Message, pipedMessage)
|
||||
}
|
||||
|
||||
return ret, nil
|
||||
return
|
||||
}
|
||||
|
||||
func assignWithConversion(targetField, sourceField reflect.Value) error {
|
||||
@@ -235,17 +234,19 @@ func readStdin() (ret string, err error) {
|
||||
reader := bufio.NewReader(os.Stdin)
|
||||
var sb strings.Builder
|
||||
for {
|
||||
line, err := reader.ReadString('\n')
|
||||
if err != nil {
|
||||
if errors.Is(err, io.EOF) {
|
||||
if line, readErr := reader.ReadString('\n'); readErr != nil {
|
||||
if errors.Is(readErr, io.EOF) {
|
||||
sb.WriteString(line)
|
||||
break
|
||||
}
|
||||
return "", fmt.Errorf("error reading piped message from stdin: %w", err)
|
||||
err = fmt.Errorf("error reading piped message from stdin: %w", readErr)
|
||||
return
|
||||
} else {
|
||||
sb.WriteString(line)
|
||||
}
|
||||
sb.WriteString(line)
|
||||
}
|
||||
return sb.String(), nil
|
||||
ret = sb.String()
|
||||
return
|
||||
}
|
||||
|
||||
func (o *Flags) BuildChatOptions() (ret *common.ChatOptions) {
|
||||
|
||||
@@ -2,8 +2,9 @@ package cli
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/atotto/clipboard"
|
||||
"os"
|
||||
|
||||
"github.com/atotto/clipboard"
|
||||
)
|
||||
|
||||
func CopyToClipboard(message string) (err error) {
|
||||
|
||||
@@ -6,11 +6,12 @@ import (
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/gabriel-vasile/mimetype"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/gabriel-vasile/mimetype"
|
||||
)
|
||||
|
||||
type Attachment struct {
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
goopenai "github.com/sashabaranov/go-openai"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestNormalizeMessages(t *testing.T) {
|
||||
|
||||
@@ -2,6 +2,7 @@ package common
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/samber/lo"
|
||||
)
|
||||
|
||||
|
||||
@@ -3,11 +3,12 @@ package core
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"github.com/danielmiessler/fabric/plugins/ai/exolab"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
|
||||
"github.com/danielmiessler/fabric/plugins/ai/exolab"
|
||||
|
||||
"github.com/samber/lo"
|
||||
|
||||
"github.com/danielmiessler/fabric/common"
|
||||
@@ -19,6 +20,7 @@ import (
|
||||
"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/litellm"
|
||||
"github.com/danielmiessler/fabric/plugins/ai/lmstudio"
|
||||
"github.com/danielmiessler/fabric/plugins/ai/mistral"
|
||||
"github.com/danielmiessler/fabric/plugins/ai/ollama"
|
||||
@@ -52,11 +54,22 @@ func NewPluginRegistry(db *fsdb.Db) (ret *PluginRegistry, err error) {
|
||||
|
||||
ret.Defaults = tools.NeeDefaults(ret.GetModels)
|
||||
|
||||
ret.VendorsAll.AddVendors(openai.NewClient(), ollama.NewClient(), azure.NewClient(), groq.NewClient(),
|
||||
ret.VendorsAll.AddVendors(
|
||||
openai.NewClient(),
|
||||
ollama.NewClient(),
|
||||
azure.NewClient(),
|
||||
groq.NewClient(),
|
||||
gemini.NewClient(),
|
||||
//gemini_openai.NewClient(),
|
||||
anthropic.NewClient(), siliconcloud.NewClient(),
|
||||
openrouter.NewClient(), lmstudio.NewClient(), mistral.NewClient(), deepseek.NewClient(), exolab.NewClient())
|
||||
anthropic.NewClient(),
|
||||
siliconcloud.NewClient(),
|
||||
openrouter.NewClient(),
|
||||
lmstudio.NewClient(),
|
||||
mistral.NewClient(),
|
||||
deepseek.NewClient(),
|
||||
exolab.NewClient(),
|
||||
litellm.NewClient(),
|
||||
)
|
||||
_ = ret.Configure()
|
||||
|
||||
return
|
||||
|
||||
18
flake.lock
generated
18
flake.lock
generated
@@ -26,11 +26,11 @@
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1729448365,
|
||||
"narHash": "sha256-oquZeWTYWTr5IxfwEzgsxjtD8SSFZYLdO9DaQb70vNU=",
|
||||
"lastModified": 1733668782,
|
||||
"narHash": "sha256-tPsqU00FhgdFr0JiQUiBMgPVbl1jbPCY5gbFiJycL3I=",
|
||||
"owner": "nix-community",
|
||||
"repo": "gomod2nix",
|
||||
"rev": "5d387097aa716f35dd99d848dc26d8d5b62a104c",
|
||||
"rev": "514283ec89c39ad0079ff2f3b1437404e4cba608",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -41,11 +41,11 @@
|
||||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1729665710,
|
||||
"narHash": "sha256-AlcmCXJZPIlO5dmFzV3V2XF6x/OpNWUV8Y/FMPGd8Z4=",
|
||||
"lastModified": 1736344531,
|
||||
"narHash": "sha256-8YVQ9ZbSfuUk2bUf2KRj60NRraLPKPS0Q4QFTbc+c2c=",
|
||||
"owner": "nixos",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "2768c7d042a37de65bb1b5b3268fc987e534c49d",
|
||||
"rev": "bffc22eb12172e6db3c5dde9e3e5628f8e3e7912",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -100,11 +100,11 @@
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1729613947,
|
||||
"narHash": "sha256-XGOvuIPW1XRfPgHtGYXd5MAmJzZtOuwlfKDgxX5KT3s=",
|
||||
"lastModified": 1736154270,
|
||||
"narHash": "sha256-p2r8xhQZ3TYIEKBoiEhllKWQqWNJNoT9v64Vmg4q8Zw=",
|
||||
"owner": "numtide",
|
||||
"repo": "treefmt-nix",
|
||||
"rev": "aac86347fb5063960eccb19493e0cadcdb4205ca",
|
||||
"rev": "13c913f5deb3a5c08bb810efd89dc8cb24dd968b",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
||||
@@ -33,7 +33,7 @@
|
||||
let
|
||||
pkgs = nixpkgs.legacyPackages.${system};
|
||||
in
|
||||
treefmt-nix.lib.evalModule pkgs ./treefmt.nix
|
||||
treefmt-nix.lib.evalModule pkgs ./nix/treefmt.nix
|
||||
);
|
||||
in
|
||||
{
|
||||
@@ -49,7 +49,7 @@
|
||||
pkgs = nixpkgs.legacyPackages.${system};
|
||||
goEnv = gomod2nix.legacyPackages.${system}.mkGoEnv { pwd = ./.; };
|
||||
in
|
||||
import ./shell.nix {
|
||||
import ./nix/shell.nix {
|
||||
inherit pkgs goEnv;
|
||||
inherit (gomod2nix.legacyPackages.${system}) gomod2nix;
|
||||
}
|
||||
@@ -62,7 +62,7 @@
|
||||
in
|
||||
{
|
||||
default = self.packages.${system}.fabric;
|
||||
fabric = pkgs.callPackage ./pkgs/fabric {
|
||||
fabric = pkgs.callPackage ./nix/pkgs/fabric {
|
||||
inherit (gomod2nix.legacyPackages.${system}) buildGoApplication;
|
||||
};
|
||||
inherit (gomod2nix.legacyPackages.${system}) gomod2nix;
|
||||
|
||||
2
go.mod
2
go.mod
@@ -19,6 +19,7 @@ require (
|
||||
github.com/samber/lo v1.49.1
|
||||
github.com/sashabaranov/go-openai v1.38.0
|
||||
github.com/stretchr/testify v1.10.0
|
||||
golang.org/x/term v0.29.0
|
||||
golang.org/x/text v0.22.0
|
||||
google.golang.org/api v0.223.0
|
||||
gopkg.in/yaml.v2 v2.4.0
|
||||
@@ -41,7 +42,6 @@ require (
|
||||
github.com/bytedance/sonic/loader v0.2.3 // indirect
|
||||
github.com/cloudflare/circl v1.6.0 // indirect
|
||||
github.com/cloudwego/base64x v0.1.5 // indirect
|
||||
github.com/cloudwego/iasm v0.2.0 // indirect
|
||||
github.com/cyphar/filepath-securejoin v0.4.1 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/emirpasic/gods v1.18.1 // indirect
|
||||
|
||||
99
go.sum
99
go.sum
@@ -1,21 +1,13 @@
|
||||
cloud.google.com/go v0.116.0 h1:B3fRrSDkLRt5qSHWe40ERJvhvnQwdZiHu0bJOpldweE=
|
||||
cloud.google.com/go v0.116.0/go.mod h1:cEPSRWPzZEswwdr9BxE6ChEn01dWlTaF05LiC2Xs70U=
|
||||
cloud.google.com/go v0.118.3 h1:jsypSnrE/w4mJysioGdMBg4MiW/hHx/sArFpaBWHdME=
|
||||
cloud.google.com/go v0.118.3/go.mod h1:Lhs3YLnBlwJ4KA6nuObNMZ/fCbOQBPuWKPoE0Wa/9Vc=
|
||||
cloud.google.com/go/ai v0.8.0 h1:rXUEz8Wp2OlrM8r1bfmpF2+VKqc1VJpafE3HgzRnD/w=
|
||||
cloud.google.com/go/ai v0.8.0/go.mod h1:t3Dfk4cM61sytiggo2UyGsDVW3RF1qGZaUKDrZFyqkE=
|
||||
cloud.google.com/go/ai v0.10.0 h1:hwj6CI6sMKubXodoJJGTy/c2T1RbbLGM6TL3QoAvzU8=
|
||||
cloud.google.com/go/ai v0.10.0/go.mod h1:kvnt2KeHqX8+41PVeMRBETDyQAp/RFvBWGdx/aGjNMo=
|
||||
cloud.google.com/go/auth v0.14.1 h1:AwoJbzUdxA/whv1qj3TLKwh3XX5sikny2fc40wUl+h0=
|
||||
cloud.google.com/go/auth v0.14.1/go.mod h1:4JHUxlGXisL0AW8kXPtUF6ztuOksyfUQNFjfsOCXkPM=
|
||||
cloud.google.com/go/auth v0.15.0 h1:Ly0u4aA5vG/fsSsxu98qCQBemXtAtJf+95z9HK+cxps=
|
||||
cloud.google.com/go/auth v0.15.0/go.mod h1:WJDGqZ1o9E9wKIL+IwStfyn/+s59zl4Bi+1KQNVXLZ8=
|
||||
cloud.google.com/go/auth/oauth2adapt v0.2.7 h1:/Lc7xODdqcEw8IrZ9SvwnlLX6j9FHQM74z6cBk9Rw6M=
|
||||
cloud.google.com/go/auth/oauth2adapt v0.2.7/go.mod h1:NTbTTzfvPl1Y3V1nPpOgl2w6d/FjO7NNUQaWSox6ZMc=
|
||||
cloud.google.com/go/compute/metadata v0.6.0 h1:A6hENjEsCDtC1k8byVsgwvVcioamEHvZ4j01OwKxG9I=
|
||||
cloud.google.com/go/compute/metadata v0.6.0/go.mod h1:FjyFAW1MW0C203CEOMDTu3Dk1FlqW3Rga40jzHL4hfg=
|
||||
cloud.google.com/go/longrunning v0.5.7 h1:WLbHekDbjK1fVFD3ibpFFVoyizlLRl73I7YKuAKilhU=
|
||||
cloud.google.com/go/longrunning v0.5.7/go.mod h1:8GClkudohy1Fxm3owmBGid8W0pSgodEMwEAztp38Xng=
|
||||
cloud.google.com/go/longrunning v0.6.4 h1:3tyw9rO3E2XVXzSApn1gyEEnH2K9SynNQjMlBi3uHLg=
|
||||
cloud.google.com/go/longrunning v0.6.4/go.mod h1:ttZpLCe6e7EXvn9OxpBRx7kZEB0efv8yBO6YnVMfhJs=
|
||||
dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s=
|
||||
@@ -23,67 +15,45 @@ dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
|
||||
github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY=
|
||||
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
|
||||
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
|
||||
github.com/ProtonMail/go-crypto v1.1.3 h1:nRBOetoydLeUb4nHajyO2bKqMLfWQ/ZPwkXqXxPxCFk=
|
||||
github.com/ProtonMail/go-crypto v1.1.3/go.mod h1:rA3QumHc/FZ8pAHreoekgiAbzpNsfQAosU5td4SnOrE=
|
||||
github.com/ProtonMail/go-crypto v1.1.5 h1:eoAQfK2dwL+tFSFpr7TbOaPNUbPiJj4fLYwwGE1FQO4=
|
||||
github.com/ProtonMail/go-crypto v1.1.5/go.mod h1:rA3QumHc/FZ8pAHreoekgiAbzpNsfQAosU5td4SnOrE=
|
||||
github.com/anaskhan96/soup v1.2.5 h1:V/FHiusdTrPrdF4iA1YkVxsOpdNcgvqT1hG+YtcZ5hM=
|
||||
github.com/anaskhan96/soup v1.2.5/go.mod h1:6YnEp9A2yywlYdM4EgDz9NEHclocMepEtku7wg6Cq3s=
|
||||
github.com/andybalholm/cascadia v1.3.2 h1:3Xi6Dw5lHF15JtdcmAHD3i1+T8plmv7BQ/nsViSLyss=
|
||||
github.com/andybalholm/cascadia v1.3.2/go.mod h1:7gtRlve5FxPPgIgX36uWBX58OdBsSS6lUvCFb+h7KvU=
|
||||
github.com/andybalholm/cascadia v1.3.3 h1:AG2YHrzJIm4BZ19iwJ/DAua6Btl3IwJX+VI4kktS1LM=
|
||||
github.com/andybalholm/cascadia v1.3.3/go.mod h1:xNd9bqTn98Ln4DwST8/nG+H0yuB8Hmgu1YHNnWw0GeA=
|
||||
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8=
|
||||
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4=
|
||||
github.com/anthropics/anthropic-sdk-go v0.2.0-alpha.4 h1:TdGQS+RoR4AUO6gqUL74yK1dz/Arrt/WG+dxOj6Yo6A=
|
||||
github.com/anthropics/anthropic-sdk-go v0.2.0-alpha.4/go.mod h1:GJxtdOs9K4neo8Gg65CjJ7jNautmldGli5/OFNabOoo=
|
||||
github.com/anthropics/anthropic-sdk-go v0.2.0-alpha.11 h1:O3/AMObKntZyu1KH6Xks6E0gbE8w6HVaKHE+/vXARzM=
|
||||
github.com/anthropics/anthropic-sdk-go v0.2.0-alpha.11/go.mod h1:GJxtdOs9K4neo8Gg65CjJ7jNautmldGli5/OFNabOoo=
|
||||
github.com/apache/arrow/go/arrow v0.0.0-20211112161151-bc219186db40/go.mod h1:Q7yQnSMnLvcXlZ8RV+jwz/6y1rQTqbX6C82SndT52Zs=
|
||||
github.com/araddon/dateparse v0.0.0-20210429162001-6b43995a97de h1:FxWPpzIjnTlhPwqqXc4/vE0f7GvRjuAsbW+HOIe8KnA=
|
||||
github.com/araddon/dateparse v0.0.0-20210429162001-6b43995a97de/go.mod h1:DCaWoUhZrYW9p1lxo/cm8EmUOOzAPSEZNGF2DK1dJgw=
|
||||
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
|
||||
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
|
||||
github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4=
|
||||
github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI=
|
||||
github.com/bytedance/sonic v1.12.4 h1:9Csb3c9ZJhfUWeMtpCDCq6BUoH5ogfDFLUgQ/jG+R0k=
|
||||
github.com/bytedance/sonic v1.12.4/go.mod h1:B8Gt/XvtZ3Fqj+iSKMypzymZxw/FVwgIGKzMzT9r/rk=
|
||||
github.com/bytedance/sonic v1.12.9 h1:Od1BvK55NnewtGaJsTDeAOSnLVO2BTSLOe0+ooKokmQ=
|
||||
github.com/bytedance/sonic v1.12.9/go.mod h1:uVvFidNmlt9+wa31S1urfwwthTWteBgG0hWuoKAXTx8=
|
||||
github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
|
||||
github.com/bytedance/sonic/loader v0.2.1 h1:1GgorWTqf12TA8mma4DDSbaQigE2wOgQo7iCjjJv3+E=
|
||||
github.com/bytedance/sonic/loader v0.2.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
|
||||
github.com/bytedance/sonic/loader v0.2.3 h1:yctD0Q3v2NOGfSWPLPvG2ggA2kV6TS6s4wioyEqssH0=
|
||||
github.com/bytedance/sonic/loader v0.2.3/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI=
|
||||
github.com/cloudflare/circl v1.5.0 h1:hxIWksrX6XN5a1L2TI/h53AGPhNHoUBo+TD1ms9+pys=
|
||||
github.com/cloudflare/circl v1.5.0/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs=
|
||||
github.com/cloudflare/circl v1.6.0 h1:cr5JKic4HI+LkINy2lg3W2jF8sHCVTBncJr5gIIq7qk=
|
||||
github.com/cloudflare/circl v1.6.0/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs=
|
||||
github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/0Y=
|
||||
github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w=
|
||||
github.com/cloudwego/base64x v0.1.5 h1:XPciSp1xaq2VCSt6lF0phncD4koWyULpl5bUxbfCyP4=
|
||||
github.com/cloudwego/base64x v0.1.5/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w=
|
||||
github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg=
|
||||
github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY=
|
||||
github.com/cyphar/filepath-securejoin v0.3.4 h1:VBWugsJh2ZxJmLFSM06/0qzQyiQX2Qs0ViKrUAcqdZ8=
|
||||
github.com/cyphar/filepath-securejoin v0.3.4/go.mod h1:8s/MCNJREmFK0H02MF6Ihv1nakJe4L/w3WZLHNkvlYM=
|
||||
github.com/cyphar/filepath-securejoin v0.4.1 h1:JyxxyPEaktOD+GAnqIqTf9A8tHyAG22rowi7HkoSU1s=
|
||||
github.com/cyphar/filepath-securejoin v0.4.1/go.mod h1:Sdj7gXlvMcPZsbhwhQ33GguGLDGQL7h7bg04C/+u9jI=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/elazarl/goproxy v1.2.1 h1:njjgvO6cRG9rIqN2ebkqy6cQz2Njkx7Fsfv/zIZqgug=
|
||||
github.com/elazarl/goproxy v1.2.1/go.mod h1:YfEbZtqP4AetfO6d40vWchF3znWX7C7Vd6ZMfdL8z64=
|
||||
github.com/elazarl/goproxy v1.4.0 h1:4GyuSbFa+s26+3rmYNSuUVsx+HgPrV1bk1jXI0l9wjM=
|
||||
github.com/elazarl/goproxy v1.4.0/go.mod h1:X/5W/t+gzDyLfHW4DrMdpjqYjpXsURlBt9lpBDxZZZQ=
|
||||
github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=
|
||||
github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ=
|
||||
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
|
||||
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
|
||||
github.com/gabriel-vasile/mimetype v1.4.6 h1:3+PzJTKLkvgjeTbts6msPJt4DixhT4YtFNf1gtGe3zc=
|
||||
github.com/gabriel-vasile/mimetype v1.4.6/go.mod h1:JX1qVKqZd40hUPpAfiNTe0Sne7hdfKSbOqqmkq8GCXc=
|
||||
github.com/gabriel-vasile/mimetype v1.4.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3GqacKw1NM=
|
||||
github.com/gabriel-vasile/mimetype v1.4.8/go.mod h1:ByKUIKGjh1ODkGM1asKUbQZOLGrPjydw3hYPU2YU9t8=
|
||||
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
|
||||
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
|
||||
github.com/gin-contrib/sse v1.0.0 h1:y3bT1mUWUxDpW4JLQg/HnTqV4rozuW4tC9eFKTxYI9E=
|
||||
github.com/gin-contrib/sse v1.0.0/go.mod h1:zNuFdwarAygJBht0NTKiSi3jRf6RbqeILZ9Sp6Slhe0=
|
||||
github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU=
|
||||
@@ -92,14 +62,10 @@ github.com/gliderlabs/ssh v0.3.8 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c=
|
||||
github.com/gliderlabs/ssh v0.3.8/go.mod h1:xYoytBv1sV0aL3CavoDuJIQNURXkkfPA/wxQ1pL1fAU=
|
||||
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI=
|
||||
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic=
|
||||
github.com/go-git/go-billy/v5 v5.6.0 h1:w2hPNtoehvJIxR00Vb4xX94qHQi/ApZfX+nBE2Cjio8=
|
||||
github.com/go-git/go-billy/v5 v5.6.0/go.mod h1:sFDq7xD3fn3E0GOwUSZqHo9lrkmx8xJhA0ZrfvjBRGM=
|
||||
github.com/go-git/go-billy/v5 v5.6.2 h1:6Q86EsPXMa7c3YZ3aLAQsMA0VlWmy43r6FHqa/UNbRM=
|
||||
github.com/go-git/go-billy/v5 v5.6.2/go.mod h1:rcFC2rAsp/erv7CMz9GczHcuD0D32fWzH+MJAU+jaUU=
|
||||
github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4=
|
||||
github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII=
|
||||
github.com/go-git/go-git/v5 v5.13.0 h1:vLn5wlGIh/X78El6r3Jr+30W16Blk0CTcxTYcYPWi5E=
|
||||
github.com/go-git/go-git/v5 v5.13.0/go.mod h1:Wjo7/JyVKtQgUNdXYXIepzWfJQkUEIGvkvVkiXRR/zw=
|
||||
github.com/go-git/go-git/v5 v5.13.2 h1:7O7xvsK7K+rZPKW6AQR1YyNhfywkv7B8/FsP3ki6Zv0=
|
||||
github.com/go-git/go-git/v5 v5.13.2/go.mod h1:hWdW5P4YZRjmpGHwRH2v3zkWcNl6HeXaXQEMGb3NJ9A=
|
||||
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||
@@ -113,33 +79,25 @@ github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/o
|
||||
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
|
||||
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
|
||||
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
|
||||
github.com/go-playground/validator/v10 v10.22.1 h1:40JcKH+bBNGFczGuoBYgX4I6m/i27HYW8P9FDk5PbgA=
|
||||
github.com/go-playground/validator/v10 v10.22.1/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
|
||||
github.com/go-playground/validator/v10 v10.25.0 h1:5Dh7cjvzR7BRZadnsVOzPhWsrwUr0nmsZJxEAnFLNO8=
|
||||
github.com/go-playground/validator/v10 v10.25.0/go.mod h1:GGzBIJMuE98Ic/kJsBXbz1x/7cByt++cQ+YOuDM5wus=
|
||||
github.com/go-shiori/dom v0.0.0-20230515143342-73569d674e1c h1:wpkoddUomPfHiOziHZixGO5ZBS73cKqVzZipfrLmO1w=
|
||||
github.com/go-shiori/dom v0.0.0-20230515143342-73569d674e1c/go.mod h1:oVDCh3qjJMLVUSILBRwrm+Bc6RNXGZYtoh9xdvf1ffM=
|
||||
github.com/go-shiori/go-readability v0.0.0-20241012063810-92284fa8a71f h1:cypj7SJh+47G9J3VCPdMzT3uWcXWAWDJA54ErTfOigI=
|
||||
github.com/go-shiori/go-readability v0.0.0-20241012063810-92284fa8a71f/go.mod h1:YWa00ashoPZMAOElrSn4E1cJErhDVU6PWAll4Hxzn+w=
|
||||
github.com/go-shiori/go-readability v0.0.0-20250217085726-9f5bf5ca7612 h1:BYLNYdZaepitbZreRIa9xeCQZocWmy/wj4cGIH0qyw0=
|
||||
github.com/go-shiori/go-readability v0.0.0-20250217085726-9f5bf5ca7612/go.mod h1:wgqthQa8SAYs0yyljVeCOQlZ027VW5CmLsbi9jWC08c=
|
||||
github.com/goccy/go-json v0.10.3 h1:KZ5WoDbxAIgm2HNbYckL0se1fHD6rz5j4ywS6ebzDqA=
|
||||
github.com/goccy/go-json v0.10.3/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
|
||||
github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4=
|
||||
github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
|
||||
github.com/gogs/chardet v0.0.0-20211120154057-b7413eaefb8f h1:3BSP1Tbs2djlpprl7wCLuiqMaUh5SJkkzI2gDs+FgLs=
|
||||
github.com/gogs/chardet v0.0.0-20211120154057-b7413eaefb8f/go.mod h1:Pcatq5tYkCW2Q6yrR2VRHlbHpZ/R4/7qyL1TCF7vl14=
|
||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE=
|
||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ=
|
||||
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw=
|
||||
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
|
||||
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
|
||||
github.com/google/generative-ai-go v0.19.0 h1:R71szggh8wHMCUlEMsW2A/3T+5LdEIkiaHSYgSpUgdg=
|
||||
github.com/google/generative-ai-go v0.19.0/go.mod h1:JYolL13VG7j79kM5BtHz4qwONHkeJQzOCkKXnpqtS/E=
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/s2a-go v0.1.9 h1:LGD7gtMgezd8a/Xak7mEWL0PjoTQFvpRudN895yqKW0=
|
||||
github.com/google/s2a-go v0.1.9/go.mod h1:YA0Ei2ZQL3acow2O62kdp9UlnvMmU7kA6Eutn0dXayM=
|
||||
@@ -160,8 +118,6 @@ github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHm
|
||||
github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4=
|
||||
github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM=
|
||||
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
||||
github.com/klauspost/cpuid/v2 v2.2.9 h1:66ze0taIn2H33fBvCkXuv9BmCwDfafmiIVpKV9kKGuY=
|
||||
github.com/klauspost/cpuid/v2 v2.2.9/go.mod h1:rqkxqrZ1EhYM9G+hXH7YdowN5R5RGN6NK4QwQ3WMXF8=
|
||||
github.com/klauspost/cpuid/v2 v2.2.10 h1:tBs3QSyvjDyFTq3uoc/9xFpCuOsJQFNPiAhYdw2skhE=
|
||||
github.com/klauspost/cpuid/v2 v2.2.10/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
|
||||
github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
|
||||
@@ -182,24 +138,16 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||
github.com/ollama/ollama v0.4.1 h1:41x4/L6HrsmQUqG9loN0q2643PHkLpblIlVqXAdByWs=
|
||||
github.com/ollama/ollama v0.4.1/go.mod h1:QDxM/t2teuubbfN/FT2pBRMPF0K1N3IakgT1OZBD4NY=
|
||||
github.com/ollama/ollama v0.5.12 h1:qM+k/ozyHLJzEQoAEPrUQ0qXqsgDEEdpIVwuwScrd2U=
|
||||
github.com/ollama/ollama v0.5.12/go.mod h1:ibdmDvb/TjKY1OArBWIazL3pd1DHTk8eG2MMjEkWhiI=
|
||||
github.com/onsi/gomega v1.34.1 h1:EUMJIKUjM8sKjYbtxQI9A4z2o+rruxnzNvpknOXie6k=
|
||||
github.com/onsi/gomega v1.34.1/go.mod h1:kU1QgUvBDLXBJq618Xvm2LUX6rSAfRaFRTcdOeDLwwY=
|
||||
github.com/otiai10/copy v1.14.0 h1:dCI/t1iTdYGtkvCuBG2BgR6KZa83PTclw4U5n2wAllU=
|
||||
github.com/otiai10/copy v1.14.0/go.mod h1:ECfuL02W+/FkTWZWgQqXPWZgW9oeKCSQ5qVfSc4qc4w=
|
||||
github.com/otiai10/copy v1.14.1 h1:5/7E6qsUMBaH5AnQ0sSLzzTg1oTECmcCmT6lvF45Na8=
|
||||
github.com/otiai10/copy v1.14.1/go.mod h1:oQwrEDDOci3IM8dJF0d8+jnbfPDllW6vUjNc3DoZm9I=
|
||||
github.com/otiai10/mint v1.5.1 h1:XaPLeE+9vGbuyEHem1JNk3bYc7KKqyI/na0/mLd/Kks=
|
||||
github.com/otiai10/mint v1.5.1/go.mod h1:MJm72SBthJjz8qhefc4z1PYEieWmy8Bku7CjcAqyUSM=
|
||||
github.com/otiai10/mint v1.6.3 h1:87qsV/aw1F5as1eH1zS/yqHY85ANKVMgkDrf9rcxbQs=
|
||||
github.com/otiai10/mint v1.6.3/go.mod h1:MJm72SBthJjz8qhefc4z1PYEieWmy8Bku7CjcAqyUSM=
|
||||
github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M=
|
||||
github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc=
|
||||
github.com/pjbgf/sha1cd v0.3.0 h1:4D5XXmUUBUl/xQ6IjCkEAbqXskkq/4O7LmGn0AqMDs4=
|
||||
github.com/pjbgf/sha1cd v0.3.0/go.mod h1:nZ1rrWOcGJ5uZgEEVL1VUM9iRQiZvWdbZjkKyFzPPsI=
|
||||
github.com/pjbgf/sha1cd v0.3.2 h1:a9wb0bp1oC2TGwStyn0Umc/IGKQnEgF0vVaZ8QF8eo4=
|
||||
github.com/pjbgf/sha1cd v0.3.2/go.mod h1:zQWigSxVmsHEZow5qaLtPYxpcKMMQpa09ixqBxuCS6A=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
@@ -209,20 +157,14 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN
|
||||
github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||
github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
|
||||
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
|
||||
github.com/samber/lo v1.47.0 h1:z7RynLwP5nbyRscyvcD043DWYoOcYRv3mV8lBeqOCLc=
|
||||
github.com/samber/lo v1.47.0/go.mod h1:RmDH9Ct32Qy3gduHQuKJ3gW1fMHAnE/fAzQuf6He5cU=
|
||||
github.com/samber/lo v1.49.1 h1:4BIFyVfuQSEpluc7Fua+j1NolZHiEHEpaSEKdsH0tew=
|
||||
github.com/samber/lo v1.49.1/go.mod h1:dO6KHFzUKXgP8LDhU0oI8d2hekjXnGOu0DB8Jecxd6o=
|
||||
github.com/sashabaranov/go-openai v1.35.6 h1:oi0rwCvyxMxgFALDGnyqFTyCJm6n72OnEG3sybIFR0g=
|
||||
github.com/sashabaranov/go-openai v1.35.6/go.mod h1:lj5b/K+zjTSFxVLijLSTDZuP7adOgerWeFyZLUhAKRg=
|
||||
github.com/sashabaranov/go-openai v1.38.0 h1:hNN5uolKwdbpiqOn7l+Z2alch/0n0rSFyg4n+GZxR5k=
|
||||
github.com/sashabaranov/go-openai v1.38.0/go.mod h1:lj5b/K+zjTSFxVLijLSTDZuP7adOgerWeFyZLUhAKRg=
|
||||
github.com/scylladb/termtables v0.0.0-20191203121021-c4c0b6d42ff4/go.mod h1:C1a7PQSMz9NShzorzCiG2fk9+xuCgLkPeCvMHYR2OWg=
|
||||
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8=
|
||||
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4=
|
||||
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
|
||||
github.com/skeema/knownhosts v1.3.0 h1:AM+y0rI04VksttfwjkSTNQorvGqmwATnvnAHpSgc0LY=
|
||||
github.com/skeema/knownhosts v1.3.0/go.mod h1:sPINvnADmT/qYH1kfv+ePMmOBTH6Tbl7b5LvTDjFK7M=
|
||||
github.com/skeema/knownhosts v1.3.1 h1:X2osQ+RAjK76shCbvhHHHVl3ZlgDm8apHEHFqRjnBY8=
|
||||
github.com/skeema/knownhosts v1.3.1/go.mod h1:r7KTdC8l4uxWRyK2TpQZ/1o5HaSzh06ePQNxPwTcfiY=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
@@ -241,8 +183,6 @@ github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXl
|
||||
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
|
||||
github.com/tidwall/gjson v1.14.4 h1:uo0p8EbA09J7RQaflQ1aBRffTR7xedD2bcIVSYxLnkM=
|
||||
github.com/tidwall/gjson v1.14.4/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
|
||||
github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY=
|
||||
github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
|
||||
github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
|
||||
@@ -261,12 +201,8 @@ github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
|
||||
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.58.0 h1:PS8wXpbyaDJQ2VDHHncMe9Vct0Zn1fEjpsjrLxGJoSc=
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.58.0/go.mod h1:HDBUsEjOuRC0EzKZ1bSaRGZWUBAzo+MhAcUUORSr4D0=
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.59.0 h1:rgMkmiGfix9vFJDcDi1PK8WEQP4FLQwLDfhp5ZLpFeE=
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.59.0/go.mod h1:ijPqXp5P6IRRByFVVg9DY8P5HkxkHE5ARIa+86aXPf4=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.58.0 h1:yd02MEjBdJkG3uabWP9apV+OuWRIXGDuJEUJbOHmCFU=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.58.0/go.mod h1:umTcuxiv1n/s/S6/c2AT/g2CQ7u5C59sHDNmfSwgz7Q=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.59.0 h1:CV7UdSGJt/Ao6Gp4CXckLxVRRsRgDHoI8XjbL3PDl8s=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.59.0/go.mod h1:FRmFuRJfag1IZ2dPkHnEoSFVgTVPUd2qf5Vi69hLb8I=
|
||||
go.opentelemetry.io/otel v1.34.0 h1:zRLXxLCgL1WyKsPVrgbSdMN4c0FMkDAskSTQP+0hdUY=
|
||||
@@ -279,8 +215,6 @@ go.opentelemetry.io/otel/sdk/metric v1.32.0 h1:rZvFnvmvawYb0alrYkjraqJq0Z4ZUJAiy
|
||||
go.opentelemetry.io/otel/sdk/metric v1.32.0/go.mod h1:PWeZlq0zt9YkYAp3gjKZ0eicRYvOh1Gd+X99x6GHpCQ=
|
||||
go.opentelemetry.io/otel/trace v1.34.0 h1:+ouXS2V8Rd4hp4580a8q23bg0azF2nI8cqLYnC8mh/k=
|
||||
go.opentelemetry.io/otel/trace v1.34.0/go.mod h1:Svm7lSjQD7kG7KJ/MUHPVXSDGz2OX4h0M2jHBhmSfRE=
|
||||
golang.org/x/arch v0.12.0 h1:UsYJhbzPYGsT0HbEdmYcqtCv8UNGvnaL561NnIUvaKg=
|
||||
golang.org/x/arch v0.12.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
|
||||
golang.org/x/arch v0.14.0 h1:z9JUEZWr8x4rR0OU6c4/4t6E6jOZ8/QBS2bBYBm4tx4=
|
||||
golang.org/x/arch v0.14.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
@@ -290,8 +224,6 @@ golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliY
|
||||
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
|
||||
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
|
||||
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
|
||||
golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc=
|
||||
golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc=
|
||||
golang.org/x/crypto v0.35.0 h1:b15kiHdrGCHrP6LvwaQ3c03kgNhhiMgvlhxHQhmg2Xs=
|
||||
golang.org/x/crypto v0.35.0/go.mod h1:dy7dXNW32cAb/6/PRuTNsix8T+vJAqvuIy5Bli/x0YQ=
|
||||
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8=
|
||||
@@ -307,18 +239,13 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v
|
||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
|
||||
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
||||
golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
|
||||
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
|
||||
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
|
||||
golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4=
|
||||
golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0=
|
||||
golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k=
|
||||
golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8=
|
||||
golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk=
|
||||
golang.org/x/oauth2 v0.25.0 h1:CY4y7XT9v0cRI9oupztF8AgiIu99L/ksR/Xp/6jrZ70=
|
||||
golang.org/x/oauth2 v0.25.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
|
||||
golang.org/x/oauth2 v0.27.0 h1:da9Vo7/tDv5RH/7nZDz1eMGS/q1Vv1N/7FCrBhI9I3M=
|
||||
golang.org/x/oauth2 v0.27.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
@@ -327,7 +254,6 @@ golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
|
||||
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ=
|
||||
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w=
|
||||
golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
@@ -342,28 +268,24 @@ golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU=
|
||||
golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
|
||||
golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||
golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY=
|
||||
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
|
||||
golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
|
||||
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
|
||||
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
|
||||
golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM=
|
||||
golang.org/x/term v0.28.0 h1:/Ts8HFuMR2E6IP/jlo7QVLZHggjKQbhu/7H0LJFr3Gg=
|
||||
golang.org/x/term v0.28.0/go.mod h1:Sw/lC2IAUZ92udQNf3WodGtn4k/XoLyZoh8v/8uiwek=
|
||||
golang.org/x/term v0.29.0 h1:L6pJp37ocefwRRtYPKSWOWzOtWSxVajvz2ldH/xi3iU=
|
||||
golang.org/x/term v0.29.0/go.mod h1:6bl4lRlvVuDgSf3179VpIxBF0o10JUpXWOnI7nErv7s=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
@@ -373,12 +295,9 @@ golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
|
||||
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
|
||||
golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM=
|
||||
golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY=
|
||||
golang.org/x/time v0.9.0 h1:EsRrnYcQiGH+5FfbgvV4AP7qEZstoyrHB0DzarOQ4ZY=
|
||||
golang.org/x/time v0.9.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||
golang.org/x/time v0.10.0 h1:3usCWA8tQn0L8+hFJQNgzpWbd89begxN66o1Ojdn5L4=
|
||||
golang.org/x/time v0.10.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
@@ -388,22 +307,14 @@ golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
|
||||
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/api v0.220.0 h1:3oMI4gdBgB72WFVwE1nerDD8W3HUOS4kypK6rRLbGns=
|
||||
google.golang.org/api v0.220.0/go.mod h1:26ZAlY6aN/8WgpCzjPNy18QpYaz7Zgg1h0qe1GkZEmY=
|
||||
google.golang.org/api v0.223.0 h1:JUTaWEriXmEy5AhvdMgksGGPEFsYfUKaPEYXd4c3Wvc=
|
||||
google.golang.org/api v0.223.0/go.mod h1:C+RS7Z+dDwds2b+zoAk5hN/eSfsiCn0UDrYof/M4d2M=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20241209162323-e6fa225c2576 h1:CkkIfIt50+lT6NHAVoRYEyAvQGFM7xEwXUUywFvEb3Q=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20241209162323-e6fa225c2576/go.mod h1:1R3kvZ1dtP3+4p4d3G8uJ8rFk/fWlScl38vanWACI08=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20250224174004-546df14abb99 h1:ilJhrCga0AptpJZXmUYG4MCrx/zf3l1okuYz7YK9PPw=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20250224174004-546df14abb99/go.mod h1:Xsh8gBVxGCcbV8ZeTB9wI5XPyZ5RvC6V3CTeeplHbiA=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250127172529-29210b9bc287 h1:J1H9f+LEdWAfHcez/4cvaVBox7cOYT+IU6rgqj5x++8=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250127172529-29210b9bc287/go.mod h1:8BS3B93F/U1juMFq9+EDk+qOT5CO1R9IzXxG3PTqiRk=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250224174004-546df14abb99 h1:ZSlhAUqC4r8TPzqLXQ0m3upBNZeF+Y8jQ3c4CR3Ujms=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250224174004-546df14abb99/go.mod h1:LuRYeWDFV6WOn90g357N17oMCaxpgCnbi/44qJvDn2I=
|
||||
google.golang.org/grpc v1.70.0 h1:pWFv03aZoHzlRKHWicjsZytKAiYCtNS0dHbXnIdq7jQ=
|
||||
google.golang.org/grpc v1.70.0/go.mod h1:ofIJqVKDXx/JiXrwr2IG4/zwdH9txy3IlF40RmcJSQw=
|
||||
google.golang.org/protobuf v1.36.4 h1:6A3ZDJHn/eNqc1i+IdefRzy/9PokBTPvcqMySR7NNIM=
|
||||
google.golang.org/protobuf v1.36.4/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
|
||||
google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM=
|
||||
google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
|
||||
3
main.go
3
main.go
@@ -2,9 +2,10 @@ package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/jessevdk/go-flags"
|
||||
"os"
|
||||
|
||||
"github.com/jessevdk/go-flags"
|
||||
|
||||
"github.com/danielmiessler/fabric/cli"
|
||||
)
|
||||
|
||||
|
||||
@@ -6,9 +6,11 @@
|
||||
buildGoApplication {
|
||||
pname = "fabric-ai";
|
||||
version = import ./version.nix;
|
||||
src = ../../.;
|
||||
pwd = ../../.;
|
||||
modules = ../../gomod2nix.toml;
|
||||
src = ../../../.;
|
||||
pwd = ../../../.;
|
||||
modules = ./gomod2nix.toml;
|
||||
|
||||
doCheck = false;
|
||||
|
||||
ldflags = [
|
||||
"-s"
|
||||
@@ -55,9 +55,6 @@ schema = 3
|
||||
[mod."github.com/cloudwego/base64x"]
|
||||
version = "v0.1.5"
|
||||
hash = "sha256-MyUYTveN48DhnL8mwAgCRuMExLct98uzSPsmYlfaa4I="
|
||||
[mod."github.com/cloudwego/iasm"]
|
||||
version = "v0.2.0"
|
||||
hash = "sha256-TzIP2N3HOesXrKACsRr/ShcoqttwPGZPckIepsTyHOA="
|
||||
[mod."github.com/cyphar/filepath-securejoin"]
|
||||
version = "v0.4.1"
|
||||
hash = "sha256-NOV6MfbkcQbfhNmfADQw2SJmZ6q1nw0wwg8Pm2tf2DM="
|
||||
@@ -256,6 +253,9 @@ schema = 3
|
||||
[mod."golang.org/x/sys"]
|
||||
version = "v0.30.0"
|
||||
hash = "sha256-BuhWtwDkciVioc03rxty6G2vcZVnPX85lI7tgQOFVP8="
|
||||
[mod."golang.org/x/term"]
|
||||
version = "v0.29.0"
|
||||
hash = "sha256-aIupP/iNJKzHPUt0F7SaXc3u17h8plEPyQeypO7ilW8="
|
||||
[mod."golang.org/x/text"]
|
||||
version = "v0.22.0"
|
||||
hash = "sha256-kUwLNFk9K/YuWmO5/u2IshrmhT2CCuk+mAShSlTTeZo="
|
||||
1
nix/pkgs/fabric/version.nix
Normal file
1
nix/pkgs/fabric/version.nix
Normal file
@@ -0,0 +1 @@
|
||||
"1.4.155"
|
||||
@@ -6,6 +6,7 @@
|
||||
statix.enable = true;
|
||||
nixfmt.enable = true;
|
||||
|
||||
goimports.enable = true;
|
||||
gofmt.enable = true;
|
||||
};
|
||||
}
|
||||
@@ -12,7 +12,7 @@ Take a step back and think step-by-step about how to achieve the best possible r
|
||||
|
||||
- Extract 10 to 20 of the best insights from the input and from a combination of the raw input and the IDEAS above into a section called INSIGHTS. These INSIGHTS should be fewer, more refined, more insightful, and more abstracted versions of the best ideas in the content.
|
||||
|
||||
- Extract 15 to 30 of the most surprising, insightful, and/or interesting quotes from the input into a section called QUOTES:. Use the exact quote text from the input.
|
||||
- Extract 15 to 30 of the most surprising, insightful, and/or interesting quotes from the input into a section called QUOTES:. Use the exact quote text from the input. Include the name of the speaker of the quote at the end.
|
||||
|
||||
- Extract 15 to 30 of the most practical and useful personal habits of the speakers, or mentioned by the speakers, in the content into a section called HABITS. Examples include but aren't limited to: sleep schedule, reading habits, things they always do, things they always avoid, productivity tips, diet, exercise, etc.
|
||||
|
||||
|
||||
@@ -207,3 +207,4 @@ Brief one-line summary from AI analysis of what each pattern does.
|
||||
203. **write_nuclei_template_rule**: Generates Nuclei YAML templates for detecting vulnerabilities using HTTP requests, matchers, extractors, and dynamic data extraction.
|
||||
204. **write_pull-request**: Drafts detailed pull request descriptions, explaining changes, providing reasoning, and identifying potential bugs from the git diff command output.
|
||||
205. **write_semgrep_rule**: Creates accurate and working Semgrep rules based on input, following syntax guidelines and specific language considerations.
|
||||
206. **youtubbe_summary**: Create concise, timestamped Youtube video summaries that highlight key points.
|
||||
|
||||
41
patterns/youtube_summary/system.md
Normal file
41
patterns/youtube_summary/system.md
Normal file
@@ -0,0 +1,41 @@
|
||||
# IDENTITY and PURPOSE
|
||||
|
||||
You are an AI assistant specialized in creating concise, informative summaries of YouTube video content based on transcripts. Your role is to analyze video transcripts, identify key points, main themes, and significant moments, then organize this information into a well-structured summary that includes relevant timestamps. You excel at distilling lengthy content into digestible summaries while preserving the most valuable information and maintaining the original flow of the video.
|
||||
|
||||
Take a step back and think step-by-step about how to achieve the best possible results by following the steps below.
|
||||
|
||||
## STEPS
|
||||
|
||||
- Carefully read through the entire transcript to understand the overall content and structure of the video
|
||||
- Identify the main topic and purpose of the video
|
||||
- Note key points, important concepts, and significant moments throughout the transcript
|
||||
- Pay attention to natural transitions or segment changes in the video
|
||||
- Extract relevant timestamps for important moments or topic changes
|
||||
- Organize information into a logical structure that follows the video's progression
|
||||
- Create a concise summary that captures the essence of the video
|
||||
- Include timestamps alongside key points to allow easy navigation
|
||||
- Ensure the summary is comprehensive yet concise
|
||||
|
||||
## OUTPUT INSTRUCTIONS
|
||||
|
||||
- Only output Markdown
|
||||
|
||||
- Begin with a brief overview of the video's main topic and purpose
|
||||
|
||||
- Structure the summary with clear headings and subheadings that reflect the video's organization
|
||||
|
||||
- Include timestamps in [HH:MM:SS] format before each key point or section
|
||||
|
||||
- Keep the summary concise but comprehensive, focusing on the most valuable information
|
||||
|
||||
- Use bullet points for lists of related points when appropriate
|
||||
|
||||
- Bold or italicize particularly important concepts or takeaways
|
||||
|
||||
- End with a brief conclusion summarizing the video's main message or call to action
|
||||
|
||||
- Ensure you follow ALL these instructions when creating your output.
|
||||
|
||||
## INPUT
|
||||
|
||||
INPUT:
|
||||
@@ -1 +0,0 @@
|
||||
"1.4.144"
|
||||
@@ -1,10 +1,11 @@
|
||||
package azure
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/danielmiessler/fabric/plugins"
|
||||
"github.com/danielmiessler/fabric/plugins/ai/openai"
|
||||
goopenai "github.com/sashabaranov/go-openai"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func NewClient() (ret *Client) {
|
||||
@@ -12,6 +13,8 @@ func NewClient() (ret *Client) {
|
||||
ret.Client = openai.NewClientCompatible("Azure", "", ret.configure)
|
||||
ret.ApiDeployments = ret.AddSetupQuestionCustom("deployments", true,
|
||||
"Enter your Azure deployments (comma separated)")
|
||||
ret.ApiVersion = ret.AddSetupQuestionCustom("API Version", false,
|
||||
"Enter the Azure API version (optional)")
|
||||
|
||||
return
|
||||
}
|
||||
@@ -19,13 +22,18 @@ func NewClient() (ret *Client) {
|
||||
type Client struct {
|
||||
*openai.Client
|
||||
ApiDeployments *plugins.SetupQuestion
|
||||
ApiVersion *plugins.SetupQuestion
|
||||
|
||||
apiDeployments []string
|
||||
}
|
||||
|
||||
func (oi *Client) configure() (err error) {
|
||||
oi.apiDeployments = strings.Split(oi.ApiDeployments.Value, ",")
|
||||
oi.ApiClient = goopenai.NewClientWithConfig(goopenai.DefaultAzureConfig(oi.ApiKey.Value, oi.ApiBaseURL.Value))
|
||||
config := goopenai.DefaultAzureConfig(oi.ApiKey.Value, oi.ApiBaseURL.Value)
|
||||
if oi.ApiVersion.Value != "" {
|
||||
config.APIVersion = oi.ApiVersion.Value
|
||||
}
|
||||
oi.ApiClient = goopenai.NewClientWithConfig(config)
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -13,6 +13,9 @@ func TestNewClientInitialization(t *testing.T) {
|
||||
if client.ApiDeployments == nil {
|
||||
t.Errorf("Expected ApiDeployments to be initialized, got nil")
|
||||
}
|
||||
if client.ApiVersion == nil {
|
||||
t.Errorf("Expected ApiVersion to be initialized, got nil")
|
||||
}
|
||||
if client.Client == nil {
|
||||
t.Errorf("Expected Client to be initialized, got nil")
|
||||
}
|
||||
@@ -24,6 +27,7 @@ func TestClientConfigure(t *testing.T) {
|
||||
client.ApiDeployments.Value = "deployment1,deployment2"
|
||||
client.ApiKey.Value = "test-api-key"
|
||||
client.ApiBaseURL.Value = "https://example.com"
|
||||
client.ApiVersion.Value = "2021-01-01"
|
||||
|
||||
err := client.configure()
|
||||
if err != nil {
|
||||
@@ -43,6 +47,10 @@ func TestClientConfigure(t *testing.T) {
|
||||
if client.ApiClient == nil {
|
||||
t.Errorf("Expected ApiClient to be initialized, got nil")
|
||||
}
|
||||
|
||||
if client.ApiVersion.Value != "2021-01-01" {
|
||||
t.Errorf("Expected API version to be '2021-01-01', got %s", client.ApiVersion.Value)
|
||||
}
|
||||
}
|
||||
|
||||
// Test generated using Keploy
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
package dryrun
|
||||
|
||||
import (
|
||||
"github.com/danielmiessler/fabric/common"
|
||||
"github.com/sashabaranov/go-openai"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/danielmiessler/fabric/common"
|
||||
"github.com/sashabaranov/go-openai"
|
||||
)
|
||||
|
||||
// Test generated using Keploy
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
package exolab
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/danielmiessler/fabric/plugins"
|
||||
"github.com/danielmiessler/fabric/plugins/ai/openai"
|
||||
"strings"
|
||||
|
||||
goopenai "github.com/sashabaranov/go-openai"
|
||||
)
|
||||
|
||||
@@ -4,9 +4,10 @@ import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/danielmiessler/fabric/plugins"
|
||||
goopenai "github.com/sashabaranov/go-openai"
|
||||
"strings"
|
||||
|
||||
"github.com/danielmiessler/fabric/common"
|
||||
"github.com/google/generative-ai-go/genai"
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
package gemini
|
||||
|
||||
import (
|
||||
"github.com/google/generative-ai-go/genai"
|
||||
"testing"
|
||||
|
||||
"github.com/google/generative-ai-go/genai"
|
||||
)
|
||||
|
||||
// Test generated using Keploy
|
||||
|
||||
15
plugins/ai/litellm/litellm.go
Normal file
15
plugins/ai/litellm/litellm.go
Normal file
@@ -0,0 +1,15 @@
|
||||
package litellm
|
||||
|
||||
import (
|
||||
"github.com/danielmiessler/fabric/plugins/ai/openai"
|
||||
)
|
||||
|
||||
func NewClient() (ret *Client) {
|
||||
ret = &Client{}
|
||||
ret.Client = openai.NewClientCompatible("LiteLLM", "http://localhost:4000", nil)
|
||||
return
|
||||
}
|
||||
|
||||
type Client struct {
|
||||
*openai.Client
|
||||
}
|
||||
@@ -32,15 +32,15 @@ func NewClientCompatible(vendorName string, defaultBaseUrl string, configureCust
|
||||
EnvNamePrefix: plugins.BuildEnvVariablePrefix(vendorName),
|
||||
ConfigureCustom: configureCustom,
|
||||
}
|
||||
ret.ApiBaseURL = ret.AddSetupQuestion("API Base URL", false)
|
||||
ret.ApiBaseURL.Value = defaultBaseUrl
|
||||
ret.ApiUrl = ret.AddSetupQuestionCustom("API URL", true,
|
||||
fmt.Sprintf("Enter your %v URL (as a reminder, it is usually %v')", vendorName, defaultBaseUrl))
|
||||
return
|
||||
}
|
||||
|
||||
// Client represents the LM Studio client.
|
||||
type Client struct {
|
||||
*plugins.PluginBase
|
||||
ApiBaseURL *plugins.SetupQuestion
|
||||
ApiUrl *plugins.SetupQuestion
|
||||
HttpClient *http.Client
|
||||
}
|
||||
|
||||
@@ -50,14 +50,9 @@ func (c *Client) configure() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Configure sets up the client configuration.
|
||||
func (c *Client) Configure() error {
|
||||
return c.ConfigureCustom()
|
||||
}
|
||||
|
||||
// ListModels returns a list of available models.
|
||||
func (c *Client) ListModels() ([]string, error) {
|
||||
url := fmt.Sprintf("%s/models", c.ApiBaseURL.Value)
|
||||
url := fmt.Sprintf("%s/models", c.ApiUrl.Value)
|
||||
|
||||
req, err := http.NewRequest("GET", url, nil)
|
||||
if err != nil {
|
||||
@@ -92,13 +87,8 @@ func (c *Client) ListModels() ([]string, error) {
|
||||
return models, nil
|
||||
}
|
||||
|
||||
// // SendStream sends a stream of messages (not implemented for LM Studio).
|
||||
// func (c *Client) SendStream(msgs []*goopenai.ChatCompletionMessage, opts *common.ChatOptions, channel chan string) error {
|
||||
// return fmt.Errorf("streaming is not currently supported for LM Studio")
|
||||
// }
|
||||
|
||||
func (c *Client) SendStream(msgs []*goopenai.ChatCompletionMessage, opts *common.ChatOptions, channel chan string) error {
|
||||
url := fmt.Sprintf("%s/chat/completions", c.ApiBaseURL.Value)
|
||||
func (c *Client) SendStream(msgs []*goopenai.ChatCompletionMessage, opts *common.ChatOptions, channel chan string) (err error) {
|
||||
url := fmt.Sprintf("%s/chat/completions", c.ApiUrl.Value)
|
||||
|
||||
payload := map[string]interface{}{
|
||||
"messages": msgs,
|
||||
@@ -106,85 +96,85 @@ func (c *Client) SendStream(msgs []*goopenai.ChatCompletionMessage, opts *common
|
||||
"stream": true, // Enable streaming
|
||||
}
|
||||
|
||||
jsonPayload, err := json.Marshal(payload)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to marshal payload: %w", err)
|
||||
var jsonPayload []byte
|
||||
if jsonPayload, err = json.Marshal(payload); err != nil {
|
||||
err = fmt.Errorf("failed to marshal payload: %w", err)
|
||||
return
|
||||
}
|
||||
|
||||
req, err := http.NewRequest("POST", url, bytes.NewBuffer(jsonPayload))
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create request: %w", err)
|
||||
var req *http.Request
|
||||
if req, err = http.NewRequest("POST", url, bytes.NewBuffer(jsonPayload)); err != nil {
|
||||
err = fmt.Errorf("failed to create request: %w", err)
|
||||
return
|
||||
}
|
||||
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
|
||||
resp, err := c.HttpClient.Do(req)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to send request: %w", err)
|
||||
var resp *http.Response
|
||||
if resp, err = c.HttpClient.Do(req); err != nil {
|
||||
err = fmt.Errorf("failed to send request: %w", err)
|
||||
return
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return fmt.Errorf("unexpected status code: %d", resp.StatusCode)
|
||||
err = fmt.Errorf("unexpected status code: %d", resp.StatusCode)
|
||||
return
|
||||
}
|
||||
|
||||
// Close channel when function exits
|
||||
defer close(channel)
|
||||
|
||||
reader := bufio.NewReader(resp.Body)
|
||||
for {
|
||||
line, err := reader.ReadBytes('\n')
|
||||
if err != nil {
|
||||
var line []byte
|
||||
if line, err = reader.ReadBytes('\n'); err != nil {
|
||||
if err == io.EOF {
|
||||
err = nil
|
||||
break
|
||||
}
|
||||
return fmt.Errorf("error reading response: %w", err)
|
||||
err = fmt.Errorf("error reading response: %w", err)
|
||||
return
|
||||
}
|
||||
|
||||
// Ignore empty lines
|
||||
if len(line) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
// Remove OpenAI-style prefix
|
||||
if bytes.HasPrefix(line, []byte("data: ")) {
|
||||
line = bytes.TrimPrefix(line, []byte("data: "))
|
||||
}
|
||||
|
||||
// Handle [DONE] signal
|
||||
if string(line) == "[DONE]" {
|
||||
break
|
||||
}
|
||||
|
||||
// Parse JSON response
|
||||
var result map[string]interface{}
|
||||
if err := json.Unmarshal(line, &result); err != nil {
|
||||
if err = json.Unmarshal(line, &result); err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
// Extract content from streaming chunks
|
||||
choices, ok := result["choices"].([]interface{})
|
||||
if !ok || len(choices) == 0 {
|
||||
var choices []interface{}
|
||||
var ok bool
|
||||
if choices, ok = result["choices"].([]interface{}); !ok || len(choices) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
delta, ok := choices[0].(map[string]interface{})["delta"].(map[string]interface{})
|
||||
if !ok {
|
||||
var delta map[string]interface{}
|
||||
if delta, ok = choices[0].(map[string]interface{})["delta"].(map[string]interface{}); !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
content, _ := delta["content"].(string)
|
||||
|
||||
// Send data to channel
|
||||
channel <- content
|
||||
var content string
|
||||
if content, _ = delta["content"].(string); content != "" {
|
||||
channel <- content
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
return
|
||||
}
|
||||
|
||||
// Send sends a single message and returns the response.
|
||||
func (c *Client) Send(ctx context.Context, msgs []*goopenai.ChatCompletionMessage, opts *common.ChatOptions) (string, error) {
|
||||
url := fmt.Sprintf("%s/chat/completions", c.ApiBaseURL.Value)
|
||||
func (c *Client) Send(ctx context.Context, msgs []*goopenai.ChatCompletionMessage, opts *common.ChatOptions) (content string, err error) {
|
||||
url := fmt.Sprintf("%s/chat/completions", c.ApiUrl.Value)
|
||||
|
||||
payload := map[string]interface{}{
|
||||
"messages": msgs,
|
||||
@@ -192,54 +182,61 @@ func (c *Client) Send(ctx context.Context, msgs []*goopenai.ChatCompletionMessag
|
||||
// Add other options from opts if supported by LM Studio
|
||||
}
|
||||
|
||||
jsonPayload, err := json.Marshal(payload)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to marshal payload: %w", err)
|
||||
var jsonPayload []byte
|
||||
if jsonPayload, err = json.Marshal(payload); err != nil {
|
||||
err = fmt.Errorf("failed to marshal payload: %w", err)
|
||||
return
|
||||
}
|
||||
|
||||
req, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewBuffer(jsonPayload))
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to create request: %w", err)
|
||||
var req *http.Request
|
||||
if req, err = http.NewRequestWithContext(ctx, "POST", url, bytes.NewBuffer(jsonPayload)); err != nil {
|
||||
err = fmt.Errorf("failed to create request: %w", err)
|
||||
return
|
||||
}
|
||||
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
|
||||
resp, err := c.HttpClient.Do(req)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to send request: %w", err)
|
||||
var resp *http.Response
|
||||
if resp, err = c.HttpClient.Do(req); err != nil {
|
||||
err = fmt.Errorf("failed to send request: %w", err)
|
||||
return
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return "", fmt.Errorf("unexpected status code: %d", resp.StatusCode)
|
||||
err = fmt.Errorf("unexpected status code: %d", resp.StatusCode)
|
||||
return
|
||||
}
|
||||
|
||||
var result map[string]interface{}
|
||||
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
|
||||
return "", fmt.Errorf("failed to decode response: %w", err)
|
||||
if err = json.NewDecoder(resp.Body).Decode(&result); err != nil {
|
||||
err = fmt.Errorf("failed to decode response: %w", err)
|
||||
return
|
||||
}
|
||||
|
||||
choices, ok := result["choices"].([]interface{})
|
||||
if !ok || len(choices) == 0 {
|
||||
return "", fmt.Errorf("invalid response format: missing or empty choices")
|
||||
var choices []interface{}
|
||||
var ok bool
|
||||
if choices, ok = result["choices"].([]interface{}); !ok || len(choices) == 0 {
|
||||
err = fmt.Errorf("invalid response format: missing or empty choices")
|
||||
return
|
||||
}
|
||||
|
||||
message, ok := choices[0].(map[string]interface{})["message"].(map[string]interface{})
|
||||
if !ok {
|
||||
return "", fmt.Errorf("invalid response format: missing message in first choice")
|
||||
var message map[string]interface{}
|
||||
if message, ok = choices[0].(map[string]interface{})["message"].(map[string]interface{}); !ok {
|
||||
err = fmt.Errorf("invalid response format: missing message in first choice")
|
||||
return
|
||||
}
|
||||
|
||||
content, ok := message["content"].(string)
|
||||
if !ok {
|
||||
return "", fmt.Errorf("invalid response format: missing or non-string content in message")
|
||||
if content, ok = message["content"].(string); !ok {
|
||||
err = fmt.Errorf("invalid response format: missing or non-string content in message")
|
||||
return
|
||||
}
|
||||
|
||||
return content, nil
|
||||
return
|
||||
}
|
||||
|
||||
// Complete sends a completion request and returns the response.
|
||||
func (c *Client) Complete(ctx context.Context, prompt string, opts *common.ChatOptions) (string, error) {
|
||||
url := fmt.Sprintf("%s/completions", c.ApiBaseURL.Value)
|
||||
func (c *Client) Complete(ctx context.Context, prompt string, opts *common.ChatOptions) (text string, err error) {
|
||||
url := fmt.Sprintf("%s/completions", c.ApiUrl.Value)
|
||||
|
||||
payload := map[string]interface{}{
|
||||
"prompt": prompt,
|
||||
@@ -247,49 +244,55 @@ func (c *Client) Complete(ctx context.Context, prompt string, opts *common.ChatO
|
||||
// Add other options from opts if supported by LM Studio
|
||||
}
|
||||
|
||||
jsonPayload, err := json.Marshal(payload)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to marshal payload: %w", err)
|
||||
var jsonPayload []byte
|
||||
if jsonPayload, err = json.Marshal(payload); err != nil {
|
||||
err = fmt.Errorf("failed to marshal payload: %w", err)
|
||||
return
|
||||
}
|
||||
|
||||
req, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewBuffer(jsonPayload))
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to create request: %w", err)
|
||||
var req *http.Request
|
||||
if req, err = http.NewRequestWithContext(ctx, "POST", url, bytes.NewBuffer(jsonPayload)); err != nil {
|
||||
err = fmt.Errorf("failed to create request: %w", err)
|
||||
return
|
||||
}
|
||||
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
|
||||
resp, err := c.HttpClient.Do(req)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to send request: %w", err)
|
||||
var resp *http.Response
|
||||
if resp, err = c.HttpClient.Do(req); err != nil {
|
||||
err = fmt.Errorf("failed to send request: %w", err)
|
||||
return
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return "", fmt.Errorf("unexpected status code: %d", resp.StatusCode)
|
||||
err = fmt.Errorf("unexpected status code: %d", resp.StatusCode)
|
||||
return
|
||||
}
|
||||
|
||||
var result map[string]interface{}
|
||||
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
|
||||
return "", fmt.Errorf("failed to decode response: %w", err)
|
||||
if err = json.NewDecoder(resp.Body).Decode(&result); err != nil {
|
||||
err = fmt.Errorf("failed to decode response: %w", err)
|
||||
return
|
||||
}
|
||||
|
||||
choices, ok := result["choices"].([]interface{})
|
||||
if !ok || len(choices) == 0 {
|
||||
return "", fmt.Errorf("invalid response format: missing or empty choices")
|
||||
var choices []interface{}
|
||||
var ok bool
|
||||
if choices, ok = result["choices"].([]interface{}); !ok || len(choices) == 0 {
|
||||
err = fmt.Errorf("invalid response format: missing or empty choices")
|
||||
return
|
||||
}
|
||||
|
||||
text, ok := choices[0].(map[string]interface{})["text"].(string)
|
||||
if !ok {
|
||||
return "", fmt.Errorf("invalid response format: missing or non-string text in first choice")
|
||||
if text, ok = choices[0].(map[string]interface{})["text"].(string); !ok {
|
||||
err = fmt.Errorf("invalid response format: missing or non-string text in first choice")
|
||||
return
|
||||
}
|
||||
|
||||
return text, nil
|
||||
return
|
||||
}
|
||||
|
||||
// GetEmbeddings returns embeddings for the given input.
|
||||
func (c *Client) GetEmbeddings(ctx context.Context, input string, opts *common.ChatOptions) ([]float64, error) {
|
||||
url := fmt.Sprintf("%s/embeddings", c.ApiBaseURL.Value)
|
||||
func (c *Client) GetEmbeddings(ctx context.Context, input string, opts *common.ChatOptions) (embeddings []float64, err error) {
|
||||
url := fmt.Sprintf("%s/embeddings", c.ApiUrl.Value)
|
||||
|
||||
payload := map[string]interface{}{
|
||||
"input": input,
|
||||
@@ -297,26 +300,30 @@ func (c *Client) GetEmbeddings(ctx context.Context, input string, opts *common.C
|
||||
// Add other options from opts if supported by LM Studio
|
||||
}
|
||||
|
||||
jsonPayload, err := json.Marshal(payload)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to marshal payload: %w", err)
|
||||
var jsonPayload []byte
|
||||
if jsonPayload, err = json.Marshal(payload); err != nil {
|
||||
err = fmt.Errorf("failed to marshal payload: %w", err)
|
||||
return
|
||||
}
|
||||
|
||||
req, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewBuffer(jsonPayload))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create request: %w", err)
|
||||
var req *http.Request
|
||||
if req, err = http.NewRequestWithContext(ctx, "POST", url, bytes.NewBuffer(jsonPayload)); err != nil {
|
||||
err = fmt.Errorf("failed to create request: %w", err)
|
||||
return
|
||||
}
|
||||
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
|
||||
resp, err := c.HttpClient.Do(req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to send request: %w", err)
|
||||
var resp *http.Response
|
||||
if resp, err = c.HttpClient.Do(req); err != nil {
|
||||
err = fmt.Errorf("failed to send request: %w", err)
|
||||
return
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return nil, fmt.Errorf("unexpected status code: %d", resp.StatusCode)
|
||||
err = fmt.Errorf("unexpected status code: %d", resp.StatusCode)
|
||||
return
|
||||
}
|
||||
|
||||
var result struct {
|
||||
@@ -325,34 +332,16 @@ func (c *Client) GetEmbeddings(ctx context.Context, input string, opts *common.C
|
||||
} `json:"data"`
|
||||
}
|
||||
|
||||
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
|
||||
return nil, fmt.Errorf("failed to decode response: %w", err)
|
||||
if err = json.NewDecoder(resp.Body).Decode(&result); err != nil {
|
||||
err = fmt.Errorf("failed to decode response: %w", err)
|
||||
return
|
||||
}
|
||||
|
||||
if len(result.Data) == 0 {
|
||||
return nil, fmt.Errorf("no embeddings returned")
|
||||
err = fmt.Errorf("no embeddings returned")
|
||||
return
|
||||
}
|
||||
|
||||
return result.Data[0].Embedding, nil
|
||||
}
|
||||
|
||||
// GetName returns the name of the vendor.
|
||||
func (c *Client) GetName() string {
|
||||
return c.Name
|
||||
}
|
||||
|
||||
// IsConfigured checks if the client is configured.
|
||||
func (c *Client) IsConfigured() bool {
|
||||
return c.ApiBaseURL != nil && c.ApiBaseURL.Value != ""
|
||||
}
|
||||
|
||||
// Setup performs any necessary setup for the client.
|
||||
func (c *Client) Setup() error {
|
||||
return c.Configure()
|
||||
}
|
||||
|
||||
// SetupFillEnvFileContent fills the environment file content.
|
||||
func (c *Client) SetupFillEnvFileContent(buffer *bytes.Buffer) {
|
||||
envName := fmt.Sprintf("%s_API_BASE_URL", c.EnvNamePrefix)
|
||||
buffer.WriteString(fmt.Sprintf("%s=%s\n", envName, c.ApiBaseURL.Value))
|
||||
embeddings = result.Data[0].Embedding
|
||||
return
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@ func NewClient() (ret *Client) {
|
||||
}
|
||||
|
||||
ret.ApiUrl = ret.AddSetupQuestionCustom("API URL", true,
|
||||
"Enter your Ollama URL (as a reminder, it is usually http://localhost:11434)")
|
||||
"Enter your Ollama URL (as a reminder, it is usually http://localhost:1234/v1')")
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
@@ -4,10 +4,11 @@ import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/danielmiessler/fabric/plugins"
|
||||
"io"
|
||||
"log/slog"
|
||||
|
||||
"github.com/danielmiessler/fabric/plugins"
|
||||
|
||||
"github.com/danielmiessler/fabric/common"
|
||||
"github.com/samber/lo"
|
||||
"github.com/sashabaranov/go-openai"
|
||||
|
||||
@@ -2,6 +2,7 @@ package ai
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/danielmiessler/fabric/plugins"
|
||||
goopenai "github.com/sashabaranov/go-openai"
|
||||
|
||||
|
||||
@@ -4,8 +4,9 @@ import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/danielmiessler/fabric/plugins"
|
||||
"sync"
|
||||
|
||||
"github.com/danielmiessler/fabric/plugins"
|
||||
)
|
||||
|
||||
func NewVendorsManager() *VendorsManager {
|
||||
@@ -93,7 +94,6 @@ func (o *VendorsManager) readModels() (err error) {
|
||||
for result := range resultsChan {
|
||||
if result.err != nil {
|
||||
fmt.Println(result.vendorName, result.err)
|
||||
cancel() // Cancel remaining goroutines if needed
|
||||
} else {
|
||||
o.Models.AddGroupItems(result.vendorName, result.models...)
|
||||
}
|
||||
|
||||
@@ -2,10 +2,11 @@ package fsdb
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/joho/godotenv"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
"github.com/joho/godotenv"
|
||||
)
|
||||
|
||||
func NewDb(dir string) (db *Db) {
|
||||
|
||||
@@ -2,6 +2,7 @@ package fsdb
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/danielmiessler/fabric/common"
|
||||
goopenai "github.com/sashabaranov/go-openai"
|
||||
)
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
package fsdb
|
||||
|
||||
import (
|
||||
goopenai "github.com/sashabaranov/go-openai"
|
||||
"testing"
|
||||
|
||||
goopenai "github.com/sashabaranov/go-openai"
|
||||
)
|
||||
|
||||
func TestSessions_GetOrCreateSession(t *testing.T) {
|
||||
|
||||
@@ -2,6 +2,7 @@ package converter
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
|
||||
"github.com/go-shiori/go-readability"
|
||||
)
|
||||
|
||||
|
||||
@@ -59,7 +59,7 @@ func (o *YouTube) GetVideoOrPlaylistId(url string) (videoId string, playlistId s
|
||||
}
|
||||
|
||||
// Video ID pattern
|
||||
videoPattern := `(?:https?:\/\/)?(?:www\.)?(?:youtube\.com\/(?:[^\/\n\s]+\/\S+\/|(?:v|e(?:mbed)?)\/|(?:s(?:horts)\/)|\S*?[?&]v=)|youtu\.be\/)([a-zA-Z0-9_-]*)`
|
||||
videoPattern := `(?:https?:\/\/)?(?:www\.)?(?:youtube\.com\/(?:live\/|[^\/\n\s]+\/\S+\/|(?:v|e(?:mbed)?)\/|(?:s(?:horts)\/)|\S*?[?&]v=)|youtu\.be\/)([a-zA-Z0-9_-]*)`
|
||||
videoRe := regexp.MustCompile(videoPattern)
|
||||
videoMatch := videoRe.FindStringSubmatch(url)
|
||||
if len(videoMatch) > 1 {
|
||||
|
||||
@@ -5,13 +5,14 @@ import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/danielmiessler/fabric/core"
|
||||
"github.com/gin-gonic/gin"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/danielmiessler/fabric/core"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type OllamaModel struct {
|
||||
|
||||
@@ -2,10 +2,11 @@ package restapi
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/danielmiessler/fabric/plugins/db"
|
||||
"github.com/gin-gonic/gin"
|
||||
"io"
|
||||
"net/http"
|
||||
|
||||
"github.com/danielmiessler/fabric/plugins/db"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// StorageHandler defines the handler for storage-related operations
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
package main
|
||||
|
||||
var version = "v1.4.144"
|
||||
var version = "v1.4.155"
|
||||
|
||||
298
web/Web Interface Update README Files/pr-1284-update.md
Normal file
298
web/Web Interface Update README Files/pr-1284-update.md
Normal file
@@ -0,0 +1,298 @@
|
||||
This Cummulative PR adds several Web UI and functionality improvements to make pattern selection more intuitive with the addition of pattern descriptions, ability to save favorite patterns, a Pattern TAG system, powerful multilingual capabilities, PDF-to-markdown functionnalities, a help reference section, more robust Youtube processing and a variety of other ui improvements.
|
||||
|
||||
## 🎥 Demo Video
|
||||
https://youtu.be/bhwtWXoMASA
|
||||
|
||||
updated to include latest enhancement: Pattern tiles search (last min.)
|
||||
https://youtu.be/fcVitd4Kb98
|
||||
|
||||
|
||||
|
||||
## 🌟 Key Features
|
||||
|
||||
### 1. Web UI and Pattern Selection Improvements
|
||||
- Pattern Descriptions
|
||||
- Pattern Tags
|
||||
- Pattern Favourites
|
||||
- Pattern Search bar
|
||||
- PDF to markdown (pdf as pattern input)
|
||||
- Better handling of Youtube url
|
||||
- Multilingual Support
|
||||
- Web UI refinements for clearer interaction
|
||||
- Help section via modal
|
||||
|
||||
### 2. Multilingual Support System
|
||||
- Seamless language switching via UI dropdown
|
||||
- Persistent language state management
|
||||
- Pattern processing now use the selected language seamlessly
|
||||
|
||||
### 3. YouTube Integration Enhancement
|
||||
- Robust language handling for YouTube transcript processing
|
||||
- Chunk-based language maintenance for long transcripts
|
||||
- Consistent language output throughout transcript analysis
|
||||
|
||||
### 4. Enhanced Tag Management Integration
|
||||
|
||||
The tag filtering system has been deeply integrated into the Pattern Selection interface through several UI enhancements:
|
||||
|
||||
1. **Dual-Position Tag Panel**
|
||||
- Sliding panel positioned to the right of pattern modal
|
||||
- Dynamic toggle button that adapts position and text based on panel state
|
||||
- Smooth transitions for opening/closing animations
|
||||
|
||||
2. **Tag Selection Visibility**
|
||||
- New dedicated tag display section in pattern modal
|
||||
- Visual separation through subtle background styling
|
||||
- Immediate feedback showing selected tags with comma separation
|
||||
- Inline reset capability for quick tag clearing
|
||||
|
||||
3. **Improved User Experience**
|
||||
- Clear visual hierarchy between pattern list and tag filtering
|
||||
- Multiple ways to manage tags (panel or quick reset)
|
||||
- Consistent styling with existing design language
|
||||
- Space-efficient tag brick layout in 3-column grid
|
||||
|
||||
4. **Technical Implementation**
|
||||
- Reactive tag state management
|
||||
- Efficient tag filtering logic
|
||||
- Proper event dispatching between components
|
||||
- Maintained accessibility standards
|
||||
- Responsive design considerations
|
||||
|
||||
|
||||
5. **PDF to Markdown conversion functionality for the web interface**
|
||||
- Automatic detection and processing of PDF files in chat
|
||||
- Conversion to markdown format for LLM processing
|
||||
- Installation instructions from the pdf-to-markdown repository
|
||||
|
||||
The PDF conversion module has been integrated in the svelte web browser interface. Once installed, it will automatically detect pdf files in the chat interface and convert them to markdown
|
||||
|
||||
|
||||
## HOW TO INSTALL PDF-TO-MARKDOWN
|
||||
If you need to update the web component follow the instructions in "Web Interface MOD Readme Files/WEB V2 Install Guide.md".
|
||||
|
||||
Assuming your web install is up to date and web svelte config complete, you can simply follow these steps to add Pdf-to-mardown.
|
||||
|
||||
# FROM FABRIC ROOT DIRECTORY
|
||||
cd .. web
|
||||
|
||||
# Install in this sequence:
|
||||
# Step 1
|
||||
npm install -D patch-package
|
||||
# Step 2
|
||||
npm install -D pdfjs-dist@2.5.207
|
||||
# Step 3
|
||||
npm install -D github:jzillmann/pdf-to-markdown#modularize
|
||||
|
||||
These enhancements create a more intuitive and efficient pattern discovery experience, allowing users to quickly filter and find relevant patterns while maintaining a clean, modern interface.
|
||||
|
||||
|
||||
## 🛠 Technical Implementation
|
||||
|
||||
### Language Support Architecture
|
||||
```typescript
|
||||
// Language state management
|
||||
export const languageStore = writable<string>('');
|
||||
|
||||
// Chat input language detection
|
||||
if (qualifier === 'fr') {
|
||||
languageStore.set('fr');
|
||||
userInput = userInput.replace(/--fr\s*/, '');
|
||||
}
|
||||
|
||||
// Service layer integration
|
||||
const language = get(languageStore) || 'en';
|
||||
const languageInstruction = language !== 'en'
|
||||
? `. Please use the language '${language}' for the output.`
|
||||
: '';
|
||||
```
|
||||
|
||||
### YouTube Processing Enhancement
|
||||
```typescript
|
||||
// Process stream with language instruction per chunk
|
||||
await chatService.processStream(
|
||||
stream,
|
||||
(content: string, response?: StreamResponse) => {
|
||||
if (currentLanguage !== 'en') {
|
||||
content = `${content}. Please use the language '${currentLanguage}' for the output.`;
|
||||
}
|
||||
// Update messages...
|
||||
}
|
||||
);
|
||||
```
|
||||
# Pattern Descriptions and Tags Management
|
||||
|
||||
This document explains the complete workflow for managing pattern descriptions and tags, including how to process new patterns and maintain metadata.
|
||||
|
||||
## System Overview
|
||||
|
||||
The pattern system follows this hierarchy:
|
||||
1. `~/.config/fabric/patterns/` directory: The source of truth for available patterns
|
||||
2. `pattern_extracts.json`: Contains first 500 words of each pattern for reference
|
||||
3. `pattern_descriptions.json`: Stores pattern metadata (descriptions and tags)
|
||||
4. `web/static/data/pattern_descriptions.json`: Web-accessible copy for the interface
|
||||
|
||||
## Pattern Processing Workflow
|
||||
|
||||
### 1. Adding New Patterns
|
||||
- Add patterns to `~/.config/fabric/patterns/`
|
||||
- Run extract_patterns.py to process new additions:
|
||||
```bash
|
||||
python extract_patterns.py
|
||||
|
||||
The Python Script automatically:
|
||||
- Creates pattern extracts for reference
|
||||
- Adds placeholder entries in descriptions file
|
||||
- Syncs to web interface
|
||||
|
||||
### 2. Pattern Extract Creation
|
||||
The script extracts first 500 words from each pattern's system.md file to:
|
||||
|
||||
- Provide context for writing descriptions
|
||||
- Maintain reference material
|
||||
- Aid in pattern categorization
|
||||
|
||||
### 3. Description and Tag Management
|
||||
Pattern descriptions and tags are managed in pattern_descriptions.json:
|
||||
|
||||
|
||||
{
|
||||
"patterns": [
|
||||
{
|
||||
"patternName": "pattern_name",
|
||||
"description": "[Description pending]",
|
||||
"tags": []
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
## Completing Pattern Metadata
|
||||
|
||||
### Writing Descriptions
|
||||
1. Check pattern_descriptions.json for "[Description pending]" entries
|
||||
2. Reference pattern_extracts.json for context
|
||||
|
||||
3. How to update Pattern short descriptions (one sentence).
|
||||
|
||||
You can update your descriptions in pattern_descriptions.json manually or using LLM assistance (prefered approach).
|
||||
|
||||
Tell AI to look for "Description pending" entries in this file and write a short description based on the extract info in the pattern_extracts.json file. You can also ask your LLM to add tags for those newly added patterns, using other patterns tag assignments as example.
|
||||
|
||||
### Managing Tags
|
||||
1. Add appropriate tags to new patterns
|
||||
2. Update existing tags as needed
|
||||
3. Tags are stored as arrays: ["TAG1", "TAG2"]
|
||||
4. Edit pattern_descriptions.json directly to modify tags
|
||||
5. Make tags your own. You can delete, replace, amend existing tags.
|
||||
|
||||
## File Synchronization
|
||||
|
||||
The script maintains synchronization between:
|
||||
- Local pattern_descriptions.json
|
||||
- Web interface copy in static/data/
|
||||
- No manual file copying needed
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. Run extract_patterns.py when:
|
||||
- Adding new patterns
|
||||
- Updating existing patterns
|
||||
- Modifying pattern structure
|
||||
|
||||
2. Description Writing:
|
||||
- Use pattern extracts for context
|
||||
- Keep descriptions clear and concise
|
||||
- Focus on pattern purpose and usage
|
||||
|
||||
3. Tag Management:
|
||||
- Use consistent tag categories
|
||||
- Apply multiple tags when relevant
|
||||
- Update tags to reflect pattern evolution
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
If patterns are not showing in the web interface:
|
||||
1. Verify pattern_descriptions.json format
|
||||
2. Check web static copy exists
|
||||
3. Ensure proper file permissions
|
||||
4. Run extract_patterns.py to resync
|
||||
|
||||
## File Structure
|
||||
|
||||
fabric/
|
||||
├── patterns/ # Pattern source files
|
||||
├── PATTERN_DESCRIPTIONS/
|
||||
│ ├── extract_patterns.py # Pattern processing script
|
||||
│ ├── pattern_extracts.json # Pattern content references
|
||||
│ └── pattern_descriptions.json # Pattern metadata
|
||||
└── web/
|
||||
└── static/
|
||||
└── data/
|
||||
└── pattern_descriptions.json # Web interface copy
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## 🎯 Usage Examples
|
||||
|
||||
### 1. Using Language Qualifiers
|
||||
```
|
||||
User: What is the weather?
|
||||
AI: The weather information...
|
||||
|
||||
User: --fr What is the weather?
|
||||
AI: Voici les informations météo...
|
||||
```
|
||||
|
||||
### 2. Global Settings
|
||||
1. Select language from dropdown
|
||||
2. All interactions use selected language
|
||||
3. Automatic reset to English after each message
|
||||
|
||||
### 3. YouTube Analysis
|
||||
```
|
||||
User: Analyze this YouTube video --fr
|
||||
AI: [Provides analysis in French, maintaining language throughout the transcript]
|
||||
```
|
||||
|
||||
## 💡 Key Benefits
|
||||
|
||||
1. **Enhanced User Experience**
|
||||
- Intuitive language switching
|
||||
- Consistent language handling
|
||||
- Seamless integration with existing features
|
||||
|
||||
2. **Robust Implementation**
|
||||
- Simple yet powerful design
|
||||
- No complex language detection needed
|
||||
- Direct AI instruction approach
|
||||
|
||||
3. **Maintainable Architecture**
|
||||
- Clean separation of concerns
|
||||
- Stateful language management
|
||||
- Easy to extend for new languages
|
||||
|
||||
4. **YouTube Integration**
|
||||
- Handles long transcripts effectively
|
||||
- Maintains language consistency
|
||||
- Robust chunk processing
|
||||
|
||||
## 🔄 Implementation Notes
|
||||
|
||||
1. **State Management**
|
||||
- Language persists until changed
|
||||
- Resets to English after each message
|
||||
- Handles UI state updates efficiently
|
||||
|
||||
2. **Error Handling**
|
||||
- Invalid qualifiers are ignored
|
||||
- Unknown languages default to English
|
||||
- Proper store reset on errors
|
||||
|
||||
3. **Best Practices**
|
||||
- Clear language instructions
|
||||
- Consistent state management
|
||||
- Robust error handling
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
## PDF TO MARKDOWN CONVERSION IMPLEMENTATION
|
||||
|
||||
- PDF to Markdown conversion functionality for the web interface
|
||||
- Automatic detection and processing of PDF files in chat
|
||||
- Conversion to markdown format for LLM processing
|
||||
- Installation instructions from the pdf-to-markdown repository
|
||||
|
||||
The PDF conversion module has been integrated in the svelte web browser interface. Once installed, it will automatically detect pdf files in the chat interface and convert them to markdown automatically for llm processing.
|
||||
|
||||
|
||||
## HOW TO INSTALL
|
||||
If you need to update the web component follow the instructions in "Web Interface MOD Readme Files/WEB V2 Install Guide.md".
|
||||
|
||||
Assuming your install is up to date and web svelte config complete, you can simply follow these steps to add Pdf-to-mardown.
|
||||
|
||||
# FROM FABRIC ROOT DIRECTORY
|
||||
cd .. web
|
||||
|
||||
# Install in this sequence:
|
||||
# Step 1
|
||||
npm install -D patch-package
|
||||
# Step 2
|
||||
npm install -D pdfjs-dist@2.5.207
|
||||
# Step 3
|
||||
npm install -D github:jzillmann/pdf-to-markdown#modularize
|
||||
|
||||
|
||||
## 🎥 Demo Video (see 4min)
|
||||
https://youtu.be/bhwtWXoMASA
|
||||
|
||||
# Integration with Svelte
|
||||
|
||||
The integration approach focused on using the library's high-level API while maintaining SSR compatibility:
|
||||
|
||||
- Create PdfConversionService for PDF processing
|
||||
- Handle file uploads in ChatInput component
|
||||
- Convert PDF content to markdown text
|
||||
- Integrate with existing chat processing flow
|
||||
|
||||
|
||||
|
||||
### How it Works
|
||||
|
||||
The PDF to Markdown conversion is implemented as a separate module located in the `pdf-to-markdown` directory. It leverages the `pdf-parse` library (likely via `PdfParser.ts`) to parse PDF documents and extract text content. The core logic resides in `PdfPipeline.ts`, which orchestrates the PDF parsing and conversion process. `Pdf-to-Markdown` is a folk from `pdf.js` - Mozilla's PDF parsing & rendering platform which is used as a raw parser
|
||||
|
||||
Here's a simplified breakdown of the process:
|
||||
|
||||
1. **PDF Parsing:** The `PdfParser.ts` uses `pdf-parse` to read the PDF file and extract text content from each page.
|
||||
2. **Content Extraction:** The extracted text content is processed to identify text elements, formatting, and structure.
|
||||
3. **Markdown Conversion:** The `PdfPipeline.ts` then converts the extracted and processed text content into Markdown format. This involves mapping PDF elements to Markdown syntax, attempting to preserve formatting like headings, lists, and basic text styles.
|
||||
4. **Frontend Integration:** The `PdfConversionService.ts` in the `web/src/lib/services` directory acts as a frontend service that utilizes the `pdf-to-markdown` module. It provides a `convertToMarkdown` function that takes a File object (PDF file) as input, calls the `pdf-to-markdown` module to perform the conversion, and returns the Markdown output as a string.
|
||||
5. **Chat Input Integration:** The `ChatInput.svelte` component uses the `PdfConversionService` to convert uploaded PDF files to Markdown before sending the content to the chat service for pattern processing.
|
||||
|
||||
|
||||
|
||||
### File Changes
|
||||
|
||||
The following files were added or modified to implement the PDF to Markdown conversion:
|
||||
|
||||
**New files:**
|
||||
|
||||
* `pdf-to-markdown/`: (New directory for the PDF to Markdown module)
|
||||
* `pdf-to-markdown/package.json`: Defines dependencies and build scripts for the PDF to Markdown module.
|
||||
* `pdf-to-markdown/tsconfig.json`: TypeScript configuration for the PDF to Markdown module.
|
||||
* `pdf-to-markdown/src/`: Source code directory for the PDF to Markdown module.
|
||||
* `pdf-to-markdown/src/index.ts`: Entry point of the PDF to Markdown module.
|
||||
* `pdf-to-markdown/src/PdfPipeline.ts`: Core logic for PDF to Markdown conversion pipeline.
|
||||
* `pdf-to-markdown/src/PdfParser.ts`: PDF parsing logic using `pdf-parse`.
|
||||
|
||||
* `web/src/lib/services/PdfConversionService.ts`: (New file)
|
||||
* Frontend service to use the `pdf-to-markdown` module and expose `convertToMarkdown` function.
|
||||
|
||||
**Modified files:**
|
||||
|
||||
* `web/src/lib/components/chat/ChatInput.svelte`:
|
||||
* Modified to import and use the `PdfConversionService` in the `readFileContent` function to handle PDF files.
|
||||
* Modified `readFileContent` to call `pdfService.convertToMarkdown` for PDF files.
|
||||
|
||||
These file changes introduce the new PDF to Markdown conversion functionality and integrate it into the chat input component of the web interface.
|
||||
|
||||
@@ -14,18 +14,74 @@
|
||||
import { featureFlags } from "$lib/config/features";
|
||||
import { getDrawerStore } from '@skeletonlabs/skeleton';
|
||||
import { systemPrompt, selectedPatternName } from "$lib/store/pattern-store";
|
||||
import { onMount } from "svelte";
|
||||
|
||||
const drawerStore = getDrawerStore();
|
||||
function openDrawer() {
|
||||
drawerStore.open({});
|
||||
}
|
||||
|
||||
// Column width state (percentage values)
|
||||
let leftColumnWidth = 50;
|
||||
let rightColumnWidth = 50;
|
||||
let isDragging = false;
|
||||
|
||||
// Handle resize functionality
|
||||
function startResize(e: MouseEvent | KeyboardEvent) {
|
||||
isDragging = true;
|
||||
e.preventDefault();
|
||||
|
||||
// Add event listeners for drag and release
|
||||
window.addEventListener('mousemove', handleResize);
|
||||
window.addEventListener('mouseup', stopResize);
|
||||
}
|
||||
|
||||
// Handle keyboard events for accessibility
|
||||
function handleKeyDown(e: KeyboardEvent) {
|
||||
// Only respond to Enter or Space key
|
||||
if (e.key === 'Enter' || e.key === ' ') {
|
||||
startResize(e);
|
||||
}
|
||||
}
|
||||
|
||||
function handleResize(e: MouseEvent) {
|
||||
if (!isDragging) return;
|
||||
|
||||
// Get container dimensions
|
||||
const container = document.querySelector('.chat-container');
|
||||
if (!container) return;
|
||||
|
||||
const containerRect = container.getBoundingClientRect();
|
||||
const containerWidth = containerRect.width;
|
||||
|
||||
// Calculate percentage based on mouse position
|
||||
const percentage = ((e.clientX - containerRect.left) / containerWidth) * 100;
|
||||
|
||||
// Apply constraints (left: 25-50%, right: 50-75%)
|
||||
leftColumnWidth = Math.min(Math.max(percentage, 25), 50);
|
||||
rightColumnWidth = 100 - leftColumnWidth;
|
||||
}
|
||||
|
||||
function stopResize() {
|
||||
isDragging = false;
|
||||
window.removeEventListener('mousemove', handleResize);
|
||||
window.removeEventListener('mouseup', stopResize);
|
||||
}
|
||||
|
||||
// Clean up event listeners when component is destroyed
|
||||
onMount(() => {
|
||||
return () => {
|
||||
window.removeEventListener('mousemove', handleResize);
|
||||
window.removeEventListener('mouseup', stopResize);
|
||||
};
|
||||
});
|
||||
|
||||
$: showObsidian = $featureFlags.enableObsidianIntegration;
|
||||
</script>
|
||||
|
||||
<div class="flex gap-0 p-2 w-full h-screen">
|
||||
<div class="chat-container flex gap-0 p-2 w-full h-screen">
|
||||
<!-- Left Column -->
|
||||
<aside class="w-[50%] flex flex-col gap-2 pr-2">
|
||||
<aside class="flex flex-col gap-2 pr-2" style="width: {leftColumnWidth}%">
|
||||
<!-- Dropdowns Group -->
|
||||
<div class="bg-background/5 p-2 rounded-lg">
|
||||
<div class="rounded-lg bg-background/10">
|
||||
@@ -56,8 +112,17 @@
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
<!-- Resize Handle -->
|
||||
<button
|
||||
class="resize-handle"
|
||||
on:mousedown={startResize}
|
||||
on:keydown={handleKeyDown}
|
||||
type="button"
|
||||
aria-label="Resize chat panels"
|
||||
></button>
|
||||
|
||||
<!-- Right Column -->
|
||||
<div class="flex flex-col w-[50%] gap-2">
|
||||
<div class="flex flex-col gap-2" style="width: {rightColumnWidth}%">
|
||||
<!-- Header with Obsidian Settings -->
|
||||
<div class="flex items-center justify-between px-2 py-1">
|
||||
<div class="flex items-center gap-2">
|
||||
@@ -102,8 +167,41 @@
|
||||
<NoteDrawer />
|
||||
|
||||
<style>
|
||||
.loading-message {
|
||||
animation: flash 1.5s ease-in-out infinite;
|
||||
.resize-handle {
|
||||
width: 6px;
|
||||
margin: 0 -3px;
|
||||
height: 100%;
|
||||
cursor: col-resize;
|
||||
position: relative;
|
||||
z-index: 10;
|
||||
transition: background-color 0.2s;
|
||||
}
|
||||
|
||||
.resize-handle::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
height: 100%;
|
||||
width: 2px;
|
||||
background-color: rgba(255, 255, 255, 0.1);
|
||||
transition: background-color 0.2s, width 0.2s;
|
||||
}
|
||||
|
||||
.resize-handle:hover::after,
|
||||
.resize-handle:focus::after {
|
||||
background-color: rgba(255, 255, 255, 0.3);
|
||||
width: 4px;
|
||||
}
|
||||
|
||||
.resize-handle:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.resize-handle:focus-visible::after {
|
||||
background-color: rgba(255, 255, 255, 0.5);
|
||||
width: 4px;
|
||||
}
|
||||
|
||||
@keyframes flash {
|
||||
|
||||
@@ -13,6 +13,12 @@
|
||||
// import { obsidianSettings } from '$lib/store/obsidian-store';
|
||||
import { languageStore } from '$lib/store/language-store';
|
||||
import { obsidianSettings, updateObsidianSettings } from '$lib/store/obsidian-store';
|
||||
import { PdfConversionService } from '$lib/services/PdfConversionService';
|
||||
|
||||
const pdfService = new PdfConversionService();
|
||||
|
||||
|
||||
|
||||
|
||||
const chatService = new ChatService();
|
||||
let userInput = "";
|
||||
@@ -22,7 +28,8 @@
|
||||
let uploadedFiles: string[] = [];
|
||||
let fileContents: string[] = [];
|
||||
let isProcessingFiles = false;
|
||||
|
||||
let isFileIndicatorVisible = false; // Add new variable
|
||||
let fileButtonKey = false; // Add new key variable for FileButton
|
||||
function detectYouTubeURL(input: string): boolean {
|
||||
const youtubePattern = /(?:https?:\/\/)?(?:www\.)?(?:youtube\.com|youtu\.be)/i;
|
||||
const isYoutube = youtubePattern.test(input);
|
||||
@@ -76,43 +83,162 @@
|
||||
}
|
||||
|
||||
async function handleFileUpload(e: Event) {
|
||||
if (!files || files.length === 0) return;
|
||||
uploadedFiles = []; // Clear uploadedFiles at the beginning
|
||||
if (!files || files.length === 0) return;
|
||||
|
||||
if (uploadedFiles.length >= 5 || (uploadedFiles.length + files.length) > 5) {
|
||||
toastStore.trigger({
|
||||
message: 'Maximum 5 files allowed',
|
||||
background: 'variant-filled-error'
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
isProcessingFiles = true;
|
||||
try {
|
||||
for (let i = 0; i < files.length && uploadedFiles.length < 5; i++) {
|
||||
const file = files[i];
|
||||
const content = await readFileContent(file);
|
||||
fileContents.push(content);
|
||||
uploadedFiles = [...uploadedFiles, file.name];
|
||||
}
|
||||
} catch (error) {
|
||||
toastStore.trigger({
|
||||
message: 'Error processing files: ' + (error as Error).message,
|
||||
background: 'variant-filled-error'
|
||||
});
|
||||
} finally {
|
||||
isProcessingFiles = false;
|
||||
}
|
||||
}
|
||||
|
||||
function readFileContent(file: File): Promise<string> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const reader = new FileReader();
|
||||
reader.onload = (e) => resolve(e.target?.result as string);
|
||||
reader.onerror = (e) => reject(new Error('Failed to read file'));
|
||||
reader.readAsText(file);
|
||||
if (uploadedFiles.length >= 5 || (uploadedFiles.length + files.length) > 5) {
|
||||
toastStore.trigger({
|
||||
message: 'Maximum 5 files allowed',
|
||||
background: 'variant-filled-error'
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
isProcessingFiles = true;
|
||||
try {
|
||||
// Add processing indicator to message store
|
||||
messageStore.update(messages => [...messages, {
|
||||
role: 'system',
|
||||
content: 'Processing files...',
|
||||
format: 'loading'
|
||||
}]);
|
||||
|
||||
for (let i = 0; i < files.length && uploadedFiles.length < 5; i++) {
|
||||
const file = files[i];
|
||||
const content = await readFileContent(file);
|
||||
fileContents.push(content);
|
||||
uploadedFiles = [...uploadedFiles, file.name];
|
||||
|
||||
// Update processing status per file
|
||||
messageStore.update(messages => {
|
||||
const newMessages = [...messages];
|
||||
const lastMessage = newMessages[newMessages.length - 1];
|
||||
if (lastMessage?.format === 'loading') {
|
||||
lastMessage.content = `Processing ${file.name} (${file.type})...`;
|
||||
}
|
||||
return newMessages;
|
||||
});
|
||||
}
|
||||
|
||||
// Remove processing message on completion
|
||||
messageStore.update(messages =>
|
||||
messages.filter(m => m.format !== 'loading')
|
||||
);
|
||||
|
||||
} catch (error) {
|
||||
toastStore.trigger({
|
||||
message: 'Error processing files: ' + (error as Error).message,
|
||||
background: 'variant-filled-error'
|
||||
});
|
||||
|
||||
// Clean up processing message on error
|
||||
messageStore.update(messages =>
|
||||
messages.filter(m => m.format !== 'loading')
|
||||
);
|
||||
} finally {
|
||||
isProcessingFiles = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
async function readFileContent(file: File): Promise<string> {
|
||||
// Log initial file metadata
|
||||
console.log('Reading file:', {
|
||||
name: file.name,
|
||||
type: file.type,
|
||||
size: file.size,
|
||||
lastModified: new Date(file.lastModified).toISOString()
|
||||
});
|
||||
|
||||
// Handle PDF files
|
||||
if (file.type === 'application/pdf') {
|
||||
try {
|
||||
// Start PDF processing
|
||||
console.log('Starting PDF conversion process');
|
||||
const markdown = await pdfService.convertToMarkdown(file);
|
||||
|
||||
// Validate conversion result
|
||||
console.log('PDF conversion completed:', {
|
||||
resultLength: markdown.length,
|
||||
preview: markdown.substring(0, 100)
|
||||
});
|
||||
|
||||
// Ensure we have valid content
|
||||
if (!markdown || markdown.trim().length === 0) {
|
||||
throw new Error('PDF conversion returned empty content');
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Add to fileContents for pattern processing
|
||||
fileContents.push(markdown);
|
||||
|
||||
// Prepare enhanced prompt with system instructions
|
||||
const enhancedPrompt = `${$systemPrompt}\nAnalyze and process the provided content according to these instructions.`;
|
||||
|
||||
// Format final content with proper labeling
|
||||
const finalContent = `${userInput}\n\nFile Contents (PDF):\n${markdown}`;
|
||||
|
||||
// Process through pattern system
|
||||
await sendMessage(finalContent, enhancedPrompt);
|
||||
|
||||
return markdown;
|
||||
|
||||
} catch (error) {
|
||||
console.error('PDF Conversion error:', {
|
||||
error,
|
||||
fileName: file.name,
|
||||
fileSize: file.size
|
||||
});
|
||||
|
||||
const errorMessage = error instanceof Error
|
||||
? error.message
|
||||
: 'Unknown error during PDF conversion';
|
||||
|
||||
throw new Error(`Failed to convert PDF ${file.name}: ${errorMessage}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Handle text files
|
||||
return new Promise((resolve, reject) => {
|
||||
const reader = new FileReader();
|
||||
|
||||
reader.onload = async (e) => {
|
||||
const content = e.target?.result as string;
|
||||
console.log('Text file processed:', {
|
||||
fileName: file.name,
|
||||
contentLength: content.length,
|
||||
preview: content.substring(0, 100)
|
||||
});
|
||||
// resolve(content);
|
||||
const enhancedPrompt = `${$systemPrompt}\nAnalyze and process the provided content according to these instructions.`;
|
||||
const finalContent = `${userInput}\n\nFile Contents (Text):\n${content}`;
|
||||
await sendMessage(finalContent, enhancedPrompt);
|
||||
resolve(content);
|
||||
};
|
||||
|
||||
reader.onerror = (e) => {
|
||||
console.error('FileReader error:', {
|
||||
error: reader.error,
|
||||
fileName: file.name
|
||||
});
|
||||
reject(new Error(`Failed to read ${file.name}: ${reader.error?.message}`));
|
||||
};
|
||||
|
||||
// Start reading the file
|
||||
reader.readAsText(file);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
async function saveToObsidian(content: string) {
|
||||
if (!$obsidianSettings.saveToObsidian) {
|
||||
console.log('Obsidian saving is disabled');
|
||||
@@ -251,31 +377,56 @@
|
||||
}
|
||||
}
|
||||
|
||||
async function handleSubmit() {
|
||||
if (!userInput.trim()) return;
|
||||
|
||||
|
||||
try {
|
||||
console.log('\n=== Submit Handler Start ===');
|
||||
|
||||
if (isYouTubeURL) {
|
||||
console.log('2a. Starting YouTube flow');
|
||||
await processYouTubeURL(userInput);
|
||||
return;
|
||||
}
|
||||
|
||||
const finalContent = fileContents.length > 0
|
||||
? userInput + '\n\nFile Contents:\n' + fileContents.join('\n\n')
|
||||
: userInput;
|
||||
|
||||
await sendMessage(finalContent);
|
||||
|
||||
userInput = "";
|
||||
uploadedFiles = [];
|
||||
fileContents = [];
|
||||
} catch (error) {
|
||||
console.error('Chat submission error:', error);
|
||||
|
||||
async function handleSubmit() {
|
||||
if (!userInput.trim()) return;
|
||||
|
||||
try {
|
||||
console.log('\n=== Submit Handler Start ===');
|
||||
|
||||
if (isYouTubeURL) {
|
||||
console.log('2a. Starting YouTube flow');
|
||||
await processYouTubeURL(userInput);
|
||||
return;
|
||||
}
|
||||
|
||||
const enhancedPrompt = fileContents.length > 0
|
||||
? `${$systemPrompt}\nAnalyze and process the provided content according to these instructions.`
|
||||
: $systemPrompt;
|
||||
|
||||
// Hide raw content from display but keep it for processing
|
||||
messageStore.update(messages => [...messages, {
|
||||
role: 'system',
|
||||
content: 'Processing content...',
|
||||
format: 'loading'
|
||||
}]);
|
||||
|
||||
userInput = ""; // Reset userInput BEFORE sendMessage
|
||||
uploadedFiles = []; // Reset uploadedFiles BEFORE sendMessage
|
||||
fileContents = []; // Reset fileContents BEFORE sendMessage
|
||||
fileButtonKey = !fileButtonKey; // Toggle key to force re-creation
|
||||
|
||||
|
||||
const finalContent = fileContents.length > 0
|
||||
? `${userInput}\n\nFile Contents (${uploadedFiles.map(f => f.endsWith('.pdf') ? 'PDF' : 'Text').join(', ')}):\n${fileContents.join('\n\n---\n\n')}`
|
||||
: userInput;
|
||||
|
||||
|
||||
|
||||
|
||||
await sendMessage(finalContent, enhancedPrompt);
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
} catch (error) {
|
||||
console.error('Chat submission error:', error);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function handleKeydown(event: KeyboardEvent) {
|
||||
if (event.key === 'Enter' && !event.shiftKey) {
|
||||
@@ -300,11 +451,12 @@
|
||||
/>
|
||||
<div class="absolute bottom-3 right-3 flex items-center gap-2">
|
||||
<div class="flex items-center gap-2">
|
||||
{#if uploadedFiles.length > 0}
|
||||
{#if isFileIndicatorVisible}
|
||||
<span class="text-xs text-white/70">
|
||||
{uploadedFiles.length} file{uploadedFiles.length > 1 ? 's' : ''} attached
|
||||
</span>
|
||||
{/if}
|
||||
{#key fileButtonKey}
|
||||
<FileButton
|
||||
name="file-upload"
|
||||
button="btn-icon variant-ghost"
|
||||
@@ -313,12 +465,10 @@
|
||||
disabled={isProcessingFiles || uploadedFiles.length >= 5}
|
||||
class="h-10 w-10 bg-primary-800/30 hover:bg-primary-800/50 rounded-full transition-colors"
|
||||
>
|
||||
{#if uploadedFiles.length > 0}
|
||||
<FileCheck class="w-5 h-5" />
|
||||
{:else}
|
||||
<Paperclip class="w-5 h-5" />
|
||||
{/if}
|
||||
<Paperclip class="w-5 h-5" />
|
||||
|
||||
</FileButton>
|
||||
{/key}
|
||||
<Button
|
||||
type="button"
|
||||
variant="ghost"
|
||||
|
||||
@@ -7,11 +7,13 @@
|
||||
import { onMount } from 'svelte';
|
||||
import Modal from '$lib/components/ui/modal/Modal.svelte';
|
||||
import PatternList from '$lib/components/patterns/PatternList.svelte';
|
||||
import PatternTilesModal from '$lib/components/ui/modal/PatternTilesModal.svelte';
|
||||
import HelpModal from '$lib/components/ui/help/HelpModal.svelte';
|
||||
import { selectedPatternName } from '$lib/store/pattern-store';
|
||||
|
||||
let isMenuOpen = false;
|
||||
let showPatternModal = false;
|
||||
let showPatternTilesModal = false;
|
||||
let showHelpModal = false;
|
||||
|
||||
function goToGithub() {
|
||||
@@ -70,15 +72,33 @@
|
||||
</ul>
|
||||
</nav>
|
||||
|
||||
<div class="flex items-center gap-2">
|
||||
<button name="pattern-description"
|
||||
on:click={() => showPatternModal = true}
|
||||
class="inline-flex h-9 items-center justify-center rounded-full border bg-background px-3 text-sm font-medium transition-colors hover:bg-accent hover:text-accent-foreground gap-2"
|
||||
aria-label="Pattern Description"
|
||||
>
|
||||
<FileText class="h-4 w-4" />
|
||||
<span>Pattern Description</span>
|
||||
</button>
|
||||
<div class="flex items-center gap-4">
|
||||
<!-- Pattern Buttons Group -->
|
||||
<div class="flex items-center gap-3 mr-4">
|
||||
<!-- Pattern Tiles Button -->
|
||||
<button name="pattern-tiles"
|
||||
on:click={() => showPatternTilesModal = true}
|
||||
class="inline-flex h-10 items-center justify-center rounded-full border bg-background px-4 text-sm font-medium transition-colors hover:bg-accent hover:text-accent-foreground gap-2"
|
||||
aria-label="Pattern Tiles"
|
||||
>
|
||||
<FileText class="h-4 w-4" />
|
||||
<span>Pattern Tiles</span>
|
||||
</button>
|
||||
|
||||
<!-- Or text -->
|
||||
<span class="text-sm text-foreground/60 mx-1">or</span>
|
||||
|
||||
<!-- Pattern List Button -->
|
||||
<button name="pattern-list"
|
||||
on:click={() => showPatternModal = true}
|
||||
class="inline-flex h-10 items-center justify-center rounded-full border bg-background px-4 text-sm font-medium transition-colors hover:bg-accent hover:text-accent-foreground gap-2"
|
||||
aria-label="Pattern List"
|
||||
>
|
||||
<FileText class="h-4 w-4" />
|
||||
<span>Pattern List</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
|
||||
<button name="github"
|
||||
on:click={goToGithub}
|
||||
@@ -166,3 +186,16 @@
|
||||
on:close={() => showHelpModal = false}
|
||||
/>
|
||||
</Modal>
|
||||
|
||||
<Modal
|
||||
show={showPatternTilesModal}
|
||||
on:close={() => showPatternTilesModal = false}
|
||||
>
|
||||
<PatternTilesModal
|
||||
on:close={() => showPatternTilesModal = false}
|
||||
on:select={(e) => {
|
||||
selectedPatternName.set(e.detail);
|
||||
showPatternTilesModal = false;
|
||||
}}
|
||||
/>
|
||||
</Modal>
|
||||
|
||||
@@ -1,52 +1,19 @@
|
||||
|
||||
<script lang="ts">
|
||||
import { onMount, createEventDispatcher } from 'svelte';
|
||||
import { get } from 'svelte/store';
|
||||
import type { Pattern } from '$lib/interfaces/pattern-interface';
|
||||
import { favorites } from '$lib/store/favorites-store';
|
||||
import { patterns, patternAPI, systemPrompt, selectedPatternName } from '$lib/store/pattern-store';
|
||||
import { Input } from "$lib/components/ui/input";
|
||||
import TagFilterPanel from './TagFilterPanel.svelte';
|
||||
|
||||
import { createEventDispatcher, onMount } from 'svelte';
|
||||
import TagFilterPanel from '$lib/components/patterns/TagFilterPanel.svelte';
|
||||
let tagFilterRef: TagFilterPanel;
|
||||
|
||||
|
||||
const dispatch = createEventDispatcher<{
|
||||
close: void;
|
||||
select: string;
|
||||
tagsChanged: string[]; // Add this line
|
||||
}>();
|
||||
|
||||
|
||||
let patternsContainer: HTMLDivElement;
|
||||
let sortBy: 'alphabetical' | 'favorites' = 'alphabetical';
|
||||
let searchText = ""; // For pattern filtering
|
||||
let selectedTags: string[] = [];
|
||||
|
||||
// First filter patterns by both text and tags
|
||||
// First filter patterns by both text and tags
|
||||
$: filteredPatterns = $patterns
|
||||
.filter((p: Pattern) =>
|
||||
p.Name.toLowerCase().includes(searchText.toLowerCase())
|
||||
)
|
||||
.filter((p: Pattern) =>
|
||||
selectedTags.length === 0 ||
|
||||
(p.tags && selectedTags.every(tag => p.tags.includes(tag)))
|
||||
);
|
||||
|
||||
// Then sort the filtered patterns
|
||||
$: sortedPatterns = sortBy === 'alphabetical'
|
||||
? [...filteredPatterns].sort((a: Pattern, b: Pattern) => a.Name.localeCompare(b.Name))
|
||||
: [
|
||||
...filteredPatterns.filter((p: Pattern) => $favorites.includes(p.Name)).sort((a: Pattern, b: Pattern) => a.Name.localeCompare(b.Name)),
|
||||
...filteredPatterns.filter((p: Pattern) => !$favorites.includes(p.Name)).sort((a: Pattern, b: Pattern) => a.Name.localeCompare(b.Name))
|
||||
];
|
||||
|
||||
|
||||
function handleTagFilter(event: CustomEvent<string[]>) {
|
||||
selectedTags = event.detail;
|
||||
}
|
||||
|
||||
|
||||
let selectedTags: string[] = [];
|
||||
import { cn } from "$lib/utils/utils";
|
||||
import type { Pattern } from '$lib/interfaces/pattern-interface';
|
||||
import { patterns, patternAPI, selectedPatternName } from '$lib/store/pattern-store';
|
||||
import { favorites } from '$lib/store/favorites-store';
|
||||
import { Input } from "$lib/components/ui/input";
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
let searchQuery = '';
|
||||
let showOnlyFavorites = false;
|
||||
|
||||
onMount(async () => {
|
||||
try {
|
||||
await patternAPI.loadPatterns();
|
||||
@@ -54,156 +21,231 @@ function handleTagFilter(event: CustomEvent<string[]>) {
|
||||
console.error('Error loading patterns:', error);
|
||||
}
|
||||
});
|
||||
|
||||
function toggleFavorite(name: string) {
|
||||
favorites.toggleFavorite(name);
|
||||
}
|
||||
|
||||
|
||||
|
||||
function toggleFavorite(patternName: string) {
|
||||
favorites.toggleFavorite(patternName);
|
||||
}
|
||||
|
||||
function selectPattern(patternName: string) {
|
||||
patternAPI.selectPattern(patternName);
|
||||
dispatch('select', patternName);
|
||||
}
|
||||
|
||||
function closeModal() {
|
||||
dispatch('close');
|
||||
}
|
||||
|
||||
function handleTagFilter(event: CustomEvent<string[]>) {
|
||||
selectedTags = event.detail;
|
||||
}
|
||||
|
||||
function toggleFavoritesFilter() {
|
||||
showOnlyFavorites = !showOnlyFavorites;
|
||||
}
|
||||
|
||||
// Apply filtering based on search query, favorites filter, and tag selection
|
||||
$: filteredPatterns = $patterns
|
||||
.filter(p => {
|
||||
// Apply favorites filter if enabled
|
||||
if (showOnlyFavorites && !$favorites.includes(p.Name)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Apply tag filter if any tags are selected
|
||||
if (selectedTags.length > 0) {
|
||||
if (!p.tags || !selectedTags.every(tag => p.tags.includes(tag))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Apply search filter if query exists
|
||||
if (searchQuery.trim()) {
|
||||
return (
|
||||
p.Name.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
||||
p.Description.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
||||
(p.tags && p.tags.some(tag => tag.toLowerCase().includes(searchQuery.toLowerCase())))
|
||||
);
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
</script>
|
||||
|
||||
<div class="bg-primary-800 rounded-lg flex flex-col h-[85vh] w-[600px] shadow-lg relative">
|
||||
|
||||
<div class="flex flex-col border-b border-primary-700/30">
|
||||
<div class="flex justify-between items-center p-4">
|
||||
<b class="text-lg text-muted-foreground font-bold">Pattern Descriptions</b>
|
||||
<button
|
||||
on:click={() => dispatch('close')}
|
||||
class="text-muted-foreground hover:text-primary-300 transition-colors"
|
||||
>
|
||||
✕
|
||||
</button>
|
||||
<b class="text-lg text-muted-foreground font-bold">Pattern Descriptions</b>
|
||||
<button
|
||||
on:click={closeModal}
|
||||
class="text-muted-foreground hover:text-primary-300 transition-colors"
|
||||
>
|
||||
✕
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="px-4 pb-4 flex items-center justify-between">
|
||||
<div class="flex gap-4">
|
||||
<label class="flex items-center gap-2 text-sm text-muted-foreground">
|
||||
<input
|
||||
type="radio"
|
||||
bind:group={sortBy}
|
||||
value="alphabetical"
|
||||
class="radio"
|
||||
>
|
||||
Alphabetical
|
||||
</label>
|
||||
<label class="flex items-center gap-2 text-sm text-muted-foreground">
|
||||
<input
|
||||
type="radio"
|
||||
bind:group={sortBy}
|
||||
value="favorites"
|
||||
class="radio"
|
||||
>
|
||||
Favorites First
|
||||
</label>
|
||||
</div>
|
||||
<div class="w-64 mr-4">
|
||||
<Input
|
||||
bind:value={searchText}
|
||||
placeholder="Search patterns..."
|
||||
class="text-emerald-900"
|
||||
/>
|
||||
<div class="flex-1 flex items-center">
|
||||
<div class="flex-1 mr-2">
|
||||
<Input
|
||||
bind:value={searchQuery}
|
||||
placeholder="Search patterns..."
|
||||
class="text-emerald-900"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Favorites button similar to PatternTilesModal -->
|
||||
<button
|
||||
on:click={toggleFavoritesFilter}
|
||||
class={cn(
|
||||
"px-3 py-1.5 rounded-md text-sm font-medium transition-all",
|
||||
showOnlyFavorites
|
||||
? "bg-yellow-500/20 text-yellow-300 border border-yellow-500/30"
|
||||
: "bg-primary-700/30 text-primary-300 border border-primary-600/20 hover:bg-primary-700/50"
|
||||
)}
|
||||
>
|
||||
<span class="mr-1">{showOnlyFavorites ? "★" : "☆"}</span>
|
||||
Favorites
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- New tag display section -->
|
||||
<!-- Selected tags display -->
|
||||
<div class="px-4 pb-2">
|
||||
<div class="text-sm text-white/70 bg-primary-700/30 rounded-md p-2 flex justify-between items-center">
|
||||
<div>Tags: {selectedTags.length ? selectedTags.join(', ') : 'none'}</div>
|
||||
<button
|
||||
class="px-2 py-1 text-xs text-white/70 bg-primary-600/30 rounded hover:bg-primary-600/50 transition-colors"
|
||||
on:click={() => {
|
||||
selectedTags = [];
|
||||
dispatch('tagsChanged', selectedTags);
|
||||
}}
|
||||
>
|
||||
reset
|
||||
</button>
|
||||
<div class="flex flex-wrap gap-1 items-center">
|
||||
<span class="mr-1">Tags:</span>
|
||||
{#if selectedTags.length > 0}
|
||||
{#each selectedTags as tag}
|
||||
<div class="inline-flex items-center px-2 py-1 rounded-full text-xs font-medium bg-primary-600/40 text-primary-200 border border-primary-500/30">
|
||||
{tag}
|
||||
<button
|
||||
class="ml-1 text-xs text-primary-300 hover:text-primary-100"
|
||||
on:click={() => {
|
||||
selectedTags = selectedTags.filter(t => t !== tag);
|
||||
}}
|
||||
>
|
||||
×
|
||||
</button>
|
||||
</div>
|
||||
{/each}
|
||||
{:else}
|
||||
<span class="text-primary-300/50">none</span>
|
||||
{/if}
|
||||
</div>
|
||||
<button
|
||||
class="px-2 py-1 text-xs text-white/70 bg-primary-600/30 rounded hover:bg-primary-600/50 transition-colors"
|
||||
on:click={() => {
|
||||
selectedTags = [];
|
||||
if (tagFilterRef && typeof tagFilterRef.reset === 'function') {
|
||||
tagFilterRef.reset();
|
||||
}
|
||||
}}
|
||||
>
|
||||
reset
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
<div class="patterns-container p-4 flex-1 overflow-y-auto">
|
||||
{#if filteredPatterns.length === 0}
|
||||
<div class="flex justify-center items-center h-full">
|
||||
<p class="text-primary-300">
|
||||
{showOnlyFavorites
|
||||
? "No favorite patterns found. Add some favorites first!"
|
||||
: "No patterns found matching your search."}
|
||||
</p>
|
||||
</div>
|
||||
{:else}
|
||||
<div class="patterns-list space-y-2">
|
||||
{#each filteredPatterns as pattern}
|
||||
<div class="pattern-item bg-primary/10 rounded-lg p-3">
|
||||
<div class="flex justify-between items-start gap-4 mb-2">
|
||||
<button
|
||||
class="text-xl font-bold text-primary-300 hover:text-primary-100 cursor-pointer transition-colors text-left w-full"
|
||||
on:click={() => selectPattern(pattern.Name)}
|
||||
>
|
||||
{pattern.Name}
|
||||
</button>
|
||||
<button
|
||||
class="text-muted-foreground hover:text-primary-300 transition-colors"
|
||||
on:click|stopPropagation={() => toggleFavorite(pattern.Name)}
|
||||
>
|
||||
{#if $favorites.includes(pattern.Name)}
|
||||
<span class="text-yellow-400">★</span>
|
||||
{:else}
|
||||
<span class="text-primary-400 hover:text-yellow-300">☆</span>
|
||||
{/if}
|
||||
</button>
|
||||
</div>
|
||||
<p class="text-sm text-muted-foreground break-words leading-relaxed">{pattern.Description}</p>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<TagFilterPanel
|
||||
patterns={$patterns}
|
||||
on:tagsChanged={handleTagFilter}
|
||||
bind:this={tagFilterRef}
|
||||
/>
|
||||
|
||||
<div
|
||||
class="patterns-container p-4 flex-1 overflow-y-auto"
|
||||
bind:this={patternsContainer}
|
||||
>
|
||||
<div class="patterns-list space-y-2">
|
||||
{#each sortedPatterns as pattern}
|
||||
<div class="pattern-item bg-primary/10 rounded-lg p-3">
|
||||
<div class="flex justify-between items-start gap-4 mb-2">
|
||||
<button
|
||||
class="text-xl font-bold text-primary-300 hover:text-primary-100 cursor-pointer transition-colors text-left w-full"
|
||||
on:click={() => {
|
||||
console.log('Selecting pattern:', pattern.Name);
|
||||
patternAPI.selectPattern(pattern.Name);
|
||||
searchText = "";
|
||||
tagFilterRef.reset();
|
||||
dispatch('select', pattern.Name);
|
||||
dispatch('close');
|
||||
}}
|
||||
on:keydown={(e) => {
|
||||
if (e.key === 'Enter' || e.key === ' ') {
|
||||
e.preventDefault();
|
||||
e.currentTarget.click();
|
||||
}
|
||||
}}
|
||||
>
|
||||
{pattern.Name}
|
||||
</button>
|
||||
<button
|
||||
class="text-muted-foreground hover:text-primary-300 transition-colors"
|
||||
on:click={() => toggleFavorite(pattern.Name)}
|
||||
>
|
||||
{#if $favorites.includes(pattern.Name)}
|
||||
★
|
||||
{:else}
|
||||
☆
|
||||
{/if}
|
||||
</button>
|
||||
</div>
|
||||
<p class="text-sm text-muted-foreground break-words leading-relaxed">{pattern.Description}</p>
|
||||
</div>
|
||||
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
patterns={$patterns}
|
||||
on:tagsChanged={handleTagFilter}
|
||||
bind:this={tagFilterRef}
|
||||
hideToggleButton={false}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.patterns-container {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
scrollbar-width: thin;
|
||||
-ms-overflow-style: thin;
|
||||
}
|
||||
/* Custom scrollbar styling */
|
||||
.custom-scrollbar::-webkit-scrollbar {
|
||||
width: 4px;
|
||||
}
|
||||
|
||||
.patterns-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
max-width: 560px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
.custom-scrollbar::-webkit-scrollbar-track {
|
||||
background: rgba(31, 41, 55, 0.2);
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.pattern-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
.custom-scrollbar::-webkit-scrollbar-thumb {
|
||||
background: rgba(156, 163, 175, 0.3);
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.pattern-item:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
.custom-scrollbar::-webkit-scrollbar-thumb:hover {
|
||||
background: rgba(156, 163, 175, 0.5);
|
||||
}
|
||||
|
||||
/* h3.pattern-name {
|
||||
word-break: break-all;
|
||||
hyphens: auto;
|
||||
overflow-wrap: break-word;
|
||||
} */
|
||||
|
||||
.custom-scrollbar {
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: rgba(156, 163, 175, 0.3) rgba(31, 41, 55, 0.2);
|
||||
}
|
||||
|
||||
.patterns-container {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
scrollbar-width: thin;
|
||||
-ms-overflow-style: thin;
|
||||
}
|
||||
|
||||
.patterns-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
max-width: 560px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.pattern-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
.pattern-item:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -7,17 +7,10 @@
|
||||
}>();
|
||||
|
||||
export let patterns: Pattern[];
|
||||
export let hideToggleButton = false; // New prop to hide the toggle button when used in modal
|
||||
let selectedTags: string[] = [];
|
||||
let isExpanded = false;
|
||||
|
||||
// Add console log to see what tags we're getting
|
||||
$: console.log('Available tags:', Array.from(new Set(patterns.flatMap(p => p.tags))));
|
||||
|
||||
// Add these debug logs
|
||||
$: console.log('Patterns received:', patterns);
|
||||
$: console.log('Tags extracted:', patterns.map(p => p.tags));
|
||||
$: console.log('Panel expanded:', isExpanded);
|
||||
|
||||
function toggleTag(tag: string) {
|
||||
selectedTags = selectedTags.includes(tag)
|
||||
? selectedTags.filter(t => t !== tag)
|
||||
@@ -36,15 +29,16 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="tag-panel {isExpanded ? 'expanded' : ''}" style="z-index: 50">
|
||||
<div class="tag-panel {isExpanded ? 'expanded' : ''} {hideToggleButton ? 'embedded' : ''}" style="z-index: 50">
|
||||
{#if !hideToggleButton}
|
||||
<div class="panel-header">
|
||||
<button class="close-btn" on:click={togglePanel}>
|
||||
{isExpanded ? 'Close Filter Tags ◀' : 'Open Filter Tags ▶'}
|
||||
</button>
|
||||
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<div class="panel-content">
|
||||
<div class="panel-content {hideToggleButton ? 'always-visible' : ''}">
|
||||
<div class="reset-container">
|
||||
<button
|
||||
class="reset-btn"
|
||||
@@ -56,7 +50,7 @@
|
||||
Reset All Tags
|
||||
</button>
|
||||
</div>
|
||||
{#each Array.from(new Set(patterns.flatMap(p => p.tags))).sort() as tag}
|
||||
{#each Array.from(new Set(patterns.flatMap(p => p.tags || []))).sort() as tag}
|
||||
<button
|
||||
class="tag-brick {selectedTags.includes(tag) ? 'selected' : ''}"
|
||||
on:click={() => toggleTag(tag)}
|
||||
@@ -67,6 +61,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<style>
|
||||
/* Default positioning for standalone mode */
|
||||
.tag-panel {
|
||||
position: fixed; /* Change to fixed positioning */
|
||||
left: calc(50% + 300px); /* Position starts after modal's right edge */
|
||||
@@ -76,19 +71,37 @@
|
||||
transition: left 0.3s ease;
|
||||
}
|
||||
|
||||
/* When embedded in another component, use relative positioning */
|
||||
.tag-panel.embedded {
|
||||
position: relative;
|
||||
left: auto;
|
||||
top: auto;
|
||||
transform: none;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.tag-panel.expanded {
|
||||
left: calc(50% + 360px); /* Final position just to the right of modal */
|
||||
}
|
||||
|
||||
|
||||
.panel-content {
|
||||
.panel-content {
|
||||
display: none;
|
||||
padding: 12px;
|
||||
flex-wrap: wrap;
|
||||
gap: 6px;
|
||||
max-height: 80vh;
|
||||
overflow-y: auto;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
}
|
||||
|
||||
/* Adjust max-height when embedded */
|
||||
.embedded .panel-content {
|
||||
max-height: 100%;
|
||||
}
|
||||
|
||||
/* When used in modal, always show content */
|
||||
.panel-content.always-visible {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.tag-brick {
|
||||
@@ -102,7 +115,6 @@
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
|
||||
.reset-container {
|
||||
width: 100%;
|
||||
padding-bottom: 8px;
|
||||
@@ -124,29 +136,9 @@
|
||||
background: rgba(255,255,255,0.1);
|
||||
}
|
||||
|
||||
|
||||
|
||||
.expanded .panel-content {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
|
||||
/* .toggle-btn {
|
||||
position: absolute;
|
||||
left: -30px;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
padding: 8px;
|
||||
background: var(--primary-800);
|
||||
border-radius: 4px 0 0 4px;
|
||||
cursor: pointer;
|
||||
.expanded .panel-content {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
font-size: 0.9rem;
|
||||
box-shadow: -2px 0 5px rgba(0,0,0,0.2);
|
||||
} */
|
||||
|
||||
}
|
||||
|
||||
.panel-header {
|
||||
padding: 8px;
|
||||
@@ -178,17 +170,11 @@
|
||||
margin-left: -50px;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
.close-btn:hover {
|
||||
background: rgba(255,255,255,0.1);
|
||||
}
|
||||
|
||||
|
||||
|
||||
.tag-brick.selected {
|
||||
background: var(--primary-300);
|
||||
}
|
||||
.tag-brick.selected {
|
||||
background: var(--primary-300);
|
||||
}
|
||||
</style>
|
||||
|
||||
|
||||
@@ -48,8 +48,8 @@
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h3 class="text-base font-bold text-primary-300 mb-2">File Support</h3>
|
||||
<p class="text-sm text-muted-foreground">You can attach files to your messages using the paperclip icon. The AI will analyze the content of text files.</p>
|
||||
<h3 class="text-base font-bold text-primary-300 mb-2">Upload PDFs and other txt files</h3>
|
||||
<p class="text-sm text-muted-foreground">Now accepts PDFs and other txt files as input using the paperclip icon. PDFs are converted to markdown automatically.</p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
|
||||
@@ -9,15 +9,23 @@
|
||||
export let show = false;
|
||||
</script>
|
||||
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events a11y-no-noninteractive-element-interactions a11y-no-static-element-interactions -->
|
||||
{#if show}
|
||||
<div
|
||||
class="fixed inset-0 bg-black/50 flex items-center justify-center z-50 mt-2"
|
||||
on:click={() => dispatch('close')}
|
||||
on:keydown={(e) => e.key === 'Escape' && dispatch('close')}
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
aria-label="Modal dialog"
|
||||
tabindex="-1"
|
||||
transition:fade={{ duration: 200 }}
|
||||
>
|
||||
<div
|
||||
class="relative"
|
||||
on:click|stopPropagation
|
||||
role="document"
|
||||
aria-label="Modal content"
|
||||
transition:scale={{ duration: 200 }}
|
||||
>
|
||||
<slot />
|
||||
|
||||
333
web/src/lib/components/ui/modal/PatternTilesModal.svelte
Normal file
333
web/src/lib/components/ui/modal/PatternTilesModal.svelte
Normal file
@@ -0,0 +1,333 @@
|
||||
<script lang="ts">
|
||||
import { createEventDispatcher, onMount } from 'svelte';
|
||||
import TagFilterPanel from '$lib/components/patterns/TagFilterPanel.svelte';
|
||||
let tagFilterRef: TagFilterPanel;
|
||||
let selectedTags: string[] = [];
|
||||
import { cn } from "$lib/utils/utils";
|
||||
import type { Pattern } from '$lib/interfaces/pattern-interface';
|
||||
import { patterns, patternAPI, selectedPatternName } from '$lib/store/pattern-store';
|
||||
import { favorites } from '$lib/store/favorites-store';
|
||||
import { Input } from "$lib/components/ui/input";
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
let isTagPanelOpen = false;
|
||||
let searchQuery = '';
|
||||
let showOnlyFavorites = false;
|
||||
|
||||
onMount(async () => {
|
||||
try {
|
||||
await patternAPI.loadPatterns();
|
||||
} catch (error) {
|
||||
console.error('Error loading patterns:', error);
|
||||
}
|
||||
});
|
||||
|
||||
function toggleFavorite(patternName: string) {
|
||||
favorites.toggleFavorite(patternName);
|
||||
}
|
||||
|
||||
function selectPattern(patternName: string) {
|
||||
patternAPI.selectPattern(patternName);
|
||||
dispatch('select', patternName);
|
||||
}
|
||||
|
||||
function closeModal() {
|
||||
dispatch('close');
|
||||
}
|
||||
|
||||
function toggleTagPanel() {
|
||||
isTagPanelOpen = !isTagPanelOpen;
|
||||
}
|
||||
|
||||
function handleTagFilter(event: CustomEvent<string[]>) {
|
||||
selectedTags = event.detail;
|
||||
}
|
||||
|
||||
function toggleFavoritesFilter() {
|
||||
showOnlyFavorites = !showOnlyFavorites;
|
||||
}
|
||||
|
||||
// Apply filtering based on search query, favorites filter, and tag selection
|
||||
$: filteredPatterns = $patterns
|
||||
.filter(p => {
|
||||
// Apply favorites filter if enabled
|
||||
if (showOnlyFavorites && !$favorites.includes(p.Name)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Apply tag filter if any tags are selected
|
||||
if (selectedTags.length > 0) {
|
||||
if (!p.tags || !selectedTags.every(tag => p.tags.includes(tag))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Apply search filter if query exists
|
||||
if (searchQuery.trim()) {
|
||||
return (
|
||||
p.Name.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
||||
p.Description.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
||||
(p.tags && p.tags.some(tag => tag.toLowerCase().includes(searchQuery.toLowerCase())))
|
||||
);
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
</script>
|
||||
|
||||
<!-- Main container with flexible layout -->
|
||||
<div class="flex h-[85vh]">
|
||||
<!-- Modal container with responsive positioning -->
|
||||
<div class={cn(
|
||||
"flex flex-col bg-primary-800 rounded-lg shadow-xl transition-all duration-300",
|
||||
isTagPanelOpen
|
||||
? "w-[75vw]"
|
||||
: "w-full max-w-[95vw] mx-auto"
|
||||
)}>
|
||||
<!-- Header with grid layout -->
|
||||
<div class="grid grid-cols-[auto_auto_1fr_auto] items-center p-4 border-b border-primary-700/30 sticky top-0 bg-primary-800 z-10">
|
||||
<!-- Left column: Title -->
|
||||
<h2 class="text-xl font-semibold text-primary-200 mr-4">Pattern Library</h2>
|
||||
|
||||
<!-- Second column: Search -->
|
||||
<div class="mr-4">
|
||||
<Input
|
||||
bind:value={searchQuery}
|
||||
placeholder="Search patterns..."
|
||||
class="w-full min-w-[300px] text-emerald-900"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Third column: Favorites button -->
|
||||
<div class="flex items-center">
|
||||
<button
|
||||
on:click={toggleFavoritesFilter}
|
||||
class={cn(
|
||||
"px-3 py-1.5 rounded-md text-sm font-medium transition-all",
|
||||
showOnlyFavorites
|
||||
? "bg-yellow-500/20 text-yellow-300 border border-yellow-500/30"
|
||||
: "bg-primary-700/30 text-primary-300 border border-primary-600/20 hover:bg-primary-700/50"
|
||||
)}
|
||||
>
|
||||
<span class="mr-1">{showOnlyFavorites ? "★" : "☆"}</span>
|
||||
Favorites
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Fourth column: Other controls -->
|
||||
<div class="flex items-center gap-3 justify-end">
|
||||
<!-- Single tag panel toggle button -->
|
||||
<button
|
||||
on:click={toggleTagPanel}
|
||||
class={cn(
|
||||
"px-3 py-1.5 rounded-md text-sm font-medium transition-all",
|
||||
isTagPanelOpen
|
||||
? "bg-blue-500/20 text-blue-300 border border-blue-500/30"
|
||||
: "bg-primary-700/30 text-primary-300 border border-primary-600/20 hover:bg-primary-700/50"
|
||||
)}
|
||||
>
|
||||
{isTagPanelOpen ? "Close Filter Tags ◀" : "Open Filter Tags ▶"}
|
||||
</button>
|
||||
|
||||
<!-- Close modal button -->
|
||||
<button
|
||||
on:click={closeModal}
|
||||
class="px-2 py-2 rounded-full bg-primary-700/40 text-primary-200 hover:bg-primary-700/60 hover:text-primary-100"
|
||||
aria-label="Close modal"
|
||||
>
|
||||
<span class="text-xl font-bold">×</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Selected tags display -->
|
||||
{#if selectedTags.length > 0}
|
||||
<div class="px-4 pb-2 pt-2 border-b border-primary-700/30">
|
||||
<div class="text-sm text-white/70 bg-primary-700/30 rounded-md p-2 flex justify-between items-center">
|
||||
<div class="flex flex-wrap gap-1 items-center">
|
||||
<span class="mr-1">Tags:</span>
|
||||
{#each selectedTags as tag}
|
||||
<div class="inline-flex items-center px-2 py-1 rounded-full text-xs font-medium bg-primary-600/40 text-primary-200 border border-primary-500/30">
|
||||
{tag}
|
||||
<button
|
||||
class="ml-1 text-xs text-primary-300 hover:text-primary-100"
|
||||
on:click={() => {
|
||||
selectedTags = selectedTags.filter(t => t !== tag);
|
||||
}}
|
||||
>
|
||||
×
|
||||
</button>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
<button
|
||||
class="px-2 py-1 text-xs text-white/70 bg-primary-600/30 rounded hover:bg-primary-600/50 transition-colors"
|
||||
on:click={() => {
|
||||
selectedTags = [];
|
||||
if (tagFilterRef && typeof tagFilterRef.reset === 'function') {
|
||||
tagFilterRef.reset();
|
||||
}
|
||||
}}
|
||||
>
|
||||
reset
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<!-- Pattern tiles grid with scrolling -->
|
||||
<div class="flex-1 overflow-y-auto p-4 pattern-grid-container">
|
||||
{#if filteredPatterns.length === 0}
|
||||
<div class="flex justify-center items-center h-full">
|
||||
<p class="text-primary-300">
|
||||
{showOnlyFavorites
|
||||
? "No favorite patterns found. Add some favorites first!"
|
||||
: "No patterns found matching your search."}
|
||||
</p>
|
||||
</div>
|
||||
{:else}
|
||||
<div class={cn(
|
||||
"grid grid-cols-1 sm:grid-cols-2 gap-4",
|
||||
isTagPanelOpen
|
||||
? "md:grid-cols-2 lg:grid-cols-3"
|
||||
: "md:grid-cols-3 lg:grid-cols-4 xl:grid-cols-5"
|
||||
)}>
|
||||
{#each filteredPatterns as pattern}
|
||||
<button
|
||||
class="text-left border-2 border-primary-600/40 rounded-lg shadow-md hover:shadow-lg p-4 flex flex-col h-58 bg-primary-700/30 hover:bg-primary-700/50 transition-all transform hover:-translate-y-1 duration-200"
|
||||
on:click={() => selectPattern(pattern.Name)}
|
||||
>
|
||||
<div class="flex justify-between items-start mb-2">
|
||||
<h3 class="pattern-name font-bold text-base text-primary-200 leading-tight break-all overflow-hidden pr-2 w-[85%]">{pattern.Name}</h3>
|
||||
<button
|
||||
on:click|stopPropagation={() => toggleFavorite(pattern.Name)}
|
||||
class="focus:outline-none ml-1 mt-0.5"
|
||||
aria-label={$favorites.includes(pattern.Name) ? 'Remove from favorites' : 'Add to favorites'}
|
||||
>
|
||||
{#if $favorites.includes(pattern.Name)}
|
||||
<span class="text-yellow-400 text-xl">★</span>
|
||||
{:else}
|
||||
<span class="text-primary-400 text-xl hover:text-yellow-300">☆</span>
|
||||
{/if}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Pattern description with scrolling if needed -->
|
||||
<div class="flex-grow overflow-y-auto mb-1 pr-1 custom-scrollbar">
|
||||
<p class="text-sm text-primary-300/90 leading-relaxed">{pattern.Description}</p>
|
||||
</div>
|
||||
|
||||
<!-- Tags section -->
|
||||
{#if pattern.tags && pattern.tags.length > 0}
|
||||
<div class="flex flex-wrap gap-1 mt-2">
|
||||
{#each pattern.tags as tag}
|
||||
<span class="inline-flex items-center px-1 py-0.25 rounded-full text-[8px] font-medium bg-primary-600/40 text-primary-200 border border-primary-500/30">
|
||||
{tag}
|
||||
</span>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
</button>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Tag filter panel - positioned on the right when open -->
|
||||
{#if isTagPanelOpen}
|
||||
<div class="tag-panel-container">
|
||||
<div class="tag-panel-header">
|
||||
<button class="tag-panel-close" on:click={toggleTagPanel}>
|
||||
<span class="text-lg">×</span>
|
||||
</button>
|
||||
<h3 class="text-sm font-medium text-primary-200">Filter Tags</h3>
|
||||
</div>
|
||||
<div class="tag-panel-content">
|
||||
<TagFilterPanel
|
||||
patterns={$patterns}
|
||||
on:tagsChanged={handleTagFilter}
|
||||
bind:this={tagFilterRef}
|
||||
hideToggleButton={true}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<style>
|
||||
/* Custom scrollbar styling remains the same */
|
||||
.custom-scrollbar::-webkit-scrollbar {
|
||||
width: 4px;
|
||||
}
|
||||
|
||||
.custom-scrollbar::-webkit-scrollbar-track {
|
||||
background: rgba(31, 41, 55, 0.2);
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.custom-scrollbar::-webkit-scrollbar-thumb {
|
||||
background: rgba(156, 163, 175, 0.3);
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.custom-scrollbar::-webkit-scrollbar-thumb:hover {
|
||||
background: rgba(156, 163, 175, 0.5);
|
||||
}
|
||||
|
||||
/* Add this to your <style> section */
|
||||
h3.pattern-name {
|
||||
word-break: break-all; /* Force breaks anywhere if needed */
|
||||
hyphens: auto; /* Enable hyphenation */
|
||||
overflow-wrap: break-word; /* Fallback */
|
||||
}
|
||||
|
||||
.custom-scrollbar {
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: rgba(156, 163, 175, 0.3) rgba(31, 41, 55, 0.2);
|
||||
}
|
||||
|
||||
.pattern-grid-container {
|
||||
overflow-y: auto;
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: rgba(156, 163, 175, 0.3) rgba(31, 41, 55, 0.2);
|
||||
}
|
||||
|
||||
/* Tag panel styling */
|
||||
.tag-panel-container {
|
||||
width: 20vw;
|
||||
height: 100%;
|
||||
background-color: #1e293b; /* Use a solid color instead of var */
|
||||
border-left: 1px solid rgba(255, 255, 255, 0.1);
|
||||
z-index: 20;
|
||||
box-shadow: -2px 0 10px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
.tag-panel-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 12px;
|
||||
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
.tag-panel-close {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
border-radius: 50%;
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
margin-right: 8px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.tag-panel-close:hover {
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
}
|
||||
|
||||
.tag-panel-content {
|
||||
height: calc(100% - 49px);
|
||||
overflow-y: auto;
|
||||
}
|
||||
</style>
|
||||
@@ -14,6 +14,7 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<!-- svelte-ignore a11y-no-noninteractive-element-interactions a11y-mouse-events-have-key-events -->
|
||||
<div class="tooltip-container">
|
||||
<div
|
||||
class="tooltip-trigger"
|
||||
@@ -21,6 +22,8 @@
|
||||
on:mouseleave={hideTooltip}
|
||||
on:focusin={showTooltip}
|
||||
on:focusout={hideTooltip}
|
||||
role="tooltip"
|
||||
aria-label="Tooltip trigger"
|
||||
>
|
||||
<slot />
|
||||
</div>
|
||||
@@ -33,9 +36,11 @@
|
||||
class:bottom="{position === 'bottom'}"
|
||||
class:left="{position === 'left'}"
|
||||
class:right="{position === 'right'}"
|
||||
role="tooltip"
|
||||
aria-label={text}
|
||||
>
|
||||
{text}
|
||||
<div class="tooltip-arrow" />
|
||||
<div class="tooltip-arrow" role="presentation" />
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
@@ -159,28 +159,31 @@ export class ChatService {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
private createChatPrompt(userInput: string, systemPromptText?: string): ChatPrompt {
|
||||
const config = get(modelConfig);
|
||||
const language = get(languageStore);
|
||||
|
||||
const languageInstruction = language !== 'en'
|
||||
? `You MUST respond in ${language} language. ALL output, including section headers, titles, and formatting, MUST be translated into ${language}. It is CRITICAL that you translate ALL headers, such as SUMMARY, IDEAS, QUOTES, TAKEAWAYS, MAIN POINTS, etc., into ${language}. Maintain markdown formatting in the response. Do not output any English headers.`
|
||||
? `You MUST respond in ${language} language. All output must be in ${language}. `
|
||||
// ? `You MUST respond in ${language} language. ALL output, including section headers, titles, and formatting, MUST be translated into ${language}. It is CRITICAL that you translate ALL headers, such as SUMMARY, IDEAS, QUOTES, TAKEAWAYS, MAIN POINTS, etc., into ${language}. Maintain markdown formatting in the response. Do not output any English headers.`
|
||||
: '';
|
||||
|
||||
const finalSystemPrompt = languageInstruction + (systemPromptText ?? get(systemPrompt));
|
||||
console.log('Final system prompt in createChatPrompt:', finalSystemPrompt);
|
||||
|
||||
const finalUserInput = language !== 'en'
|
||||
? `${userInput}\n\nIMPORTANT: Respond in ${language} language only.`
|
||||
: userInput;
|
||||
|
||||
|
||||
return {
|
||||
userInput: finalUserInput,
|
||||
systemPrompt: finalSystemPrompt,
|
||||
model: config.model,
|
||||
patternName: get(selectedPatternName)
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
81
web/src/lib/services/PdfConversionService.ts
Normal file
81
web/src/lib/services/PdfConversionService.ts
Normal file
@@ -0,0 +1,81 @@
|
||||
import { createPipeline, transformers } from 'pdf-to-markdown-core/lib/src';
|
||||
import { PARSE_SCHEMA } from 'pdf-to-markdown-core/lib/src/PdfParser';
|
||||
import * as pdfjs from 'pdfjs-dist';
|
||||
|
||||
export class PdfConversionService {
|
||||
constructor() {
|
||||
if (typeof window !== 'undefined') {
|
||||
console.log('PDF.js version:', pdfjs.version);
|
||||
const workerUrl = new URL(
|
||||
'pdfjs-dist/build/pdf.worker.min.js',
|
||||
import.meta.url
|
||||
);
|
||||
console.log('Worker URL:', workerUrl.href);
|
||||
pdfjs.GlobalWorkerOptions.workerSrc = workerUrl.href;
|
||||
console.log('Worker configuration complete');
|
||||
}
|
||||
}
|
||||
|
||||
async convertToMarkdown(file: File): Promise<string> {
|
||||
console.log('Starting PDF conversion:', {
|
||||
fileName: file.name,
|
||||
fileSize: file.size
|
||||
});
|
||||
|
||||
const buffer = await file.arrayBuffer();
|
||||
console.log('Buffer created:', buffer.byteLength);
|
||||
|
||||
const pipeline = createPipeline(pdfjs, {
|
||||
transformConfig: {
|
||||
transformers
|
||||
}
|
||||
});
|
||||
console.log('Pipeline created');
|
||||
|
||||
const result = await pipeline.parse(
|
||||
buffer,
|
||||
(progress) => console.log('Processing:', {
|
||||
stage: progress.stages,
|
||||
details: progress.stageDetails,
|
||||
progress: progress.stageProgress
|
||||
})
|
||||
);
|
||||
console.log('Parse complete, validating result');
|
||||
|
||||
const transformed = result.transform();
|
||||
console.log('Transform applied:', transformed);
|
||||
|
||||
const markdown = transformed.convert({
|
||||
convert: (items) => {
|
||||
console.log('PDF Structure:', {
|
||||
itemCount: items.length,
|
||||
firstItem: items[0],
|
||||
schema: PARSE_SCHEMA // ['transform', 'width', 'height', 'str', 'fontName', 'dir']
|
||||
});
|
||||
|
||||
const text = items
|
||||
.map(item => item.value('str')) // Using 'str' instead of 'text' based on PARSE_SCHEMA
|
||||
.filter(Boolean)
|
||||
.join('\n');
|
||||
|
||||
console.log('Converted text:', {
|
||||
length: text.length,
|
||||
preview: text.substring(0, 100)
|
||||
});
|
||||
|
||||
return text;
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
return markdown;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
BIN
web/static/brain.png
Normal file
BIN
web/static/brain.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.0 MiB |
File diff suppressed because it is too large
Load Diff
BIN
web/static/electric.png
Normal file
BIN
web/static/electric.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.7 MiB |
BIN
web/static/fabric-logo.png
Normal file
BIN
web/static/fabric-logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 42 MiB |
BIN
web/static/fabric-summarize.png
Normal file
BIN
web/static/fabric-summarize.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 491 KiB |
BIN
web/static/favicon.png
Normal file
BIN
web/static/favicon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 42 MiB |
BIN
web/static/obsidian-logo.png
Normal file
BIN
web/static/obsidian-logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 86 KiB |
@@ -4,6 +4,11 @@ import { defineConfig } from 'vite';
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [sveltekit(), purgeCss()],
|
||||
build: {
|
||||
commonjsOptions: {
|
||||
transformMixedEsModules: true
|
||||
}
|
||||
},
|
||||
define: {
|
||||
'process.env': {
|
||||
NODE_ENV: JSON.stringify(process.env.NODE_ENV)
|
||||
|
||||
Reference in New Issue
Block a user