mirror of
https://github.com/danielmiessler/Fabric.git
synced 2026-01-10 14:58:02 -05:00
Compare commits
146 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ce9d4ad831 | ||
|
|
657bcab48c | ||
|
|
cd11dcc7a9 | ||
|
|
22040a42f2 | ||
|
|
705ccd750b | ||
|
|
db7c2b70cb | ||
|
|
9dc9bfa1d5 | ||
|
|
6b93658191 | ||
|
|
ea7a425a26 | ||
|
|
9582978adb | ||
|
|
453d8e75e4 | ||
|
|
901a010efd | ||
|
|
f744e25b39 | ||
|
|
096f40df68 | ||
|
|
29f4534001 | ||
|
|
ed9324d611 | ||
|
|
438b3c5211 | ||
|
|
efeeb7a796 | ||
|
|
6b1ff0ab21 | ||
|
|
acb925f5a9 | ||
|
|
761293ede7 | ||
|
|
e004e50037 | ||
|
|
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 | ||
|
|
f9395fa108 | ||
|
|
22d2a3ee19 | ||
|
|
b64178c292 | ||
|
|
f7d38fb51f | ||
|
|
ea6c0b9025 | ||
|
|
30fa5ee575 | ||
|
|
1e345af0bc | ||
|
|
952f584158 | ||
|
|
b23b20f540 | ||
|
|
1980edbe1c | ||
|
|
bf618f4a25 | ||
|
|
e4617190d8 | ||
|
|
49fe59f403 | ||
|
|
821faa0894 | ||
|
|
af39e38394 | ||
|
|
8774971b98 | ||
|
|
1286afeb76 | ||
|
|
4725a94f00 | ||
|
|
15ac5351cf | ||
|
|
f69cda8fab | ||
|
|
e9e6549528 | ||
|
|
f1550e1d1d | ||
|
|
1fe00633c4 | ||
|
|
a0e1f7204d | ||
|
|
bb1d4f9ca4 | ||
|
|
942771af60 | ||
|
|
1f0bf7b58b | ||
|
|
d56dcb8b16 | ||
|
|
ca83506089 | ||
|
|
267562e1d2 | ||
|
|
0ac8924b0d | ||
|
|
540186acca | ||
|
|
b26d466394 | ||
|
|
12603b619b | ||
|
|
9af69d456e | ||
|
|
61f0b5848c | ||
|
|
cbc82ec045 | ||
|
|
c8af946c87 | ||
|
|
bc216fdfef | ||
|
|
8befac61af | ||
|
|
bebc8c20b5 | ||
|
|
9a1a46e203 | ||
|
|
a5ab81b5c8 | ||
|
|
31be01f3b3 | ||
|
|
52e2995c55 | ||
|
|
f314671f65 | ||
|
|
292fd75699 | ||
|
|
0a07072be0 | ||
|
|
5d31e90650 | ||
|
|
8bff9764f8 | ||
|
|
40c4cb46be | ||
|
|
8a0f9814e6 | ||
|
|
717eb585b5 | ||
|
|
e10a2c9b09 | ||
|
|
c6ebfd3ad7 | ||
|
|
0369087b91 | ||
|
|
d8a415698c | ||
|
|
2bfb087b55 | ||
|
|
8782f78178 | ||
|
|
90c4f244ae | ||
|
|
98fe1fbae2 | ||
|
|
d25be21939 | ||
|
|
6e0f7b5192 | ||
|
|
f522f6b3bd |
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
|
||||
|
||||
2
.github/workflows/patterns.yaml
vendored
2
.github/workflows/patterns.yaml
vendored
@@ -27,7 +27,7 @@ jobs:
|
||||
run: zip -r patterns.zip patterns/
|
||||
|
||||
- name: Upload Patterns Artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: patterns
|
||||
path: patterns.zip
|
||||
|
||||
4
.github/workflows/release.yml
vendored
4
.github/workflows/release.yml
vendored
@@ -81,14 +81,14 @@ jobs:
|
||||
|
||||
- name: Upload build artifact
|
||||
if: matrix.os != 'windows-latest'
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: fabric-${OS}-${{ matrix.arch }}
|
||||
path: fabric-${OS}-${{ matrix.arch }}
|
||||
|
||||
- name: Upload build artifact
|
||||
if: matrix.os == 'windows-latest'
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: fabric-windows-${{ matrix.arch }}.exe
|
||||
path: fabric-windows-${{ matrix.arch }}.exe
|
||||
|
||||
@@ -13,6 +13,7 @@ permissions:
|
||||
|
||||
jobs:
|
||||
update-version:
|
||||
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
@@ -24,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]"
|
||||
@@ -62,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"
|
||||
|
||||
8
.gitignore
vendored
8
.gitignore
vendored
@@ -342,3 +342,11 @@ web/.svelte-kit
|
||||
|
||||
# End of https://www.toptal.com/developers/gitignore/api/node
|
||||
|
||||
web/myfiles/Obsidian_perso_not_share/
|
||||
ENV
|
||||
web/package-lock.json
|
||||
.gitignore_backup
|
||||
web/static/*.png
|
||||
|
||||
# Local VSCode project settings
|
||||
.vscode/
|
||||
|
||||
52
Alma.md
52
Alma.md
@@ -1,3 +1,5 @@
|
||||
# SPQA Policy and State for Alma Security
|
||||
|
||||
## Document Purpose
|
||||
|
||||
This document captures the SPQA policy and State for Alma Security, a security startup out of Redwood City, Ca.
|
||||
@@ -22,7 +24,7 @@ The mission of Alma Security is to ensure businesses can continuously authentica
|
||||
|
||||
## Company Goals (G1 means goal 1, G2 is goal 2, etc. Treat each item (goal/kpi/etc) as half as important as the one before it.)
|
||||
|
||||
NOTE: Some goals are things like project rollouts which serve the higher goals. In that case they shouldn't always be considered so much lower priority because one is serving the other.
|
||||
NOTE: Some goals are things like project rollout which serve the higher goals. In that case they shouldn't always be considered so much lower priority because one is serving the other.
|
||||
|
||||
## Company Goals
|
||||
|
||||
@@ -37,7 +39,7 @@ NOTE: Some goals are things like project rollouts which serve the higher goals.
|
||||
|
||||
## Company KPIs
|
||||
|
||||
- K1: Current marketshare percentage
|
||||
- K1: Current market share percentage
|
||||
- K2: Number of active customers
|
||||
- K3: Current churn percentage
|
||||
- K4: Launched_in_Europe (yes/no)
|
||||
@@ -45,22 +47,22 @@ NOTE: Some goals are things like project rollouts which serve the higher goals.
|
||||
|
||||
-----------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
## Security Team Mission
|
||||
## Security Team Mission
|
||||
|
||||
- SM1: Protect Alma Security's customers and intellectual property from security and privacy incidents.
|
||||
|
||||
## Security Team Goals
|
||||
|
||||
- SG1: Secure all customer data -- especially biometric -- from security and privacy incidents.
|
||||
- SG1: Secure all customer data -- especially biometric -- from security and privacy incidents.
|
||||
- SG2: Protect Alma Security's intellectual property from being captured by unauthorized parties.
|
||||
- SG3: Reach a time to detect malicious behavior of less than 4 minutes by January 2025
|
||||
- SG4: Ensure the public trusts our product, because it's an authentication product we can't survive if people don't trust us.
|
||||
- SG5: Reach a time to remediate critical vulnerabilties on crown jewel systems of less than 16 hours by August 2025
|
||||
- SG6: Reach a time to remediate critical vulnerabilties on all systems of less than 3 days by August 2025
|
||||
- SG5: Reach a time to remediate critical vulnerabilities on crown jewel systems of less than 16 hours by August 2025
|
||||
- SG6: Reach a time to remediate critical vulnerabilities on all systems of less than 3 days by August 2025
|
||||
- SG5: Reach a time to remediate critical vulnerabilities on crown jewel systems of less than 16 hours by August 2025
|
||||
- SG6: Reach a time to remediate critical vulnerabilities on all systems of less than 3 days by August 2025
|
||||
- SG7: Complete audit of Apple Passkey integration by February 2025
|
||||
- SG8: Complete remediation of Apple Passkey vulns by February 2025
|
||||
- SG8: Complete remediation of Apple Passkey vulnerabilities by February 2025
|
||||
|
||||
## Security Team KPIs (How we measure the team)
|
||||
|
||||
@@ -72,7 +74,7 @@ NOTE: Some goals are things like project rollouts which serve the higher goals.
|
||||
|
||||
## Risk Register (The things we're most worried about)
|
||||
|
||||
- R1: Our infrastructure security team is understaffed by 50% after 5 key people left
|
||||
- R1: Our infrastructure security team is understaffed by 50% after 5 key people left
|
||||
- R2: We are not currently monitoring our external perimeter for attack surface related vulnerabilities like open ports, listening applications, unknown hosts, unknown subdomains pointing to these things, etc. We only do scans once every couple of months and we don't really have anyone to look at the results
|
||||
- R3: It takes us multiple days to investigate potential malicious behavior on our systems.
|
||||
- R4: We lack a full list of our assets, including externally facing hosts, S3 buckets, etc., which make up our attack surface
|
||||
@@ -92,7 +94,7 @@ So our risk register looks like this:
|
||||
2. Our perimeter is not being monitored for attack surface related vulnerabilities
|
||||
3. It takes us too long to detect and start investigating malicious behavior on our systems
|
||||
4. We do not have a full list of our assets, which makes it difficult to know what we need to protect
|
||||
5. We have a low public trust score due to the events of 2022
|
||||
5. We have a low public trust score due to the events of 2022
|
||||
|
||||
### Strategies
|
||||
|
||||
@@ -136,14 +138,14 @@ $INSERT GRAPHS OF KPI PROGRESS OVER TIME HERE$
|
||||
- alma.amazon-domain.com is our primary S3 bucket that contains everything, but it's not public readable or listable
|
||||
- We have a root account for our AWS account that doesn't yet have 2FA on it, but we're working on fixing that within a few weeks (but it's been open for a few months)
|
||||
- We also use Postgres for all our databases.
|
||||
- Developers have root access to the all kubernetes nodes via SSH on port 45,001 using a shared developer key issued during laptop provisioning.
|
||||
- Developers have root access to the all kubernetes nodes via SSH on port 45,001 using a shared developer key issued during laptop provisioning.
|
||||
- We're a kubernetes shop and do everything through AWS
|
||||
- We're logging most stuff to Cloudtrail and we kind of use guarduty, but we don't have a 24/7 team to monitor alerts and logs. We should add that to our list of challenges next time we update our overarll policy
|
||||
- We're logging most stuff to Cloudtrail and we kind of use Amazon Guarduty, but we don't have a 24/7 team to monitor alerts and logs. We should add that to our list of challenges next time we update our overall policy
|
||||
- We also have a Windows infrastructure because some key personnel came from Microsoft. The DC is hosted in our head office which is in Redwood City, and anyone who works in that office (most of the 300 employees) uses that to log in when they start work. The domain is ALMA.
|
||||
- There's a domain-joined fileserver running Windows 2012 that most people use to upload new ideas and plans for new products. It uses Windows authentication from the domain.
|
||||
- We use a palo alto firewall with 2fa using windows authenticator tied to SSO.
|
||||
- There's a domain-joined fileserver running Windows 2012 that most people use to upload new ideas and plans for new products. It uses Windows authentication from the domain.
|
||||
- We use a Palo Alto Networks firewall with 2fa using windows authenticator tied to SSO.
|
||||
- The name of the AI system doing all this context creation using SPQA is Alma, which is also the name of the company.
|
||||
- We use Workday for HR stuff. Slack for realtime communications. Outlook 365 as a service. Sentinel One on the workstations and laptops. Servers in AWS are mostly Amazon Linux 2 with a few Ubuntu boxes that are a few years old.
|
||||
- We use Workday for HR stuff. Slack for realtime communications. Outlook 365 as a service. Sentinel One on the workstations and laptops. Servers in AWS are mostly Amazon Linux 2 with a few Ubuntu boxes that are a few years old.
|
||||
- We also primarily use Postgres for all of our systems.
|
||||
|
||||
## Team
|
||||
@@ -160,19 +162,19 @@ PROJECT NAME | PROJECT DESCRIPTION | PROJECT PRIORITY | PROJECT MEMBERS | START
|
||||
|
||||
WAF Install | Install a WAF in front of our main web app | Critical | Nadia Khan | 2024-01-01 - Ongoing | In Progress | $112K one-time, $9K/month
|
||||
|
||||
Multi-Factor Authentication (MFA) Rollout | Implement MFA across all internal and external systems | Critical | Chris Magaan | 2024-01-15 | 2024-05-01 | Planned | $80K one-time, $5K/month
|
||||
Multi-Factor Authentication (MFA) Rollout | Implement MFA across all internal and external systems | Critical | Chris Magann | 2024-01-15 | 2024-05-01 | Planned | $80K one-time, $5K/month
|
||||
|
||||
Procure and Implement ASM | Implement continuous monitoring for attack surface vulnerabilities | High | Tigan Wang | 2024-02-15 | 2024-06-15 | Not Started | $75K one-time, $6K/month
|
||||
Procure and Implement ASM | Implement continuous monitoring for attack surface vulnerabilities | High | Tigan Wang | 2024-02-15 | 2024-06-15 | Not Started | $75K one-time, $6K/month
|
||||
|
||||
Data Encryption Upgrade | Upgrade encryption protocols for all sensitive data | Medium | Nadia Khan | 2024-04-01 | 2024-08-01 | Planned | $95K one-time
|
||||
Data Encryption Upgrade | Upgrade encryption protocols for all sensitive data | Medium | Nadia Khan | 2024-04-01 | 2024-08-01 | Planned | $95K one-time
|
||||
|
||||
Incident Response Enhancement | Develop and implement a 24/7 incident response team | High | Nadia Khan | 2024-03-01 | 2024-07-01 | In Progress | $150K one-time, $10K/month
|
||||
Incident Response Enhancement | Develop and implement a 24/7 incident response team | High | Nadia Khan | 2024-03-01 | 2024-07-01 | In Progress | $150K one-time, $10K/month
|
||||
|
||||
Cloud Security Optimization | Optimize AWS cloud security configurations and practices | Medium | Tigan Wang | 2024-02-01 | 2024-06-01 | In Progress | $100K one-time, $8K/month
|
||||
Cloud Security Optimization | Optimize AWS cloud security configurations and practices | Medium | Tigan Wang | 2024-02-01 | 2024-06-01 | In Progress | $100K one-time, $8K/month
|
||||
|
||||
S3 Bucket Security | Review and secure all S3 buckets to prevent data breaches | High | Chris Magaan | 2024-01-10 | 2024-04-10 | In Progress | $70K one-time, $5K/month
|
||||
S3 Bucket Security | Review and secure all S3 buckets to prevent data breaches | High | Chris Magann | 2024-01-10 | 2024-04-10 | In Progress | $70K one-time, $5K/month
|
||||
|
||||
SQL Injection Mitigation | Implement measures to eliminate SQL injection vulnerabilities | High | Tigan Wang | 2024-01-20 | 2024-05-20 | Not Started | $60K one-time
|
||||
SQL Injection Mitigation | Implement measures to eliminate SQL injection vulnerabilities | High | Tigan Wang | 2024-01-20 | 2024-05-20 | Not Started | $60K one-time
|
||||
|
||||
## SECURITY POSTURE (To be referenced for compliance questions and security questionnaires)
|
||||
|
||||
@@ -286,7 +288,9 @@ First draft of the incident response plan created, but not tested.
|
||||
June 2019
|
||||
|
||||
Enforced MFA for Google Workspace admin accounts; standard user
|
||||
|
||||
## CURRENT STATE (KPIs, Metrics, Project Activity Updates, etc.)
|
||||
|
||||
- October 2022: Current time to detect malicious behavior is 81 hours
|
||||
- October 2022: Current time to start investigating malicious behavior is 82 hours
|
||||
- October 2022: Current time to remediate critical vulnerabilities on crown jewel systems is 21 days
|
||||
@@ -308,7 +312,7 @@ Enforced MFA for Google Workspace admin accounts; standard user
|
||||
- January 2024: Current time to start investigating malicious behavior is 14 hours
|
||||
- January 2024: Current time to remediate critical vulnerabilities on crown jewel systems is 8 days
|
||||
- January 2024: Current time to remediate critical vulnerabilities on all systems is 12 days
|
||||
- March 2024: We're now remediating crits on crown jewels in less than 6 days
|
||||
- April 2024: We're now remediating all criticals within 11 days
|
||||
- July 2024: Criticals are now being fixed in 9 days
|
||||
- March 2024: We're now remediating critical vulnerabilities on crown jewels in less than 6 days
|
||||
- April 2024: We're now remediating all critical vulnerabilities within 11 days
|
||||
- July 2024: critical vulnerabilities are now being fixed in 9 days
|
||||
- On August 5 we got remediation of critical vulnerabilities down to 7 days
|
||||
|
||||
9
ENV
9
ENV
@@ -1,9 +0,0 @@
|
||||
DEFAULT_VENDOR=OpenRouter
|
||||
DEFAULT_MODEL=openai/gpt-3.5-turbo-0125
|
||||
DEFAULT_MODEL_CONTEXT_LENGTH=128K
|
||||
PATTERNS_LOADER_GIT_REPO_URL=https://github.com/danielmiessler/fabric.git
|
||||
PATTERNS_LOADER_GIT_REPO_PATTERNS_FOLDER=patterns
|
||||
OPENROUTER_API_KEY=sk-or-v1-
|
||||
OPENROUTER_API_BASE_URL=https://openrouter.ai/api/v1
|
||||
YOUTUBE_API_KEY=AIzaS
|
||||
JINA_AI_API_KEY=jina_57
|
||||
124
Pattern_Descriptions/README_Pattern_Descriptions_and_Tags_MGT.md
Normal file
124
Pattern_Descriptions/README_Pattern_Descriptions_and_Tags_MGT.md
Normal file
@@ -0,0 +1,124 @@
|
||||
# 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
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
114
Pattern_Descriptions/extract_patterns.py
Normal file
114
Pattern_Descriptions/extract_patterns.py
Normal file
@@ -0,0 +1,114 @@
|
||||
import os
|
||||
import json
|
||||
import shutil
|
||||
|
||||
def load_existing_file(filepath):
|
||||
"""Load existing JSON file or return default structure"""
|
||||
if os.path.exists(filepath):
|
||||
with open(filepath, 'r', encoding='utf-8') as f:
|
||||
return json.load(f)
|
||||
return {"patterns": []}
|
||||
|
||||
def get_pattern_extract(pattern_path):
|
||||
"""Extract first 500 words from pattern's system.md file"""
|
||||
system_md_path = os.path.join(pattern_path, "system.md")
|
||||
with open(system_md_path, 'r', encoding='utf-8') as f:
|
||||
content = ' '.join(f.read().split()[:500])
|
||||
return content
|
||||
|
||||
def extract_pattern_info():
|
||||
script_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
patterns_dir = os.path.expanduser("~/.config/fabric/patterns")
|
||||
print(f"\nScanning patterns directory: {patterns_dir}")
|
||||
|
||||
extracts_path = os.path.join(script_dir, "pattern_extracts.json")
|
||||
descriptions_path = os.path.join(script_dir, "pattern_descriptions.json")
|
||||
|
||||
existing_extracts = load_existing_file(extracts_path)
|
||||
existing_descriptions = load_existing_file(descriptions_path)
|
||||
|
||||
existing_extract_names = {p["patternName"] for p in existing_extracts["patterns"]}
|
||||
existing_description_names = {p["patternName"] for p in existing_descriptions["patterns"]}
|
||||
print(f"Found existing patterns: {len(existing_extract_names)}")
|
||||
|
||||
new_extracts = []
|
||||
new_descriptions = []
|
||||
|
||||
|
||||
for dirname in sorted(os.listdir(patterns_dir)):
|
||||
# Only log new pattern processing
|
||||
if dirname not in existing_extract_names:
|
||||
print(f"Processing new pattern: {dirname}")
|
||||
|
||||
|
||||
pattern_path = os.path.join(patterns_dir, dirname)
|
||||
system_md_path = os.path.join(pattern_path, "system.md")
|
||||
print(f"Checking system.md at: {system_md_path}")
|
||||
|
||||
if os.path.isdir(pattern_path) and os.path.exists(system_md_path):
|
||||
print(f"Valid pattern directory found: {dirname}")
|
||||
try:
|
||||
if dirname not in existing_extract_names:
|
||||
print(f"Creating new extract for: {dirname}")
|
||||
pattern_extract = get_pattern_extract(pattern_path) # Pass directory path
|
||||
new_extracts.append({
|
||||
"patternName": dirname,
|
||||
"pattern_extract": pattern_extract
|
||||
})
|
||||
|
||||
if dirname not in existing_description_names:
|
||||
print(f"Creating new description for: {dirname}")
|
||||
new_descriptions.append({
|
||||
"patternName": dirname,
|
||||
"description": "[Description pending]",
|
||||
"tags": []
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error processing {dirname}: {str(e)}")
|
||||
else:
|
||||
print(f"Invalid pattern directory or missing system.md: {dirname}")
|
||||
|
||||
print(f"\nProcessing summary:")
|
||||
print(f"New extracts created: {len(new_extracts)}")
|
||||
print(f"New descriptions added: {len(new_descriptions)}")
|
||||
|
||||
existing_extracts["patterns"].extend(new_extracts)
|
||||
existing_descriptions["patterns"].extend(new_descriptions)
|
||||
|
||||
return existing_extracts, existing_descriptions, len(new_descriptions)
|
||||
|
||||
|
||||
def update_web_static(descriptions_path):
|
||||
"""Copy pattern descriptions to web static directory"""
|
||||
script_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
static_dir = os.path.join(script_dir, "..", "web", "static", "data")
|
||||
os.makedirs(static_dir, exist_ok=True)
|
||||
static_path = os.path.join(static_dir, "pattern_descriptions.json")
|
||||
shutil.copy2(descriptions_path, static_path)
|
||||
|
||||
def save_pattern_files():
|
||||
"""Save both pattern files and sync to web"""
|
||||
script_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
extracts_path = os.path.join(script_dir, "pattern_extracts.json")
|
||||
descriptions_path = os.path.join(script_dir, "pattern_descriptions.json")
|
||||
|
||||
pattern_extracts, pattern_descriptions, new_count = extract_pattern_info()
|
||||
|
||||
# Save files
|
||||
with open(extracts_path, 'w', encoding='utf-8') as f:
|
||||
json.dump(pattern_extracts, f, indent=2, ensure_ascii=False)
|
||||
|
||||
with open(descriptions_path, 'w', encoding='utf-8') as f:
|
||||
json.dump(pattern_descriptions, f, indent=2, ensure_ascii=False)
|
||||
|
||||
# Update web static
|
||||
update_web_static(descriptions_path)
|
||||
|
||||
print(f"\nProcessing complete:")
|
||||
print(f"Total patterns: {len(pattern_descriptions['patterns'])}")
|
||||
print(f"New patterns added: {new_count}")
|
||||
|
||||
if __name__ == "__main__":
|
||||
save_pattern_files()
|
||||
|
||||
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
828
Pattern_Descriptions/pattern_extracts.json
Normal file
828
Pattern_Descriptions/pattern_extracts.json
Normal file
File diff suppressed because one or more lines are too long
171
README.md
171
README.md
@@ -34,13 +34,18 @@
|
||||
- [`fabric`](#fabric)
|
||||
- [Navigation](#navigation)
|
||||
- [Updates](#updates)
|
||||
- [Intro videos](#intro-videos)
|
||||
- [What and why](#what-and-why)
|
||||
- [Intro videos](#intro-videos)
|
||||
- [Philosophy](#philosophy)
|
||||
- [Breaking problems into components](#breaking-problems-into-components)
|
||||
- [Too many prompts](#too-many-prompts)
|
||||
- [Installation](#installation)
|
||||
- [Get Latest Release Binaries](#get-latest-release-binaries)
|
||||
- [Windows:](#windows)
|
||||
- [MacOS (arm64):](#macos-arm64)
|
||||
- [MacOS (amd64):](#macos-amd64)
|
||||
- [Linux (amd64):](#linux-amd64)
|
||||
- [Linux (arm64):](#linux-arm64)
|
||||
- [From Source](#from-source)
|
||||
- [Environment Variables](#environment-variables)
|
||||
- [Setup](#setup)
|
||||
@@ -52,12 +57,15 @@
|
||||
- [Our approach to prompting](#our-approach-to-prompting)
|
||||
- [Examples](#examples)
|
||||
- [Just use the Patterns](#just-use-the-patterns)
|
||||
- [Prompt Strategies](#prompt-strategies)
|
||||
- [Custom Patterns](#custom-patterns)
|
||||
- [Helper Apps](#helper-apps)
|
||||
- [`to_pdf`](#to_pdf)
|
||||
- [`to_pdf` Installation](#to_pdf-installation)
|
||||
- [pbpaste](#pbpaste)
|
||||
- [Web Interface](#Web_Interface)
|
||||
- [Web Interface](#web-interface)
|
||||
- [Installing](#installing)
|
||||
- [Streamlit UI](#streamlit-ui)
|
||||
- [Meta](#meta)
|
||||
- [Primary contributors](#primary-contributors)
|
||||
|
||||
@@ -66,9 +74,9 @@
|
||||
## Updates
|
||||
|
||||
> [!NOTE]
|
||||
> February 5, 2025
|
||||
> February 24, 2025
|
||||
>
|
||||
> - Remember that `fabric` supports `o1` and `o3` models, but you need to 1) not use `-s`, and 2) use the `--raw` flag because the o1 and o3 models don't support the `--stream` option or temperature settings.
|
||||
> - Fabric now supports Sonnet 3.7! Update and use `-S` to select it as your default if you want, or just use the shortcut `-m claude-3-7-sonnet-latest`. Enjoy!
|
||||
|
||||
## What and why
|
||||
|
||||
@@ -204,8 +212,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 +282,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
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
@@ -306,11 +349,6 @@ for pattern_file in ~/.config/fabric/patterns/*; do
|
||||
}
|
||||
"
|
||||
done
|
||||
|
||||
yt() {
|
||||
local video_link="$1"
|
||||
fabric -y "$video_link" --transcript
|
||||
}
|
||||
```
|
||||
|
||||
This will allow you to use the patterns as aliases like in the above for example `summarize` instead of `fabric --pattern summarize --stream`, however if you pass in an extra argument like this `summarize "my_article_title"` your output will be saved in the destination that you set in `obsidian_base="/path/to/obsidian"` in the following format `YYYY-MM-DD-my_article_title.md` where the date gets autogenerated for you.
|
||||
@@ -356,48 +394,60 @@ Usage:
|
||||
fabric [OPTIONS]
|
||||
|
||||
Application Options:
|
||||
-p, --pattern= Choose a pattern from the available patterns
|
||||
-v, --variable= Values for pattern variables, e.g. -v=#role:expert -v=#points:30"
|
||||
-C, --context= Choose a context from the available contexts
|
||||
--session= Choose a session from the available sessions
|
||||
-a, --attachment= Attachment path or URL (e.g. for OpenAI image recognition messages)
|
||||
-S, --setup Run setup for all reconfigurable parts of fabric
|
||||
-t, --temperature= Set temperature (default: 0.7)
|
||||
-T, --topp= Set top P (default: 0.9)
|
||||
-s, --stream Stream
|
||||
-P, --presencepenalty= Set presence penalty (default: 0.0)
|
||||
-r, --raw Use the defaults of the model without sending chat options (like temperature etc.) and use the user role instead of the system role for patterns.
|
||||
-F, --frequencypenalty= Set frequency penalty (default: 0.0)
|
||||
-l, --listpatterns List all patterns
|
||||
-L, --listmodels List all available models
|
||||
-x, --listcontexts List all contexts
|
||||
-X, --listsessions List all sessions
|
||||
-U, --updatepatterns Update patterns
|
||||
-c, --copy Copy to clipboard
|
||||
-m, --model= Choose model
|
||||
-o, --output= Output to file
|
||||
--output-session Output the entire session (also a temporary one) to the output file
|
||||
-n, --latest= Number of latest patterns to list (default: 0)
|
||||
-d, --changeDefaultModel Change default model
|
||||
-y, --youtube= YouTube video "URL" to grab transcript, comments from it and send to chat
|
||||
--transcript Grab transcript from YouTube video and send to chat (it used per default).
|
||||
--comments Grab comments from YouTube video and send to chat
|
||||
--metadata Grab metadata from YouTube video and send to chat
|
||||
-g, --language= Specify the Language Code for the chat, e.g. -g=en -g=zh
|
||||
-u, --scrape_url= Scrape website URL to markdown using Jina AI
|
||||
-q, --scrape_question= Search question using Jina AI
|
||||
-e, --seed= Seed to be used for LMM generation
|
||||
-w, --wipecontext= Wipe context
|
||||
-W, --wipesession= Wipe session
|
||||
--printcontext= Print context
|
||||
--printsession= Print session
|
||||
--readability Convert HTML input into a clean, readable view
|
||||
--serve Initiate the API server
|
||||
--dry-run Show what would be sent to the model without actually sending it
|
||||
--version Print current version
|
||||
-p, --pattern= Choose a pattern from the available patterns
|
||||
-v, --variable= Values for pattern variables, e.g. -v=#role:expert -v=#points:30
|
||||
-C, --context= Choose a context from the available contexts
|
||||
--session= Choose a session from the available sessions
|
||||
-a, --attachment= Attachment path or URL (e.g. for OpenAI image recognition messages)
|
||||
-S, --setup Run setup for all reconfigurable parts of fabric
|
||||
-t, --temperature= Set temperature (default: 0.7)
|
||||
-T, --topp= Set top P (default: 0.9)
|
||||
-s, --stream Stream
|
||||
-P, --presencepenalty= Set presence penalty (default: 0.0)
|
||||
-r, --raw Use the defaults of the model without sending chat options (like temperature etc.) and use the user role instead of the system role for patterns.
|
||||
-F, --frequencypenalty= Set frequency penalty (default: 0.0)
|
||||
-l, --listpatterns List all patterns
|
||||
-L, --listmodels List all available models
|
||||
-x, --listcontexts List all contexts
|
||||
-X, --listsessions List all sessions
|
||||
-U, --updatepatterns Update patterns
|
||||
-c, --copy Copy to clipboard
|
||||
-m, --model= Choose model
|
||||
--modelContextLength= Model context length (only affects ollama)
|
||||
-o, --output= Output to file
|
||||
--output-session Output the entire session (also a temporary one) to the output file
|
||||
-n, --latest= Number of latest patterns to list (default: 0)
|
||||
-d, --changeDefaultModel Change default model
|
||||
-y, --youtube= YouTube video or play list "URL" to grab transcript, comments from it and send to chat or print it put to the console and store it in the output file
|
||||
--playlist Prefer playlist over video if both ids are present in the URL
|
||||
--transcript Grab transcript from YouTube video and send to chat (it is used per default).
|
||||
--transcript-with-timestamps Grab transcript from YouTube video with timestamps and send to chat
|
||||
--comments Grab comments from YouTube video and send to chat
|
||||
--metadata Output video metadata
|
||||
-g, --language= Specify the Language Code for the chat, e.g. -g=en -g=zh
|
||||
-u, --scrape_url= Scrape website URL to markdown using Jina AI
|
||||
-q, --scrape_question= Search question using Jina AI
|
||||
-e, --seed= Seed to be used for LMM generation
|
||||
-w, --wipecontext= Wipe context
|
||||
-W, --wipesession= Wipe session
|
||||
--printcontext= Print context
|
||||
--printsession= Print session
|
||||
--readability Convert HTML input into a clean, readable view
|
||||
--input-has-vars Apply variables to user input
|
||||
--dry-run Show what would be sent to the model without actually sending it
|
||||
--serve Serve the Fabric Rest API
|
||||
--serveOllama Serve the Fabric Rest API with ollama endpoints
|
||||
--address= The address to bind the REST API (default: :8080)
|
||||
--config= Path to YAML config file
|
||||
--version Print current version
|
||||
--listextensions List all registered extensions
|
||||
--addextension= Register a new extension from config file path
|
||||
--rmextension= Remove a registered extension by name
|
||||
--strategy= Choose a strategy from the available strategies
|
||||
--liststrategies List all strategies
|
||||
|
||||
Help Options:
|
||||
-h, --help Show this help message
|
||||
-h, --help Show this help message
|
||||
|
||||
```
|
||||
|
||||
@@ -468,6 +518,21 @@ You can use any of the Patterns you see there in any AI application that you hav
|
||||
|
||||
The wisdom of crowds for the win.
|
||||
|
||||
### Prompt Strategies
|
||||
|
||||
Fabric also implements prompt strategies like "Chain of Thought" or "Chain of Draft" which can
|
||||
be used in addition to the basic patterns.
|
||||
|
||||
See the [Thinking Faster by Writing Less](https://arxiv.org/pdf/2502.18600) paper and
|
||||
the [Thought Generation section of Learn Prompting](https://learnprompting.org/docs/advanced/thought_generation/introduction) for examples of prompt strategies.
|
||||
|
||||
Each strategy is available as a small `json` file in the [`/strategies`](https://github.com/danielmiessler/fabric/tree/main/strategies) directory.
|
||||
|
||||
The prompt modification of the strategy is applied to the system prompt and passed on to the
|
||||
LLM in the chat session.
|
||||
|
||||
Use `fabric -S` and select the option to install the strategies in your `~/.config/fabric` directory.
|
||||
|
||||
## Custom Patterns
|
||||
|
||||
You may want to use Fabric to create your own custom Patterns—but not share them with others. No problem!
|
||||
@@ -540,7 +605,7 @@ alias pbpaste='xclip -selection clipboard -o'
|
||||
|
||||
## Web Interface
|
||||
|
||||
Fabric now includes a built-in web interface that provides a GUI alternative to the command-line interface and an out-of-the-box website for those who want to get started with web development or blogging.
|
||||
Fabric now includes a built-in web interface that provides a GUI alternative to the command-line interface and an out-of-the-box website for those who want to get started with web development or blogging.
|
||||
You can use this app as a GUI interface for Fabric, a ready to go blog-site, or a website template for your own projects.
|
||||
|
||||
The `web/src/lib/content` directory includes starter `.obsidian/` and `templates/` directories, allowing you to open up the `web/src/lib/content/` directory as an [Obsidian.md](https://obsidian.md) vault. You can place your posts in the posts directory when you're ready to publish.
|
||||
|
||||
215
Web Interface MOD Readme Files/WEB V2 Install Guide.md
Normal file
215
Web Interface MOD Readme Files/WEB V2 Install Guide.md
Normal file
@@ -0,0 +1,215 @@
|
||||
# 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
|
||||
|
||||
# The following three lines of code are path examples, replace with your actual path.
|
||||
|
||||
# Add fabric to PATH
|
||||
export PATH="/Users/USERNAME/Documents/fabric:$PATH"
|
||||
|
||||
# Define the base directory for Obsidian notes
|
||||
obsidian_base="/Users/USERNAME/Documents/fabric/web/myfiles/Fabric_obsidian"
|
||||
|
||||
# Define the patterns directory
|
||||
patterns_dir="/Users/USERNAME/Documents/fabric/patterns"
|
||||
|
||||
|
||||
# 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.
|
||||
|
||||
Also if you modify patterns, descriptions or tags in Pattern_Descriptions/pattern_descriptions.json, make sure to copy the file over in web/static/data/pattern_descriptions.json
|
||||
|
||||
_____ ______ ______
|
||||
|
||||
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
|
||||
295
Web Interface MOD Readme Files/pr-1284-update.md
Normal file
295
Web Interface MOD Readme Files/pr-1284-update.md
Normal file
@@ -0,0 +1,295 @@
|
||||
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/XMzjgqvdltM
|
||||
|
||||
|
||||
|
||||
## 🌟 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
|
||||
|
||||
@@ -156,6 +156,11 @@ func Cli(version string) (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
if currentFlags.ListStrategies {
|
||||
err = registry.Strategies.ListStrategies()
|
||||
return
|
||||
}
|
||||
|
||||
// if the interactive flag is set, run the interactive function
|
||||
// if currentFlags.Interactive {
|
||||
// interactive.Interactive()
|
||||
@@ -166,7 +171,7 @@ func Cli(version string) (err error) {
|
||||
var messageTools string
|
||||
|
||||
if currentFlags.YouTube != "" {
|
||||
if registry.YouTube.IsConfigured() == false {
|
||||
if !registry.YouTube.IsConfigured() {
|
||||
err = fmt.Errorf("YouTube is not configured, please run the setup procedure")
|
||||
return
|
||||
}
|
||||
@@ -241,7 +246,7 @@ func Cli(version string) (err error) {
|
||||
}
|
||||
|
||||
var chatter *core.Chatter
|
||||
if chatter, err = registry.GetChatter(currentFlags.Model, currentFlags.ModelContextLength, currentFlags.Stream, currentFlags.DryRun); err != nil {
|
||||
if chatter, err = registry.GetChatter(currentFlags.Model, currentFlags.ModelContextLength, currentFlags.Strategy, currentFlags.Stream, currentFlags.DryRun); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"github.com/danielmiessler/fabric/core"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/danielmiessler/fabric/core"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
|
||||
34
cli/flags.go
34
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
|
||||
@@ -70,6 +69,8 @@ type Flags struct {
|
||||
ListExtensions bool `long:"listextensions" description:"List all registered extensions"`
|
||||
AddExtension string `long:"addextension" description:"Register a new extension from config file path"`
|
||||
RemoveExtension string `long:"rmextension" description:"Remove a registered extension by name"`
|
||||
Strategy string `long:"strategy" description:"Choose a strategy from the available strategies" default:""`
|
||||
ListStrategies bool `long:"liststrategies" description:"List all strategies"`
|
||||
}
|
||||
|
||||
var debug = false
|
||||
@@ -115,14 +116,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 +153,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 +170,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 +236,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) {
|
||||
@@ -266,13 +269,14 @@ func (o *Flags) BuildChatRequest(Meta string) (ret *common.ChatRequest, err erro
|
||||
ContextName: o.Context,
|
||||
SessionName: o.Session,
|
||||
PatternName: o.Pattern,
|
||||
StrategyName: o.Strategy,
|
||||
PatternVariables: o.PatternVariables,
|
||||
InputHasVars: o.InputHasVars,
|
||||
Meta: Meta,
|
||||
}
|
||||
|
||||
var message *goopenai.ChatCompletionMessage
|
||||
if o.Attachments == nil || len(o.Attachments) == 0 {
|
||||
if len(o.Attachments) == 0 {
|
||||
if o.Message != "" {
|
||||
message = &goopenai.ChatCompletionMessage{
|
||||
Role: goopenai.ChatMessageRoleUser,
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -13,6 +13,7 @@ type ChatRequest struct {
|
||||
Language string
|
||||
Meta string
|
||||
InputHasVars bool
|
||||
StrategyName string
|
||||
}
|
||||
|
||||
type ChatOptions 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"
|
||||
)
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
"github.com/danielmiessler/fabric/common"
|
||||
"github.com/danielmiessler/fabric/plugins/ai"
|
||||
"github.com/danielmiessler/fabric/plugins/db/fsdb"
|
||||
"github.com/danielmiessler/fabric/plugins/strategy"
|
||||
"github.com/danielmiessler/fabric/plugins/template"
|
||||
)
|
||||
|
||||
@@ -24,6 +25,7 @@ type Chatter struct {
|
||||
model string
|
||||
modelContextLength int
|
||||
vendor ai.Vendor
|
||||
strategy string
|
||||
}
|
||||
|
||||
func (o *Chatter) Send(request *common.ChatRequest, opts *common.ChatOptions) (session *fsdb.Session, err error) {
|
||||
@@ -35,6 +37,9 @@ func (o *Chatter) Send(request *common.ChatRequest, opts *common.ChatOptions) (s
|
||||
if len(vendorMessages) == 0 {
|
||||
if session.Name != "" {
|
||||
err = o.db.Sessions.SaveSession(session)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
err = fmt.Errorf("no messages provided")
|
||||
return
|
||||
@@ -140,6 +145,19 @@ func (o *Chatter) BuildSession(request *common.ChatRequest, raw bool) (session *
|
||||
}
|
||||
|
||||
systemMessage := strings.TrimSpace(contextContent) + strings.TrimSpace(patternContent)
|
||||
|
||||
// Apply strategy if specified
|
||||
if request.StrategyName != "" {
|
||||
strategy, err := strategy.LoadStrategy(request.StrategyName)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not load strategy %s: %v", request.StrategyName, err)
|
||||
}
|
||||
if strategy != nil && strategy.Prompt != "" {
|
||||
// prepend the strategy prompt to the system message
|
||||
systemMessage = fmt.Sprintf("%s\n%s", strategy.Prompt, systemMessage)
|
||||
}
|
||||
}
|
||||
|
||||
if request.Language != "" {
|
||||
systemMessage = fmt.Sprintf("%s. Please use the language '%s' for the output.", systemMessage, request.Language)
|
||||
}
|
||||
|
||||
@@ -7,6 +7,9 @@ import (
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
|
||||
"github.com/danielmiessler/fabric/plugins/ai/exolab"
|
||||
"github.com/danielmiessler/fabric/plugins/strategy"
|
||||
|
||||
"github.com/samber/lo"
|
||||
|
||||
"github.com/danielmiessler/fabric/common"
|
||||
@@ -18,6 +21,8 @@ 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"
|
||||
"github.com/danielmiessler/fabric/plugins/ai/openai"
|
||||
@@ -40,6 +45,7 @@ func NewPluginRegistry(db *fsdb.Db) (ret *PluginRegistry, err error) {
|
||||
YouTube: youtube.NewYouTube(),
|
||||
Language: lang.NewLanguage(),
|
||||
Jina: jina.NewClient(),
|
||||
Strategies: strategy.NewStrategiesManager(),
|
||||
}
|
||||
|
||||
var homedir string
|
||||
@@ -50,11 +56,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(), mistral.NewClient(), deepseek.NewClient())
|
||||
anthropic.NewClient(),
|
||||
siliconcloud.NewClient(),
|
||||
openrouter.NewClient(),
|
||||
lmstudio.NewClient(),
|
||||
mistral.NewClient(),
|
||||
deepseek.NewClient(),
|
||||
exolab.NewClient(),
|
||||
litellm.NewClient(),
|
||||
)
|
||||
_ = ret.Configure()
|
||||
|
||||
return
|
||||
@@ -71,6 +88,7 @@ type PluginRegistry struct {
|
||||
Language *lang.Language
|
||||
Jina *jina.Client
|
||||
TemplateExtensions *template.ExtensionManager
|
||||
Strategies *strategy.StrategiesManager
|
||||
}
|
||||
|
||||
func (o *PluginRegistry) SaveEnvFile() (err error) {
|
||||
@@ -79,6 +97,7 @@ func (o *PluginRegistry) SaveEnvFile() (err error) {
|
||||
|
||||
o.Defaults.Settings.FillEnvFileContent(&envFileContent)
|
||||
o.PatternsLoader.SetupFillEnvFileContent(&envFileContent)
|
||||
o.Strategies.SetupFillEnvFileContent(&envFileContent)
|
||||
|
||||
for _, vendor := range o.VendorManager.Vendors {
|
||||
vendor.SetupFillEnvFileContent(&envFileContent)
|
||||
@@ -94,7 +113,7 @@ func (o *PluginRegistry) SaveEnvFile() (err error) {
|
||||
|
||||
func (o *PluginRegistry) Setup() (err error) {
|
||||
setupQuestion := plugins.NewSetupQuestion("Enter the number of the plugin to setup")
|
||||
groupsPlugins := common.NewGroupsItemsSelector[plugins.Plugin]("Available plugins (please configure all required plugins):",
|
||||
groupsPlugins := common.NewGroupsItemsSelector("Available plugins (please configure all required plugins):",
|
||||
func(plugin plugins.Plugin) string {
|
||||
var configuredLabel string
|
||||
if plugin.IsConfigured() {
|
||||
@@ -110,7 +129,7 @@ func (o *PluginRegistry) Setup() (err error) {
|
||||
return vendor
|
||||
})...)
|
||||
|
||||
groupsPlugins.AddGroupItems("Tools", o.Defaults, o.PatternsLoader, o.YouTube, o.Language, o.Jina)
|
||||
groupsPlugins.AddGroupItems("Tools", o.Defaults, o.PatternsLoader, o.YouTube, o.Language, o.Jina, o.Strategies)
|
||||
|
||||
for {
|
||||
groupsPlugins.Print()
|
||||
@@ -191,7 +210,7 @@ func (o *PluginRegistry) Configure() (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
func (o *PluginRegistry) GetChatter(model string, modelContextLength int, stream bool, dryRun bool) (ret *Chatter, err error) {
|
||||
func (o *PluginRegistry) GetChatter(model string, modelContextLength int, strategy string, stream bool, dryRun bool) (ret *Chatter, err error) {
|
||||
ret = &Chatter{
|
||||
db: o.Db,
|
||||
Stream: stream,
|
||||
@@ -243,5 +262,6 @@ func (o *PluginRegistry) GetChatter(model string, modelContextLength int, stream
|
||||
model, defaultModel, defaultVendor, errMsg)
|
||||
return
|
||||
}
|
||||
ret.strategy = strategy
|
||||
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;
|
||||
|
||||
91
go.mod
91
go.mod
@@ -1,65 +1,63 @@
|
||||
module github.com/danielmiessler/fabric
|
||||
|
||||
go 1.22.8
|
||||
|
||||
toolchain go1.23.1
|
||||
go 1.23.4
|
||||
|
||||
require (
|
||||
github.com/anaskhan96/soup v1.2.5
|
||||
github.com/anthropics/anthropic-sdk-go v0.2.0-alpha.4
|
||||
github.com/anthropics/anthropic-sdk-go v0.2.0-alpha.11
|
||||
github.com/atotto/clipboard v0.1.4
|
||||
github.com/gabriel-vasile/mimetype v1.4.6
|
||||
github.com/gabriel-vasile/mimetype v1.4.8
|
||||
github.com/gin-gonic/gin v1.10.0
|
||||
github.com/go-git/go-git/v5 v5.12.0
|
||||
github.com/go-shiori/go-readability v0.0.0-20241012063810-92284fa8a71f
|
||||
github.com/go-git/go-git/v5 v5.13.2
|
||||
github.com/go-shiori/go-readability v0.0.0-20250217085726-9f5bf5ca7612
|
||||
github.com/google/generative-ai-go v0.19.0
|
||||
github.com/jessevdk/go-flags v1.6.1
|
||||
github.com/joho/godotenv v1.5.1
|
||||
github.com/ollama/ollama v0.4.1
|
||||
github.com/otiai10/copy v1.14.0
|
||||
github.com/ollama/ollama v0.5.12
|
||||
github.com/otiai10/copy v1.14.1
|
||||
github.com/pkg/errors v0.9.1
|
||||
github.com/samber/lo v1.47.0
|
||||
github.com/sashabaranov/go-openai v1.35.6
|
||||
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/text v0.21.0
|
||||
google.golang.org/api v0.220.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
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
)
|
||||
|
||||
require (
|
||||
cloud.google.com/go v0.116.0 // indirect
|
||||
cloud.google.com/go/ai v0.8.0 // indirect
|
||||
cloud.google.com/go/auth v0.14.1 // indirect
|
||||
cloud.google.com/go v0.118.3 // indirect
|
||||
cloud.google.com/go/ai v0.10.0 // indirect
|
||||
cloud.google.com/go/auth v0.15.0 // indirect
|
||||
cloud.google.com/go/auth/oauth2adapt v0.2.7 // indirect
|
||||
cloud.google.com/go/compute/metadata v0.6.0 // indirect
|
||||
cloud.google.com/go/longrunning v0.5.7 // indirect
|
||||
cloud.google.com/go/longrunning v0.6.4 // indirect
|
||||
dario.cat/mergo v1.0.1 // indirect
|
||||
github.com/Microsoft/go-winio v0.6.2 // indirect
|
||||
github.com/ProtonMail/go-crypto v1.1.2 // indirect
|
||||
github.com/andybalholm/cascadia v1.3.2 // indirect
|
||||
github.com/ProtonMail/go-crypto v1.1.5 // indirect
|
||||
github.com/andybalholm/cascadia v1.3.3 // indirect
|
||||
github.com/araddon/dateparse v0.0.0-20210429162001-6b43995a97de // indirect
|
||||
github.com/bytedance/sonic v1.12.4 // indirect
|
||||
github.com/bytedance/sonic/loader v0.2.1 // indirect
|
||||
github.com/cloudflare/circl v1.5.0 // indirect
|
||||
github.com/cloudwego/base64x v0.1.4 // indirect
|
||||
github.com/cloudwego/iasm v0.2.0 // indirect
|
||||
github.com/cyphar/filepath-securejoin v0.3.4 // indirect
|
||||
github.com/bytedance/sonic v1.12.9 // indirect
|
||||
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/cyphar/filepath-securejoin v0.4.1 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/emirpasic/gods v1.18.1 // indirect
|
||||
github.com/felixge/httpsnoop v1.0.4 // indirect
|
||||
github.com/gin-contrib/sse v0.1.0 // indirect
|
||||
github.com/gin-contrib/sse v1.0.0 // indirect
|
||||
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
|
||||
github.com/go-git/go-billy/v5 v5.6.0 // indirect
|
||||
github.com/go-git/go-billy/v5 v5.6.2 // indirect
|
||||
github.com/go-logr/logr v1.4.2 // indirect
|
||||
github.com/go-logr/stdr v1.2.2 // indirect
|
||||
github.com/go-playground/locales v0.14.1 // indirect
|
||||
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||
github.com/go-playground/validator/v10 v10.22.1 // indirect
|
||||
github.com/go-playground/validator/v10 v10.25.0 // indirect
|
||||
github.com/go-shiori/dom v0.0.0-20230515143342-73569d674e1c // indirect
|
||||
github.com/goccy/go-json v0.10.3 // indirect
|
||||
github.com/goccy/go-json v0.10.5 // indirect
|
||||
github.com/gogs/chardet v0.0.0-20211120154057-b7413eaefb8f // indirect
|
||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
|
||||
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect
|
||||
github.com/google/s2a-go v0.1.9 // indirect
|
||||
github.com/google/uuid v1.6.0 // indirect
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.3.4 // indirect
|
||||
@@ -67,17 +65,18 @@ require (
|
||||
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
github.com/kevinburke/ssh_config v1.2.0 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.2.9 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.2.10 // indirect
|
||||
github.com/leodido/go-urn v1.4.0 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/otiai10/mint v1.6.3 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.2.3 // indirect
|
||||
github.com/pjbgf/sha1cd v0.3.0 // indirect
|
||||
github.com/pjbgf/sha1cd v0.3.2 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect
|
||||
github.com/skeema/knownhosts v1.3.0 // indirect
|
||||
github.com/tidwall/gjson v1.14.4 // indirect
|
||||
github.com/skeema/knownhosts v1.3.1 // indirect
|
||||
github.com/tidwall/gjson v1.18.0 // indirect
|
||||
github.com/tidwall/match v1.1.1 // indirect
|
||||
github.com/tidwall/pretty v1.2.1 // indirect
|
||||
github.com/tidwall/sjson v1.2.5 // indirect
|
||||
@@ -85,21 +84,21 @@ require (
|
||||
github.com/ugorji/go/codec v1.2.12 // indirect
|
||||
github.com/xanzy/ssh-agent v0.3.3 // indirect
|
||||
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.58.0 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.58.0 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.59.0 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.59.0 // indirect
|
||||
go.opentelemetry.io/otel v1.34.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.34.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.34.0 // indirect
|
||||
golang.org/x/arch v0.12.0 // indirect
|
||||
golang.org/x/crypto v0.32.0 // indirect
|
||||
golang.org/x/net v0.34.0 // indirect
|
||||
golang.org/x/oauth2 v0.25.0 // indirect
|
||||
golang.org/x/sync v0.10.0 // indirect
|
||||
golang.org/x/sys v0.29.0 // indirect
|
||||
golang.org/x/time v0.9.0 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20241209162323-e6fa225c2576 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250127172529-29210b9bc287 // indirect
|
||||
golang.org/x/arch v0.14.0 // indirect
|
||||
golang.org/x/crypto v0.35.0 // indirect
|
||||
golang.org/x/net v0.35.0 // indirect
|
||||
golang.org/x/oauth2 v0.27.0 // indirect
|
||||
golang.org/x/sync v0.11.0 // indirect
|
||||
golang.org/x/sys v0.30.0 // indirect
|
||||
golang.org/x/time v0.10.0 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20250224174004-546df14abb99 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250224174004-546df14abb99 // indirect
|
||||
google.golang.org/grpc v1.70.0 // indirect
|
||||
google.golang.org/protobuf v1.36.4 // indirect
|
||||
google.golang.org/protobuf v1.36.5 // indirect
|
||||
gopkg.in/warnings.v0 v0.1.2 // indirect
|
||||
)
|
||||
|
||||
282
go.sum
282
go.sum
@@ -1,106 +1,73 @@
|
||||
cel.dev/expr v0.19.0/go.mod h1:MrpN08Q+lEBs+bGYdLxxHkZoUSsCp0nSKTs0nTymJgw=
|
||||
cloud.google.com/go v0.116.0 h1:B3fRrSDkLRt5qSHWe40ERJvhvnQwdZiHu0bJOpldweE=
|
||||
cloud.google.com/go v0.116.0/go.mod h1:cEPSRWPzZEswwdr9BxE6ChEn01dWlTaF05LiC2Xs70U=
|
||||
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/auth v0.14.1 h1:AwoJbzUdxA/whv1qj3TLKwh3XX5sikny2fc40wUl+h0=
|
||||
cloud.google.com/go/auth v0.14.1/go.mod h1:4JHUxlGXisL0AW8kXPtUF6ztuOksyfUQNFjfsOCXkPM=
|
||||
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.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.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/iam v1.2.0/go.mod h1:zITGuWgsLZxd8OwAlX+eMFgZDXzBm7icj1PVTYG766Q=
|
||||
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/storage v1.43.0/go.mod h1:ajvxEa7WmZS1PxvKRq4bq0tFT3vMd502JwstCcYv0Q0=
|
||||
cloud.google.com/go/translate v1.10.3/go.mod h1:GW0vC1qvPtd3pgtypCv4k4U8B7EdgK9/QEF2aJEUovs=
|
||||
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=
|
||||
dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.25.0/go.mod h1:obipzmGjfSjam60XLwGfqUkJsfiheAl+TUjG+4yzyPM=
|
||||
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.2 h1:A7JbD57ThNqh7XjmHE+PXpQ3Dqt3BrSAC0AL0Go3KS0=
|
||||
github.com/ProtonMail/go-crypto v1.1.2/go.mod h1:rA3QumHc/FZ8pAHreoekgiAbzpNsfQAosU5td4SnOrE=
|
||||
github.com/agnivade/levenshtein v1.1.1/go.mod h1:veldBMzWxcCG2ZvUTKD2kJNRdCk5hVbJomOvKkmgYbo=
|
||||
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/apache/arrow/go/arrow v0.0.0-20211112161151-bc219186db40/go.mod h1:Q7yQnSMnLvcXlZ8RV+jwz/6y1rQTqbX6C82SndT52Zs=
|
||||
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/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/aws/aws-sdk-go-v2 v1.30.3/go.mod h1:nIQjQVp5sfpQcTc9mPSr1B0PaWK5ByX9MOoDadSN4lc=
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.3/go.mod h1:UbnqO+zjqk3uIt9yCACHJ9IVNhyhOCnYk8yA19SAWrM=
|
||||
github.com/aws/aws-sdk-go-v2/config v1.27.27/go.mod h1:MVYamCg76dFNINkZFu4n4RjDixhVr51HLj4ErWzrVwg=
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.17.27/go.mod h1:gniiwbGahQByxan6YjQUMcW4Aov6bLC3m+evgcoN4r4=
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.11/go.mod h1:SeSUYBLsMYFoRvHE0Tjvn7kbxaUhl75CJi1sbfhMxkU=
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.15/go.mod h1:U9ke74k1n2bf+RIgoX1SXFed1HLs51OgUSs+Ph0KJP8=
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.15/go.mod h1:ZQLZqhcu+JhSrA9/NXRm8SkDvsycE+JkV3WGY41e+IM=
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0/go.mod h1:8tu/lYfQfFe6IGnaOdrpVgEL2IrrDOf6/m9RQum4NkY=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.3/go.mod h1:GlAeCkHwugxdHaueRr4nhPuY+WW+gR8UjlcqzPr1SPI=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.17/go.mod h1:RkZEx4l0EHYDJpWppMJ3nD9wZJAa8/0lq9aVC+r2UII=
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.22.4/go.mod h1:ooyCOXjvJEsUw7x+ZDHeISPMhtwI3ZCB7ggFMcFfWLU=
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.26.4/go.mod h1:0oxfLkpz3rQ/CHlx5hB7H69YUpFiI1tql6Q6Ne+1bCw=
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.30.3/go.mod h1:zwySh8fpFyXp9yOr/KVzxOl8SRqgf/IDw5aUt9UKFcQ=
|
||||
github.com/aws/smithy-go v1.20.3/go.mod h1:krry+ya/rV9RDcV/Q16kpu6ypI4K2czasz0NC3qS14E=
|
||||
github.com/bwesterb/go-ristretto v1.2.3/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0=
|
||||
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/census-instrumentation/opencensus-proto v0.4.1/go.mod h1:4T9NM4+4Vw91VeyqjLS6ao50K5bOcLKN6Q42XnYaRYw=
|
||||
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/chewxy/hm v1.0.0/go.mod h1:qg9YI4q6Fkj/whwHR1D+bOGeF7SniIP40VweVepLjg0=
|
||||
github.com/chewxy/math32 v1.10.1/go.mod h1:dOB2rcuFrCn6UHrze36WSLVPKtzPMRAQvBvUwkSsLqs=
|
||||
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/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/0Y=
|
||||
github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w=
|
||||
github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg=
|
||||
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.6.0 h1:cr5JKic4HI+LkINy2lg3W2jF8sHCVTBncJr5gIIq7qk=
|
||||
github.com/cloudflare/circl v1.6.0/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs=
|
||||
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/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY=
|
||||
github.com/cncf/xds/go v0.0.0-20240905190251-b4127c9b8d78/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8=
|
||||
github.com/containerd/console v1.0.3/go.mod h1:7LqA/THxQ86k76b8c/EMSiaJ3h1eZkMkXar0TQ1gf3U=
|
||||
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/d4l3k/go-bfloat16 v0.0.0-20211005043715-690c3bdd05f1/go.mod h1:uw2gLcxEuYUlAd/EXyjc/v55nd3+47YAgWbSXVxPrNI=
|
||||
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 v0.0.0-20230808193330-2592e75ae04a h1:mATvB/9r/3gvcejNsXKSkQ6lcIaNec2nyfOdlTBR2lU=
|
||||
github.com/elazarl/goproxy v0.0.0-20230808193330-2592e75ae04a/go.mod h1:Ro8st/ElPeALwNFlcTpWmkr6IoMFfkjXAvTHpevnDsM=
|
||||
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/envoyproxy/go-control-plane v0.13.1/go.mod h1:X45hY0mufo6Fd0KW3rqsGvQMw58jvjymeCzBU3mWyHw=
|
||||
github.com/envoyproxy/protoc-gen-validate v1.1.0/go.mod h1:sXRDRVmzEbkM7CVcM06s9shE/m23dg3wzjl0UWqJ2q4=
|
||||
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/gin-contrib/cors v1.7.2/go.mod h1:SUJVARKgQ40dmrzgXEVxj2m7Ig1v1qIboQkPDTQ9t2E=
|
||||
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/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 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=
|
||||
github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y=
|
||||
github.com/gliderlabs/ssh v0.3.7 h1:iV3Bqi942d9huXnzEF2Mt+CY9gLu8DNM4Obd+8bODRE=
|
||||
github.com/gliderlabs/ssh v0.3.7/go.mod h1:zpHEXBstFnQYtGnB8k8kQLol82umzn/2/snG7alWVD8=
|
||||
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.12.0 h1:7Md+ndsjrzZxbddRDZjF14qK+NN56sy6wkqaVrjZtys=
|
||||
github.com/go-git/go-git/v5 v5.12.0/go.mod h1:FTM9VKtnI2m65hNI/TenDDDnUf2Q9FHnXYjuz9i5OEY=
|
||||
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=
|
||||
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
|
||||
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
@@ -112,31 +79,26 @@ 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/goccy/go-json v0.10.3 h1:KZ5WoDbxAIgm2HNbYckL0se1fHD6rz5j4ywS6ebzDqA=
|
||||
github.com/goccy/go-json v0.10.3/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
|
||||
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
||||
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.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/glog v1.2.3/go.mod h1:6AhwSGph0fcJtXVM/PEHPqZlFeoLxhs7/t5UDAwmO+w=
|
||||
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/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||
github.com/google/flatbuffers v24.3.25+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8=
|
||||
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-pkcs11 v0.3.0/go.mod h1:6eQoGcuNJpa7jnd5pMGdkSaQpNDYvPlXWMcjXXThLlY=
|
||||
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/martian/v3 v3.3.3/go.mod h1:iEPrYcgCF7jA9OtScMFQyAlZZ4YXTKEtJ1E6RWzmBA0=
|
||||
github.com/google/s2a-go v0.1.9 h1:LGD7gtMgezd8a/Xak7mEWL0PjoTQFvpRudN895yqKW0=
|
||||
github.com/google/s2a-go v0.1.9/go.mod h1:YA0Ei2ZQL3acow2O62kdp9UlnvMmU7kA6Eutn0dXayM=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
@@ -145,7 +107,6 @@ github.com/googleapis/enterprise-certificate-proxy v0.3.4 h1:XYIDZApgAnrN1c855gT
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.3.4/go.mod h1:YKe7cfqYXjKGpGvmSg28/fFvhNzinZQm8DGnaburhGA=
|
||||
github.com/googleapis/gax-go/v2 v2.14.1 h1:hb0FFeiPaQskmvakKu5EbCbpntQn48jyHuvrkurSS/Q=
|
||||
github.com/googleapis/gax-go/v2 v2.14.1/go.mod h1:Hb/NubMaVM88SrNkvl8X/o8XWwDJEPqouaLeN2IUxoA=
|
||||
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
||||
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A=
|
||||
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=
|
||||
github.com/jessevdk/go-flags v1.6.1 h1:Cvu5U8UGrLay1rZfv/zP7iLpSHGUZ/Ou68T0iX1bBK4=
|
||||
@@ -157,8 +118,8 @@ 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=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
@@ -172,50 +133,40 @@ github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjS
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mattn/go-runewidth v0.0.10/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
|
||||
github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||
github.com/mmcloughlin/avo v0.5.0/go.mod h1:ChHFdoV7ql95Wi7vuq2YT1bwCJqiWdZrQ1im3VujLYM=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||
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/nlpodyssey/gopickle v0.3.0/go.mod h1:f070HJ/yR+eLi5WmM1OXJEGaTpuJEUiib19olXgYha0=
|
||||
github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
|
||||
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/mint v1.5.1 h1:XaPLeE+9vGbuyEHem1JNk3bYc7KKqyI/na0/mLd/Kks=
|
||||
github.com/otiai10/mint v1.5.1/go.mod h1:MJm72SBthJjz8qhefc4z1PYEieWmy8Bku7CjcAqyUSM=
|
||||
github.com/pdevine/tensor v0.0.0-20240510204454-f88f4562727c/go.mod h1:PSojXDXF7TbgQiD6kkd98IHOS0QqTyUEaWRiS8+BLu8=
|
||||
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.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=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod h1:t/avpk3KcrXxUnYOhZhMXJlSEyie6gQbtLq5NM3loB8=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||
github.com/rivo/uniseg v0.2.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/sashabaranov/go-openai v1.35.6 h1:oi0rwCvyxMxgFALDGnyqFTyCJm6n72OnEG3sybIFR0g=
|
||||
github.com/sashabaranov/go-openai v1.35.6/go.mod h1:lj5b/K+zjTSFxVLijLSTDZuP7adOgerWeFyZLUhAKRg=
|
||||
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.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/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||
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/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0=
|
||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
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=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||
@@ -228,11 +179,12 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
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=
|
||||
github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
|
||||
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
|
||||
@@ -244,19 +196,15 @@ github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
|
||||
github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE=
|
||||
github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
|
||||
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
|
||||
github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM=
|
||||
github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw=
|
||||
github.com/xtgo/set v1.0.0/go.mod h1:d3NHzGzSa0NmB2NhFyECA+QdRp29oEn2xbT+TpeFoM8=
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
|
||||
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/detectors/gcp v1.32.0/go.mod h1:TVqo0Sda4Cv8gCIixd7LuLwW4EylumVWfhjZJjDD4DU=
|
||||
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/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/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.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=
|
||||
go.opentelemetry.io/otel v1.34.0/go.mod h1:OWFPOQ+h4G8xpyjgqo4SxJYdDQ/qmRH+wivy7zzx9oI=
|
||||
go.opentelemetry.io/otel/metric v1.34.0 h1:+eTR3U0MyfWjRDhmFMxe2SsW64QrZ84AOhvqS7Y+PoQ=
|
||||
@@ -267,19 +215,23 @@ 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=
|
||||
go4.org/unsafe/assume-no-moving-gc v0.0.0-20231121144256-b99613f794b6/go.mod h1:FftLjUGFEDu5k8lt0ddY+HcrH/qU/0qk+H8j9/nTl3E=
|
||||
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=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
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.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
|
||||
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.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=
|
||||
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY=
|
||||
golang.org/x/image v0.14.0/go.mod h1:HUYqC05R2ZcZ3ejNQsIHQDQiwWM4JBqmm6MKANTp4LE=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
@@ -287,16 +239,24 @@ 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.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0=
|
||||
golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k=
|
||||
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/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.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8=
|
||||
golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk=
|
||||
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=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ=
|
||||
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/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=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
@@ -308,46 +268,55 @@ 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.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU=
|
||||
golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
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.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.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.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.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=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
|
||||
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/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
|
||||
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/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.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=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
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=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
gonum.org/v1/gonum v0.15.0/go.mod h1:xzZVBJBtS+Mz4q0Yl2LJTk+OxOg4jiXZ7qBoM0uISGo=
|
||||
google.golang.org/api v0.220.0 h1:3oMI4gdBgB72WFVwE1nerDD8W3HUOS4kypK6rRLbGns=
|
||||
google.golang.org/api v0.220.0/go.mod h1:26ZAlY6aN/8WgpCzjPNy18QpYaz7Zgg1h0qe1GkZEmY=
|
||||
google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds=
|
||||
google.golang.org/genproto v0.0.0-20240903143218-8af14fe29dc1/go.mod h1:hL97c3SYopEHblzpxRL4lSs523++l8DYxGM1FQiYmb4=
|
||||
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/bytestream v0.0.0-20250127172529-29210b9bc287/go.mod h1:7VGktjvijnuhf2AobFqsoaBGnG8rImcxqoL+QPBPRq4=
|
||||
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/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-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-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=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
@@ -360,7 +329,4 @@ gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gorgonia.org/vecf32 v0.9.0/go.mod h1:NCc+5D2oxddRL11hd+pCB1PEyXWOyiQxfZ/1wwhOXCA=
|
||||
gorgonia.org/vecf64 v0.9.0/go.mod h1:hp7IOWCnRiVQKON73kkC/AUMtEXyf9kGlVrtPQ9ccVA=
|
||||
nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50=
|
||||
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
|
||||
|
||||
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"
|
||||
@@ -2,14 +2,14 @@ schema = 3
|
||||
|
||||
[mod]
|
||||
[mod."cloud.google.com/go"]
|
||||
version = "v0.116.0"
|
||||
hash = "sha256-e62GvNveg3bRi4O+eBARqgQ2sinobx+SVGR9WE7jKgs="
|
||||
version = "v0.118.3"
|
||||
hash = "sha256-y3YHioDLx9/asf2AWuincnq4BVO2S/GQFxpa1dEpxKs="
|
||||
[mod."cloud.google.com/go/ai"]
|
||||
version = "v0.8.0"
|
||||
hash = "sha256-833SmzVY8+tci2RozAlcdKQZ63RlU2CmeY/8xttP+WI="
|
||||
version = "v0.10.0"
|
||||
hash = "sha256-huE2q1HBA6d9FQ152HFQhOe9fX0QlLFVuFO3XAfln8U="
|
||||
[mod."cloud.google.com/go/auth"]
|
||||
version = "v0.14.1"
|
||||
hash = "sha256-nBYUu/RQv3aAUgUaYbXJ3bCNkJfF9W05NThkwrL3sZg="
|
||||
version = "v0.15.0"
|
||||
hash = "sha256-N9xjLPDLhG5cqUx94tNccv74Q/fIlukWU6NbWpuNi+I="
|
||||
[mod."cloud.google.com/go/auth/oauth2adapt"]
|
||||
version = "v0.2.7"
|
||||
hash = "sha256-U+pXaY0kPnSeBzHWxELZ75bZnb74nygwIVZDdXYcP5g="
|
||||
@@ -17,8 +17,8 @@ schema = 3
|
||||
version = "v0.6.0"
|
||||
hash = "sha256-E8/cwio4xR8buCryR4HwR7+agb4M3zqgXSm7rBglmIY="
|
||||
[mod."cloud.google.com/go/longrunning"]
|
||||
version = "v0.5.7"
|
||||
hash = "sha256-hZUbysdaEbFB2nDAg+wjOZHt6E99oEnH7Lo6IQr7FxU="
|
||||
version = "v0.6.4"
|
||||
hash = "sha256-Q0JtsyxSgVwi91ZhvefpAq8fKbblRrtQ2bQhQYiTY48="
|
||||
[mod."dario.cat/mergo"]
|
||||
version = "v1.0.1"
|
||||
hash = "sha256-wcG6+x0k6KzOSlaPA+1RFxa06/RIAePJTAjjuhLbImw="
|
||||
@@ -26,17 +26,17 @@ schema = 3
|
||||
version = "v0.6.2"
|
||||
hash = "sha256-tVNWDUMILZbJvarcl/E7tpSnkn7urqgSHa2Eaka5vSU="
|
||||
[mod."github.com/ProtonMail/go-crypto"]
|
||||
version = "v1.1.2"
|
||||
hash = "sha256-7pTf7aJt2mGC/u8/+AQ1erGypAO0Rg0HqlIOLeiqLEg="
|
||||
version = "v1.1.5"
|
||||
hash = "sha256-N5Zn0f/NF3ezyGou2kRw9BwM25feJqnp7TPkRt6oK6I="
|
||||
[mod."github.com/anaskhan96/soup"]
|
||||
version = "v1.2.5"
|
||||
hash = "sha256-t8yCyK2y7x2qaI/3Yw16q3zVFqu+3acLcPgTr1MIKWg="
|
||||
[mod."github.com/andybalholm/cascadia"]
|
||||
version = "v1.3.2"
|
||||
hash = "sha256-Nc9SkqJO/ecincVcUBFITy24TMmMGj5o0Q8EgdNhrEk="
|
||||
version = "v1.3.3"
|
||||
hash = "sha256-jv7ZshpSd7FZzKKN6hqlUgiR8C3y85zNIS/hq7g76Ho="
|
||||
[mod."github.com/anthropics/anthropic-sdk-go"]
|
||||
version = "v0.2.0-alpha.4"
|
||||
hash = "sha256-8a85Hd4J7eaWvN+J6MImsapStbse5WDDjlODZk3PMzk="
|
||||
version = "v0.2.0-alpha.11"
|
||||
hash = "sha256-0wl62e6AVhDY3KkoYrfAHFtBrwNC4nzqrR55iyCJlwk="
|
||||
[mod."github.com/araddon/dateparse"]
|
||||
version = "v0.0.0-20210429162001-6b43995a97de"
|
||||
hash = "sha256-UuX84naeRGMsFOgIgRoBHG5sNy1CzBkWPKmd6VbLwFw="
|
||||
@@ -44,23 +44,20 @@ schema = 3
|
||||
version = "v0.1.4"
|
||||
hash = "sha256-ZZ7U5X0gWOu8zcjZcWbcpzGOGdycwq0TjTFh/eZHjXk="
|
||||
[mod."github.com/bytedance/sonic"]
|
||||
version = "v1.12.4"
|
||||
hash = "sha256-i6bLujq1dYN+yN2iusMuXrNVkT17bkuR5r5D48qDvpo="
|
||||
version = "v1.12.9"
|
||||
hash = "sha256-smlXGC4n6fkOiVR+A3VGd71xp+cYo42MSHuWq7H3jew="
|
||||
[mod."github.com/bytedance/sonic/loader"]
|
||||
version = "v0.2.1"
|
||||
hash = "sha256-+gPRZtBOJbAnXp/jdMlPmesc62JGH8akQ1UK9VRI7E4="
|
||||
version = "v0.2.3"
|
||||
hash = "sha256-c0m1nl1jv76LVaUgFFNjZU9jss/hoSWXyCRimhRWYjM="
|
||||
[mod."github.com/cloudflare/circl"]
|
||||
version = "v1.5.0"
|
||||
hash = "sha256-j7T4cfbfmhlbaO+kNKveTnk95JbkEOX0IVw8D9bGTkQ="
|
||||
version = "v1.6.0"
|
||||
hash = "sha256-a+SVfnHYC8Fb+NQLboNg5P9sry+WutzuNetVHFVAAo0="
|
||||
[mod."github.com/cloudwego/base64x"]
|
||||
version = "v0.1.4"
|
||||
hash = "sha256-umCZR3iNmHFm+BC76kfpdcRG+pTQd6Jcu/c2kQDnyfw="
|
||||
[mod."github.com/cloudwego/iasm"]
|
||||
version = "v0.2.0"
|
||||
hash = "sha256-TzIP2N3HOesXrKACsRr/ShcoqttwPGZPckIepsTyHOA="
|
||||
version = "v0.1.5"
|
||||
hash = "sha256-MyUYTveN48DhnL8mwAgCRuMExLct98uzSPsmYlfaa4I="
|
||||
[mod."github.com/cyphar/filepath-securejoin"]
|
||||
version = "v0.3.4"
|
||||
hash = "sha256-I9dV5gtKk3hH39taAWxvvJEXMi4YoHSxeESVyjpl1MU="
|
||||
version = "v0.4.1"
|
||||
hash = "sha256-NOV6MfbkcQbfhNmfADQw2SJmZ6q1nw0wwg8Pm2tf2DM="
|
||||
[mod."github.com/davecgh/go-spew"]
|
||||
version = "v1.1.1"
|
||||
hash = "sha256-nhzSUrE1fCkN0+RL04N4h8jWmRFPPPWbCuDc7Ss0akI="
|
||||
@@ -71,11 +68,11 @@ schema = 3
|
||||
version = "v1.0.4"
|
||||
hash = "sha256-c1JKoRSndwwOyOxq9ddCe+8qn7mG9uRq2o/822x5O/c="
|
||||
[mod."github.com/gabriel-vasile/mimetype"]
|
||||
version = "v1.4.6"
|
||||
hash = "sha256-W/uPcE22Fduw1XmX8Ujf1S9SYVOcEoE1wzK4I0/vapw="
|
||||
version = "v1.4.8"
|
||||
hash = "sha256-ElqfQtnoGHyVqtN0mJjeWakQ6N5x+nVaX3+uOV7Q5Xk="
|
||||
[mod."github.com/gin-contrib/sse"]
|
||||
version = "v0.1.0"
|
||||
hash = "sha256-zYbMTao+1F+385Lvsba9roLmmt9eYqr57sUWo0LCVhw="
|
||||
version = "v1.0.0"
|
||||
hash = "sha256-xnaabOxDN+ojnHQC7mHd/876Z9nWFScW+JrMm1HWREw="
|
||||
[mod."github.com/gin-gonic/gin"]
|
||||
version = "v1.10.0"
|
||||
hash = "sha256-esJasHrJtuTBwGPGAoc/XSb428J8va+tPGcZ0gTfsgc="
|
||||
@@ -83,11 +80,11 @@ schema = 3
|
||||
version = "v1.5.1-0.20230307220236-3a3c6141e376"
|
||||
hash = "sha256-f4k0gSYuo0/q3WOoTxl2eFaj7WZpdz29ih6CKc8Ude8="
|
||||
[mod."github.com/go-git/go-billy/v5"]
|
||||
version = "v5.6.0"
|
||||
hash = "sha256-Hw+odNozpiixXqmsbahihdV+TBxpusm6/hDLngf7kUg="
|
||||
version = "v5.6.2"
|
||||
hash = "sha256-VgbxcLkHjiSyRIfKS7E9Sn8OynCrMGUDkwFz6K2TVL4="
|
||||
[mod."github.com/go-git/go-git/v5"]
|
||||
version = "v5.12.0"
|
||||
hash = "sha256-mD8EWOQ25FtKBWVSQhQ8V1Rr0tC/ySFZQ9GMDLRqwQU="
|
||||
version = "v5.13.2"
|
||||
hash = "sha256-voZQHN2OSYcoQF2bIjsdRrHT5NohZ/8q9RrmY7j2Lbc="
|
||||
[mod."github.com/go-logr/logr"]
|
||||
version = "v1.4.2"
|
||||
hash = "sha256-/W6qGilFlZNTb9Uq48xGZ4IbsVeSwJiAMLw4wiNYHLI="
|
||||
@@ -101,23 +98,23 @@ schema = 3
|
||||
version = "v0.18.1"
|
||||
hash = "sha256-2/B2qP51zfiY+k8G0w0D03KXUc7XpWj6wKY7NjNP/9E="
|
||||
[mod."github.com/go-playground/validator/v10"]
|
||||
version = "v10.22.1"
|
||||
hash = "sha256-EsgeltH0ow6saxLvTFVtIyHVqWI3Fiu1AE2Qmnsmowg="
|
||||
version = "v10.25.0"
|
||||
hash = "sha256-198CQ0f+WC7UNxCCPg6rpogez6c5ivpignJNhx+z0W4="
|
||||
[mod."github.com/go-shiori/dom"]
|
||||
version = "v0.0.0-20230515143342-73569d674e1c"
|
||||
hash = "sha256-4lm9KZfR2XnfZU9KTG+4jqLYZqbfL74AMO4y3dKpIbg="
|
||||
[mod."github.com/go-shiori/go-readability"]
|
||||
version = "v0.0.0-20241012063810-92284fa8a71f"
|
||||
hash = "sha256-NgciyWylVSjzkt5xWF1Xk1Xbxgq3PsHW5PZ8oifjZVY="
|
||||
version = "v0.0.0-20250217085726-9f5bf5ca7612"
|
||||
hash = "sha256-yleBb+OmxLbQ0PT4yV2PNBAAE6UFxSRGGpylY8SrSqw="
|
||||
[mod."github.com/goccy/go-json"]
|
||||
version = "v0.10.3"
|
||||
hash = "sha256-ZOzfwCXh+qp+hp+UnC0t422hUV0Cq5KANXkx8hcLp7s="
|
||||
version = "v0.10.5"
|
||||
hash = "sha256-/EtlGihP0/7oInzMC5E0InZ4b5Ad3s4xOpqotloi3xw="
|
||||
[mod."github.com/gogs/chardet"]
|
||||
version = "v0.0.0-20211120154057-b7413eaefb8f"
|
||||
hash = "sha256-4MeqBJsh4U+ZEbfdDwdciTYMlQWkCil2KJbUxHjBSIo="
|
||||
[mod."github.com/golang/groupcache"]
|
||||
version = "v0.0.0-20210331224755-41bb18bfe9da"
|
||||
hash = "sha256-7Gs7CS9gEYZkbu5P4hqPGBpeGZWC64VDwraSKFF+VR0="
|
||||
version = "v0.0.0-20241129210726-2c02b8208cf8"
|
||||
hash = "sha256-AdLZ3dJLe/yduoNvZiXugZxNfmwJjNQyQGsIdzYzH74="
|
||||
[mod."github.com/google/generative-ai-go"]
|
||||
version = "v0.19.0"
|
||||
hash = "sha256-x2K1nkRwtne9MeP5B8FpwavYqQx564go5LzmcBJ0KT4="
|
||||
@@ -149,8 +146,8 @@ schema = 3
|
||||
version = "v1.2.0"
|
||||
hash = "sha256-Ta7ZOmyX8gG5tzWbY2oES70EJPfI90U7CIJS9EAce0s="
|
||||
[mod."github.com/klauspost/cpuid/v2"]
|
||||
version = "v2.2.9"
|
||||
hash = "sha256-6UnDBLqlTsKVeZNl5snKQiEBb8xGK5yyg2eZBg7QHLs="
|
||||
version = "v2.2.10"
|
||||
hash = "sha256-o21Tk5sD7WhhLUoqSkymnjLbzxl0mDJCTC1ApfZJrC0="
|
||||
[mod."github.com/leodido/go-urn"]
|
||||
version = "v1.4.0"
|
||||
hash = "sha256-Q6kplWkY37Tzy6GOme3Wut40jFK4Izun+ij/BJvcEu0="
|
||||
@@ -164,17 +161,20 @@ schema = 3
|
||||
version = "v1.0.2"
|
||||
hash = "sha256-+W9EIW7okXIXjWEgOaMh58eLvBZ7OshW2EhaIpNLSBU="
|
||||
[mod."github.com/ollama/ollama"]
|
||||
version = "v0.4.1"
|
||||
hash = "sha256-FKQRSqVNgsASea9h2B+wbpu4Qid0Dt3H02fKdqFTwuk="
|
||||
version = "v0.5.12"
|
||||
hash = "sha256-Uf4GZdD77RZ5KJtz3iYVRDVCHqEh0UEihzquO4/nrss="
|
||||
[mod."github.com/otiai10/copy"]
|
||||
version = "v1.14.0"
|
||||
hash = "sha256-xsaL1ddkPS544y0Jv7u/INUALBYmYq29ddWvysLXk4A="
|
||||
version = "v1.14.1"
|
||||
hash = "sha256-8RR7u17SbYg9AeBXVHIv5ZMU+kHmOcx0rLUKyz6YtU0="
|
||||
[mod."github.com/otiai10/mint"]
|
||||
version = "v1.6.3"
|
||||
hash = "sha256-/FT3dYP2+UiW/qe1pxQ7HiS8et4+KHGPIMhc+8mHvzw="
|
||||
[mod."github.com/pelletier/go-toml/v2"]
|
||||
version = "v2.2.3"
|
||||
hash = "sha256-fE++SVgnCGdnFZoROHWuYjIR7ENl7k9KKxQrRTquv/o="
|
||||
[mod."github.com/pjbgf/sha1cd"]
|
||||
version = "v0.3.0"
|
||||
hash = "sha256-kX9BdLh2dxtGNaDvc24NORO+C0AZ7JzbrXrtecCdB7w="
|
||||
version = "v0.3.2"
|
||||
hash = "sha256-jdbiRhU8xc1C5c8m7BSCj71PUXHY3f7TWFfxDKKpUMk="
|
||||
[mod."github.com/pkg/errors"]
|
||||
version = "v0.9.1"
|
||||
hash = "sha256-mNfQtcrQmu3sNg/7IwiieKWOgFQOVVe2yXgKBpe/wZw="
|
||||
@@ -182,23 +182,23 @@ schema = 3
|
||||
version = "v1.0.0"
|
||||
hash = "sha256-/FtmHnaGjdvEIKAJtrUfEhV7EVo5A/eYrtdnUkuxLDA="
|
||||
[mod."github.com/samber/lo"]
|
||||
version = "v1.47.0"
|
||||
hash = "sha256-jMXexVTlPdZ40STRpBLv7b+BIRqdxxra12Pl2Mj7Nz8="
|
||||
version = "v1.49.1"
|
||||
hash = "sha256-xMQS9Sx2Bpvwo/9JvSVkJ4RXYOSHm642WRqWA6y0AnU="
|
||||
[mod."github.com/sashabaranov/go-openai"]
|
||||
version = "v1.35.6"
|
||||
hash = "sha256-Ef81pLy9oJXtWg6Nj1gSbPOOccwmgYrr6ka3GQ1rVas="
|
||||
version = "v1.38.0"
|
||||
hash = "sha256-p6C/7oTWgnRjZLNrLLdIzaXvm+1WCrUd1fjZkjuiz1s="
|
||||
[mod."github.com/sergi/go-diff"]
|
||||
version = "v1.3.2-0.20230802210424-5b0b94c5c0d3"
|
||||
hash = "sha256-UcLU83CPMbSoKI8RLvLJ7nvGaE2xRSL1RjoHCVkMzUM="
|
||||
[mod."github.com/skeema/knownhosts"]
|
||||
version = "v1.3.0"
|
||||
hash = "sha256-piR5IdfqxK9nxyErJ+IRDLnkaeNQwX93ztTFZyPm5MQ="
|
||||
version = "v1.3.1"
|
||||
hash = "sha256-kjqQDzuncQNTuOYegqVZExwuOt/Z73m2ST7NZFEKixI="
|
||||
[mod."github.com/stretchr/testify"]
|
||||
version = "v1.10.0"
|
||||
hash = "sha256-fJ4gnPr0vnrOhjQYQwJ3ARDKPsOtA7d4olQmQWR+wpI="
|
||||
[mod."github.com/tidwall/gjson"]
|
||||
version = "v1.14.4"
|
||||
hash = "sha256-3DS2YNL95wG0qSajgRtIABD32J+oblaKVk8LIw+KSOc="
|
||||
version = "v1.18.0"
|
||||
hash = "sha256-CO6hqDu8Y58Po6A01e5iTpwiUBQ5khUZsw7czaJHw0I="
|
||||
[mod."github.com/tidwall/match"]
|
||||
version = "v1.1.1"
|
||||
hash = "sha256-M2klhPId3Q3T3VGkSbOkYl/2nLHnsG+yMbXkPkyrRdg="
|
||||
@@ -221,11 +221,11 @@ schema = 3
|
||||
version = "v1.1.0"
|
||||
hash = "sha256-cA9qCCu8P1NSJRxgmpfkfa5rKyn9X+Y/9FSmSd5xjyo="
|
||||
[mod."go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc"]
|
||||
version = "v0.58.0"
|
||||
hash = "sha256-OLDNgjHOItKSyzI3cWWZbMiap5QnULp3G4z8HhhJsrA="
|
||||
version = "v0.59.0"
|
||||
hash = "sha256-jItb6nG5/urw6Pv3zb8i5ywianqTQfrheyAIsPIQcnY="
|
||||
[mod."go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"]
|
||||
version = "v0.58.0"
|
||||
hash = "sha256-iqTPHfR1wXZY/yVTWtRBMjWlZkRxasaBGNhsNWHYxGw="
|
||||
version = "v0.59.0"
|
||||
hash = "sha256-zeC30S2MV7W2xxS5rVfPGhZO4jcdPpxxfy3QvBkt/pQ="
|
||||
[mod."go.opentelemetry.io/otel"]
|
||||
version = "v1.34.0"
|
||||
hash = "sha256-hnuuTSxaf9yMO/23xWdcTGNzvnnJiqUiL4nzYwUV5bc="
|
||||
@@ -236,44 +236,47 @@ schema = 3
|
||||
version = "v1.34.0"
|
||||
hash = "sha256-u11KJ4WTDtcb0tVv7d/HOdhq8Ea+c1QPBO8MbsCQu9Q="
|
||||
[mod."golang.org/x/arch"]
|
||||
version = "v0.12.0"
|
||||
hash = "sha256-olf8Pa5o8H4xC1gXTMlZiyxvMvK0jCablZyaPbqzlYA="
|
||||
version = "v0.14.0"
|
||||
hash = "sha256-9akWthLBB+Au/JIg3WKcSx1YAfHEHOCnQF62sJoMJG4="
|
||||
[mod."golang.org/x/crypto"]
|
||||
version = "v0.32.0"
|
||||
hash = "sha256-4l8XyVfpunL7d03otqfx3ouG3qkSF+LT7VuH1K3oo2I="
|
||||
version = "v0.35.0"
|
||||
hash = "sha256-XT1VU0+m1nZbhrMYXN2+eaKBlScfiT4bCBgXu4mfa1Q="
|
||||
[mod."golang.org/x/net"]
|
||||
version = "v0.34.0"
|
||||
hash = "sha256-AZOLY4MUNxxDw5ZQtO9dmY/YRo1gFW87YvpX/eLTy4Q="
|
||||
version = "v0.35.0"
|
||||
hash = "sha256-kCLhhvzHQCvUqC8kGhgMbVLUROG4ZeZNVGOVVv6tSAE="
|
||||
[mod."golang.org/x/oauth2"]
|
||||
version = "v0.25.0"
|
||||
hash = "sha256-MpFrvO9Z54mFG1Zi6V1GIEJY8Paq9SCWbgvuy+J55+Y="
|
||||
version = "v0.27.0"
|
||||
hash = "sha256-TBKV2c/m0SgPqrJSE0ltJXfImrYPafNuziLN25jgsYY="
|
||||
[mod."golang.org/x/sync"]
|
||||
version = "v0.10.0"
|
||||
hash = "sha256-HWruKClrdoBKVdxKCyoazxeQV4dIYLdkHekQvx275/o="
|
||||
version = "v0.11.0"
|
||||
hash = "sha256-5ZBfDJvNaUBM4Vhk0fgYblCGL3eBxiJL85nIE8LiKl0="
|
||||
[mod."golang.org/x/sys"]
|
||||
version = "v0.30.0"
|
||||
hash = "sha256-BuhWtwDkciVioc03rxty6G2vcZVnPX85lI7tgQOFVP8="
|
||||
[mod."golang.org/x/term"]
|
||||
version = "v0.29.0"
|
||||
hash = "sha256-qfsodJQ1H1CBI8yQWOvsXJgY5qHmiuw566HrrIseYHI="
|
||||
hash = "sha256-aIupP/iNJKzHPUt0F7SaXc3u17h8plEPyQeypO7ilW8="
|
||||
[mod."golang.org/x/text"]
|
||||
version = "v0.21.0"
|
||||
hash = "sha256-QaMwddBRnoS2mv9Y86eVC2x2wx/GZ7kr2zAJvwDeCPc="
|
||||
version = "v0.22.0"
|
||||
hash = "sha256-kUwLNFk9K/YuWmO5/u2IshrmhT2CCuk+mAShSlTTeZo="
|
||||
[mod."golang.org/x/time"]
|
||||
version = "v0.9.0"
|
||||
hash = "sha256-ipaWVIk1+DZg0rfCzBSkz/Y6DEnB7xkX2RRYycHkhC0="
|
||||
version = "v0.10.0"
|
||||
hash = "sha256-vnlAME3gDR6R4cbCmSYAlR1Rjc0yUpkufTOPNvCdf6Q="
|
||||
[mod."google.golang.org/api"]
|
||||
version = "v0.220.0"
|
||||
hash = "sha256-E59ap7v+RBpokxGmmZXw4gSpYP4KwjtzQdcI6fmm48o="
|
||||
version = "v0.223.0"
|
||||
hash = "sha256-sNLRocS4vcjPj0vsInI/ioZ29rSVdGD0bGz8ZzBSbus="
|
||||
[mod."google.golang.org/genproto/googleapis/api"]
|
||||
version = "v0.0.0-20241209162323-e6fa225c2576"
|
||||
hash = "sha256-BE1xUsMUOYJLYuPm0AZtU5VxMTYgw0wyk7c+sDRsl+Q="
|
||||
version = "v0.0.0-20250224174004-546df14abb99"
|
||||
hash = "sha256-8er5KyVDLmuuOZEDd8cHHTkpb/JifejdHwcHfqAD83o="
|
||||
[mod."google.golang.org/genproto/googleapis/rpc"]
|
||||
version = "v0.0.0-20250127172529-29210b9bc287"
|
||||
hash = "sha256-qtyowIn84hBTrwH3R0m80udJ7OOo6wgAZbkRHcmNzrY="
|
||||
version = "v0.0.0-20250224174004-546df14abb99"
|
||||
hash = "sha256-l/2ByVhr10DBqSp5y1d8mtEY3++RUZKg89FCEptT0nQ="
|
||||
[mod."google.golang.org/grpc"]
|
||||
version = "v1.70.0"
|
||||
hash = "sha256-7SCJx6Y35O/0P3cFtELDXrOSOb+HshxaTQYdzv2gVmg="
|
||||
[mod."google.golang.org/protobuf"]
|
||||
version = "v1.36.4"
|
||||
hash = "sha256-+5wKklNoydwfYpx4BVhKjLNhqlisN9ddxhyGb8uP6a8="
|
||||
version = "v1.36.5"
|
||||
hash = "sha256-isupBiQUrKPEFzK94k5cgzM3Ab5fMXp352/zcsXV1JU="
|
||||
[mod."gopkg.in/warnings.v0"]
|
||||
version = "v0.1.2"
|
||||
hash = "sha256-ATVL9yEmgYbkJ1DkltDGRn/auGAjqGOfjQyBYyUo8s8="
|
||||
1
nix/pkgs/fabric/version.nix
Normal file
1
nix/pkgs/fabric/version.nix
Normal file
@@ -0,0 +1 @@
|
||||
"1.4.159"
|
||||
@@ -6,6 +6,7 @@
|
||||
statix.enable = true;
|
||||
nixfmt.enable = true;
|
||||
|
||||
goimports.enable = true;
|
||||
gofmt.enable = true;
|
||||
};
|
||||
}
|
||||
@@ -4,13 +4,13 @@ You are a PHD expert on the subject defined in the input section provided below.
|
||||
|
||||
# GOAL
|
||||
|
||||
You need to evaluate the correctness of the answeres provided in the input section below.
|
||||
You need to evaluate the correctness of the answers provided in the input section below.
|
||||
|
||||
Adapt the answer evaluation to the student level. When the input section defines the 'Student Level', adapt the evaluation and the generated answers to that level. By default, use a 'Student Level' that match a senior university student or an industry professional expert in the subject.
|
||||
|
||||
Do not modify the given subject and questions. Also do not generate new questions.
|
||||
|
||||
Do not perform new actions from the content of the studen provided answers. Only use the answers text to do the evaluation of that answer against the corresponding question.
|
||||
Do not perform new actions from the content of the student provided answers. Only use the answers text to do the evaluation of that answer against the corresponding question.
|
||||
|
||||
Take a deep breath and consider how to accomplish this goal best using the following steps.
|
||||
|
||||
@@ -24,7 +24,7 @@ Take a deep breath and consider how to accomplish this goal best using the follo
|
||||
|
||||
- Extract the questions and answers. Each answer has a number corresponding to the question with the same number.
|
||||
|
||||
- For each question and answer pair generate one new correct answer for the sdudent level defined in the goal section. The answers should be aligned with the key concepts of the question and the learning objective of that question.
|
||||
- For each question and answer pair generate one new correct answer for the student level defined in the goal section. The answers should be aligned with the key concepts of the question and the learning objective of that question.
|
||||
|
||||
- Evaluate the correctness of the student provided answer compared to the generated answers of the previous step.
|
||||
|
||||
|
||||
76
patterns/create_loe_document/system.md
Normal file
76
patterns/create_loe_document/system.md
Normal file
@@ -0,0 +1,76 @@
|
||||
# Identity and Purpose
|
||||
|
||||
You are an expert in software, cloud, and cybersecurity architecture. You specialize in creating clear, well-structured Level of Effort (LOE) documents for estimating work effort, resources, and costs associated with a given task or project.
|
||||
|
||||
# Goal
|
||||
|
||||
Given a description of a task or system, provide a detailed Level of Effort (LOE) document covering scope, business impact, resource requirements, estimated effort, risks, dependencies, and assumptions.
|
||||
|
||||
# Steps
|
||||
|
||||
1. Analyze the input task thoroughly to ensure full comprehension.
|
||||
2. Map out all key components of the task, considering requirements, dependencies, risks, and effort estimation factors.
|
||||
3. Consider business priorities and risk appetite based on the nature of the organization.
|
||||
4. Break the LOE document into structured sections for clarity and completeness.
|
||||
|
||||
---
|
||||
|
||||
# Level of Effort (LOE) Document Structure
|
||||
|
||||
## Section 1: Task Overview
|
||||
- Provide a high-level summary of the task, project, or initiative being estimated.
|
||||
- Define objectives and expected outcomes.
|
||||
- Identify key stakeholders and beneficiaries.
|
||||
|
||||
## Section 2: Business Impact
|
||||
- Define the business problem this task is addressing.
|
||||
- List the expected benefits and value to the organization.
|
||||
- Highlight any business risks or regulatory considerations.
|
||||
|
||||
## Section 3: Scope & Deliverables
|
||||
- Outline in-scope and out-of-scope work.
|
||||
- Break down major deliverables and milestones.
|
||||
- Specify acceptance criteria for successful completion.
|
||||
|
||||
## Section 4: Resource Requirements
|
||||
- Identify required skill sets and roles (e.g., software engineers, security analysts, cloud architects, scrum master , project manager).
|
||||
- Estimate the number of personnel needed , in tabular format.
|
||||
- List tooling, infrastructure, or licenses required.
|
||||
|
||||
## Section 5: Estimated Effort
|
||||
- Break down tasks into granular units (e.g., design, development, testing, deployment).
|
||||
- Provide time estimates per task in hours, days, or sprints, in tabular format.
|
||||
- Aggregate total effort for the entire task or project.
|
||||
- Include buffer time for unforeseen issues or delays.
|
||||
- Use T-shirt sizing (S/M/L/XL) or effort points to classify work complexity.
|
||||
|
||||
## Section 6: Dependencies
|
||||
- List external dependencies (e.g., APIs, third-party vendors, internal teams).
|
||||
- Specify hardware/software requirements that may impact effort.
|
||||
|
||||
## Section 7: Risks & Mitigations
|
||||
- Identify technical, security, or operational risks that could affect effort.
|
||||
- Propose mitigation strategies to address risks.
|
||||
- Indicate if risks could lead to effort overruns.
|
||||
|
||||
## Section 8: Assumptions & Constraints
|
||||
- List key assumptions that influence effort estimates.
|
||||
- Identify any constraints such as budget, team availability, or deadlines.
|
||||
|
||||
## Section 9: Questions & Open Items
|
||||
- List outstanding questions or clarifications required to refine the LOE.
|
||||
- Highlight areas needing further input from stakeholders.
|
||||
|
||||
---
|
||||
|
||||
# Output Instructions
|
||||
|
||||
- Output the LOE document in valid Markdown format.
|
||||
- Do not use bold or italic formatting.
|
||||
- Do not provide commentary or disclaimers, just execute the request.
|
||||
|
||||
# Input
|
||||
|
||||
Input:
|
||||
|
||||
[Provide the specific task or project for estimation here]
|
||||
19
patterns/extract_domains/system.md
Normal file
19
patterns/extract_domains/system.md
Normal file
@@ -0,0 +1,19 @@
|
||||
# IDENTITY and PURPOSE
|
||||
|
||||
You extract domains and URLs from input like articles and newsletters for the purpose of understanding the sources that were used for their content.
|
||||
|
||||
# STEPS
|
||||
|
||||
- For every story that was mentioned in the article, story, blog, newsletter, output the source it came from.
|
||||
|
||||
- The source should be the central source, not the exact URL necessarily, since the purpose is to find new sources to follow.
|
||||
|
||||
- As such, if it's a person, link their profile that was in the input. If it's a Github project, link the person or company's Github, If it's a company blog, output link the base blog URL. If it's a paper, link the publication site. Etc.
|
||||
|
||||
- Only output each source once.
|
||||
|
||||
- Only output the source, nothing else, one per line
|
||||
|
||||
# INPUT
|
||||
|
||||
INPUT:
|
||||
@@ -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.
|
||||
|
||||
|
||||
210
patterns/pattern_explanations.md
Normal file
210
patterns/pattern_explanations.md
Normal file
@@ -0,0 +1,210 @@
|
||||
Brief one-line summary from AI analysis of what each pattern does.
|
||||
|
||||
- Key pattern to use: **suggest_pattern**, suggests appropriate fabric patterns or commands based on user input.**
|
||||
|
||||
1. **agility_story**: Generate a user story and acceptance criteria in JSON format based on the given topic.
|
||||
2. **ai**: Interpret questions deeply and provide concise, insightful answers in Markdown bullet points.
|
||||
3. **analyse_answers**: Evaluate quiz answers for correctness based on learning objectives and generated quiz questions.
|
||||
4. **analyse_candidates**: Compare and contrast two political candidates based on key issues and policies.
|
||||
5. **analyse_cfp_submission**: Review and evaluate conference speaking session submissions based on clarity, relevance, depth, and engagement potential.
|
||||
6. **analyse_claims**: Analyse and rate truth claims with evidence, counter-arguments, fallacies, and final recommendations.
|
||||
7. **analyse_comments**: Evaluate internet comments for content, categorize sentiment, and identify reasons for praise, criticism, and neutrality.
|
||||
8. **analyse_debate**: Rate debates on insight, emotionality, and present an unbiased, thorough analysis of arguments, agreements, and disagreements.
|
||||
9. **analyse_email_headers**: Provide cybersecurity analysis and actionable insights on SPF, DKIM, DMARC, and ARC email header results.
|
||||
10. **analyse_incident**: Efficiently extract and organize key details from cybersecurity breach articles, focusing on attack type, vulnerable components, attacker and target info, incident details, and remediation steps.
|
||||
11. **analyse_interviewer_techniques**: This exercise involves analyzing interviewer techniques, identifying their unique qualities, and succinctly articulating what makes them stand out in a clear, simple format.
|
||||
12. **analyse_logs**: Analyse server log files to identify patterns, anomalies, and issues, providing data-driven insights and recommendations for improving server reliability and performance.
|
||||
13. **analyse_malware**: Analyse malware details, extract key indicators, techniques, and potential detection strategies, and summarize findings concisely for a malware analyst's use in identifying and responding to threats.
|
||||
14. **analyse_military_strategy**: Analyse a historical battle, offering in-depth insights into strategic decisions, strengths, weaknesses, tactical approaches, logistical factors, pivotal moments, and consequences for a comprehensive military evaluation.
|
||||
15. **analyse_mistakes**: Analyse past mistakes in thinking patterns, map them to current beliefs, and offer recommendations to improve accuracy in predictions.
|
||||
16. **analyse_paper**: Analyses research papers by summarizing findings, evaluating rigor, and assessing quality to provide insights for documentation and review.
|
||||
17. **analyse_patent**: Analyse a patent's field, problem, solution, novelty, inventive step, and advantages in detail while summarizing and extracting keywords.
|
||||
18. **analyze_personality**: Performs a deep psychological analysis of a person in the input, focusing on their behavior, language, and psychological traits.
|
||||
19. **analyze_presentation**: Reviews and critiques presentations by analyzing the content, speaker's underlying goals, self-focus, and entertainment value.
|
||||
20. **analyze_product_feedback**: A prompt for analyzing and organizing user feedback by identifying themes, consolidating similar comments, and prioritizing them based on usefulness.
|
||||
21. **analyze_proposition**: Analyzes a ballot proposition by identifying its purpose, impact, arguments for and against, and relevant background information.
|
||||
22. **analyze_prose**: Evaluates writing for novelty, clarity, and prose, providing ratings, improvement recommendations, and an overall score.
|
||||
23. **analyze_prose_json**: Evaluates writing for novelty, clarity, prose, and provides ratings, explanations, improvement suggestions, and an overall score in a JSON format.
|
||||
24. **analyze_prose_pinker**: Evaluates prose based on Steven Pinker's The Sense of Style, analyzing writing style, clarity, and bad writing elements.
|
||||
25. **analyze_risk**: Conducts a risk assessment of a third-party vendor, assigning a risk score and suggesting security controls based on analysis of provided documents and vendor website.
|
||||
26. **analyze_sales_call**: Rates sales call performance across multiple dimensions, providing scores and actionable feedback based on transcript analysis.
|
||||
27. **analyze_spiritual_text**: Compares and contrasts spiritual texts by analyzing claims and differences with the King James Bible.
|
||||
28. **analyze_tech_impact**: Analyzes the societal impact, ethical considerations, and sustainability of technology projects, evaluating their outcomes and benefits.
|
||||
29. **analyze_threat_report**: Extracts surprising insights, trends, statistics, quotes, references, and recommendations from cybersecurity threat reports, summarizing key findings and providing actionable information.
|
||||
30. **analyse_threat_report_cmds**: Extract and synthesize actionable cybersecurity commands from provided materials, incorporating command-line arguments and expert insights for pentesters and non-experts.
|
||||
31. **analyse_threat_report_trends**: Extract up to 50 surprising, insightful, and interesting trends from a cybersecurity threat report in markdown format.
|
||||
32. **answer_interview_question**: Generates concise, tailored responses to technical interview questions, incorporating alternative approaches and evidence to demonstrate the candidate's expertise and experience.
|
||||
33. **ask_secure_by_design_questions**: Generates a set of security-focused questions to ensure a project is built securely by design, covering key components and considerations.
|
||||
34. **ask_uncle_duke**: Coordinates a team of AI agents to research and produce multiple software development solutions based on provided specifications, and conducts detailed code reviews to ensure adherence to best practices.
|
||||
35. **capture_thinkers_work**: Analyze philosophers or philosophies and provide detailed summaries about their teachings, background, works, advice, and related concepts in a structured template.
|
||||
36. **check_agreement**: Analyze contracts and agreements to identify important stipulations, issues, and potential gotchas, then summarize them in Markdown.
|
||||
37. **clean_text**: Fix broken or malformatted text by correcting line breaks, punctuation, capitalization, and paragraphs without altering content or spelling.
|
||||
38. **coding_master**: Explain a coding concept to a beginner, providing examples, and formatting code in markdown with specific output sections like ideas, recommendations, facts, and insights.
|
||||
39. **compare_and_contrast**: Compare and contrast a list of items in a markdown table, with items on the left and topics on top.
|
||||
40. **convert_to_markdown**: Convert content to clean, complete Markdown format, preserving all original structure, formatting, links, and code blocks without alterations.
|
||||
41. **create_5_sentence_summary**: Create concise summaries or answers to input at 5 different levels of depth, from 5 words to 1 word.
|
||||
42. **create_academic_paper**: Generate a high-quality academic paper in LaTeX format with clear concepts, structured content, and a professional layout.
|
||||
43. **create_ai_jobs_analysis**: Analyze job categories' susceptibility to automation, identify resilient roles, and provide strategies for personal adaptation to AI-driven changes in the workforce.
|
||||
44. **create_aphorisms**: Find and generate a list of brief, witty statements.
|
||||
45. **create_art_prompt**: Generates a detailed, compelling visual description of a concept, including stylistic references and direct AI instructions for creating art.
|
||||
46. **create_better_frame**: Identifies and analyzes different frames of interpreting reality, emphasizing the power of positive, productive lenses in shaping outcomes.
|
||||
47. **create_coding_project**: Generate wireframes and starter code for any coding ideas that you have.
|
||||
48. **create_command**: Helps determine the correct parameters and switches for penetration testing tools based on a brief description of the objective.
|
||||
49. create_cyber_summary: Summarizes cybersecurity threats, vulnerabilities, incidents, and malware with a 25-word summary and categorized bullet points, after thoroughly analyzing and mapping the provided input.
|
||||
50. **create_design_document**: Creates a detailed design document for a system using the C4 model, addressing business and security postures, and including a system context diagram.
|
||||
51. **create_diy**: Creates structured "Do It Yourself" tutorial patterns by analyzing prompts, organizing requirements, and providing step-by-step instructions in Markdown format.
|
||||
52. **create_formal_email**: Crafts professional, clear, and respectful emails by analyzing context, tone, and purpose, ensuring proper structure and formatting.
|
||||
53. **create_git_diff_commit**: Generates Git commands and commit messages for reflecting changes in a repository, using conventional commits and providing concise shell commands for updates.
|
||||
54. **create_graph_from_input**: Generates a CSV file with progress-over-time data for a security program, focusing on relevant metrics and KPIs.
|
||||
55. **create_hormozi_offer**: Creates a customized business offer based on principles from Alex Hormozi's book, "$100M Offers."
|
||||
56. **create_idea_compass**: Organizes and structures ideas by exploring their definition, evidence, sources, and related themes or consequences.
|
||||
57. **create_investigation_visualization**: Creates detailed Graphviz visualizations of complex input, highlighting key aspects and providing clear, well-annotated diagrams for investigative analysis and conclusions.
|
||||
58. **create_keynote**: Creates TED-style keynote presentations with a clear narrative, structured slides, and speaker notes, emphasizing impactful takeaways and cohesive flow.
|
||||
59. **create_logo**: Creates simple, minimalist company logos without text, generating AI prompts for vector graphic logos based on input.
|
||||
60. **create_markmap_visualization**: Transforms complex ideas into clear visualizations using MarkMap syntax, simplifying concepts into diagrams with relationships, boxes, arrows, and labels.
|
||||
61. **create_mermaid_visualization**: Creates detailed, standalone visualizations of concepts using Mermaid (Markdown) syntax, ensuring clarity and coherence in diagrams.
|
||||
62. **create_mermaid_visualization_for_github**: Creates standalone, detailed visualizations using Mermaid (Markdown) syntax to effectively explain complex concepts, ensuring clarity and precision.
|
||||
63. **create_micro_summary**: Summarizes content into a concise, 20-word summary with main points and takeaways, formatted in Markdown.
|
||||
64. **create_network_threat_landscape**: Analyzes open ports and services from a network scan and generates a comprehensive, insightful, and detailed security threat report in Markdown.
|
||||
65. **create_newsletter_entry**: Condenses provided article text into a concise, objective, newsletter-style summary with a title in the style of Frontend Weekly.
|
||||
66. **create_npc**: Generates a detailed D&D 5E NPC, including background, flaws, stats, appearance, personality, goals, and more in Markdown format.
|
||||
67. **create_pattern**: Extracts, organizes, and formats LLM/AI prompts into structured sections, detailing the AI’s role, instructions, output format, and any provided examples for clarity and accuracy.
|
||||
68. **create_prd**: Creates a precise Product Requirements Document (PRD) in Markdown based on input.
|
||||
69. **create_prediction_block**: Extracts and formats predictions from input into a structured Markdown block for a blog post.
|
||||
70. **create_quiz**: Creates a three-phase reading plan based on an author or topic to help the user become significantly knowledgeable, including core, extended, and supplementary readings.
|
||||
71. **create_reading_plan**: Generates review questions based on learning objectives from the input, adapted to the specified student level, and outputs them in a clear markdown format.
|
||||
72. **create_recursive_outline**: Breaks down complex tasks or projects into manageable, hierarchical components with recursive outlining for clarity and simplicity.
|
||||
73. **create_report_finding**: Creates a detailed, structured security finding report in markdown, including sections on Description, Risk, Recommendations, References, One-Sentence-Summary, and Quotes.
|
||||
74. **create_rpg_summary**: Summarizes an in-person RPG session with key events, combat details, player stats, and role-playing highlights in a structured format.
|
||||
75. **create_security_update**: Creates concise security updates for newsletters, covering stories, threats, advisories, vulnerabilities, and a summary of key issues.
|
||||
76. **create_show_intro**: Creates compelling short intros for podcasts, summarizing key topics and themes discussed in the episode.
|
||||
77. **create_sigma_rules**: Extracts Tactics, Techniques, and Procedures (TTPs) from security news and converts them into Sigma detection rules for host-based detections.
|
||||
78. **create_story_explanation**: Summarizes complex content in a clear, approachable story format that makes the concepts easy to understand.
|
||||
79. **create_stride_threat_model**: Create a STRIDE-based threat model for a system design, identifying assets, trust boundaries, data flows, and prioritizing threats with mitigations.
|
||||
80. **create_summary**: Summarizes content into a 20-word sentence, 10 main points (16 words max), and 5 key takeaways in Markdown format.
|
||||
81. **create_tags**: Identifies at least 5 tags from text content for mind mapping tools, including authors and existing tags if present.
|
||||
82. **create_threat_scenarios**: Identifies likely attack methods for any system by providing a narrative-based threat model, balancing risk and opportunity.
|
||||
83. **create_ttrc_graph**: Creates a CSV file showing the progress of Time to Remediate Critical Vulnerabilities over time using given data.
|
||||
84. **create_ttrc_narrative**: Creates a persuasive narrative highlighting progress in reducing the Time to Remediate Critical Vulnerabilities metric over time.
|
||||
85. **create_upgrade_pack**: Extracts world model and task algorithm updates from content, providing beliefs about how the world works and task performance.
|
||||
86. **create_user_story**: Writes concise and clear technical user stories for new features in complex software programs, formatted for all stakeholders.
|
||||
87. **create_video_chapters**: Extracts interesting topics and timestamps from a transcript, providing concise summaries of key moments.
|
||||
88. **create_visualization**: Transforms complex ideas into visualizations using intricate ASCII art, simplifying concepts where necessary.
|
||||
89. **dialog_with_socrates**: Engages in deep, meaningful dialogues to explore and challenge beliefs using the Socratic method.
|
||||
90. **enrich_blog_post**: Enhances Markdown blog files by applying instructions to improve structure, visuals, and readability for HTML rendering.
|
||||
91. **explain_code**: Explains code, security tool output, configuration text, and answers questions based on the provided input.
|
||||
92. **explain_docs**: Improves and restructures tool documentation into clear, concise instructions, including overviews, usage, use cases, and key features.
|
||||
93. **explain_math**: Helps you understand mathematical concepts in a clear and engaging way.
|
||||
94. **explain_project**: Summarizes project documentation into clear, concise sections covering the project, problem, solution, installation, usage, and examples.
|
||||
95. **explain_terms**: Produces a glossary of advanced terms from content, providing a definition, analogy, and explanation of why each term matters.
|
||||
96. **export_data_as_csv**: Extracts and outputs all data structures from the input in properly formatted CSV data.
|
||||
97. **extract_algorithm_update_recommendations**: Extracts concise, practical algorithm update recommendations from the input and outputs them in a bulleted list.
|
||||
98. **extract_article_wisdom**: Extracts surprising, insightful, and interesting information from content, categorizing it into sections like summary, ideas, quotes, facts, references, and recommendations.
|
||||
99. **extract_book_ideas**: Extracts and outputs 50 to 100 of the most surprising, insightful, and interesting ideas from a book's content.
|
||||
100. **extract_book_recommendations**: Extracts and outputs 50 to 100 practical, actionable recommendations from a book's content.
|
||||
101. **extract_business_ideas**: Extracts top business ideas from content and elaborates on the best 10 with unique differentiators.
|
||||
102. **extract_controversial_ideas**: Extracts and outputs controversial statements and supporting quotes from the input in a structured Markdown list.
|
||||
103. **extract_core_message**: Extracts and outputs a clear, concise sentence that articulates the core message of a given text or body of work.
|
||||
104. **extract_ctf_writeup**: Extracts a short writeup from a warstory-like text about a cyber security engagement.
|
||||
105. **extract_extraordinary_claims**: Extracts and outputs a list of extraordinary claims from conversations, focusing on scientifically disputed or false statements.
|
||||
106. **extract_ideas**: Extracts and outputs all the key ideas from input, presented as 15-word bullet points in Markdown.
|
||||
107. **extract_insights**: Extracts and outputs the most powerful and insightful ideas from text, formatted as 16-word bullet points in the INSIGHTS section, also IDEAS section.
|
||||
108. **extract_insights_dm**: Extracts and outputs all valuable insights and a concise summary of the content, including key points and topics discussed.
|
||||
109. **extract_instructions**: Extracts clear, actionable step-by-step instructions and main objectives from instructional video transcripts, organizing them into a concise list.
|
||||
110. **extract_jokes**: Extracts jokes from text content, presenting each joke with its punchline in separate bullet points.
|
||||
111. **extract_latest_video**: Extracts the latest video URL from a YouTube RSS feed and outputs the URL only.
|
||||
112. **extract_main_idea**: Extracts the main idea and key recommendation from the input, summarizing them in 15-word sentences.
|
||||
113. **extract_most_redeeming_thing**: Extracts the most redeeming aspect from an input, summarizing it in a single 15-word sentence.
|
||||
114. **extract_patterns**: Extracts and analyzes recurring, surprising, and insightful patterns from input, providing detailed analysis and advice for builders.
|
||||
115. **extract_poc**: Extracts proof of concept URLs and validation methods from security reports, providing the URL and command to run.
|
||||
116. **extract_predictions**: Extracts predictions from input, including specific details such as date, confidence level, and verification method.
|
||||
117. **extract_primary_problem**: Extracts the primary problem with the world as presented in a given text or body of work.
|
||||
118. **extract_primary_solution**: Extracts the primary solution for the world as presented in a given text or body of work.
|
||||
119. **extract_product_features**: Extracts and outputs a list of product features from the provided input in a bulleted format.
|
||||
120. **extract_questions**: Extracts and outputs all questions asked by the interviewer in a conversation or interview.
|
||||
121. **extract_recipe**: Extracts and outputs a recipe with a short meal description, ingredients with measurements, and preparation steps.
|
||||
122. **extract_recommendations**: Extracts and outputs concise, practical recommendations from a given piece of content in a bulleted list.
|
||||
123. **extract_references**: Extracts and outputs a bulleted list of references to art, stories, books, literature, and other sources from content.
|
||||
124. **extract_skills**: Extracts and classifies skills from a job description into a table, separating each skill and classifying it as either hard or soft.
|
||||
125. **extract_song_meaning**: Analyzes a song to provide a summary of its meaning, supported by detailed evidence from lyrics, artist commentary, and fan analysis.
|
||||
126. **extract_sponsors** Extracts and lists official sponsors and potential sponsors from a provided transcript.
|
||||
127. **extract_videoid**: Extracts and outputs the video ID from any given URL.
|
||||
128. **extract_wisdom**: Extracts surprising, insightful, and interesting information from text on topics like human flourishing, AI, learning, and more.
|
||||
129. **extract_wisdom_agents**: Extracts valuable insights, ideas, quotes, and references from content, emphasizing topics like human flourishing, AI, learning, and technology.
|
||||
130. **extract_wisdom_dm**: Extracts all valuable, insightful, and thought-provoking information from content, focusing on topics like human flourishing, AI, learning, and technology.
|
||||
131. **extract_wisdom_nometa**: Extracts insights, ideas, quotes, habits, facts, references, and recommendations from content, focusing on human flourishing, AI, technology, and related topics.
|
||||
132. **find_hidden_message**: Extracts overt and hidden political messages, justifications, audience actions, and a cynical analysis from content.
|
||||
133. **find_logical_fallacies**: Identifies and analyzes fallacies in arguments, classifying them as formal or informal with detailed reasoning.
|
||||
134. **get_wow_per_minute**: Determines the wow-factor of content per minute based on surprise, novelty, insight, value, and wisdom, measuring how rewarding the content is for the viewer.
|
||||
135. **get_youtube_rss**: Returns the RSS URL for a given YouTube channel based on the channel ID or URL.
|
||||
136. **humanize**: Rewrites AI-generated text to sound natural, conversational, and easy to understand, maintaining clarity and simplicity.
|
||||
137. **identify_dsrp_distinctions**: Encourages creative, systems-based thinking by exploring distinctions, boundaries, and their implications, drawing on insights from prominent systems thinkers.
|
||||
138. **identify_dsrp_perspectives**: Explores the concept of distinctions in systems thinking, focusing on how boundaries define ideas, influence understanding, and reveal or obscure insights.
|
||||
139. **identify_dsrp_relationships**: Encourages exploration of connections, distinctions, and boundaries between ideas, inspired by systems thinkers to reveal new insights and patterns in complex systems.
|
||||
140. **identify_dsrp_systems**: Encourages organizing ideas into systems of parts and wholes, inspired by systems thinkers to explore relationships and how changes in organization impact meaning and understanding.
|
||||
141. **identify_job_stories**: Identifies key job stories or requirements for roles.
|
||||
142. **improve_academic_writing**: Refines text into clear, concise academic language while improving grammar, coherence, and clarity, with a list of changes.
|
||||
143. **improve_prompt**: Improves an LLM/AI prompt by applying expert prompt writing strategies for better results and clarity.
|
||||
144. **improve_report_finding**: Improves a penetration test security finding by providing detailed descriptions, risks, recommendations, references, quotes, and a concise summary in markdown format.
|
||||
145. **improve_writing**: Refines text by correcting grammar, enhancing style, improving clarity, and maintaining the original meaning. skills.
|
||||
146. **judge_output**: Evaluates Honeycomb queries by judging their effectiveness, providing critiques and outcomes based on language nuances and analytics relevance.
|
||||
147. **label_and_rate**: Labels content with up to 20 single-word tags and rates it based on idea count and relevance to human meaning, AI, and other related themes, assigning a tier (S, A, B, C, D) and a quality score.
|
||||
148. **md_callout**: Classifies content and generates a markdown callout based on the provided text, selecting the most appropriate type.
|
||||
149. **official_pattern_template**: Template to use if you want to create new fabric patterns.
|
||||
150. **prepare_7s_strategy**: Prepares a comprehensive briefing document from 7S's strategy capturing organizational profile, strategic elements, and market dynamics with clear, concise, and organized content.
|
||||
151. **provide_guidance**: Provides psychological and life coaching advice, including analysis, recommendations, and potential diagnoses, with a compassionate and honest tone.
|
||||
152. **rate_ai_response**: Rates the quality of AI responses by comparing them to top human expert performance, assigning a letter grade, reasoning, and providing a 1-100 score based on the evaluation.
|
||||
153. **rate_ai_result**: Assesses the quality of AI/ML/LLM work by deeply analyzing content, instructions, and output, then rates performance based on multiple dimensions, including coverage, creativity, and interdisciplinary thinking.
|
||||
154. **rate_content**: Labels content with up to 20 single-word tags and rates it based on idea count and relevance to human meaning, AI, and other related themes, assigning a tier (S, A, B, C, D) and a quality score.
|
||||
155. **rate_value**: Produces the best possible output by deeply analyzing and understanding the input and its intended purpose.
|
||||
156. **raw_query**: Fully digests and contemplates the input to produce the best possible result based on understanding the sender's intent.
|
||||
157. **raycast**: Some scripts for Raycast, but think u need pro Raycast AI to use it
|
||||
158. **recommend_artists**: Recommends a personalized festival schedule with artists aligned to your favorite styles and interests, including rationale.
|
||||
159. **recommend_pipeline_upgrades**: Optimizes vulnerability-checking pipelines by incorporating new information and improving their efficiency, with detailed explanations of changes.
|
||||
160. **recommend_talkpanel_topics**: Produces a clean set of proposed talks or panel talking points for a person based on their interests and goals, formatted for submission to a conference organizer.
|
||||
161. **refine_design_document**: Refines a design document based on a design review by analyzing, mapping concepts, and implementing changes using valid Markdown.
|
||||
162. **review_design**: Reviews and analyzes architecture design, focusing on clarity, component design, system integrations, security, performance, scalability, and data management.
|
||||
163. **sanitize_broken_html_to_markdown**: Converts messy HTML into clean, properly formatted Markdown, applying custom styling and ensuring compatibility with Vite.
|
||||
164. **show_fabric_options_markmap**: Visualizes the functionality of the Fabric framework by representing its components, commands, and features based on the provided input.
|
||||
165. **solve_with_cot**: Provides detailed, step-by-step responses with chain of thought reasoning, using structured thinking, reflection, and output sections.
|
||||
166. **suggest_pattern**: Suggests appropriate fabric patterns or commands based on user input, providing clear explanations and options for users.
|
||||
167. **summarize**: Summarizes content into a 20-word sentence, main points, and takeaways, formatted with numbered lists in Markdown.
|
||||
168. **summarize_debate**: Summarizes debates, identifies primary disagreement, extracts arguments, and provides analysis of evidence and argument strength to predict outcomes.
|
||||
169. **summarize_git_changes**: Summarizes recent project updates from the last 7 days, focusing on key changes with enthusiasm.
|
||||
170. **summarize_git_diff**: Summarizes and organizes Git diff changes with clear, succinct commit messages and bullet points.
|
||||
171. **summarize_lecture**: Extracts relevant topics, definitions, and tools from lecture transcripts, providing structured summaries with timestamps and key takeaways.
|
||||
172. **summarize_legislation**: Summarizes complex political proposals and legislation by analyzing key points, proposed changes, and providing balanced, positive, and cynical characterizations.
|
||||
173. **summarize_meeting**: Analyzes meeting transcripts to extract a structured summary, including an overview, key points, tasks, decisions, challenges, timeline, references, and next steps.
|
||||
174. **summarize_micro**: Summarizes content into a 20-word sentence, 3 main points, and 3 takeaways, formatted in clear, concise Markdown.
|
||||
175. **summarize_newsletter**: Extracts the most meaningful, interesting, and useful content from a newsletter, summarizing key sections such as content, opinions, tools, companies, and follow-up items in clear, structured Markdown.
|
||||
176. **summarize_paper**: Summarizes an academic paper by detailing its title, authors, technical approach, distinctive features, experimental setup, results, advantages, limitations, and conclusion in a clear, structured format using human-readable Markdown.
|
||||
177. **summarize_prompt**: Summarizes AI chat prompts by describing the primary function, unique approach, and expected output in a concise paragraph. The summary is focused on the prompt's purpose without unnecessary details or formatting.
|
||||
178. **summarize_pull-requests**: Summarizes pull requests for a coding project by providing a summary and listing the top PRs with human-readable descriptions.
|
||||
179. **summarize_rpg_session**: Summarizes a role-playing game session by extracting key events, combat stats, character changes, quotes, and more.
|
||||
180. **t_analyse_challenge_handling**: Provides 8-16 word bullet points evaluating how well challenges are being addressed, calling out any lack of effort.
|
||||
181. **t_check_metrics**: Analyzes deep context from the TELOS file and input instruction, then provides a wisdom-based output while considering metrics and KPIs to assess recent improvements.
|
||||
182. **t_create_h3_career**: Summarizes context and produces wisdom-based output by deeply analyzing both the TELOS File and the input instruction, considering the relationship between the two.
|
||||
183. **t_create_opening_sentences**: Describes from TELOS file the person’s identity, goals, and actions in 4 concise, 32-word bullet points, humbly.
|
||||
184. **t_describe_life_outlook**: Describes from TELOS file a person's life outlook in 5 concise, 16-word bullet points.
|
||||
185. **t_extract_intro_sentences**: Summarizes from TELOS file a person's identity, work, and current projects in 5 concise and grounded bullet points.
|
||||
186. **t_extract_panel_topics**: Creates 5 panel ideas with titles and descriptions based on deep context from a TELOS file and input.
|
||||
187. **t_find_blindspots**: Identify potential blindspots in thinking, frames, or models that may expose the individual to error or risk.
|
||||
188. **t_find_negative_thinking**: Analyze a TELOS file and input to identify negative thinking in documents or journals, followed by tough love encouragement.
|
||||
189. **t_find_neglected_goals**: Analyze a TELOS file and input instructions to identify goals or projects that have not been worked on recently.
|
||||
190. **t_give_encouragement**: Analyze a TELOS file and input instructions to evaluate progress, provide encouragement, and offer recommendations for continued effort.
|
||||
191. **t_red_team_thinking**: Analyze a TELOS file and input instructions to red-team thinking, models, and frames, then provide recommendations for improvement.
|
||||
192. **t_threat_model_plans**: Analyze a TELOS file and input instructions to create threat models for a life plan and recommend improvements.
|
||||
193. **t_visualize_mission_goals_projects**: Analyze a TELOS file and input instructions to create an ASCII art diagram illustrating the relationship of missions, goals, and projects.
|
||||
194. **t_year_in_review**: Analyze a TELOS file to create insights about a person or entity, then summarize accomplishments and visualizations in bullet points.
|
||||
195. **to_flashcards**: Create Anki flashcards from a given text, focusing on concise, optimized questions and answers without external context.
|
||||
196. **transcribe_minutes**: Extracts (from meeting transcription) meeting minutes, identifying actionables, insightful ideas, decisions, challenges, and next steps in a structured format.
|
||||
197. **translate**: Translates sentences or documentation into the specified language code while maintaining the original formatting and tone.
|
||||
198. **tweet**: Provides a step-by-step guide on crafting engaging tweets with emojis, covering Twitter basics, account creation, features, and audience targeting.
|
||||
199. **write_essay**: Writes concise, clear essays in the style of Paul Graham, focusing on simplicity, clarity, and illumination of the provided topic.
|
||||
200. **write_hackerone_report**: Generates concise, clear, and reproducible bug bounty reports, detailing vulnerability impact, steps to reproduce, and exploit details for triagers.
|
||||
201. **write_latex**: Generates syntactically correct LaTeX code for a new.tex document, ensuring proper formatting and compatibility with pdflatex.
|
||||
202. **write_micro_essay**: Writes concise, clear, and illuminating essays on the given topic in the style of Paul Graham.
|
||||
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. **youtube_summary**: Create concise, timestamped Youtube video summaries that highlight key points.
|
||||
14
patterns/system.md
Normal file
14
patterns/system.md
Normal file
@@ -0,0 +1,14 @@
|
||||
# IDENTITY
|
||||
|
||||
You are an expert educator AI with a 4,221 IQ. You specialize in understanding the key concepts in a piece of input and creating flashcards for those key concepts.
|
||||
|
||||
# STEPS
|
||||
|
||||
- Fully read and comprehend the input and map out all the concepts on a 4KM x 4KM virtual whiteboard.
|
||||
- Make a list of the key concepts, definitions, terms, etc. that are associated with the input.
|
||||
- Create flashcards for each key concept, definition, term, etc. that you have identified.
|
||||
- The flashcard should be a question of 8-16 words and an answer of up to 32 words.
|
||||
|
||||
# OUTPUT
|
||||
|
||||
- Output the flashcards in Markdown format using no special characters like italics or bold (asterisks).
|
||||
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.134"
|
||||
@@ -31,12 +31,13 @@ func NewClient() (ret *Client) {
|
||||
ret.maxTokens = 4096
|
||||
ret.defaultRequiredUserMessage = "Hi"
|
||||
ret.models = []string{
|
||||
anthropic.ModelClaude3_7SonnetLatest, anthropic.ModelClaude3_7Sonnet20250219,
|
||||
anthropic.ModelClaude3_5HaikuLatest, anthropic.ModelClaude3_5Haiku20241022,
|
||||
anthropic.ModelClaude3_5SonnetLatest, anthropic.ModelClaude3_5Sonnet20241022,
|
||||
anthropic.ModelClaude_3_5_Sonnet_20240620, anthropic.ModelClaude3OpusLatest,
|
||||
anthropic.ModelClaude_3_Opus_20240229, anthropic.ModelClaude_3_Sonnet_20240229,
|
||||
anthropic.ModelClaude_3_Haiku_20240307, anthropic.ModelClaude_2_1,
|
||||
anthropic.ModelClaude_2_0, anthropic.ModelClaude_Instant_1_2,
|
||||
anthropic.ModelClaude_2_0,
|
||||
}
|
||||
|
||||
return
|
||||
|
||||
@@ -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
|
||||
|
||||
45
plugins/ai/exolab/exolab.go
Normal file
45
plugins/ai/exolab/exolab.go
Normal file
@@ -0,0 +1,45 @@
|
||||
package exolab
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/danielmiessler/fabric/plugins"
|
||||
"github.com/danielmiessler/fabric/plugins/ai/openai"
|
||||
|
||||
goopenai "github.com/sashabaranov/go-openai"
|
||||
)
|
||||
|
||||
func NewClient() (ret *Client) {
|
||||
ret = &Client{}
|
||||
ret.Client = openai.NewClientCompatibleNoSetupQuestions("Exolab", ret.configure)
|
||||
|
||||
ret.ApiBaseURL = ret.AddSetupQuestion("API Base URL", true)
|
||||
ret.ApiBaseURL.Value = "http://localhost:52415"
|
||||
|
||||
ret.ApiModels = ret.AddSetupQuestionCustom("models", true,
|
||||
"Enter your deployed Exolab models (comma separated)")
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
type Client struct {
|
||||
*openai.Client
|
||||
ApiModels *plugins.SetupQuestion
|
||||
|
||||
apiModels []string
|
||||
}
|
||||
|
||||
func (oi *Client) configure() (err error) {
|
||||
oi.apiModels = strings.Split(oi.ApiModels.Value, ",")
|
||||
|
||||
config := goopenai.DefaultConfig("")
|
||||
config.BaseURL = oi.ApiBaseURL.Value
|
||||
|
||||
oi.ApiClient = goopenai.NewClientWithConfig(config)
|
||||
return
|
||||
}
|
||||
|
||||
func (oi *Client) ListModels() (ret []string, err error) {
|
||||
ret = oi.apiModels
|
||||
return
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
347
plugins/ai/lmstudio/lmstudio.go
Normal file
347
plugins/ai/lmstudio/lmstudio.go
Normal file
@@ -0,0 +1,347 @@
|
||||
package lmstudio
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
|
||||
goopenai "github.com/sashabaranov/go-openai"
|
||||
|
||||
"github.com/danielmiessler/fabric/common"
|
||||
"github.com/danielmiessler/fabric/plugins"
|
||||
)
|
||||
|
||||
// NewClient creates a new LM Studio client with default configuration.
|
||||
func NewClient() (ret *Client) {
|
||||
return NewClientCompatible("LM Studio", "http://localhost:1234/v1", nil)
|
||||
}
|
||||
|
||||
// NewClientCompatible creates a new LM Studio client with custom configuration.
|
||||
func NewClientCompatible(vendorName string, defaultBaseUrl string, configureCustom func() error) (ret *Client) {
|
||||
ret = &Client{}
|
||||
|
||||
if configureCustom == nil {
|
||||
configureCustom = ret.configure
|
||||
}
|
||||
ret.PluginBase = &plugins.PluginBase{
|
||||
Name: vendorName,
|
||||
EnvNamePrefix: plugins.BuildEnvVariablePrefix(vendorName),
|
||||
ConfigureCustom: configureCustom,
|
||||
}
|
||||
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
|
||||
ApiUrl *plugins.SetupQuestion
|
||||
HttpClient *http.Client
|
||||
}
|
||||
|
||||
// configure sets up the HTTP client.
|
||||
func (c *Client) configure() error {
|
||||
c.HttpClient = &http.Client{}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ListModels returns a list of available models.
|
||||
func (c *Client) ListModels() ([]string, error) {
|
||||
url := fmt.Sprintf("%s/models", c.ApiUrl.Value)
|
||||
|
||||
req, err := http.NewRequest("GET", url, nil)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create request: %w", err)
|
||||
}
|
||||
|
||||
resp, err := c.HttpClient.Do(req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to send request: %w", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return nil, fmt.Errorf("unexpected status code: %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
var result struct {
|
||||
Data []struct {
|
||||
ID string `json:"id"`
|
||||
} `json:"data"`
|
||||
}
|
||||
|
||||
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
|
||||
return nil, fmt.Errorf("failed to decode response: %w", err)
|
||||
}
|
||||
|
||||
models := make([]string, len(result.Data))
|
||||
for i, model := range result.Data {
|
||||
models[i] = model.ID
|
||||
}
|
||||
|
||||
return models, nil
|
||||
}
|
||||
|
||||
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,
|
||||
"model": opts.Model,
|
||||
"stream": true, // Enable streaming
|
||||
}
|
||||
|
||||
var jsonPayload []byte
|
||||
if jsonPayload, err = json.Marshal(payload); err != nil {
|
||||
err = fmt.Errorf("failed to marshal payload: %w", err)
|
||||
return
|
||||
}
|
||||
|
||||
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")
|
||||
|
||||
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 {
|
||||
err = fmt.Errorf("unexpected status code: %d", resp.StatusCode)
|
||||
return
|
||||
}
|
||||
|
||||
defer close(channel)
|
||||
|
||||
reader := bufio.NewReader(resp.Body)
|
||||
for {
|
||||
var line []byte
|
||||
if line, err = reader.ReadBytes('\n'); err != nil {
|
||||
if err == io.EOF {
|
||||
err = nil
|
||||
break
|
||||
}
|
||||
err = fmt.Errorf("error reading response: %w", err)
|
||||
return
|
||||
}
|
||||
|
||||
if len(line) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
if bytes.HasPrefix(line, []byte("data: ")) {
|
||||
line = bytes.TrimPrefix(line, []byte("data: "))
|
||||
}
|
||||
|
||||
if string(line) == "[DONE]" {
|
||||
break
|
||||
}
|
||||
|
||||
var result map[string]interface{}
|
||||
if err = json.Unmarshal(line, &result); err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
var choices []interface{}
|
||||
var ok bool
|
||||
if choices, ok = result["choices"].([]interface{}); !ok || len(choices) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
var delta map[string]interface{}
|
||||
if delta, ok = choices[0].(map[string]interface{})["delta"].(map[string]interface{}); !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
var content string
|
||||
if content, _ = delta["content"].(string); content != "" {
|
||||
channel <- content
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
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,
|
||||
"model": opts.Model,
|
||||
// Add other options from opts if supported by LM Studio
|
||||
}
|
||||
|
||||
var jsonPayload []byte
|
||||
if jsonPayload, err = json.Marshal(payload); err != nil {
|
||||
err = fmt.Errorf("failed to marshal payload: %w", err)
|
||||
return
|
||||
}
|
||||
|
||||
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")
|
||||
|
||||
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 {
|
||||
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 {
|
||||
err = fmt.Errorf("failed to decode response: %w", err)
|
||||
return
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
if content, ok = message["content"].(string); !ok {
|
||||
err = fmt.Errorf("invalid response format: missing or non-string content in message")
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
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,
|
||||
"model": opts.Model,
|
||||
// Add other options from opts if supported by LM Studio
|
||||
}
|
||||
|
||||
var jsonPayload []byte
|
||||
if jsonPayload, err = json.Marshal(payload); err != nil {
|
||||
err = fmt.Errorf("failed to marshal payload: %w", err)
|
||||
return
|
||||
}
|
||||
|
||||
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")
|
||||
|
||||
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 {
|
||||
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 {
|
||||
err = fmt.Errorf("failed to decode response: %w", err)
|
||||
return
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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,
|
||||
"model": opts.Model,
|
||||
// Add other options from opts if supported by LM Studio
|
||||
}
|
||||
|
||||
var jsonPayload []byte
|
||||
if jsonPayload, err = json.Marshal(payload); err != nil {
|
||||
err = fmt.Errorf("failed to marshal payload: %w", err)
|
||||
return
|
||||
}
|
||||
|
||||
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")
|
||||
|
||||
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 {
|
||||
err = fmt.Errorf("unexpected status code: %d", resp.StatusCode)
|
||||
return
|
||||
}
|
||||
|
||||
var result struct {
|
||||
Data []struct {
|
||||
Embedding []float64 `json:"embedding"`
|
||||
} `json:"data"`
|
||||
}
|
||||
|
||||
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 {
|
||||
err = fmt.Errorf("no embeddings returned")
|
||||
return
|
||||
}
|
||||
|
||||
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"
|
||||
@@ -18,6 +19,16 @@ func NewClient() (ret *Client) {
|
||||
}
|
||||
|
||||
func NewClientCompatible(vendorName string, defaultBaseUrl string, configureCustom func() error) (ret *Client) {
|
||||
ret = NewClientCompatibleNoSetupQuestions(vendorName, configureCustom)
|
||||
|
||||
ret.ApiKey = ret.AddSetupQuestion("API Key", true)
|
||||
ret.ApiBaseURL = ret.AddSetupQuestion("API Base URL", false)
|
||||
ret.ApiBaseURL.Value = defaultBaseUrl
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func NewClientCompatibleNoSetupQuestions(vendorName string, configureCustom func() error) (ret *Client) {
|
||||
ret = &Client{}
|
||||
|
||||
if configureCustom == nil {
|
||||
@@ -30,10 +41,6 @@ func NewClientCompatible(vendorName string, defaultBaseUrl string, configureCust
|
||||
ConfigureCustom: configureCustom,
|
||||
}
|
||||
|
||||
ret.ApiKey = ret.AddSetupQuestion("API Key", true)
|
||||
ret.ApiBaseURL = ret.AddSetupQuestion("API Base URL", false)
|
||||
ret.ApiBaseURL.Value = defaultBaseUrl
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
231
plugins/strategy/strategy.go
Normal file
231
plugins/strategy/strategy.go
Normal file
@@ -0,0 +1,231 @@
|
||||
package strategy
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/danielmiessler/fabric/plugins"
|
||||
"github.com/danielmiessler/fabric/plugins/tools/githelper"
|
||||
)
|
||||
|
||||
const DefaultStrategiesGitRepoUrl = "https://github.com/danielmiessler/fabric.git"
|
||||
const DefaultStrategiesGitRepoFolder = "strategies"
|
||||
|
||||
func NewStrategiesManager() (sm *StrategiesManager) {
|
||||
label := "Prompt Strategies"
|
||||
strategies, err := LoadAllFiles()
|
||||
if err != nil {
|
||||
strategies = make(map[string]Strategy) // empty map
|
||||
}
|
||||
sm = &StrategiesManager{
|
||||
Strategies: strategies,
|
||||
}
|
||||
sm.PluginBase = &plugins.PluginBase{
|
||||
Name: label,
|
||||
SetupDescription: "Strategies - Downloads Prompting Strategies (like chain of thought) [required]",
|
||||
EnvNamePrefix: plugins.BuildEnvVariablePrefix(label),
|
||||
ConfigureCustom: sm.configure,
|
||||
}
|
||||
|
||||
sm.DefaultGitRepoUrl = sm.AddSetupQuestionCustom("Git Repo Url", true,
|
||||
"Enter the default Git repository URL for the strategies")
|
||||
sm.DefaultGitRepoUrl.Value = DefaultStrategiesGitRepoUrl
|
||||
|
||||
sm.DefaultFolder = sm.AddSetupQuestionCustom("Git Repo Strategies Folder", true,
|
||||
"Enter the default folder in the Git repository where strategies are stored")
|
||||
sm.DefaultFolder.Value = DefaultStrategiesGitRepoFolder
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
type StrategiesManager struct {
|
||||
*plugins.PluginBase
|
||||
Strategies map[string]Strategy
|
||||
|
||||
DefaultGitRepoUrl *plugins.SetupQuestion
|
||||
DefaultFolder *plugins.SetupQuestion
|
||||
}
|
||||
|
||||
type Strategy struct {
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
Prompt string `json:"prompt"`
|
||||
}
|
||||
|
||||
func LoadAllFiles() (strategies map[string]Strategy, err error) {
|
||||
strategies = make(map[string]Strategy)
|
||||
strategyDir, err := getStrategyDir()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
filepath.WalkDir(strategyDir, func(path string, d fs.DirEntry, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if d.IsDir() && path != strategyDir {
|
||||
return filepath.SkipDir
|
||||
}
|
||||
|
||||
if filepath.Ext(path) == ".json" {
|
||||
strategyName := strings.TrimSuffix(filepath.Base(path), ".json")
|
||||
strategy, err := LoadStrategy(strategyName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
strategies[strategy.Name] = *strategy
|
||||
}
|
||||
return nil
|
||||
})
|
||||
return
|
||||
|
||||
}
|
||||
|
||||
func (sm *StrategiesManager) IsConfigured() (ret bool) {
|
||||
ret = sm.PluginBase.IsConfigured()
|
||||
if ret {
|
||||
if len(sm.Strategies) == 0 {
|
||||
ret = false
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (sm *StrategiesManager) Setup() (err error) {
|
||||
if err = sm.PluginBase.Setup(); err != nil {
|
||||
return
|
||||
}
|
||||
if err = sm.PopulateDB(); err != nil {
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// PopulateDB downloads strategies from the internet and populates the strategies folder
|
||||
func (sm *StrategiesManager) PopulateDB() (err error) {
|
||||
stageDir, _ := getStrategyDir()
|
||||
fmt.Printf("Downloading strategies and Populating %s...\n", stageDir)
|
||||
fmt.Println()
|
||||
if err = sm.gitCloneAndCopy(); err != nil {
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (sm *StrategiesManager) gitCloneAndCopy() (err error) {
|
||||
homeDir, err := os.UserHomeDir()
|
||||
if err != nil {
|
||||
err = fmt.Errorf("could not get home directory: %v", err)
|
||||
return
|
||||
}
|
||||
strategyDir := filepath.Join(homeDir, ".config", "fabric", "strategies")
|
||||
|
||||
// Create the directory if it doesn't exist
|
||||
if err = os.MkdirAll(strategyDir, os.ModePerm); err != nil {
|
||||
return fmt.Errorf("failed to create strategies directory: %w", err)
|
||||
}
|
||||
|
||||
// Use the helper to fetch files
|
||||
err = githelper.FetchFilesFromRepo(githelper.FetchOptions{
|
||||
RepoURL: sm.DefaultGitRepoUrl.Value,
|
||||
PathPrefix: sm.DefaultFolder.Value,
|
||||
DestDir: strategyDir,
|
||||
SingleDirectory: true,
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to download strategies: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (sm *StrategiesManager) configure() (err error) {
|
||||
sm.Strategies, err = LoadAllFiles()
|
||||
return
|
||||
}
|
||||
|
||||
// getStrategyDir returns the path to the strategies directory
|
||||
func getStrategyDir() (ret string, err error) {
|
||||
homeDir, err := os.UserHomeDir()
|
||||
if err != nil {
|
||||
err = fmt.Errorf("could not get home directory: %v, using current directory instead", err)
|
||||
ret = filepath.Join(".", "strategies")
|
||||
return
|
||||
}
|
||||
return filepath.Join(homeDir, ".config", "fabric", "strategies"), nil
|
||||
}
|
||||
|
||||
// LoadStrategy loads a strategy from the given name
|
||||
func LoadStrategy(filename string) (*Strategy, error) {
|
||||
if filename == "" {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// Get the strategy directory path
|
||||
strategyDir, err := getStrategyDir()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// First try with .json extension
|
||||
strategyPath := filepath.Join(strategyDir, filename+".json")
|
||||
if _, err := os.Stat(strategyPath); os.IsNotExist(err) {
|
||||
// Try without extension
|
||||
strategyPath = filepath.Join(strategyDir, filename)
|
||||
if _, err := os.Stat(strategyPath); os.IsNotExist(err) {
|
||||
return nil, fmt.Errorf("strategy %s not found. Please run 'fabric --liststrategies' for list", filename)
|
||||
}
|
||||
}
|
||||
|
||||
data, err := os.ReadFile(strategyPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var strategy Strategy
|
||||
if err := json.Unmarshal(data, &strategy); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
strategy.Name = strings.TrimSuffix(filepath.Base(strategyPath), ".json")
|
||||
|
||||
return &strategy, nil
|
||||
}
|
||||
|
||||
// ListStrategies prints available strategies
|
||||
func (sm *StrategiesManager) ListStrategies() error {
|
||||
if len(sm.Strategies) == 0 {
|
||||
return fmt.Errorf("no strategies found. Please run 'fabric --setup' to download strategies")
|
||||
}
|
||||
fmt.Print("Available Strategies:\n\n")
|
||||
|
||||
// Get all strategy names for sorting
|
||||
names := []string{}
|
||||
for name := range sm.Strategies {
|
||||
names = append(names, name)
|
||||
}
|
||||
|
||||
// Sort the strategy names alphabetically
|
||||
sort.Strings(names)
|
||||
|
||||
// Find the longest name to align descriptions
|
||||
maxNameLength := 0
|
||||
for _, name := range names {
|
||||
if len(name) > maxNameLength {
|
||||
maxNameLength = len(name)
|
||||
}
|
||||
}
|
||||
|
||||
// Print each strategy with its description aligned
|
||||
formatString := "%-" + fmt.Sprintf("%d", maxNameLength+2) + "s %s\n"
|
||||
for _, name := range names {
|
||||
strategy := sm.Strategies[name]
|
||||
fmt.Printf(formatString, strategy.Name, strategy.Description)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -2,6 +2,7 @@ package converter
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
|
||||
"github.com/go-shiori/go-readability"
|
||||
)
|
||||
|
||||
|
||||
111
plugins/tools/githelper/githelper.go
Normal file
111
plugins/tools/githelper/githelper.go
Normal file
@@ -0,0 +1,111 @@
|
||||
package githelper
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/go-git/go-git/v5"
|
||||
"github.com/go-git/go-git/v5/plumbing/object"
|
||||
"github.com/go-git/go-git/v5/storage/memory"
|
||||
)
|
||||
|
||||
// FetchOptions defines options for fetching files from a git repo
|
||||
type FetchOptions struct {
|
||||
// RepoURL is the URL of the git repository
|
||||
RepoURL string
|
||||
|
||||
// PathPrefix is the folder within the repo to extract (e.g. "patterns/")
|
||||
PathPrefix string
|
||||
|
||||
// DestDir is where the files will be saved locally
|
||||
DestDir string
|
||||
|
||||
// SingleDirectory if true, only fetch files directly in the specified directory
|
||||
// without recursing into subdirectories
|
||||
SingleDirectory bool
|
||||
}
|
||||
|
||||
// FetchFilesFromRepo clones a git repo and extracts files from a specific folder
|
||||
func FetchFilesFromRepo(opts FetchOptions) error {
|
||||
// Ensure path prefix ends with slash
|
||||
if !strings.HasSuffix(opts.PathPrefix, "/") {
|
||||
opts.PathPrefix = opts.PathPrefix + "/"
|
||||
}
|
||||
|
||||
// Clone the repository in memory
|
||||
r, err := git.Clone(memory.NewStorage(), nil, &git.CloneOptions{
|
||||
URL: opts.RepoURL,
|
||||
Depth: 1,
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to clone repository: %w", err)
|
||||
}
|
||||
|
||||
// Get HEAD reference
|
||||
ref, err := r.Head()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get repository HEAD: %w", err)
|
||||
}
|
||||
|
||||
// Get commit object
|
||||
commit, err := r.CommitObject(ref.Hash())
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get commit: %w", err)
|
||||
}
|
||||
|
||||
// Get the file tree
|
||||
tree, err := commit.Tree()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get tree: %w", err)
|
||||
}
|
||||
|
||||
// Ensure destination directory exists
|
||||
if err := os.MkdirAll(opts.DestDir, 0755); err != nil {
|
||||
return fmt.Errorf("failed to create destination directory: %w", err)
|
||||
}
|
||||
|
||||
// Extract files from the tree
|
||||
return tree.Files().ForEach(func(f *object.File) error {
|
||||
// Only process files in the specified path
|
||||
if !strings.HasPrefix(f.Name, opts.PathPrefix) {
|
||||
return nil
|
||||
}
|
||||
|
||||
// For SingleDirectory mode, skip files in subdirectories
|
||||
if opts.SingleDirectory {
|
||||
remainingPath := strings.TrimPrefix(f.Name, opts.PathPrefix)
|
||||
if strings.Contains(remainingPath, "/") {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// Create local path for the file, removing the prefix
|
||||
relativePath := strings.TrimPrefix(f.Name, opts.PathPrefix)
|
||||
localPath := filepath.Join(opts.DestDir, relativePath)
|
||||
|
||||
// Ensure directory structure exists
|
||||
if err := os.MkdirAll(filepath.Dir(localPath), 0755); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Get file contents
|
||||
reader, err := f.Reader()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer reader.Close()
|
||||
|
||||
// Create and write to local file
|
||||
file, err := os.Create(localPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
_, err = io.Copy(file, reader)
|
||||
return err
|
||||
})
|
||||
}
|
||||
@@ -2,19 +2,13 @@ package tools
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/danielmiessler/fabric/plugins"
|
||||
"github.com/danielmiessler/fabric/plugins/db/fsdb"
|
||||
"github.com/danielmiessler/fabric/plugins/tools/githelper"
|
||||
|
||||
"github.com/go-git/go-git/v5"
|
||||
"github.com/go-git/go-git/v5/plumbing"
|
||||
"github.com/go-git/go-git/v5/plumbing/object"
|
||||
"github.com/go-git/go-git/v5/storage/memory"
|
||||
"github.com/otiai10/copy"
|
||||
)
|
||||
|
||||
@@ -89,7 +83,7 @@ func (o *PatternsLoader) Setup() (err error) {
|
||||
|
||||
// PopulateDB downloads patterns from the internet and populates the patterns folder
|
||||
func (o *PatternsLoader) PopulateDB() (err error) {
|
||||
fmt.Printf("Downloading patterns and Populating %s..\n", o.Patterns.Dir)
|
||||
fmt.Printf("Downloading patterns and Populating %s...\n", o.Patterns.Dir)
|
||||
fmt.Println()
|
||||
if err = o.gitCloneAndCopy(); err != nil {
|
||||
return
|
||||
@@ -148,156 +142,20 @@ func (o *PatternsLoader) movePatterns() (err error) {
|
||||
}
|
||||
|
||||
func (o *PatternsLoader) gitCloneAndCopy() (err error) {
|
||||
// Clones the given repository, creating the remote, the local branches
|
||||
// and fetching the objects, everything in memory:
|
||||
var r *git.Repository
|
||||
if r, err = git.Clone(memory.NewStorage(), nil, &git.CloneOptions{
|
||||
URL: o.DefaultGitRepoUrl.Value,
|
||||
}); err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
// Create temp folder if it doesn't exist
|
||||
if err = os.MkdirAll(filepath.Dir(o.tempPatternsFolder), os.ModePerm); err != nil {
|
||||
return fmt.Errorf("failed to create temp directory: %w", err)
|
||||
}
|
||||
|
||||
// ... retrieves the branch pointed by HEAD
|
||||
var ref *plumbing.Reference
|
||||
if ref, err = r.Head(); err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
|
||||
// ... retrieves the commit history for /patterns folder
|
||||
var cIter object.CommitIter
|
||||
if cIter, err = r.Log(&git.LogOptions{
|
||||
From: ref.Hash(),
|
||||
PathFilter: func(path string) bool {
|
||||
return path == o.DefaultFolder.Value || strings.HasPrefix(path, o.pathPatternsPrefix)
|
||||
},
|
||||
}); err != nil {
|
||||
fmt.Println(err)
|
||||
return err
|
||||
}
|
||||
|
||||
var changes []fsdb.DirectoryChange
|
||||
// ... iterates over the commits
|
||||
if err = cIter.ForEach(func(c *object.Commit) (err error) {
|
||||
// GetApplyVariables the files changed in this commit by comparing with its parents
|
||||
parentIter := c.Parents()
|
||||
if err = parentIter.ForEach(func(parent *object.Commit) (err error) {
|
||||
var patch *object.Patch
|
||||
if patch, err = parent.Patch(c); err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
|
||||
for _, fileStat := range patch.Stats() {
|
||||
if strings.HasPrefix(fileStat.Name, o.pathPatternsPrefix) {
|
||||
dir := filepath.Dir(fileStat.Name)
|
||||
changes = append(changes, fsdb.DirectoryChange{Dir: dir, Timestamp: c.Committer.When})
|
||||
}
|
||||
}
|
||||
return
|
||||
}); err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
return
|
||||
}); err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
|
||||
// Sort changes by timestamp
|
||||
sort.Slice(changes, func(i, j int) bool {
|
||||
return changes[i].Timestamp.Before(changes[j].Timestamp)
|
||||
// Use the helper to fetch files
|
||||
err = githelper.FetchFilesFromRepo(githelper.FetchOptions{
|
||||
RepoURL: o.DefaultGitRepoUrl.Value,
|
||||
PathPrefix: o.DefaultFolder.Value,
|
||||
DestDir: o.tempPatternsFolder,
|
||||
})
|
||||
|
||||
if err = o.makeUniqueList(changes); err != nil {
|
||||
return
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to download patterns: %w", err)
|
||||
}
|
||||
|
||||
var commit *object.Commit
|
||||
if commit, err = r.CommitObject(ref.Hash()); err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
|
||||
var tree *object.Tree
|
||||
if tree, err = commit.Tree(); err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
|
||||
if err = tree.Files().ForEach(func(f *object.File) (err error) {
|
||||
if strings.HasPrefix(f.Name, o.pathPatternsPrefix) {
|
||||
// Create the local file path
|
||||
localPath := filepath.Join(os.TempDir(), f.Name)
|
||||
|
||||
// Create the directories if they don't exist
|
||||
if err = os.MkdirAll(filepath.Dir(localPath), os.ModePerm); err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
|
||||
// Write the file to the local filesystem
|
||||
var blob *object.Blob
|
||||
if blob, err = r.BlobObject(f.Hash); err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
err = o.writeBlobToFile(blob, localPath)
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}); err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (o *PatternsLoader) writeBlobToFile(blob *object.Blob, path string) (err error) {
|
||||
var reader io.ReadCloser
|
||||
if reader, err = blob.Reader(); err != nil {
|
||||
return
|
||||
}
|
||||
defer reader.Close()
|
||||
|
||||
// Create the file
|
||||
var file *os.File
|
||||
if file, err = os.Create(path); err != nil {
|
||||
return
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
// Copy the contents of the blob to the file
|
||||
if _, err = io.Copy(file, reader); err != nil {
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (o *PatternsLoader) makeUniqueList(changes []fsdb.DirectoryChange) (err error) {
|
||||
uniqueItems := make(map[string]bool)
|
||||
for _, change := range changes {
|
||||
if strings.TrimSpace(change.Dir) != "" && !strings.Contains(change.Dir, "=>") {
|
||||
pattern := strings.ReplaceAll(change.Dir, o.pathPatternsPrefix, "")
|
||||
pattern = strings.TrimSpace(pattern)
|
||||
uniqueItems[pattern] = true
|
||||
}
|
||||
}
|
||||
|
||||
finalList := make([]string, 0, len(uniqueItems))
|
||||
for _, change := range changes {
|
||||
pattern := strings.ReplaceAll(change.Dir, o.pathPatternsPrefix, "")
|
||||
pattern = strings.TrimSpace(pattern)
|
||||
if _, exists := uniqueItems[pattern]; exists {
|
||||
finalList = append(finalList, pattern)
|
||||
delete(uniqueItems, pattern) // Remove to avoid duplicates in the final list
|
||||
}
|
||||
}
|
||||
|
||||
joined := strings.Join(finalList, "\n")
|
||||
err = os.WriteFile(o.Patterns.UniquePatternsFilePath, []byte(joined), 0o644)
|
||||
return
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -55,6 +55,7 @@ func (h *ChatHandler) HandleChat(c *gin.Context) {
|
||||
|
||||
if err := c.BindJSON(&request); err != nil {
|
||||
log.Printf("Error binding JSON: %v", err)
|
||||
c.Writer.Header().Set("Strict-Transport-Security", "max-age=63072000; includeSubDomains")
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": fmt.Sprintf("Invalid request format: %v", err)})
|
||||
return
|
||||
}
|
||||
@@ -86,7 +87,7 @@ func (h *ChatHandler) HandleChat(c *gin.Context) {
|
||||
go func(p PromptRequest) {
|
||||
defer close(streamChan)
|
||||
|
||||
chatter, err := h.registry.GetChatter(p.Model, 2048, false, false)
|
||||
chatter, err := h.registry.GetChatter(p.Model, 2048, "", false, false)
|
||||
if err != nil {
|
||||
log.Printf("Error creating chatter: %v", err)
|
||||
streamChan <- fmt.Sprintf("Error: %v", err)
|
||||
@@ -191,6 +192,26 @@ func writeSSEResponse(w gin.ResponseWriter, response StreamResponse) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
/*
|
||||
func detectFormat(content string) string {
|
||||
if strings.HasPrefix(content, "graph TD") ||
|
||||
strings.HasPrefix(content, "gantt") ||
|
||||
strings.HasPrefix(content, "flowchart") ||
|
||||
strings.HasPrefix(content, "sequenceDiagram") ||
|
||||
strings.HasPrefix(content, "classDiagram") ||
|
||||
strings.HasPrefix(content, "stateDiagram") {
|
||||
return "mermaid"
|
||||
}
|
||||
if strings.Contains(content, "```") ||
|
||||
strings.Contains(content, "#") ||
|
||||
strings.Contains(content, "*") ||
|
||||
strings.Contains(content, "_") ||
|
||||
strings.Contains(content, "-") {
|
||||
return "markdown"
|
||||
}
|
||||
return "plain"
|
||||
}
|
||||
*/
|
||||
func detectFormat(content string) string {
|
||||
if strings.HasPrefix(content, "graph TD") ||
|
||||
strings.HasPrefix(content, "gantt") ||
|
||||
@@ -200,12 +221,5 @@ func detectFormat(content string) string {
|
||||
strings.HasPrefix(content, "stateDiagram") {
|
||||
return "mermaid"
|
||||
}
|
||||
if strings.Contains(content, "```") ||
|
||||
strings.Contains(content, "#") ||
|
||||
strings.Contains(content, "*") ||
|
||||
strings.Contains(content, "_") ||
|
||||
strings.Contains(content, "-") {
|
||||
return "markdown"
|
||||
}
|
||||
return "plain"
|
||||
return "markdown"
|
||||
}
|
||||
|
||||
@@ -65,6 +65,7 @@ func (h *ConfigHandler) GetConfig(c *gin.Context) {
|
||||
"openrouter": os.Getenv("OPENROUTER_API_KEY"),
|
||||
"silicon": os.Getenv("SILICON_API_KEY"),
|
||||
"deepseek": os.Getenv("DEEPSEEK_API_KEY"),
|
||||
"lmstudio": os.Getenv("LM_STUDIO_API_BASE_URL"),
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, config)
|
||||
@@ -86,23 +87,25 @@ func (h *ConfigHandler) UpdateConfig(c *gin.Context) {
|
||||
OpenRouterApiKey string `json:"openrouter_api_key"`
|
||||
SiliconApiKey string `json:"silicon_api_key"`
|
||||
DeepSeekApiKey string `json:"deepseek_api_key"`
|
||||
LMStudioURL string `json:"lm_studio_base_url"`
|
||||
}
|
||||
|
||||
if err := c.BindJSON(&config); err != nil {
|
||||
if err := c.ShouldBindJSON(&config); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
envVars := map[string]string{
|
||||
"OPENAI_API_KEY": config.OpenAIApiKey,
|
||||
"ANTHROPIC_API_KEY": config.AnthropicApiKey,
|
||||
"GROQ_API_KEY": config.GroqApiKey,
|
||||
"MISTRAL_API_KEY": config.MistralApiKey,
|
||||
"GEMINI_API_KEY": config.GeminiApiKey,
|
||||
"OLLAMA_URL": config.OllamaURL,
|
||||
"OPENROUTER_API_KEY": config.OpenRouterApiKey,
|
||||
"SILICON_API_KEY": config.SiliconApiKey,
|
||||
"DEEPSEEK_API_KEY": config.DeepSeekApiKey,
|
||||
"OPENAI_API_KEY": config.OpenAIApiKey,
|
||||
"ANTHROPIC_API_KEY": config.AnthropicApiKey,
|
||||
"GROQ_API_KEY": config.GroqApiKey,
|
||||
"MISTRAL_API_KEY": config.MistralApiKey,
|
||||
"GEMINI_API_KEY": config.GeminiApiKey,
|
||||
"OLLAMA_URL": config.OllamaURL,
|
||||
"OPENROUTER_API_KEY": config.OpenRouterApiKey,
|
||||
"SILICON_API_KEY": config.SiliconApiKey,
|
||||
"DEEPSEEK_API_KEY": config.DeepSeekApiKey,
|
||||
"LM_STUDIO_API_BASE_URL": config.LMStudioURL,
|
||||
}
|
||||
|
||||
var envContent strings.Builder
|
||||
|
||||
@@ -14,6 +14,6 @@ type ContextsHandler struct {
|
||||
// NewContextsHandler creates a new ContextsHandler
|
||||
func NewContextsHandler(r *gin.Engine, contexts *fsdb.ContextsEntity) (ret *ContextsHandler) {
|
||||
ret = &ContextsHandler{
|
||||
StorageHandler: NewStorageHandler[fsdb.Context](r, "contexts", contexts), contexts: contexts}
|
||||
StorageHandler: NewStorageHandler(r, "contexts", contexts), contexts: contexts}
|
||||
return
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -16,7 +16,7 @@ type PatternsHandler struct {
|
||||
// NewPatternsHandler creates a new PatternsHandler
|
||||
func NewPatternsHandler(r *gin.Engine, patterns *fsdb.PatternsEntity) (ret *PatternsHandler) {
|
||||
ret = &PatternsHandler{
|
||||
StorageHandler: NewStorageHandler[fsdb.Pattern](r, "patterns", patterns), patterns: patterns}
|
||||
StorageHandler: NewStorageHandler(r, "patterns", patterns), patterns: patterns}
|
||||
|
||||
// TODO: Add custom, replacement routes here
|
||||
//r.GET("/patterns/:name", ret.Get)
|
||||
|
||||
@@ -14,6 +14,6 @@ type SessionsHandler struct {
|
||||
// NewSessionsHandler creates a new SessionsHandler
|
||||
func NewSessionsHandler(r *gin.Engine, sessions *fsdb.SessionsEntity) (ret *SessionsHandler) {
|
||||
ret = &SessionsHandler{
|
||||
StorageHandler: NewStorageHandler[fsdb.Session](r, "sessions", sessions), sessions: sessions}
|
||||
StorageHandler: NewStorageHandler(r, "sessions", sessions), sessions: sessions}
|
||||
return ret
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
4
strategies/cod.json
Normal file
4
strategies/cod.json
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"description": "Chain-of-Draft (CoD) Prompting",
|
||||
"prompt": "Think step by step, keeping a minimal draft (5 words max) for each step. Return the final answer in the required format."
|
||||
}
|
||||
4
strategies/cot.json
Normal file
4
strategies/cot.json
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"description": "Chain-of-Thought (CoT) Prompting",
|
||||
"prompt": "Think step by step to answer the question. Return the final answer in the required format."
|
||||
}
|
||||
4
strategies/ltm.json
Normal file
4
strategies/ltm.json
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"description": "Least-to-Most Prompting",
|
||||
"prompt": "Break down the problem into simpler sub-problems from easiest to hardest; answer concisely at each step."
|
||||
}
|
||||
4
strategies/reflexion.json
Normal file
4
strategies/reflexion.json
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"description": "Reflexion Prompting",
|
||||
"prompt": "Answer concisely, critique your reasoning briefly, and provide a refined answer."
|
||||
}
|
||||
4
strategies/self-consistent.json
Normal file
4
strategies/self-consistent.json
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"description": "Self-Consistency Prompting",
|
||||
"prompt": "Provide multiple reasoning paths and select the most consistent answer."
|
||||
}
|
||||
4
strategies/self-refine.json
Normal file
4
strategies/self-refine.json
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"description": "Self-Refinement",
|
||||
"prompt": "Provide an initial concise answer, critique it briefly, and refine if necessary."
|
||||
}
|
||||
4
strategies/standard.json
Normal file
4
strategies/standard.json
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"description": "Standard Prompting",
|
||||
"prompt": "Answer the question directly without any explanation or reasoning."
|
||||
}
|
||||
4
strategies/tot.json
Normal file
4
strategies/tot.json
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"description": "Tree-of-Thought (ToT) Prompting",
|
||||
"prompt": "Generate multiple reasoning paths briefly and select the best one."
|
||||
}
|
||||
@@ -1,3 +1,3 @@
|
||||
package main
|
||||
|
||||
var version = "v1.4.134"
|
||||
var version = "v1.4.159"
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
# Enhanced Pattern Selection and WEB UI Improvements
|
||||
|
||||
This PR adds several Web UI and functionality improvements to make pattern selection more intuitive and provide better context for each pattern's purpose.
|
||||
|
||||
## Demo
|
||||
Watch the demo video showcasing the new features: https://youtu.be/qVuKhCw_edk
|
||||
|
||||
## Major Improvements
|
||||
|
||||
### Pattern Selection and Description
|
||||
- Added modal interface for pattern selection
|
||||
- Added short pattern descriptions for each pattern
|
||||
- Added Select Pattern to execute from Modal
|
||||
- Added scroll functionality to System Instructions frame
|
||||
- **Added search functionality in pattern selection modal**
|
||||
- Real-time pattern filtering as you type
|
||||
- Case-insensitive partial name matching
|
||||
- Maintains favorites sorting while filtering
|
||||
|
||||
### User Experience
|
||||
- Implemented favorites functionality for quick access to frequently used patterns
|
||||
- Improved YouTube transcript handling
|
||||
- Enhanced UI components for better user experience
|
||||
- **Added Obsidian integration for pattern execution output**
|
||||
- Save pattern results directly to Obsidian from web interface
|
||||
- Configurable note naming
|
||||
- Seamless integration with existing Obsidian workflow
|
||||
|
||||
## Technical Improvements
|
||||
- Added backend support for new features
|
||||
- Improved pattern management and selection
|
||||
- Enhanced state management for patterns and favorites
|
||||
|
||||
## Key Files Modified
|
||||
|
||||
### Backend Changes
|
||||
- `fabric/restapi/`: Added new endpoints and functionality for pattern management
|
||||
- `chat.go`, `patterns.go`: Enhanced pattern handling
|
||||
- `configuration.go`, `models.go`: Added support for new features
|
||||
- **`obsidian.go`: New Obsidian integration endpoints**
|
||||
|
||||
### Frontend Changes
|
||||
- `fabric/web/src/lib/components/`:
|
||||
- `chat/`: Enhanced chat interface components
|
||||
- `patterns/`: New pattern selection components
|
||||
- **Added pattern search functionality**
|
||||
- **Enhanced modal UI with search capabilities**
|
||||
- `ui/modal/`: Modal interface implementation
|
||||
- `fabric/web/src/lib/store/`:
|
||||
- `favorites-store.ts`: New favorites functionality
|
||||
- `pattern-store.ts`: Enhanced pattern management
|
||||
- **`obsidian-store.ts`: New Obsidian integration state management**
|
||||
- `fabric/web/src/lib/services/`:
|
||||
- `transcriptService.ts`: Improved YouTube handling
|
||||
|
||||
### Pattern Descriptions
|
||||
- `fabric/myfiles/`:
|
||||
- `pattern_descriptions.json`: Added detailed pattern descriptions
|
||||
- `extract_patterns.py`: Tool for pattern management
|
||||
|
||||
These improvements make the pattern selection process more intuitive and provide users with better context about each pattern's purpose and functionality. The addition of pattern search and Obsidian integration further enhances the user experience by providing quick access to patterns and seamless integration with external note-taking workflows.
|
||||
|
||||
## Note on Platform Compatibility
|
||||
This implementation was developed and tested on macOS. Some modifications may not be required for Windows users, particularly around system-specific paths and configurations. Windows users may need to adjust certain paths or configurations to match their environment.
|
||||
155
web/Web Interface Update README Files/language-options.md
Normal file
155
web/Web Interface Update README Files/language-options.md
Normal file
@@ -0,0 +1,155 @@
|
||||
# Language Support Implementation
|
||||
|
||||
## Overview
|
||||
The language support allows switching between languages using qualifiers (--fr, --en) in the chat input. The implementation is simple and effective, working at multiple layers of the application.
|
||||
|
||||
## Components
|
||||
|
||||
### 1. Language Store (language-store.ts)
|
||||
```typescript
|
||||
// Manages language state
|
||||
export const languageStore = writable<string>('');
|
||||
```
|
||||
|
||||
### 2. Chat Input (ChatInput.svelte)
|
||||
- Detects language qualifiers in user input
|
||||
- Updates language store
|
||||
- Strips qualifier from message
|
||||
```typescript
|
||||
// Language qualifier handling
|
||||
if (qualifier === 'fr') {
|
||||
languageStore.set('fr');
|
||||
userInput = userInput.replace(/--fr\s*/, '');
|
||||
} else if (qualifier === 'en') {
|
||||
languageStore.set('en');
|
||||
userInput = userInput.replace(/--en\s*/, '');
|
||||
}
|
||||
|
||||
// After sending message
|
||||
try {
|
||||
await sendMessage(userInput);
|
||||
languageStore.set('en'); // Reset to default after send
|
||||
} catch (error) {
|
||||
console.error('Failed to send message:', error);
|
||||
}
|
||||
```
|
||||
|
||||
### 3. Chat Service (ChatService.ts)
|
||||
- Adds language instruction to prompts
|
||||
- Defaults to English if no language specified
|
||||
```typescript
|
||||
const language = get(languageStore) || 'en';
|
||||
const languageInstruction = language !== 'en'
|
||||
? `. Please use the language '${language}' for the output.`
|
||||
: '';
|
||||
const fullInput = userInput + languageInstruction;
|
||||
```
|
||||
|
||||
### 4. Global Settings UI (Chat.svelte)
|
||||
```typescript
|
||||
// Language selector in Global Settings
|
||||
<div class="flex flex-col gap-2">
|
||||
<Label>Language</Label>
|
||||
<Select bind:value={selectedLanguage}>
|
||||
<option value="">Default</option>
|
||||
<option value="en">English</option>
|
||||
<option value="fr">French</option>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
// Script section
|
||||
let selectedLanguage = $languageStore;
|
||||
$: languageStore.set(selectedLanguage);
|
||||
```
|
||||
|
||||
## How It Works
|
||||
|
||||
1. User Input:
|
||||
- User types message with language qualifier (e.g., "--fr Hello")
|
||||
- ChatInput detects qualifier and updates language store
|
||||
- Qualifier is stripped from message
|
||||
- OR user selects language from Global Settings dropdown
|
||||
|
||||
2. Request Processing:
|
||||
- ChatService gets language from store
|
||||
- Adds language instruction to prompt
|
||||
- Sends to backend
|
||||
|
||||
3. Response:
|
||||
- AI responds in requested language
|
||||
- Response is displayed without modification
|
||||
- Language store is reset to English after message is sent
|
||||
|
||||
## Usage Examples
|
||||
|
||||
1. English (Default):
|
||||
```
|
||||
User: What is the weather?
|
||||
AI: The weather information...
|
||||
```
|
||||
|
||||
2. French:
|
||||
```
|
||||
User: --fr What is the weather?
|
||||
AI: Voici les informations météo...
|
||||
```
|
||||
|
||||
3. Using Global Settings:
|
||||
```
|
||||
1. Select "French" from language dropdown
|
||||
2. Type: What is the weather?
|
||||
3. AI responds in French
|
||||
4. Language resets to English after response
|
||||
```
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
1. Simple Design:
|
||||
- No complex language detection
|
||||
- No translation layer
|
||||
- Direct instruction to AI
|
||||
|
||||
2. Stateful:
|
||||
- Language persists until changed
|
||||
- Resets to English on page refresh
|
||||
- Resets to English after each message
|
||||
|
||||
3. Extensible:
|
||||
- Easy to add new languages
|
||||
- Just add new qualifiers and store values
|
||||
- Update Global Settings dropdown options
|
||||
|
||||
4. Error Handling:
|
||||
- Invalid qualifiers are ignored
|
||||
- Unknown languages default to English
|
||||
- Store reset on error to prevent state issues
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. Always reset language after message:
|
||||
```typescript
|
||||
// Reset stores after successful send
|
||||
languageStore.set('en');
|
||||
```
|
||||
|
||||
2. Default to English:
|
||||
```typescript
|
||||
const language = get(languageStore) || 'en';
|
||||
```
|
||||
|
||||
3. Clear language instruction:
|
||||
```typescript
|
||||
const languageInstruction = language !== 'en'
|
||||
? `. Please use the language '${language}' for the output.`
|
||||
: '';
|
||||
```
|
||||
|
||||
4. Handle UI State:
|
||||
```typescript
|
||||
// In Chat.svelte
|
||||
let selectedLanguage = $languageStore;
|
||||
$: {
|
||||
languageStore.set(selectedLanguage);
|
||||
// Update UI immediately when store changes
|
||||
selectedLanguage = $languageStore;
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
# Pattern Search Implementation Plan
|
||||
|
||||
## Component Changes (PatternList.svelte)
|
||||
|
||||
### 1. Add Search Input
|
||||
```svelte
|
||||
<div class="px-4 pb-4 flex gap-4 items-center">
|
||||
<!-- Existing sort options -->
|
||||
<div class="flex-1"> <!-- Add flex-1 to push search to right -->
|
||||
<label class="flex items-center gap-2 text-sm text-muted-foreground">
|
||||
<input type="radio" bind:group={sortBy} value="alphabetical">
|
||||
Alphabetical
|
||||
</label>
|
||||
<label class="flex items-center gap-2 text-sm text-muted-foreground">
|
||||
<input type="radio" bind:group={sortBy} value="favorites">
|
||||
Favorites First
|
||||
</label>
|
||||
</div>
|
||||
<!-- New search input -->
|
||||
<div class="w-48"> <!-- Fixed width for search -->
|
||||
<Input
|
||||
type="text"
|
||||
bind:value={searchText}
|
||||
placeholder="Search patterns..."
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
### 2. Add Search Logic
|
||||
```typescript
|
||||
// Add to script section
|
||||
let searchText = ""; // For pattern filtering
|
||||
|
||||
// Modify sortedPatterns to include search
|
||||
$: filteredPatterns = patterns.filter(p =>
|
||||
p.patternName.toLowerCase().includes(searchText.toLowerCase())
|
||||
);
|
||||
|
||||
$: sortedPatterns = sortBy === 'alphabetical'
|
||||
? [...filteredPatterns].sort((a, b) => a.patternName.localeCompare(b.patternName))
|
||||
: [
|
||||
...filteredPatterns.filter(p => $favorites.includes(p.patternName)).sort((a, b) => a.patternName.localeCompare(b.patternName)),
|
||||
...filteredPatterns.filter(p => !$favorites.includes(p.patternName)).sort((a, b) => a.patternName.localeCompare(b.patternName))
|
||||
];
|
||||
```
|
||||
|
||||
### 3. Reset Search on Selection
|
||||
```typescript
|
||||
// In pattern selection click handler
|
||||
searchText = ""; // Reset search before closing modal
|
||||
dispatch('select', pattern.patternName);
|
||||
dispatch('close');
|
||||
```
|
||||
|
||||
## Implementation Steps
|
||||
|
||||
1. Import Input component
|
||||
```typescript
|
||||
import { Input } from "$lib/components/ui/input";
|
||||
```
|
||||
|
||||
2. Add searchText variable and filtering logic
|
||||
3. Update template to include search input
|
||||
4. Add reset logic in pattern selection handler
|
||||
5. Test search functionality:
|
||||
- Partial matches work
|
||||
- Case-insensitive search
|
||||
- Search resets on selection
|
||||
- Layout maintains consistency
|
||||
|
||||
## Expected Behavior
|
||||
|
||||
- Search updates in real-time as user types
|
||||
- Matches are case-insensitive
|
||||
- Matches can be anywhere in pattern name
|
||||
- Search box clears when pattern is selected
|
||||
- Sort options (alphabetical/favorites) still work with filtered results
|
||||
- Maintains existing modal layout and styling
|
||||
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.
|
||||
|
||||
0
web/myfiles/Fabric_obsidian/.gitkeep
Normal file
0
web/myfiles/Fabric_obsidian/.gitkeep
Normal file
0
web/myfiles/inbox/.gitkeep
Normal file
0
web/myfiles/inbox/.gitkeep
Normal file
7411
web/package-lock.json
generated
7411
web/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -26,6 +26,9 @@
|
||||
"eslint-plugin-svelte": "^2.46.1",
|
||||
"lucide-svelte": "^0.309.0",
|
||||
"mdsvex": "^0.11.2",
|
||||
"patch-package": "^8.0.0",
|
||||
"pdf-to-markdown-core": "github:jzillmann/pdf-to-markdown#modularize",
|
||||
"pdfjs-dist": "^2.5.207",
|
||||
"postcss": "^8.4.49",
|
||||
"postcss-load-config": "^6.0.1",
|
||||
"rehype-autolink-headings": "^7.1.0",
|
||||
|
||||
15
web/src/lib/actions/clickOutside.ts
Normal file
15
web/src/lib/actions/clickOutside.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
export function clickOutside(node: HTMLElement, handler: () => void) {
|
||||
const handleClick = (event: MouseEvent) => {
|
||||
if (node && !node.contains(event.target as Node) && !event.defaultPrevented) {
|
||||
handler();
|
||||
}
|
||||
};
|
||||
|
||||
document.addEventListener('click', handleClick, true);
|
||||
|
||||
return {
|
||||
destroy() {
|
||||
document.removeEventListener('click', handleClick, true);
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -2,24 +2,212 @@
|
||||
import ChatInput from "./ChatInput.svelte";
|
||||
import ChatMessages from "./ChatMessages.svelte";
|
||||
import ModelConfig from "./ModelConfig.svelte";
|
||||
import Models from "./Models.svelte";
|
||||
import Patterns from "./Patterns.svelte";
|
||||
import DropdownGroup from "./DropdownGroup.svelte";
|
||||
import NoteDrawer from "$lib/components/ui/noteDrawer/NoteDrawer.svelte";
|
||||
import { Button } from "$lib/components/ui/button";
|
||||
import { Input } from "$lib/components/ui/input";
|
||||
import { Label } from "$lib/components/ui/label";
|
||||
import { Checkbox } from "$lib/components/ui/checkbox";
|
||||
import Tooltip from "$lib/components/ui/tooltip/Tooltip.svelte";
|
||||
import { Textarea } from "$lib/components/ui/textarea";
|
||||
import { obsidianSettings } from "$lib/store/obsidian-store";
|
||||
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-4 p-2 w-full">
|
||||
<aside class="w-1/5">
|
||||
<div class="flex flex-col gap-2">
|
||||
<Patterns />
|
||||
<Models />
|
||||
<div class="chat-container flex gap-0 p-2 w-full h-screen">
|
||||
<!-- Left Column -->
|
||||
<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">
|
||||
<DropdownGroup />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Model Config -->
|
||||
<div class="bg-background/5 p-2 rounded-lg">
|
||||
<ModelConfig />
|
||||
</div>
|
||||
|
||||
<!-- Message Input -->
|
||||
<div class="h-[200px] bg-background/5 rounded-lg overflow-hidden">
|
||||
<ChatInput />
|
||||
</div>
|
||||
|
||||
<!-- System Instructions -->
|
||||
<div class="flex-1 min-h-0 bg-background/5 p-2 rounded-lg">
|
||||
<div class="h-full flex flex-col">
|
||||
<Textarea
|
||||
bind:value={$systemPrompt}
|
||||
readonly={true}
|
||||
placeholder="System instructions will appear here when you select a pattern..."
|
||||
class="w-full flex-1 bg-primary-800/30 rounded-lg border-none whitespace-pre-wrap overflow-y-auto resize-none text-sm scrollbar-thin scrollbar-thumb-white/10 scrollbar-track-transparent hover:scrollbar-thumb-white/20"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
<div class="w-1/2">
|
||||
<ChatInput />
|
||||
</div>
|
||||
<!-- Resize Handle -->
|
||||
<button
|
||||
class="resize-handle"
|
||||
on:mousedown={startResize}
|
||||
on:keydown={handleKeyDown}
|
||||
type="button"
|
||||
aria-label="Resize chat panels"
|
||||
></button>
|
||||
|
||||
<div class="w-1/2">
|
||||
<ChatMessages />
|
||||
<!-- Right Column -->
|
||||
<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">
|
||||
{#if showObsidian}
|
||||
<div class="flex items-center gap-2">
|
||||
<div class="flex items-center gap-1">
|
||||
<Checkbox
|
||||
bind:checked={$obsidianSettings.saveToObsidian}
|
||||
id="save-to-obsidian"
|
||||
class="h-3 w-3"
|
||||
/>
|
||||
<Label for="save-to-obsidian" class="text-xs text-white/70">Save to Obsidian</Label>
|
||||
</div>
|
||||
{#if $obsidianSettings.saveToObsidian}
|
||||
<Input
|
||||
id="note-name"
|
||||
bind:value={$obsidianSettings.noteName}
|
||||
placeholder="Note name..."
|
||||
class="h-6 text-xs w-48 bg-white/5 border-none focus:ring-1 ring-white/20"
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
<Button variant="ghost" size="sm" class="h-6 px-2 text-xs opacity-70 hover:opacity-100" on:click={openDrawer}>
|
||||
<Tooltip text="Take Notes" position="left">
|
||||
<span>Take Notes</span>
|
||||
</Tooltip>
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<!-- Chat Area -->
|
||||
<div class="flex-1 flex flex-col min-h-0">
|
||||
<!-- Chat History -->
|
||||
<div class="flex-1 min-h-0 bg-background/5 rounded-lg overflow-y-scroll scrollbar-thin scrollbar-thumb-white/10 scrollbar-track-transparent hover:scrollbar-thumb-white/20">
|
||||
<ChatMessages />
|
||||
<div class="h-32"></div> <!-- Spacer div to ensure scrolling works properly -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<NoteDrawer />
|
||||
|
||||
<style>
|
||||
.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 {
|
||||
0% { opacity: 1; }
|
||||
50% { opacity: 0.5; }
|
||||
100% { opacity: 1; }
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -2,86 +2,432 @@
|
||||
import { Button } from "$lib/components/ui/button";
|
||||
import { Textarea } from "$lib/components/ui/textarea";
|
||||
import { sendMessage, messageStore } from '$lib/store/chat-store';
|
||||
import { systemPrompt } from '$lib/store/pattern-store';
|
||||
import { systemPrompt, selectedPatternName } from '$lib/store/pattern-store';
|
||||
import { getToastStore } from '@skeletonlabs/skeleton';
|
||||
import { FileButton } from '@skeletonlabs/skeleton';
|
||||
import { Paperclip, Send, FileCheck } from 'lucide-svelte';
|
||||
import { onMount } from 'svelte';
|
||||
import { get } from 'svelte/store';
|
||||
import { getTranscript } from '$lib/services/transcriptService';
|
||||
import { ChatService } from '$lib/services/ChatService';
|
||||
// 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 = "";
|
||||
//let files: FileList;
|
||||
let isYouTubeURL = false;
|
||||
const toastStore = getToastStore();
|
||||
|
||||
let files: File[] = [];
|
||||
let files: FileList | undefined = undefined;
|
||||
let uploadedFiles: string[] = [];
|
||||
let fileContents: string[] = [];
|
||||
let isProcessingFiles = false;
|
||||
|
||||
async function handleFileUpload(e: Event) {
|
||||
if (!files || files.length === 0) return;
|
||||
|
||||
if (uploadedFiles.length >= 5 || (uploadedFiles.length + files.length) > 5) {
|
||||
toastStore.error('Maximum 5 files allowed');
|
||||
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.error('Error processing files: ' + error.message);
|
||||
} finally {
|
||||
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);
|
||||
if (isYoutube) {
|
||||
console.log('YouTube URL detected:', input);
|
||||
console.log('Current system prompt:', $systemPrompt?.length);
|
||||
console.log('Selected pattern:', $selectedPatternName);
|
||||
}
|
||||
return isYoutube;
|
||||
}
|
||||
|
||||
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);
|
||||
function handleInput(event: Event) {
|
||||
console.log('\n=== Handle Input ===');
|
||||
const target = event.target as HTMLTextAreaElement;
|
||||
userInput = target.value;
|
||||
|
||||
const currentLanguage = get(languageStore);
|
||||
|
||||
const languageQualifiers = {
|
||||
'--en': 'en',
|
||||
'--fr': 'fr',
|
||||
'--es': 'es',
|
||||
'--de': 'de',
|
||||
'--zh': 'zh',
|
||||
'--ja': 'ja'
|
||||
};
|
||||
|
||||
let detectedLang = '';
|
||||
for (const [qualifier, lang] of Object.entries(languageQualifiers)) {
|
||||
if (userInput.includes(qualifier)) {
|
||||
detectedLang = lang;
|
||||
languageStore.set(lang);
|
||||
userInput = userInput.replace(new RegExp(`${qualifier}\\s*`), '');
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
console.log('2. Language state:', {
|
||||
previousLanguage: currentLanguage,
|
||||
currentLanguage: get(languageStore),
|
||||
detectedOverride: detectedLang,
|
||||
inputAfterLangRemoval: userInput
|
||||
});
|
||||
|
||||
isYouTubeURL = detectYouTubeURL(userInput);
|
||||
console.log('3. URL detection:', {
|
||||
isYouTube: isYouTubeURL,
|
||||
pattern: $selectedPatternName,
|
||||
systemPromptLength: $systemPrompt?.length
|
||||
});
|
||||
}
|
||||
|
||||
async function handleSubmit() {
|
||||
if (!userInput.trim()) return;
|
||||
async function handleFileUpload(e: Event) {
|
||||
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 {
|
||||
// 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 {
|
||||
let finalContent = "";
|
||||
if (fileContents.length > 0) {
|
||||
finalContent += '\n\nFile Contents:\n' + fileContents.map((content, index) =>
|
||||
`[${uploadedFiles[index]}]:\n${content}`
|
||||
).join('\n\n');
|
||||
// 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');
|
||||
}
|
||||
|
||||
const trimmedInput = userInput.trim() + '\n' + (finalContent || '');
|
||||
const trimmedSystemPrompt = $systemPrompt.trim();
|
||||
let messageHistory = JSON.stringify($messageStore);
|
||||
|
||||
$systemPrompt = "";
|
||||
userInput = "";
|
||||
uploadedFiles = [];
|
||||
fileContents = [];
|
||||
|
||||
// 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;
|
||||
|
||||
// Place the messageHistory in the sendMessage(``) function to send the message history
|
||||
// This is a WIP and temporarily disabled.
|
||||
await sendMessage(`${trimmedSystemPrompt}\n${trimmedInput}\n${finalContent}`);
|
||||
} catch (error) {
|
||||
console.error('Chat submission error:', 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');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!$obsidianSettings.noteName) {
|
||||
toastStore.trigger({
|
||||
message: 'Failed to send message. Please try again.',
|
||||
message: 'Please enter a note name in Obsidian settings',
|
||||
background: 'variant-filled-error'
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (!$selectedPatternName) {
|
||||
toastStore.trigger({
|
||||
message: 'No pattern selected',
|
||||
background: 'variant-filled-error'
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (!content) {
|
||||
toastStore.trigger({
|
||||
message: 'No content to save',
|
||||
background: 'variant-filled-error'
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch('/obsidian', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
pattern: $selectedPatternName,
|
||||
noteName: $obsidianSettings.noteName,
|
||||
content
|
||||
})
|
||||
});
|
||||
|
||||
const responseData = await response.json();
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(responseData.error || 'Failed to save to Obsidian');
|
||||
}
|
||||
// Add this after successful save
|
||||
updateObsidianSettings({
|
||||
saveToObsidian: false, // Reset the save flag
|
||||
noteName: '' // Clear the note name
|
||||
});
|
||||
toastStore.trigger({
|
||||
message: responseData.message || `Saved to Obsidian: ${responseData.fileName}`,
|
||||
background: 'variant-filled-success'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Failed to save to Obsidian:', error);
|
||||
toastStore.trigger({
|
||||
message: error instanceof Error ? error.message : 'Failed to save to Obsidian',
|
||||
background: 'variant-filled-error'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Handle keyboard shortcuts
|
||||
async function processYouTubeURL(input: string) {
|
||||
console.log('\n=== YouTube Flow Start ===');
|
||||
const originalLanguage = get(languageStore);
|
||||
|
||||
try {
|
||||
// Add processing message first
|
||||
messageStore.update(messages => [...messages, {
|
||||
role: 'system',
|
||||
content: 'Processing YouTube video...',
|
||||
format: 'loading'
|
||||
}]);
|
||||
|
||||
// Get transcript but don't display it
|
||||
const { transcript } = await getTranscript(input);
|
||||
|
||||
// Log system prompt BEFORE createChatRequest
|
||||
console.log('System prompt BEFORE createChatRequest in YouTube flow:', $systemPrompt);
|
||||
|
||||
// Log system prompt BEFORE streamChat
|
||||
console.log(`System prompt BEFORE streamChat in YouTube flow: ${$systemPrompt}`);
|
||||
|
||||
const stream = await chatService.streamChat(transcript, $systemPrompt);
|
||||
await chatService.processStream(
|
||||
stream,
|
||||
(content, response) => {
|
||||
messageStore.update(messages => {
|
||||
const newMessages = [...messages];
|
||||
// Replace the processing message with actual content
|
||||
const lastMessage = newMessages[newMessages.length - 1];
|
||||
if (lastMessage?.format === 'loading') {
|
||||
newMessages.pop();
|
||||
}
|
||||
newMessages.push({
|
||||
role: 'assistant',
|
||||
content,
|
||||
format: response?.format
|
||||
});
|
||||
return newMessages;
|
||||
});
|
||||
},
|
||||
(error) => {
|
||||
messageStore.update(messages =>
|
||||
messages.filter(m => m.format !== 'loading')
|
||||
);
|
||||
throw error;
|
||||
}
|
||||
);
|
||||
|
||||
// Handle Obsidian saving if needed
|
||||
if ($obsidianSettings.saveToObsidian) {
|
||||
let lastContent = '';
|
||||
messageStore.subscribe(messages => {
|
||||
const lastMessage = messages[messages.length - 1];
|
||||
if (lastMessage?.role === 'assistant') {
|
||||
lastContent = lastMessage.content;
|
||||
}
|
||||
})();
|
||||
if (lastContent) await saveToObsidian(lastContent);
|
||||
}
|
||||
|
||||
userInput = "";
|
||||
uploadedFiles = [];
|
||||
fileContents = [];
|
||||
} catch (error) {
|
||||
console.error('Error processing YouTube URL:', error);
|
||||
messageStore.update(messages =>
|
||||
messages.filter(m => m.format !== 'loading')
|
||||
);
|
||||
throw 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) {
|
||||
event.preventDefault();
|
||||
@@ -94,60 +440,75 @@
|
||||
});
|
||||
</script>
|
||||
|
||||
<div class="flex flex-col gap-2 h-full">
|
||||
<div class="flex-1 relative shadow-lg">
|
||||
<Textarea
|
||||
bind:value={$systemPrompt}
|
||||
on:input={(e) => $systemPrompt || ''}
|
||||
placeholder="Enter system instructions..."
|
||||
class="w-full h-full resize-none bg-primary-800/30 rounded-lg border-none"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="flex-1 relative shadow-lg">
|
||||
<Textarea
|
||||
bind:value={userInput}
|
||||
on:input={(e) => userInput}
|
||||
on:keydown={handleKeydown}
|
||||
placeholder="Enter your message..."
|
||||
class="w-full h-full resize-none bg-primary-800/30 rounded-lg border-none"
|
||||
/>
|
||||
<div class="absolute bottom-5 right-2 gap-2 flex justify-end end-7">
|
||||
|
||||
<FileButton
|
||||
name="file-upload"
|
||||
button="btn variant-default"
|
||||
bind:files
|
||||
on:change={handleFileUpload}
|
||||
disabled={isProcessingFiles || uploadedFiles.length >= 5}
|
||||
>
|
||||
{#if uploadedFiles.length > 0}
|
||||
<FileCheck class="w-4 h-4" />
|
||||
{:else}
|
||||
<Paperclip class="w-4 h-4" />
|
||||
{/if}
|
||||
</FileButton>
|
||||
{#if uploadedFiles.length > 0}
|
||||
<span class="text-sm text-gray-500 space-x-2">
|
||||
<div class="h-full flex flex-col p-2">
|
||||
<div class="relative flex-1 min-h-0 bg-primary-800/30 rounded-lg">
|
||||
<Textarea
|
||||
bind:value={userInput}
|
||||
on:input={handleInput}
|
||||
on:keydown={handleKeydown}
|
||||
placeholder="Enter your message (YouTube URLs will be automatically processed)..."
|
||||
class="w-full h-full resize-none bg-transparent border-none text-sm focus:ring-0 transition-colors p-3 pb-[48px]"
|
||||
/>
|
||||
<div class="absolute bottom-3 right-3 flex items-center gap-2">
|
||||
<div class="flex items-center gap-2">
|
||||
{#if isFileIndicatorVisible}
|
||||
<span class="text-xs text-white/70">
|
||||
{uploadedFiles.length} file{uploadedFiles.length > 1 ? 's' : ''} attached
|
||||
</span>
|
||||
{/if}
|
||||
<br>
|
||||
{#key fileButtonKey}
|
||||
<FileButton
|
||||
name="file-upload"
|
||||
button="btn-icon variant-ghost"
|
||||
bind:files
|
||||
on:change={handleFileUpload}
|
||||
disabled={isProcessingFiles || uploadedFiles.length >= 5}
|
||||
class="h-10 w-10 bg-primary-800/30 hover:bg-primary-800/50 rounded-full transition-colors"
|
||||
>
|
||||
<Paperclip class="w-5 h-5" />
|
||||
|
||||
</FileButton>
|
||||
{/key}
|
||||
<Button
|
||||
type="button"
|
||||
variant="default"
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
name="send"
|
||||
on:click={handleSubmit}
|
||||
disabled={isProcessingFiles || !userInput.trim()}
|
||||
class="h-10 w-10 bg-primary-800/30 hover:bg-primary-800/50 rounded-full transition-colors disabled:opacity-30"
|
||||
>
|
||||
<Send class="w-4 h-4" />
|
||||
<Send class="w-5 h-5" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.flex-col {
|
||||
min-height: 0;
|
||||
}
|
||||
:global(textarea) {
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: rgba(255, 255, 255, 0.2) transparent;
|
||||
}
|
||||
|
||||
:global(textarea::-webkit-scrollbar) {
|
||||
width: 6px;
|
||||
}
|
||||
|
||||
:global(textarea::-webkit-scrollbar-track) {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
:global(textarea::-webkit-scrollbar-thumb) {
|
||||
background-color: rgba(255, 255, 255, 0.2);
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
:global(textarea::-webkit-scrollbar-thumb:hover) {
|
||||
background-color: rgba(255, 255, 255, 0.3);
|
||||
}
|
||||
|
||||
:global(textarea::selection) {
|
||||
background-color: rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,44 +1,110 @@
|
||||
<script lang="ts">
|
||||
import { chatState, errorStore, streamingStore } from '$lib/store/chat-store';
|
||||
import { afterUpdate } from 'svelte';
|
||||
import { afterUpdate, onMount } from 'svelte';
|
||||
import { toastStore } from '$lib/store/toast-store';
|
||||
import { marked } from 'marked';
|
||||
import SessionManager from './SessionManager.svelte';
|
||||
import { fade, slide } from 'svelte/transition';
|
||||
import { ArrowDown } from 'lucide-svelte';
|
||||
import Modal from '$lib/components/ui/modal/Modal.svelte';
|
||||
import PatternList from '$lib/components/patterns/PatternList.svelte';
|
||||
import type { Message } from '$lib/interfaces/chat-interface';
|
||||
import { get } from 'svelte/store';
|
||||
import { selectedPatternName } from '$lib/store/pattern-store';
|
||||
|
||||
let messagesContainer: HTMLDivElement;
|
||||
|
||||
afterUpdate(() => {
|
||||
let showPatternModal = false;
|
||||
|
||||
let messagesContainer: HTMLDivElement | null = null;
|
||||
let showScrollButton = false;
|
||||
let isUserMessage = false;
|
||||
|
||||
function scrollToBottom() {
|
||||
if (messagesContainer) {
|
||||
messagesContainer.scrollTop = messagesContainer.scrollHeight;
|
||||
messagesContainer.scrollTo({ top: messagesContainer.scrollHeight, behavior: 'smooth' });
|
||||
}
|
||||
}
|
||||
|
||||
function handleScroll() {
|
||||
if (!messagesContainer) return;
|
||||
const { scrollTop, scrollHeight, clientHeight } = messagesContainer;
|
||||
showScrollButton = scrollHeight - scrollTop - clientHeight > 100;
|
||||
}
|
||||
|
||||
// Watch for changes in messages
|
||||
$: if ($chatState.messages.length > 0) {
|
||||
const lastMessage = $chatState.messages[$chatState.messages.length - 1];
|
||||
isUserMessage = lastMessage.role === 'user';
|
||||
if (isUserMessage) {
|
||||
// Only auto-scroll on user messages
|
||||
setTimeout(scrollToBottom, 100);
|
||||
}
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
if (messagesContainer) {
|
||||
messagesContainer.addEventListener('scroll', handleScroll);
|
||||
return () => {
|
||||
if (messagesContainer) {
|
||||
messagesContainer.removeEventListener('scroll', handleScroll);
|
||||
}
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
// Configure marked to be synchronous
|
||||
const renderer = new marked.Renderer();
|
||||
marked.setOptions({
|
||||
gfm: true,
|
||||
breaks: true,
|
||||
renderer,
|
||||
async: false
|
||||
});
|
||||
|
||||
function renderMarkdown(content: string, isAssistant: boolean) {
|
||||
content = content.replace(/\\n/g, '\n');
|
||||
if (!isAssistant) return content;
|
||||
try {
|
||||
return marked.parse(content);
|
||||
} catch (error) {
|
||||
console.error('Error rendering markdown:', error);
|
||||
return content;
|
||||
// New shouldRenderAsMarkdown function
|
||||
function shouldRenderAsMarkdown(message: Message): boolean {
|
||||
const pattern = get(selectedPatternName);
|
||||
if (pattern && message.role === 'assistant') {
|
||||
return message.format !== 'mermaid';
|
||||
}
|
||||
}
|
||||
return message.role === 'assistant' && message.format !== 'plain';
|
||||
}
|
||||
|
||||
// Keep the original renderContent function
|
||||
function renderContent(message: Message): string {
|
||||
const content = message.content.replace(/\\n/g, '\n');
|
||||
|
||||
if (shouldRenderAsMarkdown(message)) {
|
||||
try {
|
||||
return marked.parse(content, { async: false }) as string;
|
||||
} catch (error) {
|
||||
console.error('Error rendering markdown:', error);
|
||||
return content;
|
||||
}
|
||||
}
|
||||
return content;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
<div class="bg-primary-800/30 rounded-lg flex flex-col h-full shadow-lg">
|
||||
<div class="flex justify-between items-center mb-1 mt-1 flex-none">
|
||||
<div class="flex items-center gap-2 pl-4">
|
||||
<b class="text-sm text-muted-foreground font-bold">Chat History</b>
|
||||
<div class="flex justify-between items-center p-3 flex-none border-b border-white/5">
|
||||
<div>
|
||||
<span class="text-xs text-white/70 font-medium">Chat History</span>
|
||||
</div>
|
||||
<SessionManager />
|
||||
</div>
|
||||
|
||||
<Modal
|
||||
show={showPatternModal}
|
||||
on:close={() => showPatternModal = false}
|
||||
>
|
||||
<PatternList on:close={() => showPatternModal = false} />
|
||||
</Modal>
|
||||
|
||||
{#if $errorStore}
|
||||
<div class="error-message" transition:slide>
|
||||
<div class="bg-red-100 border-l-4 border-red-500 text-red-700 p-4 mb-4" role="alert">
|
||||
@@ -47,16 +113,28 @@
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<div class="messages-container p-4 flex-1 overflow-y-auto max-h-dvh" bind:this={messagesContainer}>
|
||||
<div class="messages-content flex flex-col gap-4">
|
||||
<div
|
||||
class="messages-container p-3 flex-1 overflow-y-auto max-h-dvh relative"
|
||||
bind:this={messagesContainer}
|
||||
>
|
||||
<div class="messages-content flex flex-col gap-3">
|
||||
{#each $chatState.messages as message}
|
||||
<div
|
||||
class="message-item {message.role === 'assistant' ? 'pl-4 bg-primary/5 rounded-lg p-2' : 'pr-4 ml-auto'}"
|
||||
class="message-item {message.role === 'system' ? 'w-full bg-blue-900/20' : message.role === 'assistant' ? 'bg-primary/5 rounded-lg p-3' : 'ml-auto'}"
|
||||
transition:fade
|
||||
class:loading-message={message.format === 'loading'}
|
||||
>
|
||||
<div class="message-header flex items-center gap-2 mb-1 {message.role === 'assistant' ? '' : 'justify-end'}">
|
||||
<span class="text-xs text-muted-foreground rounded-lg p-1 variant-glass-secondary font-bold uppercase">
|
||||
{message.role === 'assistant' ? 'AI' : 'You'}
|
||||
|
||||
|
||||
<div class="message-header flex items-center gap-2 mb-1 {message.role === 'assistant' || message.role === 'system' ? '' : 'justify-end'}">
|
||||
<span class="text-xs text-muted-foreground rounded-lg p-1 variant-glass-secondary font-bold uppercase">
|
||||
{#if message.role === 'system'}
|
||||
SYSTEM
|
||||
{:else if message.role === 'assistant'}
|
||||
AI
|
||||
{:else}
|
||||
You
|
||||
{/if}
|
||||
</span>
|
||||
{#if message.role === 'assistant' && $streamingStore}
|
||||
<span class="loading-indicator flex gap-1">
|
||||
@@ -67,9 +145,13 @@
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
{#if message.role === 'assistant'}
|
||||
<div class="prose prose-slate dark:prose-invert text-inherit prose-headings:text-inherit prose-pre:bg-primary/10 prose-pre:text-inherit text-sm max-w-none">
|
||||
{@html renderMarkdown(message.content, true)}
|
||||
{#if message.role === 'system'}
|
||||
<div class="text-blue-300 text-sm font-semibold">
|
||||
{message.content}
|
||||
</div>
|
||||
{:else if message.role === 'assistant'}
|
||||
<div class="{shouldRenderAsMarkdown(message) ? 'prose prose-slate dark:prose-invert text-inherit prose-headings:text-inherit prose-pre:bg-primary/10 prose-pre:text-inherit' : 'whitespace-pre-wrap'} text-sm max-w-none">
|
||||
{@html renderContent(message)}
|
||||
</div>
|
||||
{:else}
|
||||
<div class="whitespace-pre-wrap text-sm">
|
||||
@@ -79,15 +161,31 @@
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
{#if showScrollButton}
|
||||
<button
|
||||
class="absolute bottom-4 right-4 bg-primary/20 hover:bg-primary/30 rounded-full p-2 transition-opacity"
|
||||
on:click={scrollToBottom}
|
||||
transition:fade
|
||||
>
|
||||
<ArrowDown class="w-4 h-4" />
|
||||
</button>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
/*.chat-messages-wrapper {*/
|
||||
/* display: flex;*/
|
||||
/* flex-direction: column;*/
|
||||
/* min-height: 0;*/
|
||||
/*}*/
|
||||
|
||||
|
||||
:global(.loading-message) {
|
||||
animation: flash 1.5s ease-in-out infinite;
|
||||
}
|
||||
|
||||
@keyframes flash {
|
||||
0% { opacity: 1; }
|
||||
50% { opacity: 0.5; }
|
||||
100% { opacity: 1; }
|
||||
}
|
||||
|
||||
|
||||
.messages-container {
|
||||
flex: 1;
|
||||
@@ -99,7 +197,7 @@
|
||||
.messages-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2rem;
|
||||
gap: 0.75rem;
|
||||
}
|
||||
|
||||
.message-header {
|
||||
@@ -130,8 +228,8 @@
|
||||
}
|
||||
|
||||
@keyframes blink {
|
||||
0%, 100% { opacity: 0; }
|
||||
50% { opacity: 1; }
|
||||
0%, 100% { opacity: 0; }
|
||||
50% { opacity: 1; }
|
||||
}
|
||||
|
||||
:global(.prose pre) {
|
||||
|
||||
35
web/src/lib/components/chat/DropdownGroup.svelte
Normal file
35
web/src/lib/components/chat/DropdownGroup.svelte
Normal file
@@ -0,0 +1,35 @@
|
||||
<script lang="ts">
|
||||
import Patterns from "./Patterns.svelte";
|
||||
import Models from "./Models.svelte";
|
||||
import { Select } from "$lib/components/ui/select";
|
||||
import { languageStore } from '$lib/store/language-store';
|
||||
|
||||
const languages = [
|
||||
{ code: '', name: 'Default Language' },
|
||||
{ code: 'en', name: 'English' },
|
||||
{ code: 'fr', name: 'French' },
|
||||
{ code: 'es', name: 'Spanish' },
|
||||
{ code: 'de', name: 'German' },
|
||||
{ code: 'zh', name: 'Chinese' },
|
||||
{ code: 'ja', name: 'Japanese' }
|
||||
];
|
||||
</script>
|
||||
|
||||
<div class="flex flex-col gap-3">
|
||||
<div class="w-[50%]">
|
||||
<Patterns />
|
||||
</div>
|
||||
<div class="w-[50%]">
|
||||
<Models />
|
||||
</div>
|
||||
<div class="w-[50%]">
|
||||
<Select
|
||||
bind:value={$languageStore}
|
||||
class="bg-primary-800/30 border-none hover:bg-primary-800/40 transition-colors"
|
||||
>
|
||||
{#each languages as lang}
|
||||
<option value={lang.code}>{lang.name}</option>
|
||||
{/each}
|
||||
</Select>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,92 +1,94 @@
|
||||
<script lang="ts">
|
||||
export {};
|
||||
import { Label } from "$lib/components/ui/label";
|
||||
import { Slider } from "$lib/components/ui/slider";
|
||||
import { modelConfig } from "$lib/store/model-store";
|
||||
import Transcripts from "./Transcripts.svelte";
|
||||
import NoteDrawer from '$lib/components/ui/noteDrawer/NoteDrawer.svelte';
|
||||
import { getDrawerStore } from '@skeletonlabs/skeleton';
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import { page } from '$app/stores';
|
||||
import { beforeNavigate } from '$app/navigation';
|
||||
import { slide } from 'svelte/transition';
|
||||
import { cubicOut } from 'svelte/easing';
|
||||
import { browser } from '$app/environment';
|
||||
import { clickOutside } from '$lib/actions/clickOutside';
|
||||
import Tooltip from "$lib/components/ui/tooltip/Tooltip.svelte";
|
||||
|
||||
const drawerStore = getDrawerStore();
|
||||
function openDrawer() {
|
||||
drawerStore.open({});
|
||||
// Load expanded state from localStorage
|
||||
const STORAGE_KEY = 'modelConfigExpanded';
|
||||
let isExpanded = false;
|
||||
if (browser) {
|
||||
const stored = localStorage.getItem(STORAGE_KEY);
|
||||
isExpanded = stored ? JSON.parse(stored) : false;
|
||||
}
|
||||
|
||||
beforeNavigate(() => {
|
||||
drawerStore.close();
|
||||
});
|
||||
// Save expanded state
|
||||
function toggleExpanded() {
|
||||
isExpanded = !isExpanded;
|
||||
saveState();
|
||||
}
|
||||
|
||||
$: isVisible = $page.url.pathname.startsWith('/chat');
|
||||
function saveState() {
|
||||
if (browser) {
|
||||
localStorage.setItem(STORAGE_KEY, JSON.stringify(isExpanded));
|
||||
}
|
||||
}
|
||||
|
||||
function handleClickOutside() {
|
||||
if (isExpanded) {
|
||||
isExpanded = false;
|
||||
saveState();
|
||||
}
|
||||
}
|
||||
|
||||
const settings = [
|
||||
{ key: 'maxLength', label: 'Maximum Length', min: 1, max: 4000, step: 1, tooltip: "Maximum number of tokens in the response" },
|
||||
{ key: 'temperature', label: 'Temperature', min: 0, max: 2, step: 0.1, tooltip: "Higher values make output more random, lower values more focused" },
|
||||
{ key: 'top_p', label: 'Top P', min: 0, max: 1, step: 0.01, tooltip: "Controls diversity via nucleus sampling" },
|
||||
{ key: 'frequency', label: 'Frequency Penalty', min: 0, max: 1, step: 0.01, tooltip: "Reduces repetition of the same words" },
|
||||
{ key: 'presence', label: 'Presence Penalty', min: 0, max: 1, step: 0.01, tooltip: "Reduces repetition of similar topics" }
|
||||
] as const;
|
||||
</script>
|
||||
|
||||
<div class="p-2">
|
||||
<div class="space-y-1">
|
||||
<Label>Maximum Length ({$modelConfig.maxLength})</Label>
|
||||
<Slider
|
||||
bind:value={$modelConfig.maxLength}
|
||||
min={1}
|
||||
max={4000}
|
||||
step={1}
|
||||
/>
|
||||
</div>
|
||||
<div class="w-full" use:clickOutside={handleClickOutside}>
|
||||
<button
|
||||
class="w-full flex items-center py-2 px-2 hover:text-white/90 transition-colors rounded-t"
|
||||
on:click={toggleExpanded}
|
||||
>
|
||||
<span class="text-sm font-semibold">Model Configuration</span>
|
||||
<span class="transform transition-transform duration-200 opacity-70 ml-1 text-xs" class:rotate-180={isExpanded}>
|
||||
▼
|
||||
</span>
|
||||
</button>
|
||||
|
||||
<div class="space-y-1">
|
||||
<Label>Temperature ({$modelConfig.temperature.toFixed(1)})</Label>
|
||||
<Slider
|
||||
bind:value={$modelConfig.temperature}
|
||||
min={0}
|
||||
max={2}
|
||||
step={0.1}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="space-y-1">
|
||||
<Label>Top P ({$modelConfig.top_p.toFixed(2)})</Label>
|
||||
<Slider
|
||||
bind:value={$modelConfig.top_p}
|
||||
min={0}
|
||||
max={1}
|
||||
step={0.01}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="space-y-1">
|
||||
<Label>Frequency Penalty ({$modelConfig.frequency.toFixed(2)})</Label>
|
||||
<Slider
|
||||
bind:value={$modelConfig.frequency}
|
||||
min={0}
|
||||
max={1}
|
||||
step={0.01}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="space-y-1">
|
||||
<Label>Presence Penalty ({$modelConfig.presence.toFixed(2)})</Label>
|
||||
<Slider
|
||||
bind:value={$modelConfig.presence}
|
||||
min={0}
|
||||
max={1}
|
||||
step={0.01}
|
||||
/>
|
||||
</div>
|
||||
<br>
|
||||
<div class="space-y-1">
|
||||
<Transcripts />
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col gap-2">
|
||||
{#if isVisible}
|
||||
<div class="flex text-inherit justify-start mt-2">
|
||||
<Button
|
||||
variant="primary"
|
||||
class="btn border variant-filled-primary text-align-center"
|
||||
on:click={openDrawer}
|
||||
>Open Drawer
|
||||
</Button>
|
||||
{#if isExpanded}
|
||||
<div
|
||||
class="pt-2 px-2 space-y-3"
|
||||
transition:slide={{
|
||||
duration: 200,
|
||||
easing: cubicOut,
|
||||
}}
|
||||
>
|
||||
{#each settings as setting}
|
||||
<div class="group">
|
||||
<div class="flex justify-between items-center mb-0.5">
|
||||
<Tooltip text={setting.tooltip} position="right">
|
||||
<Label class="text-[10px] text-white/70 cursor-help group-hover:text-white/90 transition-colors">{setting.label}</Label>
|
||||
</Tooltip>
|
||||
<span class="text-[10px] font-mono text-white/50 group-hover:text-white/70 transition-colors">
|
||||
{typeof $modelConfig[setting.key] === 'number' ? $modelConfig[setting.key].toFixed(2) : $modelConfig[setting.key]}
|
||||
</span>
|
||||
</div>
|
||||
<Slider
|
||||
bind:value={$modelConfig[setting.key]}
|
||||
min={setting.min}
|
||||
max={setting.max}
|
||||
step={setting.step}
|
||||
class="h-3 group-hover:opacity-90 transition-opacity"
|
||||
/>
|
||||
</div>
|
||||
<NoteDrawer />
|
||||
{/if}
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<style>
|
||||
:global(.slider) {
|
||||
height: 0.75rem !important;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -9,8 +9,9 @@
|
||||
</script>
|
||||
|
||||
<div class="min-w-0">
|
||||
<Select
|
||||
<Select
|
||||
bind:value={$modelConfig.model}
|
||||
class="bg-primary-800/30 border-none hover:bg-primary-800/40 transition-colors"
|
||||
>
|
||||
<option value="">Default Model</option>
|
||||
{#each $availableModels as model (model.name)}
|
||||
|
||||
@@ -1,13 +1,38 @@
|
||||
<script lang="ts">
|
||||
import { onMount } from 'svelte';
|
||||
import { Select } from "$lib/components/ui/select";
|
||||
import { patterns, patternAPI } from "$lib/store/pattern-store";
|
||||
import { patterns, patternAPI, systemPrompt, selectedPatternName } from "$lib/store/pattern-store";
|
||||
import { get } from 'svelte/store';
|
||||
|
||||
let selectedPreset = "";
|
||||
let selectedPreset = $selectedPatternName || "";
|
||||
|
||||
// Subscribe to selectedPatternName changes
|
||||
selectedPatternName.subscribe(value => {
|
||||
if (value && value !== selectedPreset) {
|
||||
console.log('Pattern selected from modal:', value);
|
||||
selectedPreset = value;
|
||||
}
|
||||
});
|
||||
|
||||
// Watch selectedPreset changes
|
||||
$: if (selectedPreset) {
|
||||
console.log('Pattern selected:', selectedPreset);
|
||||
patternAPI.selectPattern(selectedPreset);
|
||||
console.log('Pattern selected from dropdown:', selectedPreset);
|
||||
try {
|
||||
patternAPI.selectPattern(selectedPreset);
|
||||
// Verify the selection
|
||||
const currentSystemPrompt = get(systemPrompt);
|
||||
const currentPattern = get(selectedPatternName);
|
||||
console.log('After dropdown selection - Pattern:', currentPattern);
|
||||
console.log('After dropdown selection - System Prompt length:', currentSystemPrompt?.length);
|
||||
|
||||
if (!currentPattern || !currentSystemPrompt) {
|
||||
console.error('Pattern selection verification failed:');
|
||||
console.error('- Selected Pattern:', currentPattern);
|
||||
console.error('- System Prompt:', currentSystemPrompt);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error in pattern selection:', error);
|
||||
}
|
||||
}
|
||||
|
||||
onMount(async () => {
|
||||
@@ -16,12 +41,13 @@
|
||||
</script>
|
||||
|
||||
<div class="min-w-0">
|
||||
<Select
|
||||
<Select
|
||||
bind:value={selectedPreset}
|
||||
>
|
||||
class="bg-primary-800/30 border-none hover:bg-primary-800/40 transition-colors"
|
||||
>
|
||||
<option value="">Load a pattern...</option>
|
||||
{#each $patterns as pattern}
|
||||
<option value={pattern.Name}>{pattern.Description}</option>
|
||||
<option value={pattern.Name}>{pattern.Name}</option>
|
||||
{/each}
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<script lang='ts'>
|
||||
import { getToastStore } from '@skeletonlabs/skeleton';
|
||||
import { Button } from "$lib/components/ui/button";
|
||||
import Input from '$lib/components/ui/input/input.svelte';
|
||||
import Input from '$lib/components/ui/input/Input.svelte';
|
||||
import { Toast } from '@skeletonlabs/skeleton';
|
||||
|
||||
let url = '';
|
||||
@@ -75,7 +75,6 @@
|
||||
|
||||
<div class="flex gap-2">
|
||||
<Input
|
||||
type="text"
|
||||
bind:value={url}
|
||||
placeholder="YouTube URL"
|
||||
class="flex-1 rounded-full border bg-background px-4"
|
||||
|
||||
@@ -1,13 +1,20 @@
|
||||
<script lang="ts">
|
||||
import { page } from '$app/stores';
|
||||
import { Sun, Moon, Menu, X, Github } from 'lucide-svelte';
|
||||
import { Sun, Moon, Menu, X, Github, FileText } from 'lucide-svelte';
|
||||
import { Avatar } from '@skeletonlabs/skeleton';
|
||||
import { fade } from 'svelte/transition';
|
||||
import { theme, cycleTheme, initTheme } from '$lib/store/theme-store';
|
||||
// import { goto } from '$app/navigation';
|
||||
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() {
|
||||
window.open('https://github.com/danielmiessler/fabric', '_blank');
|
||||
@@ -65,7 +72,34 @@
|
||||
</ul>
|
||||
</nav>
|
||||
|
||||
<div class="flex items-center gap-2">
|
||||
<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}
|
||||
class="inline-flex h-9 w-9 items-center justify-center rounded-full border bg-background text-sm font-medium transition-colors hover:bg-accent hover:text-accent-foreground"
|
||||
@@ -88,6 +122,15 @@
|
||||
<span class="sr-only">Toggle theme</span>
|
||||
</button>
|
||||
|
||||
<button name="help"
|
||||
on:click={() => showHelpModal = true}
|
||||
class="inline-flex h-9 w-9 items-center justify-center rounded-full border bg-background text-sm font-medium transition-colors hover:bg-accent hover:text-accent-foreground ml-3"
|
||||
aria-label="Help"
|
||||
>
|
||||
<span class="text-xl font-bold text-white/90 hover:text-white">?</span>
|
||||
<span class="sr-only">Help</span>
|
||||
</button>
|
||||
|
||||
<!-- Mobile Menu Button -->
|
||||
<button name="toggle-menu"
|
||||
class="inline-flex h-9 w-9 items-center justify-center rounded-lg border bg-background text-sm font-medium transition-colors hover:bg-accent hover:text-accent-foreground md:hidden"
|
||||
@@ -121,3 +164,38 @@
|
||||
</div>
|
||||
{/if}
|
||||
</header>
|
||||
|
||||
<Modal
|
||||
show={showPatternModal}
|
||||
on:close={() => showPatternModal = false}
|
||||
>
|
||||
<PatternList
|
||||
on:close={() => showPatternModal = false}
|
||||
on:select={(e) => {
|
||||
selectedPatternName.set(e.detail);
|
||||
showPatternModal = false;
|
||||
}}
|
||||
/>
|
||||
</Modal>
|
||||
|
||||
<Modal
|
||||
show={showHelpModal}
|
||||
on:close={() => showHelpModal = false}
|
||||
>
|
||||
<HelpModal
|
||||
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>
|
||||
|
||||
251
web/src/lib/components/patterns/PatternList.svelte
Normal file
251
web/src/lib/components/patterns/PatternList.svelte
Normal file
@@ -0,0 +1,251 @@
|
||||
|
||||
<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 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 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={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-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>
|
||||
|
||||
<!-- 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 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 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}
|
||||
hideToggleButton={false}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
/* Custom scrollbar styling */
|
||||
.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);
|
||||
}
|
||||
|
||||
/* 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>
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user