mirror of
https://github.com/danielmiessler/Fabric.git
synced 2026-01-09 22:38:10 -05:00
Compare commits
141 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b60bad7799 | ||
|
|
234d1303ad | ||
|
|
cd74a96be2 | ||
|
|
ceaa90a7c7 | ||
|
|
15a2eeadc9 | ||
|
|
8d02f5b21d | ||
|
|
0f8f0b6b39 | ||
|
|
fd58b6d410 | ||
|
|
2579d37c16 | ||
|
|
4f28d85e96 | ||
|
|
f529b8bb80 | ||
|
|
71437605e1 | ||
|
|
cf5753a186 | ||
|
|
433c83fe2c | ||
|
|
01770cc6e3 | ||
|
|
55fda5e025 | ||
|
|
daad5f986e | ||
|
|
3785d0a5fa | ||
|
|
8a326e9cfb | ||
|
|
5f5822f1c6 | ||
|
|
111482e46e | ||
|
|
9b56e0e996 | ||
|
|
9b830f9801 | ||
|
|
dda73d3333 | ||
|
|
40c26d9c9e | ||
|
|
cdd86b0ed9 | ||
|
|
c2e84d6db9 | ||
|
|
4208a02191 | ||
|
|
943b26eeef | ||
|
|
d6ceae9efd | ||
|
|
f57dc6d681 | ||
|
|
4e4bfc9d5d | ||
|
|
ea137c1525 | ||
|
|
f0be1d4735 | ||
|
|
e3975b9364 | ||
|
|
cd48802ea0 | ||
|
|
dceccd8e72 | ||
|
|
0813ad9c39 | ||
|
|
e391132167 | ||
|
|
c7f86d3a0c | ||
|
|
f0d92f9424 | ||
|
|
4a9bdb1479 | ||
|
|
7eed80710e | ||
|
|
fbd62be47d | ||
|
|
85cc7b8a9d | ||
|
|
1fe8afd329 | ||
|
|
e89ccf5e97 | ||
|
|
0eee89140c | ||
|
|
5571e6fafd | ||
|
|
9a4e920618 | ||
|
|
6e479999b1 | ||
|
|
f65f2501b4 | ||
|
|
4b12bd2a61 | ||
|
|
d83a3beeeb | ||
|
|
7428c8017f | ||
|
|
008ed76d37 | ||
|
|
ce9d4ad831 | ||
|
|
657bcab48c | ||
|
|
cd11dcc7a9 | ||
|
|
22040a42f2 | ||
|
|
705ccd750b | ||
|
|
db7c2b70cb | ||
|
|
9dc9bfa1d5 | ||
|
|
6b93658191 | ||
|
|
ea7a425a26 | ||
|
|
9582978adb | ||
|
|
453d8e75e4 | ||
|
|
901a010efd | ||
|
|
b5c2d069f2 | ||
|
|
f744e25b39 | ||
|
|
096f40df68 | ||
|
|
a227e61952 | ||
|
|
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 | ||
|
|
4725a94f00 | ||
|
|
15ac5351cf | ||
|
|
f69cda8fab | ||
|
|
a0e1f7204d |
3
.gitignore
vendored
3
.gitignore
vendored
@@ -347,3 +347,6 @@ 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
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
1700
Pattern_Descriptions/pattern_descriptions.json
Normal file
1700
Pattern_Descriptions/pattern_descriptions.json
Normal file
File diff suppressed because it is too large
Load Diff
@@ -122,7 +122,7 @@
|
||||
},
|
||||
{
|
||||
"patternName": "analyze_threat_report_trends",
|
||||
"pattern_extract": "# IDENTITY and PURPOSE\n\nYou are a super-intelligent cybersecurity expert. You specialize in extracting the surprising, insightful, and interesting information from cybersecurity threat reports.\n\nTake a step back and think step-by-step about how to achieve the best possible results by following the steps below.\n\n# STEPS\n\n- Read the entire threat report from an expert perspective, thinking deeply about what's new, interesting, and surprising in the report.\n\n- Extract up to 50 of the most surprising, insightful, and/or interesting trends from the input in a section called TRENDS:. If there are less than 50 then collect all of them. Make sure you extract at least 20.\n\n# OUTPUT INSTRUCTIONS\n\n- Only output Markdown.\n- Do not output the markdown code syntax, only the content.\n- Do not use bold or italics formatting in the markdown output.\n- Extract at least 20 TRENDS from the content.\n- Do not give warnings or notes; only output the requested sections.\n- You use bulleted lists for output, not numbered lists.\n- Do not repeat ideas, quotes, facts, or resources.\n- Do not start items with the same opening words.\n- Ensure you follow ALL these instructions when creating your output.\n\n# INPUT"
|
||||
"pattern_extract": "# IDENTITY and PURPOSE\n\nYou are a super-intelligent cybersecurity expert. You specialize in extracting the surprising, insightful, and interesting information from cybersecurity threat reports.\n\nTake a step back and think step-by-step about how to achieve the best possible results by following the steps below.\n\n# STEPS\n\n- Read the entire threat report from an expert perspective, thinking deeply about what's new, interesting, and surprising in the report.\n\n- Extract up to 50 of the most surprising, insightful, and/or interesting trends from the input in a section called TRENDS:. If there are less than 50 then collect all of them. Make sure you extract at least 20.\n\n# OUTPUT INSTRUCTIONS\n\n- Only output Markdown.\n- Do not output the markdown code syntax, only the content.\n- Do not use bold or italics formatting in the markdown output.\n- Extract at least 20 TRENDS from the content.\n- Do not give warnings or notes; only output the requested sections.\n- You use bulleted lists for output, not numbered lists.\n- Do not repeat trends.\n- Do not start items with the same opening words.\n- Ensure you follow ALL these instructions when creating your output.\n\n# INPUT"
|
||||
},
|
||||
{
|
||||
"patternName": "answer_interview_question",
|
||||
@@ -402,7 +402,7 @@
|
||||
},
|
||||
{
|
||||
"patternName": "extract_business_ideas",
|
||||
"pattern_extract": "# IDENTITY and PURPOSE\n\nYou are a business idea extraction assistant. You are extremely interested in business ideas that could revolutionize or just overhaul existing or new industries.\n\nTake a deep breath and think step by step about how to achieve the best result possible as defined in the steps below. You have a lot of freedom to make this work well.\n\n## OUTPUT SECTIONS\n\n1. You extract all the top business ideas from the content. It might be a few or it might be up to 40 in a section called EXTRACTED_IDEAS\n\n2. Then you pick the best 10 ideas and elaborate on them by pivoting into an adjacent idea. This will be ELABORATED_IDEAS. They should each be unique and have an interesting differentiator.\n\n## OUTPUT INSTRUCTIONS\n\n1. You only output Markdown.\n2. Do not give warnings or notes; only output the requested sections.\n3. You use numbered lists, not bullets.\n4. Do not repeat ideas, quotes, facts, or resources.\n5. Do not start items in the lists with the same opening words.\n\n# INPUT:\n\nINPUT:"
|
||||
"pattern_extract": "# IDENTITY and PURPOSE\n\nYou are a business idea extraction assistant. You are extremely interested in business ideas that could revolutionize or just overhaul existing or new industries.\n\nTake a deep breath and think step by step about how to achieve the best result possible as defined in the steps below. You have a lot of freedom to make this work well.\n\n## OUTPUT SECTIONS\n\n1. You extract all the top business ideas from the content. It might be a few or it might be up to 40 in a section called EXTRACTED_IDEAS\n\n2. Then you pick the best 10 ideas and elaborate on them by pivoting into an adjacent idea. This will be ELABORATED_IDEAS. They should each be unique and have an interesting differentiator.\n\n## OUTPUT INSTRUCTIONS\n\n1. You only output Markdown.\n2. Do not give warnings or notes; only output the requested sections.\n3. You use numbered lists, not bullets.\n4. Do not repeat ideas.\n5. Do not start items in the lists with the same opening words.\n\n# INPUT:\n\nINPUT:"
|
||||
},
|
||||
{
|
||||
"patternName": "extract_controversial_ideas",
|
||||
@@ -438,7 +438,7 @@
|
||||
},
|
||||
{
|
||||
"patternName": "extract_jokes",
|
||||
"pattern_extract": "# IDENTITY and PURPOSE\n\nYou extract jokes from text content. You are interested only in jokes.\n\nYou create bullet points that capture the joke and punchline.\n\n# OUTPUT INSTRUCTIONS\n\n- Only output Markdown.\n\n- Only extract jokes.\n\n- Each bullet should should have the joke followed by punchline on the next line.\n\n- Do not give warnings or notes; only output the requested sections.\n\n- You use bulleted lists for output, not numbered lists.\n\n- Do not repeat jokes, quotes, facts, or resources.\n\n- Ensure you follow ALL these instructions when creating your output.\n\n\n# INPUT\n"
|
||||
"pattern_extract": "# IDENTITY and PURPOSE\n\nYou extract jokes from text content. You are interested only in jokes.\n\nYou create bullet points that capture the joke and punchline.\n\n# OUTPUT INSTRUCTIONS\n\n- Only output Markdown.\n\n- Only extract jokes.\n\n- Each bullet should should have the joke followed by punchline on the next line.\n\n- Do not give warnings or notes; only output the requested sections.\n\n- You use bulleted lists for output, not numbered lists.\n\n- Do not repeat jokes.\n\n- Ensure you follow ALL these instructions when creating your output.\n\n\n# INPUT\n"
|
||||
},
|
||||
{
|
||||
"patternName": "extract_latest_video",
|
||||
@@ -446,7 +446,7 @@
|
||||
},
|
||||
{
|
||||
"patternName": "extract_main_idea",
|
||||
"pattern_extract": "# IDENTITY and PURPOSE\n\nYou extract the primary and/or most surprising, insightful, and interesting idea from any input.\n\nTake a step back and think step-by-step about how to achieve the best possible results by following the steps below.\n\n# STEPS\n\n- Fully digest the content provided.\n\n- Extract the most important idea from the content.\n\n- In a section called MAIN IDEA, write a 15-word sentence that captures the main idea.\n\n- In a section called MAIN RECOMMENDATION, write a 15-word sentence that captures what's recommended for people to do based on the idea.\n\n# OUTPUT INSTRUCTIONS\n\n- Only output Markdown.\n- Do not give warnings or notes; only output the requested sections.\n- Do not repeat ideas, quotes, facts, or resources.\n- Do not start items with the same opening words.\n- Ensure you follow ALL these instructions when creating your output.\n\n# INPUT"
|
||||
"pattern_extract": "# IDENTITY and PURPOSE\n\nYou extract the primary and/or most surprising, insightful, and interesting idea from any input.\n\nTake a step back and think step-by-step about how to achieve the best possible results by following the steps below.\n\n# STEPS\n\n- Fully digest the content provided.\n\n- Extract the most important idea from the content.\n\n- In a section called MAIN IDEA, write a 15-word sentence that captures the main idea.\n\n- In a section called MAIN RECOMMENDATION, write a 15-word sentence that captures what's recommended for people to do based on the idea.\n\n# OUTPUT INSTRUCTIONS\n\n- Only output Markdown.\n- Do not give warnings or notes; only output the requested sections.\n- Do not start items with the same opening words.\n- Ensure you follow ALL these instructions when creating your output.\n\n# INPUT"
|
||||
},
|
||||
{
|
||||
"patternName": "extract_most_redeeming_thing",
|
||||
@@ -474,7 +474,7 @@
|
||||
},
|
||||
{
|
||||
"patternName": "extract_product_features",
|
||||
"pattern_extract": "# IDENTITY and PURPOSE\n\nYou extract the list of product features from the input.\n\nTake a step back and think step-by-step about how to achieve the best possible results by following the steps below.\n\n# STEPS\n\n- Consume the whole input as a whole and think about the type of announcement or content it is.\n\n- Figure out which parts were talking about features of a product or service.\n\n- Output the list of features as a bulleted list of 16 words per bullet.\n\n# OUTPUT INSTRUCTIONS\n\n- Only output Markdown.\n\n- Do not give warnings or notes; only output the requested sections.\n\n- You use bulleted lists for output, not numbered lists.\n\n- Do not repeat ideas, quotes, facts, or resources.\n\n- Do not start items with the same opening words."
|
||||
"pattern_extract": "# IDENTITY and PURPOSE\n\nYou extract the list of product features from the input.\n\nTake a step back and think step-by-step about how to achieve the best possible results by following the steps below.\n\n# STEPS\n\n- Consume the whole input as a whole and think about the type of announcement or content it is.\n\n- Figure out which parts were talking about features of a product or service.\n\n- Output the list of features as a bulleted list of 16 words per bullet.\n\n# OUTPUT INSTRUCTIONS\n\n- Only output Markdown.\n\n- Do not give warnings or notes; only output the requested sections.\n\n- You use bulleted lists for output, not numbered lists.\n\n- Do not repeat features.\n\n- Do not start items with the same opening words."
|
||||
},
|
||||
{
|
||||
"patternName": "extract_questions",
|
||||
282
README.md
282
README.md
@@ -15,7 +15,7 @@
|
||||
</p>
|
||||
|
||||
[Updates](#updates) •
|
||||
[What and Why](#whatandwhy) •
|
||||
[What and Why](#what-and-why) •
|
||||
[Philosophy](#philosophy) •
|
||||
[Installation](#Installation) •
|
||||
[Usage](#Usage) •
|
||||
@@ -34,13 +34,21 @@
|
||||
- [`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)
|
||||
- [Using package managers](#using-package-managers)
|
||||
- [macOS (Homebrew)](#macos-homebrew)
|
||||
- [Arch Linux (AUR)](#arch-linux-aur)
|
||||
- [From Source](#from-source)
|
||||
- [Environment Variables](#environment-variables)
|
||||
- [Setup](#setup)
|
||||
@@ -52,12 +60,17 @@
|
||||
- [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)
|
||||
- [`code_helper`](#code_helper)
|
||||
- [pbpaste](#pbpaste)
|
||||
- [Web Interface](#Web_Interface)
|
||||
- [Web Interface](#web-interface)
|
||||
- [Installing](#installing)
|
||||
- [Streamlit UI](#streamlit-ui)
|
||||
- [Clipboard Support](#clipboard-support)
|
||||
- [Meta](#meta)
|
||||
- [Primary contributors](#primary-contributors)
|
||||
|
||||
@@ -82,7 +95,7 @@ Fabric was created to address this by enabling everyone to granularly apply AI t
|
||||
|
||||
## Intro videos
|
||||
|
||||
Keep in mind that many of these were recorded when Fabric was Python-based, so remember to use the current [install instructions](#Installation) below.
|
||||
Keep in mind that many of these were recorded when Fabric was Python-based, so remember to use the current [install instructions](#installation) below.
|
||||
|
||||
- [Network Chuck](https://www.youtube.com/watch?v=UbDyjIIGaxQ)
|
||||
- [David Bombal](https://www.youtube.com/watch?v=vF-MQmVxnCs)
|
||||
@@ -105,7 +118,7 @@ Our approach is to break problems into individual pieces (see below) and then ap
|
||||
|
||||
Prompts are good for this, but the biggest challenge I faced in 2023——which still exists today—is **the sheer number of AI prompts out there**. We all have prompts that are useful, but it's hard to discover new ones, know if they are good or not, _and manage different versions of the ones we like_.
|
||||
|
||||
One of <code>fabric</code>'s primary features is helping people collect and integrate prompts, which we call _Patterns_, into various parts of their lives.
|
||||
One of `fabric`'s primary features is helping people collect and integrate prompts, which we call _Patterns_, into various parts of their lives.
|
||||
|
||||
Fabric has Patterns for all sorts of life and work activities, including:
|
||||
|
||||
@@ -126,21 +139,43 @@ To install Fabric, you can use the latest release binaries or install it from th
|
||||
|
||||
### Get Latest Release Binaries
|
||||
|
||||
#### Windows:
|
||||
#### Windows
|
||||
|
||||
`https://github.com/danielmiessler/fabric/releases/latest/download/fabric-windows-amd64.exe`
|
||||
|
||||
#### MacOS (arm64):
|
||||
#### macOS (arm64)
|
||||
|
||||
`curl -L https://github.com/danielmiessler/fabric/releases/latest/download/fabric-darwin-arm64 > fabric && chmod +x fabric && ./fabric --version`
|
||||
|
||||
#### MacOS (amd64):
|
||||
#### macOS (amd64)
|
||||
|
||||
`curl -L https://github.com/danielmiessler/fabric/releases/latest/download/fabric-darwin-amd64 > fabric && chmod +x fabric && ./fabric --version`
|
||||
|
||||
#### Linux (amd64):
|
||||
#### Linux (amd64)
|
||||
|
||||
`curl -L https://github.com/danielmiessler/fabric/releases/latest/download/fabric-linux-amd64 > fabric && chmod +x fabric && ./fabric --version`
|
||||
|
||||
#### Linux (arm64):
|
||||
#### Linux (arm64)
|
||||
|
||||
`curl -L https://github.com/danielmiessler/fabric/releases/latest/download/fabric-linux-arm64 > fabric && chmod +x fabric && ./fabric --version`
|
||||
|
||||
### Using package managers
|
||||
|
||||
**NOTE:** using Homebrew or the Arch Linux package managers makes `fabric` available as `fabric-ai`, so add
|
||||
the following alias to your shell startup files to account for this:
|
||||
|
||||
```bash
|
||||
alias fabric='fabric-ai'
|
||||
```
|
||||
|
||||
#### macOS (Homebrew)
|
||||
|
||||
`brew install fabric-ai`
|
||||
|
||||
#### Arch Linux (AUR)
|
||||
|
||||
`yay -S fabric-ai`
|
||||
|
||||
### From Source
|
||||
|
||||
To install Fabric, [make sure Go is installed](https://go.dev/doc/install), and then run the following command.
|
||||
@@ -204,8 +239,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 +309,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
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
@@ -274,7 +344,7 @@ This also creates a `yt` alias that allows you to use `yt https://www.youtube.co
|
||||
|
||||
#### Save your files in markdown using aliases
|
||||
|
||||
If in addition to the above aliases you would like to have the option to save the output to your favourite markdown note vault like Obsidian then instead of the above add the following to your `.zshrc` or `.bashrc` file:
|
||||
If in addition to the above aliases you would like to have the option to save the output to your favorite markdown note vault like Obsidian then instead of the above add the following to your `.zshrc` or `.bashrc` file:
|
||||
|
||||
```bash
|
||||
# Define the base directory for Obsidian notes
|
||||
@@ -285,7 +355,7 @@ for pattern_file in ~/.config/fabric/patterns/*; do
|
||||
# Get the base name of the file (i.e., remove the directory path)
|
||||
pattern_name=$(basename "$pattern_file")
|
||||
|
||||
# Unalias any existing alias with the same name
|
||||
# Remove any existing alias with the same name
|
||||
unalias "$pattern_name" 2>/dev/null
|
||||
|
||||
# Define a function dynamically for each pattern
|
||||
@@ -306,11 +376,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.
|
||||
@@ -332,7 +397,7 @@ go install github.com/danielmiessler/fabric@latest
|
||||
fabric --setup
|
||||
```
|
||||
|
||||
Then [set your environmental variables](#environmental-variables) as shown above.
|
||||
Then [set your environmental variables](#environment-variables) as shown above.
|
||||
|
||||
### Upgrading
|
||||
|
||||
@@ -356,48 +421,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
|
||||
|
||||
```
|
||||
|
||||
@@ -427,31 +504,29 @@ Now let's look at some things you can do with Fabric.
|
||||
|
||||
1. Run the `summarize` Pattern based on input from `stdin`. In this case, the body of an article.
|
||||
|
||||
```bash
|
||||
pbpaste | fabric --pattern summarize
|
||||
```
|
||||
```bash
|
||||
pbpaste | fabric --pattern summarize
|
||||
```
|
||||
|
||||
2. Run the `analyze_claims` Pattern with the `--stream` option to get immediate and streaming results.
|
||||
|
||||
```bash
|
||||
pbpaste | fabric --stream --pattern analyze_claims
|
||||
```
|
||||
```bash
|
||||
pbpaste | fabric --stream --pattern analyze_claims
|
||||
```
|
||||
|
||||
3. Run the `extract_wisdom` Pattern with the `--stream` option to get immediate and streaming results from any Youtube video (much like in the original introduction video).
|
||||
|
||||
```bash
|
||||
fabric -y "https://youtube.com/watch?v=uXs-zPc63kM" --stream --pattern extract_wisdom
|
||||
```
|
||||
3. Run the `extract_wisdom` Pattern with the `--stream` option to get immediate and streaming results from any Youtube video (much like in the original introduction video).
|
||||
|
||||
```bash
|
||||
fabric -y "https://youtube.com/watch?v=uXs-zPc63kM" --stream --pattern extract_wisdom
|
||||
```
|
||||
|
||||
4. Create patterns- you must create a .md file with the pattern and save it to `~/.config/fabric/patterns/[yourpatternname]`.
|
||||
|
||||
|
||||
5. Run a `analyze_claims` pattern on a website. Fabric uses Jina AI to scrape the URL into markdown format before sending it to the model.
|
||||
|
||||
```bash
|
||||
fabric -u https://github.com/danielmiessler/fabric/ -p analyze_claims
|
||||
```
|
||||
```bash
|
||||
fabric -u https://github.com/danielmiessler/fabric/ -p analyze_claims
|
||||
```
|
||||
|
||||
## Just use the Patterns
|
||||
|
||||
@@ -468,21 +543,31 @@ 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!
|
||||
|
||||
Just make a directory in `~/.config/custompatterns/` (or wherever) and put your `.md` files in there.
|
||||
|
||||
When you're ready to use them, copy them into:
|
||||
|
||||
```
|
||||
~/.config/fabric/patterns/
|
||||
```
|
||||
When you're ready to use them, copy them into `~/.config/fabric/patterns/`
|
||||
|
||||
You can then use them like any other Patterns, but they won't be public unless you explicitly submit them as Pull Requests to the Fabric project. So don't worry—they're private to you.
|
||||
|
||||
|
||||
## Helper Apps
|
||||
|
||||
Fabric also makes use of some core helper apps (tools) to make it easier to integrate with your various workflows. Here are some examples:
|
||||
@@ -515,6 +600,20 @@ go install github.com/danielmiessler/fabric/plugins/tools/to_pdf@latest
|
||||
|
||||
Make sure you have a LaTeX distribution (like TeX Live or MiKTeX) installed on your system, as `to_pdf` requires `pdflatex` to be available in your system's PATH.
|
||||
|
||||
### `code_helper`
|
||||
|
||||
`code_helper` is used in conjunction with the `create_coding_feature` pattern.
|
||||
It generates a `json` representation of a directory of code that can be fed into an AI model
|
||||
with instructions to create a new feature or edit the code in a specified way.
|
||||
|
||||
See [the Create Coding Feature Pattern README](./patterns/create_coding_feature/README.md) for details.
|
||||
|
||||
Install it first using:
|
||||
|
||||
```bash
|
||||
go install github.com/danielmiessler/fabric/plugins/tools/code_helper@latest
|
||||
```
|
||||
|
||||
## pbpaste
|
||||
|
||||
The [examples](#examples) use the macOS program `pbpaste` to paste content from the clipboard to pipe into `fabric` as the input. `pbpaste` is not available on Windows or Linux, but there are alternatives.
|
||||
@@ -540,16 +639,16 @@ 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.
|
||||
|
||||
### Installing
|
||||
|
||||
The GUI can be installed by navigating to the `web` directory and using `npm install`, `pnpm install`, or your favorite package manager. Then simply run the development server to start the app.
|
||||
The GUI can be installed by navigating to the `web` directory and using `npm install`, `pnpm install`, or your favorite package manager. Then simply run the development server to start the app.
|
||||
|
||||
_You will need to run fabric in a separate terminal with the `fabric --serve` command._
|
||||
_You will need to run fabric in a separate terminal with the `fabric --serve` command._
|
||||
|
||||
**From the fabric project `web/` directory:**
|
||||
|
||||
@@ -569,7 +668,10 @@ To run the Streamlit user interface:
|
||||
|
||||
```bash
|
||||
# Install required dependencies
|
||||
pip install streamlit pandas matplotlib seaborn numpy python-dotenv
|
||||
pip install -r requirements.txt
|
||||
|
||||
# Or manually install dependencies
|
||||
pip install streamlit pandas matplotlib seaborn numpy python-dotenv pyperclip
|
||||
|
||||
# Run the Streamlit app
|
||||
streamlit run streamlit.py
|
||||
@@ -582,6 +684,14 @@ The Streamlit UI provides a user-friendly interface for:
|
||||
- Creating and editing patterns
|
||||
- Analyzing pattern results
|
||||
|
||||
#### Clipboard Support
|
||||
|
||||
The Streamlit UI supports clipboard operations across different platforms:
|
||||
|
||||
- **macOS**: Uses `pbcopy` and `pbpaste` (built-in)
|
||||
- **Windows**: Uses `pyperclip` library (install with `pip install pyperclip`)
|
||||
- **Linux**: Uses `xclip` (install with `sudo apt-get install xclip` or equivalent for your distro)
|
||||
|
||||
## Meta
|
||||
|
||||
> [!NOTE]
|
||||
|
||||
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
|
||||
@@ -1,21 +1,20 @@
|
||||
https://youtu.be/bhwtWXoMASA# Enhanced Pattern Selection,
|
||||
Pattern Descriptions, New
|
||||
Pattern TAG System, Language Support and other WEB UI Improvements V3
|
||||
|
||||
This Cummulative PR adds several Web UI and functionality improvements to make pattern selection more intuitive (pattern descriptions), ability to save favorite patterns, powerful multilingual capabilities, a Pattern TAG system, a help reference section, more robust Youtube processing and a variety of ui improvements.
|
||||
This Cummulative PR adds several Web UI and functionality improvements to make pattern selection more intuitive with the addition of pattern descriptions, ability to save favorite patterns, a Pattern TAG system, powerful multilingual capabilities, PDF-to-markdown functionnalities, a help reference section, more robust Youtube processing and a variety of other ui improvements.
|
||||
|
||||
## 🎥 Demo Video
|
||||
https://youtu.be/IhE8Iey8hSU
|
||||
https://youtu.be/XMzjgqvdltM
|
||||
|
||||
|
||||
|
||||
## 🌟 Key Features
|
||||
|
||||
### 1. Web UI and Pattern Selection Improvements
|
||||
- Enhanced pattern selection interface for better user experience
|
||||
- New pattern descriptions section accessible via modal
|
||||
- New pattern favorite list and pattern search functionnality
|
||||
- New Tag system for better pattern organization and filtering
|
||||
- Pattern Descriptions
|
||||
- Pattern Tags
|
||||
- Pattern Favourites
|
||||
- Pattern Search bar
|
||||
- PDF to markdown (pdf as pattern input)
|
||||
- Better handling of Youtube url
|
||||
- Multilingual Support
|
||||
- Web UI refinements for clearer interaction
|
||||
- Help section via modal
|
||||
|
||||
@@ -57,6 +56,31 @@ The tag filtering system has been deeply integrated into the Pattern Selection i
|
||||
- Maintained accessibility standards
|
||||
- Responsive design considerations
|
||||
|
||||
|
||||
5. **PDF to Markdown conversion functionality for the web interface**
|
||||
- Automatic detection and processing of PDF files in chat
|
||||
- Conversion to markdown format for LLM processing
|
||||
- Installation instructions from the pdf-to-markdown repository
|
||||
|
||||
The PDF conversion module has been integrated in the svelte web browser interface. Once installed, it will automatically detect pdf files in the chat interface and convert them to markdown
|
||||
|
||||
|
||||
## HOW TO INSTALL PDF-TO-MARKDOWN
|
||||
If you need to update the web component follow the instructions in "Web Interface MOD Readme Files/WEB V2 Install Guide.md".
|
||||
|
||||
Assuming your web install is up to date and web svelte config complete, you can simply follow these steps to add Pdf-to-mardown.
|
||||
|
||||
# FROM FABRIC ROOT DIRECTORY
|
||||
cd .. web
|
||||
|
||||
# Install in this sequence:
|
||||
# Step 1
|
||||
npm install -D patch-package
|
||||
# Step 2
|
||||
npm install -D pdfjs-dist@2.5.207
|
||||
# Step 3
|
||||
npm install -D github:jzillmann/pdf-to-markdown#modularize
|
||||
|
||||
These enhancements create a more intuitive and efficient pattern discovery experience, allowing users to quickly filter and find relevant patterns while maintaining a clean, modern interface.
|
||||
|
||||
|
||||
16
cli/cli.go
16
cli/cli.go
@@ -57,7 +57,7 @@ func Cli(version string) (err error) {
|
||||
|
||||
if currentFlags.Serve {
|
||||
registry.ConfigureVendors()
|
||||
err = restapi.Serve(registry, currentFlags.ServeAddress)
|
||||
err = restapi.Serve(registry, currentFlags.ServeAddress, currentFlags.ServeAPIKey)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -73,7 +73,10 @@ func Cli(version string) (err error) {
|
||||
}
|
||||
|
||||
if currentFlags.ChangeDefaultModel {
|
||||
err = registry.Defaults.Setup()
|
||||
if err = registry.Defaults.Setup(); err != nil {
|
||||
return
|
||||
}
|
||||
err = registry.SaveEnvFile()
|
||||
return
|
||||
}
|
||||
|
||||
@@ -156,6 +159,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 +174,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 +249,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
|
||||
}
|
||||
|
||||
|
||||
35
cli/flags.go
35
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
|
||||
@@ -65,11 +64,14 @@ type Flags struct {
|
||||
Serve bool `long:"serve" description:"Serve the Fabric Rest API"`
|
||||
ServeOllama bool `long:"serveOllama" description:"Serve the Fabric Rest API with ollama endpoints"`
|
||||
ServeAddress string `long:"address" description:"The address to bind the REST API" default:":8080"`
|
||||
ServeAPIKey string `long:"api-key" description:"API key used to secure server routes" default:""`
|
||||
Config string `long:"config" description:"Path to YAML config file"`
|
||||
Version bool `long:"version" description:"Print current version"`
|
||||
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 +117,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 +154,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 +171,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 +237,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 +270,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,
|
||||
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
@@ -29,7 +29,7 @@ func (a *Attachment) GetId() (ret string, err error) {
|
||||
hash = fmt.Sprintf("%x", sha256.Sum256(a.Content))
|
||||
} else if a.Path != nil {
|
||||
var content []byte
|
||||
if content, err = ioutil.ReadFile(*a.Path); err != nil {
|
||||
if content, err = os.ReadFile(*a.Path); err != nil {
|
||||
return
|
||||
}
|
||||
hash = fmt.Sprintf("%x", sha256.Sum256(content))
|
||||
@@ -83,7 +83,7 @@ func (a *Attachment) ContentBytes() (ret []byte, err error) {
|
||||
return
|
||||
}
|
||||
if a.Path != nil {
|
||||
if ret, err = ioutil.ReadFile(*a.Path); err != nil {
|
||||
if ret, err = os.ReadFile(*a.Path); err != nil {
|
||||
return
|
||||
}
|
||||
return
|
||||
@@ -94,7 +94,7 @@ func (a *Attachment) ContentBytes() (ret []byte, err error) {
|
||||
return
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
if ret, err = ioutil.ReadAll(resp.Body); err != nil {
|
||||
if ret, err = io.ReadAll(resp.Body); err != nil {
|
||||
return
|
||||
}
|
||||
return
|
||||
|
||||
@@ -13,6 +13,7 @@ type ChatRequest struct {
|
||||
Language string
|
||||
Meta string
|
||||
InputHasVars bool
|
||||
StrategyName string
|
||||
}
|
||||
|
||||
type ChatOptions struct {
|
||||
|
||||
195
common/file_manager.go
Normal file
195
common/file_manager.go
Normal file
@@ -0,0 +1,195 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// FileChangesMarker identifies the start of a file changes section in output
|
||||
const FileChangesMarker = "__CREATE_CODING_FEATURE_FILE_CHANGES__"
|
||||
|
||||
const (
|
||||
// MaxFileSize is the maximum size of a file that can be created (10MB)
|
||||
MaxFileSize = 10 * 1024 * 1024
|
||||
)
|
||||
|
||||
// FileChange represents a single file change operation to be performed
|
||||
type FileChange struct {
|
||||
Operation string `json:"operation"` // "create" or "update"
|
||||
Path string `json:"path"` // Relative path from project root
|
||||
Content string `json:"content"` // New file content
|
||||
}
|
||||
|
||||
// ParseFileChanges extracts and parses the file change marker section from LLM output
|
||||
func ParseFileChanges(output string) (changeSummary string, changes []FileChange, err error) {
|
||||
fileChangesStart := strings.Index(output, FileChangesMarker)
|
||||
if fileChangesStart == -1 {
|
||||
return output, nil, nil // No file changes section found
|
||||
}
|
||||
changeSummary = output[:fileChangesStart] // Everything before the marker
|
||||
|
||||
// Extract the JSON part
|
||||
jsonStart := fileChangesStart + len(FileChangesMarker)
|
||||
// Find the first [ after the file changes marker
|
||||
jsonArrayStart := strings.Index(output[jsonStart:], "[")
|
||||
if jsonArrayStart == -1 {
|
||||
return output, nil, fmt.Errorf("invalid %s format: no JSON array found", FileChangesMarker)
|
||||
}
|
||||
jsonStart += jsonArrayStart
|
||||
|
||||
// Find the matching closing bracket for the array with proper bracket counting
|
||||
bracketCount := 0
|
||||
jsonEnd := jsonStart
|
||||
for i := jsonStart; i < len(output); i++ {
|
||||
if output[i] == '[' {
|
||||
bracketCount++
|
||||
} else if output[i] == ']' {
|
||||
bracketCount--
|
||||
if bracketCount == 0 {
|
||||
jsonEnd = i + 1
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if bracketCount != 0 {
|
||||
return output, nil, fmt.Errorf("invalid %s format: unbalanced brackets", FileChangesMarker)
|
||||
}
|
||||
|
||||
// Extract the JSON string and fix escape sequences
|
||||
jsonStr := output[jsonStart:jsonEnd]
|
||||
|
||||
// Fix specific invalid escape sequences
|
||||
// First try with the common \C issue
|
||||
jsonStr = strings.Replace(jsonStr, `\C`, `\\C`, -1)
|
||||
|
||||
// Parse the JSON
|
||||
var fileChanges []FileChange
|
||||
err = json.Unmarshal([]byte(jsonStr), &fileChanges)
|
||||
if err != nil {
|
||||
// If still failing, try a more comprehensive fix
|
||||
jsonStr = fixInvalidEscapes(jsonStr)
|
||||
err = json.Unmarshal([]byte(jsonStr), &fileChanges)
|
||||
if err != nil {
|
||||
return changeSummary, nil, fmt.Errorf("failed to parse %s JSON: %w", FileChangesMarker, err)
|
||||
}
|
||||
}
|
||||
|
||||
// Validate file changes
|
||||
for i, change := range fileChanges {
|
||||
// Validate operation
|
||||
if change.Operation != "create" && change.Operation != "update" {
|
||||
return changeSummary, nil, fmt.Errorf("invalid operation for file change %d: %s", i, change.Operation)
|
||||
}
|
||||
|
||||
// Validate path
|
||||
if change.Path == "" {
|
||||
return changeSummary, nil, fmt.Errorf("empty path for file change %d", i)
|
||||
}
|
||||
|
||||
// Check for suspicious paths (directory traversal)
|
||||
if strings.Contains(change.Path, "..") {
|
||||
return changeSummary, nil, fmt.Errorf("suspicious path for file change %d: %s", i, change.Path)
|
||||
}
|
||||
|
||||
// Check file size
|
||||
if len(change.Content) > MaxFileSize {
|
||||
return changeSummary, nil, fmt.Errorf("file content too large for file change %d: %d bytes", i, len(change.Content))
|
||||
}
|
||||
}
|
||||
|
||||
return changeSummary, fileChanges, nil
|
||||
}
|
||||
|
||||
// fixInvalidEscapes replaces invalid escape sequences in JSON strings
|
||||
func fixInvalidEscapes(jsonStr string) string {
|
||||
validEscapes := []byte{'b', 'f', 'n', 'r', 't', '\\', '/', '"', 'u'}
|
||||
|
||||
var result strings.Builder
|
||||
inQuotes := false
|
||||
i := 0
|
||||
|
||||
for i < len(jsonStr) {
|
||||
ch := jsonStr[i]
|
||||
|
||||
// Track whether we're inside a JSON string
|
||||
if ch == '"' && (i == 0 || jsonStr[i-1] != '\\') {
|
||||
inQuotes = !inQuotes
|
||||
}
|
||||
|
||||
// Handle actual control characters inside string literals
|
||||
if inQuotes {
|
||||
// Convert literal control characters to proper JSON escape sequences
|
||||
if ch == '\n' {
|
||||
result.WriteString("\\n")
|
||||
i++
|
||||
continue
|
||||
} else if ch == '\r' {
|
||||
result.WriteString("\\r")
|
||||
i++
|
||||
continue
|
||||
} else if ch == '\t' {
|
||||
result.WriteString("\\t")
|
||||
i++
|
||||
continue
|
||||
} else if ch < 32 {
|
||||
// Handle other control characters
|
||||
fmt.Fprintf(&result, "\\u%04x", ch)
|
||||
i++
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
// Check for escape sequences only inside strings
|
||||
if inQuotes && ch == '\\' && i+1 < len(jsonStr) {
|
||||
nextChar := jsonStr[i+1]
|
||||
isValid := false
|
||||
|
||||
for _, validEscape := range validEscapes {
|
||||
if nextChar == validEscape {
|
||||
isValid = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !isValid {
|
||||
// Invalid escape sequence - add an extra backslash
|
||||
result.WriteByte('\\')
|
||||
result.WriteByte('\\')
|
||||
i++
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
result.WriteByte(ch)
|
||||
i++
|
||||
}
|
||||
|
||||
return result.String()
|
||||
}
|
||||
|
||||
// ApplyFileChanges applies the parsed file changes to the file system
|
||||
func ApplyFileChanges(projectRoot string, changes []FileChange) error {
|
||||
for i, change := range changes {
|
||||
// Get the absolute path
|
||||
absPath := filepath.Join(projectRoot, change.Path)
|
||||
|
||||
// Create directories if necessary
|
||||
dir := filepath.Dir(absPath)
|
||||
if err := os.MkdirAll(dir, 0755); err != nil {
|
||||
return fmt.Errorf("failed to create directory %s for file change %d: %w", dir, i, err)
|
||||
}
|
||||
|
||||
// Write the file
|
||||
if err := os.WriteFile(absPath, []byte(change.Content), 0644); err != nil {
|
||||
return fmt.Errorf("failed to write file %s for file change %d: %w", absPath, i, err)
|
||||
}
|
||||
|
||||
fmt.Printf("Applied %s operation to %s\n", change.Operation, change.Path)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
185
common/file_manager_test.go
Normal file
185
common/file_manager_test.go
Normal file
@@ -0,0 +1,185 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestParseFileChanges(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input string
|
||||
want int // number of expected file changes
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "No " + FileChangesMarker + " section",
|
||||
input: "This is a normal response with no file changes.",
|
||||
want: 0,
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "Valid " + FileChangesMarker + " section",
|
||||
input: `Some text before.
|
||||
` + FileChangesMarker + `
|
||||
[
|
||||
{
|
||||
"operation": "create",
|
||||
"path": "test.txt",
|
||||
"content": "Hello, World!"
|
||||
},
|
||||
{
|
||||
"operation": "update",
|
||||
"path": "other.txt",
|
||||
"content": "Updated content"
|
||||
}
|
||||
]
|
||||
Some text after.`,
|
||||
want: 2,
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "Invalid JSON in " + FileChangesMarker + " section",
|
||||
input: `Some text before.
|
||||
` + FileChangesMarker + `
|
||||
[
|
||||
{
|
||||
"operation": "create",
|
||||
"path": "test.txt",
|
||||
"content": "Hello, World!"
|
||||
},
|
||||
{
|
||||
"operation": "invalid",
|
||||
"path": "other.txt"
|
||||
"content": "Updated content"
|
||||
}
|
||||
]`,
|
||||
want: 0,
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "Invalid operation",
|
||||
input: `Some text before.
|
||||
` + FileChangesMarker + `
|
||||
[
|
||||
{
|
||||
"operation": "delete",
|
||||
"path": "test.txt",
|
||||
"content": ""
|
||||
}
|
||||
]`,
|
||||
want: 0,
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "Empty path",
|
||||
input: `Some text before.
|
||||
` + FileChangesMarker + `
|
||||
[
|
||||
{
|
||||
"operation": "create",
|
||||
"path": "",
|
||||
"content": "Hello, World!"
|
||||
}
|
||||
]`,
|
||||
want: 0,
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "Suspicious path with directory traversal",
|
||||
input: `Some text before.
|
||||
` + FileChangesMarker + `
|
||||
[
|
||||
{
|
||||
"operation": "create",
|
||||
"path": "../etc/passwd",
|
||||
"content": "Hello, World!"
|
||||
}
|
||||
]`,
|
||||
want: 0,
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
_, got, err := ParseFileChanges(tt.input)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("ParseFileChanges() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
if !tt.wantErr && len(got) != tt.want {
|
||||
t.Errorf("ParseFileChanges() got %d file changes, want %d", len(got), tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestApplyFileChanges(t *testing.T) {
|
||||
// Create a temporary directory for testing
|
||||
// Create a temporary directory for testing
|
||||
tempDir, err := os.MkdirTemp("", "file-manager-test")
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create temp dir: %v", err)
|
||||
}
|
||||
defer os.RemoveAll(tempDir)
|
||||
// Test file changes
|
||||
changes := []FileChange{
|
||||
{
|
||||
Operation: "create",
|
||||
Path: "test.txt",
|
||||
Content: "Hello, World!",
|
||||
},
|
||||
{
|
||||
Operation: "create",
|
||||
Path: "subdir/nested.txt",
|
||||
Content: "Nested content",
|
||||
},
|
||||
}
|
||||
|
||||
// Apply the changes
|
||||
if err := ApplyFileChanges(tempDir, changes); err != nil {
|
||||
t.Fatalf("ApplyFileChanges() error = %v", err)
|
||||
}
|
||||
|
||||
// Verify the first file was created correctly
|
||||
content, err := os.ReadFile(filepath.Join(tempDir, "test.txt"))
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to read created file: %v", err)
|
||||
}
|
||||
if string(content) != "Hello, World!" {
|
||||
t.Errorf("File content = %q, want %q", string(content), "Hello, World!")
|
||||
}
|
||||
|
||||
// Verify the nested file was created correctly
|
||||
content, err = os.ReadFile(filepath.Join(tempDir, "subdir/nested.txt"))
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to read created nested file: %v", err)
|
||||
}
|
||||
if string(content) != "Nested content" {
|
||||
t.Errorf("Nested file content = %q, want %q", string(content), "Nested content")
|
||||
}
|
||||
|
||||
// Test updating a file
|
||||
updateChanges := []FileChange{
|
||||
{
|
||||
Operation: "update",
|
||||
Path: "test.txt",
|
||||
Content: "Updated content",
|
||||
},
|
||||
}
|
||||
|
||||
// Apply the update
|
||||
if err := ApplyFileChanges(tempDir, updateChanges); err != nil {
|
||||
t.Fatalf("ApplyFileChanges() error = %v", err)
|
||||
}
|
||||
// Verify the file was updated correctly
|
||||
content, err = os.ReadFile(filepath.Join(tempDir, "test.txt"))
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to read updated file: %v", err)
|
||||
}
|
||||
if string(content) != "Updated content" {
|
||||
t.Errorf("Updated file content = %q, want %q", string(content), "Updated content")
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,9 @@ package core
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
goopenai "github.com/sashabaranov/go-openai"
|
||||
@@ -10,6 +12,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,8 +27,10 @@ type Chatter struct {
|
||||
model string
|
||||
modelContextLength int
|
||||
vendor ai.Vendor
|
||||
strategy string
|
||||
}
|
||||
|
||||
// Send processes a chat request and applies any file changes if using the create_coding_feature pattern
|
||||
func (o *Chatter) Send(request *common.ChatRequest, opts *common.ChatOptions) (session *fsdb.Session, err error) {
|
||||
if session, err = o.BuildSession(request, opts.Raw); err != nil {
|
||||
return
|
||||
@@ -35,6 +40,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
|
||||
@@ -74,6 +82,30 @@ func (o *Chatter) Send(request *common.ChatRequest, opts *common.ChatOptions) (s
|
||||
return
|
||||
}
|
||||
|
||||
// Process file changes if using the create_coding_feature pattern
|
||||
if request.PatternName == "create_coding_feature" {
|
||||
// Look for file changes in the response
|
||||
summary, fileChanges, parseErr := common.ParseFileChanges(message)
|
||||
if parseErr != nil {
|
||||
fmt.Printf("Warning: Failed to parse file changes: %v\n", parseErr)
|
||||
} else if len(fileChanges) > 0 {
|
||||
// Get the project root - use the current directory
|
||||
projectRoot, err := os.Getwd()
|
||||
if err != nil {
|
||||
fmt.Printf("Warning: Failed to get current directory: %v\n", err)
|
||||
// Continue without applying changes
|
||||
} else {
|
||||
if applyErr := common.ApplyFileChanges(projectRoot, fileChanges); applyErr != nil {
|
||||
fmt.Printf("Warning: Failed to apply file changes: %v\n", applyErr)
|
||||
} else {
|
||||
fmt.Println("Successfully applied file changes.")
|
||||
fmt.Printf("You can review the changes with 'git diff' if you're using git.\n\n")
|
||||
}
|
||||
}
|
||||
}
|
||||
message = summary
|
||||
}
|
||||
|
||||
session.Append(&goopenai.ChatCompletionMessage{Role: goopenai.ChatMessageRoleAssistant, Content: message})
|
||||
|
||||
if session.Name != "" {
|
||||
@@ -140,6 +172,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)
|
||||
}
|
||||
@@ -167,7 +212,7 @@ func (o *Chatter) BuildSession(request *common.ChatRequest, raw bool) (session *
|
||||
|
||||
if session.IsEmpty() {
|
||||
session = nil
|
||||
err = fmt.Errorf(NoSessionPatternUserMessages)
|
||||
err = errors.New(NoSessionPatternUserMessages)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"strconv"
|
||||
|
||||
"github.com/danielmiessler/fabric/plugins/ai/exolab"
|
||||
"github.com/danielmiessler/fabric/plugins/strategy"
|
||||
|
||||
"github.com/samber/lo"
|
||||
|
||||
@@ -20,6 +21,7 @@ import (
|
||||
"github.com/danielmiessler/fabric/plugins/ai/dryrun"
|
||||
"github.com/danielmiessler/fabric/plugins/ai/gemini"
|
||||
"github.com/danielmiessler/fabric/plugins/ai/groq"
|
||||
"github.com/danielmiessler/fabric/plugins/ai/litellm"
|
||||
"github.com/danielmiessler/fabric/plugins/ai/lmstudio"
|
||||
"github.com/danielmiessler/fabric/plugins/ai/mistral"
|
||||
"github.com/danielmiessler/fabric/plugins/ai/ollama"
|
||||
@@ -43,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
|
||||
@@ -53,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(), lmstudio.NewClient(), mistral.NewClient(), deepseek.NewClient(), exolab.NewClient())
|
||||
anthropic.NewClient(),
|
||||
siliconcloud.NewClient(),
|
||||
openrouter.NewClient(),
|
||||
lmstudio.NewClient(),
|
||||
mistral.NewClient(),
|
||||
deepseek.NewClient(),
|
||||
exolab.NewClient(),
|
||||
litellm.NewClient(),
|
||||
)
|
||||
_ = ret.Configure()
|
||||
|
||||
return
|
||||
@@ -74,6 +88,7 @@ type PluginRegistry struct {
|
||||
Language *lang.Language
|
||||
Jina *jina.Client
|
||||
TemplateExtensions *template.ExtensionManager
|
||||
Strategies *strategy.StrategiesManager
|
||||
}
|
||||
|
||||
func (o *PluginRegistry) SaveEnvFile() (err error) {
|
||||
@@ -82,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)
|
||||
@@ -97,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() {
|
||||
@@ -113,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()
|
||||
@@ -194,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,
|
||||
@@ -246,5 +262,6 @@ func (o *PluginRegistry) GetChatter(model string, modelContextLength int, stream
|
||||
model, defaultModel, defaultVendor, errMsg)
|
||||
return
|
||||
}
|
||||
ret.strategy = strategy
|
||||
return
|
||||
}
|
||||
|
||||
3
go.mod
3
go.mod
@@ -41,7 +41,6 @@ require (
|
||||
github.com/bytedance/sonic/loader v0.2.3 // indirect
|
||||
github.com/cloudflare/circl v1.6.0 // indirect
|
||||
github.com/cloudwego/base64x v0.1.5 // indirect
|
||||
github.com/cloudwego/iasm v0.2.0 // indirect
|
||||
github.com/cyphar/filepath-securejoin v0.4.1 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/emirpasic/gods v1.18.1 // indirect
|
||||
@@ -91,7 +90,7 @@ require (
|
||||
go.opentelemetry.io/otel/trace v1.34.0 // 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/net v0.36.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
|
||||
|
||||
103
go.sum
103
go.sum
@@ -1,21 +1,13 @@
|
||||
cloud.google.com/go v0.116.0 h1:B3fRrSDkLRt5qSHWe40ERJvhvnQwdZiHu0bJOpldweE=
|
||||
cloud.google.com/go v0.116.0/go.mod h1:cEPSRWPzZEswwdr9BxE6ChEn01dWlTaF05LiC2Xs70U=
|
||||
cloud.google.com/go v0.118.3 h1:jsypSnrE/w4mJysioGdMBg4MiW/hHx/sArFpaBWHdME=
|
||||
cloud.google.com/go v0.118.3/go.mod h1:Lhs3YLnBlwJ4KA6nuObNMZ/fCbOQBPuWKPoE0Wa/9Vc=
|
||||
cloud.google.com/go/ai v0.8.0 h1:rXUEz8Wp2OlrM8r1bfmpF2+VKqc1VJpafE3HgzRnD/w=
|
||||
cloud.google.com/go/ai v0.8.0/go.mod h1:t3Dfk4cM61sytiggo2UyGsDVW3RF1qGZaUKDrZFyqkE=
|
||||
cloud.google.com/go/ai v0.10.0 h1:hwj6CI6sMKubXodoJJGTy/c2T1RbbLGM6TL3QoAvzU8=
|
||||
cloud.google.com/go/ai v0.10.0/go.mod h1:kvnt2KeHqX8+41PVeMRBETDyQAp/RFvBWGdx/aGjNMo=
|
||||
cloud.google.com/go/auth v0.14.1 h1:AwoJbzUdxA/whv1qj3TLKwh3XX5sikny2fc40wUl+h0=
|
||||
cloud.google.com/go/auth v0.14.1/go.mod h1:4JHUxlGXisL0AW8kXPtUF6ztuOksyfUQNFjfsOCXkPM=
|
||||
cloud.google.com/go/auth v0.15.0 h1:Ly0u4aA5vG/fsSsxu98qCQBemXtAtJf+95z9HK+cxps=
|
||||
cloud.google.com/go/auth v0.15.0/go.mod h1:WJDGqZ1o9E9wKIL+IwStfyn/+s59zl4Bi+1KQNVXLZ8=
|
||||
cloud.google.com/go/auth/oauth2adapt v0.2.7 h1:/Lc7xODdqcEw8IrZ9SvwnlLX6j9FHQM74z6cBk9Rw6M=
|
||||
cloud.google.com/go/auth/oauth2adapt v0.2.7/go.mod h1:NTbTTzfvPl1Y3V1nPpOgl2w6d/FjO7NNUQaWSox6ZMc=
|
||||
cloud.google.com/go/compute/metadata v0.6.0 h1:A6hENjEsCDtC1k8byVsgwvVcioamEHvZ4j01OwKxG9I=
|
||||
cloud.google.com/go/compute/metadata v0.6.0/go.mod h1:FjyFAW1MW0C203CEOMDTu3Dk1FlqW3Rga40jzHL4hfg=
|
||||
cloud.google.com/go/longrunning v0.5.7 h1:WLbHekDbjK1fVFD3ibpFFVoyizlLRl73I7YKuAKilhU=
|
||||
cloud.google.com/go/longrunning v0.5.7/go.mod h1:8GClkudohy1Fxm3owmBGid8W0pSgodEMwEAztp38Xng=
|
||||
cloud.google.com/go/longrunning v0.6.4 h1:3tyw9rO3E2XVXzSApn1gyEEnH2K9SynNQjMlBi3uHLg=
|
||||
cloud.google.com/go/longrunning v0.6.4/go.mod h1:ttZpLCe6e7EXvn9OxpBRx7kZEB0efv8yBO6YnVMfhJs=
|
||||
dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s=
|
||||
@@ -23,67 +15,45 @@ dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
|
||||
github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY=
|
||||
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
|
||||
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
|
||||
github.com/ProtonMail/go-crypto v1.1.3 h1:nRBOetoydLeUb4nHajyO2bKqMLfWQ/ZPwkXqXxPxCFk=
|
||||
github.com/ProtonMail/go-crypto v1.1.3/go.mod h1:rA3QumHc/FZ8pAHreoekgiAbzpNsfQAosU5td4SnOrE=
|
||||
github.com/ProtonMail/go-crypto v1.1.5 h1:eoAQfK2dwL+tFSFpr7TbOaPNUbPiJj4fLYwwGE1FQO4=
|
||||
github.com/ProtonMail/go-crypto v1.1.5/go.mod h1:rA3QumHc/FZ8pAHreoekgiAbzpNsfQAosU5td4SnOrE=
|
||||
github.com/anaskhan96/soup v1.2.5 h1:V/FHiusdTrPrdF4iA1YkVxsOpdNcgvqT1hG+YtcZ5hM=
|
||||
github.com/anaskhan96/soup v1.2.5/go.mod h1:6YnEp9A2yywlYdM4EgDz9NEHclocMepEtku7wg6Cq3s=
|
||||
github.com/andybalholm/cascadia v1.3.2 h1:3Xi6Dw5lHF15JtdcmAHD3i1+T8plmv7BQ/nsViSLyss=
|
||||
github.com/andybalholm/cascadia v1.3.2/go.mod h1:7gtRlve5FxPPgIgX36uWBX58OdBsSS6lUvCFb+h7KvU=
|
||||
github.com/andybalholm/cascadia v1.3.3 h1:AG2YHrzJIm4BZ19iwJ/DAua6Btl3IwJX+VI4kktS1LM=
|
||||
github.com/andybalholm/cascadia v1.3.3/go.mod h1:xNd9bqTn98Ln4DwST8/nG+H0yuB8Hmgu1YHNnWw0GeA=
|
||||
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8=
|
||||
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4=
|
||||
github.com/anthropics/anthropic-sdk-go v0.2.0-alpha.4 h1:TdGQS+RoR4AUO6gqUL74yK1dz/Arrt/WG+dxOj6Yo6A=
|
||||
github.com/anthropics/anthropic-sdk-go v0.2.0-alpha.4/go.mod h1:GJxtdOs9K4neo8Gg65CjJ7jNautmldGli5/OFNabOoo=
|
||||
github.com/anthropics/anthropic-sdk-go v0.2.0-alpha.11 h1:O3/AMObKntZyu1KH6Xks6E0gbE8w6HVaKHE+/vXARzM=
|
||||
github.com/anthropics/anthropic-sdk-go v0.2.0-alpha.11/go.mod h1:GJxtdOs9K4neo8Gg65CjJ7jNautmldGli5/OFNabOoo=
|
||||
github.com/apache/arrow/go/arrow v0.0.0-20211112161151-bc219186db40/go.mod h1:Q7yQnSMnLvcXlZ8RV+jwz/6y1rQTqbX6C82SndT52Zs=
|
||||
github.com/araddon/dateparse v0.0.0-20210429162001-6b43995a97de h1:FxWPpzIjnTlhPwqqXc4/vE0f7GvRjuAsbW+HOIe8KnA=
|
||||
github.com/araddon/dateparse v0.0.0-20210429162001-6b43995a97de/go.mod h1:DCaWoUhZrYW9p1lxo/cm8EmUOOzAPSEZNGF2DK1dJgw=
|
||||
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
|
||||
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
|
||||
github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4=
|
||||
github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI=
|
||||
github.com/bytedance/sonic v1.12.4 h1:9Csb3c9ZJhfUWeMtpCDCq6BUoH5ogfDFLUgQ/jG+R0k=
|
||||
github.com/bytedance/sonic v1.12.4/go.mod h1:B8Gt/XvtZ3Fqj+iSKMypzymZxw/FVwgIGKzMzT9r/rk=
|
||||
github.com/bytedance/sonic v1.12.9 h1:Od1BvK55NnewtGaJsTDeAOSnLVO2BTSLOe0+ooKokmQ=
|
||||
github.com/bytedance/sonic v1.12.9/go.mod h1:uVvFidNmlt9+wa31S1urfwwthTWteBgG0hWuoKAXTx8=
|
||||
github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
|
||||
github.com/bytedance/sonic/loader v0.2.1 h1:1GgorWTqf12TA8mma4DDSbaQigE2wOgQo7iCjjJv3+E=
|
||||
github.com/bytedance/sonic/loader v0.2.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
|
||||
github.com/bytedance/sonic/loader v0.2.3 h1:yctD0Q3v2NOGfSWPLPvG2ggA2kV6TS6s4wioyEqssH0=
|
||||
github.com/bytedance/sonic/loader v0.2.3/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI=
|
||||
github.com/cloudflare/circl v1.5.0 h1:hxIWksrX6XN5a1L2TI/h53AGPhNHoUBo+TD1ms9+pys=
|
||||
github.com/cloudflare/circl v1.5.0/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs=
|
||||
github.com/cloudflare/circl v1.6.0 h1:cr5JKic4HI+LkINy2lg3W2jF8sHCVTBncJr5gIIq7qk=
|
||||
github.com/cloudflare/circl v1.6.0/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs=
|
||||
github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/0Y=
|
||||
github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w=
|
||||
github.com/cloudwego/base64x v0.1.5 h1:XPciSp1xaq2VCSt6lF0phncD4koWyULpl5bUxbfCyP4=
|
||||
github.com/cloudwego/base64x v0.1.5/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w=
|
||||
github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg=
|
||||
github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY=
|
||||
github.com/cyphar/filepath-securejoin v0.3.4 h1:VBWugsJh2ZxJmLFSM06/0qzQyiQX2Qs0ViKrUAcqdZ8=
|
||||
github.com/cyphar/filepath-securejoin v0.3.4/go.mod h1:8s/MCNJREmFK0H02MF6Ihv1nakJe4L/w3WZLHNkvlYM=
|
||||
github.com/cyphar/filepath-securejoin v0.4.1 h1:JyxxyPEaktOD+GAnqIqTf9A8tHyAG22rowi7HkoSU1s=
|
||||
github.com/cyphar/filepath-securejoin v0.4.1/go.mod h1:Sdj7gXlvMcPZsbhwhQ33GguGLDGQL7h7bg04C/+u9jI=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/elazarl/goproxy v1.2.1 h1:njjgvO6cRG9rIqN2ebkqy6cQz2Njkx7Fsfv/zIZqgug=
|
||||
github.com/elazarl/goproxy v1.2.1/go.mod h1:YfEbZtqP4AetfO6d40vWchF3znWX7C7Vd6ZMfdL8z64=
|
||||
github.com/elazarl/goproxy v1.4.0 h1:4GyuSbFa+s26+3rmYNSuUVsx+HgPrV1bk1jXI0l9wjM=
|
||||
github.com/elazarl/goproxy v1.4.0/go.mod h1:X/5W/t+gzDyLfHW4DrMdpjqYjpXsURlBt9lpBDxZZZQ=
|
||||
github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=
|
||||
github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ=
|
||||
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
|
||||
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
|
||||
github.com/gabriel-vasile/mimetype v1.4.6 h1:3+PzJTKLkvgjeTbts6msPJt4DixhT4YtFNf1gtGe3zc=
|
||||
github.com/gabriel-vasile/mimetype v1.4.6/go.mod h1:JX1qVKqZd40hUPpAfiNTe0Sne7hdfKSbOqqmkq8GCXc=
|
||||
github.com/gabriel-vasile/mimetype v1.4.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3GqacKw1NM=
|
||||
github.com/gabriel-vasile/mimetype v1.4.8/go.mod h1:ByKUIKGjh1ODkGM1asKUbQZOLGrPjydw3hYPU2YU9t8=
|
||||
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
|
||||
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
|
||||
github.com/gin-contrib/sse v1.0.0 h1:y3bT1mUWUxDpW4JLQg/HnTqV4rozuW4tC9eFKTxYI9E=
|
||||
github.com/gin-contrib/sse v1.0.0/go.mod h1:zNuFdwarAygJBht0NTKiSi3jRf6RbqeILZ9Sp6Slhe0=
|
||||
github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU=
|
||||
@@ -92,14 +62,10 @@ github.com/gliderlabs/ssh v0.3.8 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c=
|
||||
github.com/gliderlabs/ssh v0.3.8/go.mod h1:xYoytBv1sV0aL3CavoDuJIQNURXkkfPA/wxQ1pL1fAU=
|
||||
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI=
|
||||
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic=
|
||||
github.com/go-git/go-billy/v5 v5.6.0 h1:w2hPNtoehvJIxR00Vb4xX94qHQi/ApZfX+nBE2Cjio8=
|
||||
github.com/go-git/go-billy/v5 v5.6.0/go.mod h1:sFDq7xD3fn3E0GOwUSZqHo9lrkmx8xJhA0ZrfvjBRGM=
|
||||
github.com/go-git/go-billy/v5 v5.6.2 h1:6Q86EsPXMa7c3YZ3aLAQsMA0VlWmy43r6FHqa/UNbRM=
|
||||
github.com/go-git/go-billy/v5 v5.6.2/go.mod h1:rcFC2rAsp/erv7CMz9GczHcuD0D32fWzH+MJAU+jaUU=
|
||||
github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4=
|
||||
github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII=
|
||||
github.com/go-git/go-git/v5 v5.13.0 h1:vLn5wlGIh/X78El6r3Jr+30W16Blk0CTcxTYcYPWi5E=
|
||||
github.com/go-git/go-git/v5 v5.13.0/go.mod h1:Wjo7/JyVKtQgUNdXYXIepzWfJQkUEIGvkvVkiXRR/zw=
|
||||
github.com/go-git/go-git/v5 v5.13.2 h1:7O7xvsK7K+rZPKW6AQR1YyNhfywkv7B8/FsP3ki6Zv0=
|
||||
github.com/go-git/go-git/v5 v5.13.2/go.mod h1:hWdW5P4YZRjmpGHwRH2v3zkWcNl6HeXaXQEMGb3NJ9A=
|
||||
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||
@@ -113,33 +79,25 @@ github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/o
|
||||
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
|
||||
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
|
||||
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
|
||||
github.com/go-playground/validator/v10 v10.22.1 h1:40JcKH+bBNGFczGuoBYgX4I6m/i27HYW8P9FDk5PbgA=
|
||||
github.com/go-playground/validator/v10 v10.22.1/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
|
||||
github.com/go-playground/validator/v10 v10.25.0 h1:5Dh7cjvzR7BRZadnsVOzPhWsrwUr0nmsZJxEAnFLNO8=
|
||||
github.com/go-playground/validator/v10 v10.25.0/go.mod h1:GGzBIJMuE98Ic/kJsBXbz1x/7cByt++cQ+YOuDM5wus=
|
||||
github.com/go-shiori/dom v0.0.0-20230515143342-73569d674e1c h1:wpkoddUomPfHiOziHZixGO5ZBS73cKqVzZipfrLmO1w=
|
||||
github.com/go-shiori/dom v0.0.0-20230515143342-73569d674e1c/go.mod h1:oVDCh3qjJMLVUSILBRwrm+Bc6RNXGZYtoh9xdvf1ffM=
|
||||
github.com/go-shiori/go-readability v0.0.0-20241012063810-92284fa8a71f h1:cypj7SJh+47G9J3VCPdMzT3uWcXWAWDJA54ErTfOigI=
|
||||
github.com/go-shiori/go-readability v0.0.0-20241012063810-92284fa8a71f/go.mod h1:YWa00ashoPZMAOElrSn4E1cJErhDVU6PWAll4Hxzn+w=
|
||||
github.com/go-shiori/go-readability v0.0.0-20250217085726-9f5bf5ca7612 h1:BYLNYdZaepitbZreRIa9xeCQZocWmy/wj4cGIH0qyw0=
|
||||
github.com/go-shiori/go-readability v0.0.0-20250217085726-9f5bf5ca7612/go.mod h1:wgqthQa8SAYs0yyljVeCOQlZ027VW5CmLsbi9jWC08c=
|
||||
github.com/goccy/go-json v0.10.3 h1:KZ5WoDbxAIgm2HNbYckL0se1fHD6rz5j4ywS6ebzDqA=
|
||||
github.com/goccy/go-json v0.10.3/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
|
||||
github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4=
|
||||
github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
|
||||
github.com/gogs/chardet v0.0.0-20211120154057-b7413eaefb8f h1:3BSP1Tbs2djlpprl7wCLuiqMaUh5SJkkzI2gDs+FgLs=
|
||||
github.com/gogs/chardet v0.0.0-20211120154057-b7413eaefb8f/go.mod h1:Pcatq5tYkCW2Q6yrR2VRHlbHpZ/R4/7qyL1TCF7vl14=
|
||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE=
|
||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ=
|
||||
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw=
|
||||
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
|
||||
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
|
||||
github.com/google/generative-ai-go v0.19.0 h1:R71szggh8wHMCUlEMsW2A/3T+5LdEIkiaHSYgSpUgdg=
|
||||
github.com/google/generative-ai-go v0.19.0/go.mod h1:JYolL13VG7j79kM5BtHz4qwONHkeJQzOCkKXnpqtS/E=
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/s2a-go v0.1.9 h1:LGD7gtMgezd8a/Xak7mEWL0PjoTQFvpRudN895yqKW0=
|
||||
github.com/google/s2a-go v0.1.9/go.mod h1:YA0Ei2ZQL3acow2O62kdp9UlnvMmU7kA6Eutn0dXayM=
|
||||
@@ -160,8 +118,6 @@ github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHm
|
||||
github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4=
|
||||
github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM=
|
||||
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
||||
github.com/klauspost/cpuid/v2 v2.2.9 h1:66ze0taIn2H33fBvCkXuv9BmCwDfafmiIVpKV9kKGuY=
|
||||
github.com/klauspost/cpuid/v2 v2.2.9/go.mod h1:rqkxqrZ1EhYM9G+hXH7YdowN5R5RGN6NK4QwQ3WMXF8=
|
||||
github.com/klauspost/cpuid/v2 v2.2.10 h1:tBs3QSyvjDyFTq3uoc/9xFpCuOsJQFNPiAhYdw2skhE=
|
||||
github.com/klauspost/cpuid/v2 v2.2.10/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
|
||||
github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
|
||||
@@ -182,24 +138,16 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||
github.com/ollama/ollama v0.4.1 h1:41x4/L6HrsmQUqG9loN0q2643PHkLpblIlVqXAdByWs=
|
||||
github.com/ollama/ollama v0.4.1/go.mod h1:QDxM/t2teuubbfN/FT2pBRMPF0K1N3IakgT1OZBD4NY=
|
||||
github.com/ollama/ollama v0.5.12 h1:qM+k/ozyHLJzEQoAEPrUQ0qXqsgDEEdpIVwuwScrd2U=
|
||||
github.com/ollama/ollama v0.5.12/go.mod h1:ibdmDvb/TjKY1OArBWIazL3pd1DHTk8eG2MMjEkWhiI=
|
||||
github.com/onsi/gomega v1.34.1 h1:EUMJIKUjM8sKjYbtxQI9A4z2o+rruxnzNvpknOXie6k=
|
||||
github.com/onsi/gomega v1.34.1/go.mod h1:kU1QgUvBDLXBJq618Xvm2LUX6rSAfRaFRTcdOeDLwwY=
|
||||
github.com/otiai10/copy v1.14.0 h1:dCI/t1iTdYGtkvCuBG2BgR6KZa83PTclw4U5n2wAllU=
|
||||
github.com/otiai10/copy v1.14.0/go.mod h1:ECfuL02W+/FkTWZWgQqXPWZgW9oeKCSQ5qVfSc4qc4w=
|
||||
github.com/otiai10/copy v1.14.1 h1:5/7E6qsUMBaH5AnQ0sSLzzTg1oTECmcCmT6lvF45Na8=
|
||||
github.com/otiai10/copy v1.14.1/go.mod h1:oQwrEDDOci3IM8dJF0d8+jnbfPDllW6vUjNc3DoZm9I=
|
||||
github.com/otiai10/mint v1.5.1 h1:XaPLeE+9vGbuyEHem1JNk3bYc7KKqyI/na0/mLd/Kks=
|
||||
github.com/otiai10/mint v1.5.1/go.mod h1:MJm72SBthJjz8qhefc4z1PYEieWmy8Bku7CjcAqyUSM=
|
||||
github.com/otiai10/mint v1.6.3 h1:87qsV/aw1F5as1eH1zS/yqHY85ANKVMgkDrf9rcxbQs=
|
||||
github.com/otiai10/mint v1.6.3/go.mod h1:MJm72SBthJjz8qhefc4z1PYEieWmy8Bku7CjcAqyUSM=
|
||||
github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M=
|
||||
github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc=
|
||||
github.com/pjbgf/sha1cd v0.3.0 h1:4D5XXmUUBUl/xQ6IjCkEAbqXskkq/4O7LmGn0AqMDs4=
|
||||
github.com/pjbgf/sha1cd v0.3.0/go.mod h1:nZ1rrWOcGJ5uZgEEVL1VUM9iRQiZvWdbZjkKyFzPPsI=
|
||||
github.com/pjbgf/sha1cd v0.3.2 h1:a9wb0bp1oC2TGwStyn0Umc/IGKQnEgF0vVaZ8QF8eo4=
|
||||
github.com/pjbgf/sha1cd v0.3.2/go.mod h1:zQWigSxVmsHEZow5qaLtPYxpcKMMQpa09ixqBxuCS6A=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
@@ -209,20 +157,14 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN
|
||||
github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||
github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
|
||||
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
|
||||
github.com/samber/lo v1.47.0 h1:z7RynLwP5nbyRscyvcD043DWYoOcYRv3mV8lBeqOCLc=
|
||||
github.com/samber/lo v1.47.0/go.mod h1:RmDH9Ct32Qy3gduHQuKJ3gW1fMHAnE/fAzQuf6He5cU=
|
||||
github.com/samber/lo v1.49.1 h1:4BIFyVfuQSEpluc7Fua+j1NolZHiEHEpaSEKdsH0tew=
|
||||
github.com/samber/lo v1.49.1/go.mod h1:dO6KHFzUKXgP8LDhU0oI8d2hekjXnGOu0DB8Jecxd6o=
|
||||
github.com/sashabaranov/go-openai v1.35.6 h1:oi0rwCvyxMxgFALDGnyqFTyCJm6n72OnEG3sybIFR0g=
|
||||
github.com/sashabaranov/go-openai v1.35.6/go.mod h1:lj5b/K+zjTSFxVLijLSTDZuP7adOgerWeFyZLUhAKRg=
|
||||
github.com/sashabaranov/go-openai v1.38.0 h1:hNN5uolKwdbpiqOn7l+Z2alch/0n0rSFyg4n+GZxR5k=
|
||||
github.com/sashabaranov/go-openai v1.38.0/go.mod h1:lj5b/K+zjTSFxVLijLSTDZuP7adOgerWeFyZLUhAKRg=
|
||||
github.com/scylladb/termtables v0.0.0-20191203121021-c4c0b6d42ff4/go.mod h1:C1a7PQSMz9NShzorzCiG2fk9+xuCgLkPeCvMHYR2OWg=
|
||||
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8=
|
||||
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4=
|
||||
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
|
||||
github.com/skeema/knownhosts v1.3.0 h1:AM+y0rI04VksttfwjkSTNQorvGqmwATnvnAHpSgc0LY=
|
||||
github.com/skeema/knownhosts v1.3.0/go.mod h1:sPINvnADmT/qYH1kfv+ePMmOBTH6Tbl7b5LvTDjFK7M=
|
||||
github.com/skeema/knownhosts v1.3.1 h1:X2osQ+RAjK76shCbvhHHHVl3ZlgDm8apHEHFqRjnBY8=
|
||||
github.com/skeema/knownhosts v1.3.1/go.mod h1:r7KTdC8l4uxWRyK2TpQZ/1o5HaSzh06ePQNxPwTcfiY=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
@@ -241,8 +183,6 @@ github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXl
|
||||
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
|
||||
github.com/tidwall/gjson v1.14.4 h1:uo0p8EbA09J7RQaflQ1aBRffTR7xedD2bcIVSYxLnkM=
|
||||
github.com/tidwall/gjson v1.14.4/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
|
||||
github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY=
|
||||
github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
|
||||
github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
|
||||
@@ -261,12 +201,8 @@ github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
|
||||
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.58.0 h1:PS8wXpbyaDJQ2VDHHncMe9Vct0Zn1fEjpsjrLxGJoSc=
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.58.0/go.mod h1:HDBUsEjOuRC0EzKZ1bSaRGZWUBAzo+MhAcUUORSr4D0=
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.59.0 h1:rgMkmiGfix9vFJDcDi1PK8WEQP4FLQwLDfhp5ZLpFeE=
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.59.0/go.mod h1:ijPqXp5P6IRRByFVVg9DY8P5HkxkHE5ARIa+86aXPf4=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.58.0 h1:yd02MEjBdJkG3uabWP9apV+OuWRIXGDuJEUJbOHmCFU=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.58.0/go.mod h1:umTcuxiv1n/s/S6/c2AT/g2CQ7u5C59sHDNmfSwgz7Q=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.59.0 h1:CV7UdSGJt/Ao6Gp4CXckLxVRRsRgDHoI8XjbL3PDl8s=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.59.0/go.mod h1:FRmFuRJfag1IZ2dPkHnEoSFVgTVPUd2qf5Vi69hLb8I=
|
||||
go.opentelemetry.io/otel v1.34.0 h1:zRLXxLCgL1WyKsPVrgbSdMN4c0FMkDAskSTQP+0hdUY=
|
||||
@@ -279,8 +215,6 @@ go.opentelemetry.io/otel/sdk/metric v1.32.0 h1:rZvFnvmvawYb0alrYkjraqJq0Z4ZUJAiy
|
||||
go.opentelemetry.io/otel/sdk/metric v1.32.0/go.mod h1:PWeZlq0zt9YkYAp3gjKZ0eicRYvOh1Gd+X99x6GHpCQ=
|
||||
go.opentelemetry.io/otel/trace v1.34.0 h1:+ouXS2V8Rd4hp4580a8q23bg0azF2nI8cqLYnC8mh/k=
|
||||
go.opentelemetry.io/otel/trace v1.34.0/go.mod h1:Svm7lSjQD7kG7KJ/MUHPVXSDGz2OX4h0M2jHBhmSfRE=
|
||||
golang.org/x/arch v0.12.0 h1:UsYJhbzPYGsT0HbEdmYcqtCv8UNGvnaL561NnIUvaKg=
|
||||
golang.org/x/arch v0.12.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
|
||||
golang.org/x/arch v0.14.0 h1:z9JUEZWr8x4rR0OU6c4/4t6E6jOZ8/QBS2bBYBm4tx4=
|
||||
golang.org/x/arch v0.14.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
@@ -290,8 +224,6 @@ golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliY
|
||||
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
|
||||
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
|
||||
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
|
||||
golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc=
|
||||
golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc=
|
||||
golang.org/x/crypto v0.35.0 h1:b15kiHdrGCHrP6LvwaQ3c03kgNhhiMgvlhxHQhmg2Xs=
|
||||
golang.org/x/crypto v0.35.0/go.mod h1:dy7dXNW32cAb/6/PRuTNsix8T+vJAqvuIy5Bli/x0YQ=
|
||||
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8=
|
||||
@@ -307,18 +239,13 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v
|
||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
|
||||
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
||||
golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
|
||||
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
|
||||
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
|
||||
golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4=
|
||||
golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0=
|
||||
golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k=
|
||||
golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8=
|
||||
golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk=
|
||||
golang.org/x/oauth2 v0.25.0 h1:CY4y7XT9v0cRI9oupztF8AgiIu99L/ksR/Xp/6jrZ70=
|
||||
golang.org/x/oauth2 v0.25.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
|
||||
golang.org/x/net v0.36.0 h1:vWF2fRbw4qslQsQzgFqZff+BItCvGFQqKzKIzx1rmoA=
|
||||
golang.org/x/net v0.36.0/go.mod h1:bFmbeoIPfrw4sMHNhb4J9f6+tPziuGjq7Jk/38fxi1I=
|
||||
golang.org/x/oauth2 v0.27.0 h1:da9Vo7/tDv5RH/7nZDz1eMGS/q1Vv1N/7FCrBhI9I3M=
|
||||
golang.org/x/oauth2 v0.27.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
@@ -327,7 +254,6 @@ golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
|
||||
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ=
|
||||
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w=
|
||||
golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
@@ -342,28 +268,24 @@ golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU=
|
||||
golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
|
||||
golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||
golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY=
|
||||
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
|
||||
golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
|
||||
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
|
||||
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
|
||||
golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM=
|
||||
golang.org/x/term v0.28.0 h1:/Ts8HFuMR2E6IP/jlo7QVLZHggjKQbhu/7H0LJFr3Gg=
|
||||
golang.org/x/term v0.28.0/go.mod h1:Sw/lC2IAUZ92udQNf3WodGtn4k/XoLyZoh8v/8uiwek=
|
||||
golang.org/x/term v0.29.0 h1:L6pJp37ocefwRRtYPKSWOWzOtWSxVajvz2ldH/xi3iU=
|
||||
golang.org/x/term v0.29.0/go.mod h1:6bl4lRlvVuDgSf3179VpIxBF0o10JUpXWOnI7nErv7s=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
@@ -373,12 +295,9 @@ golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
|
||||
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
|
||||
golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM=
|
||||
golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY=
|
||||
golang.org/x/time v0.9.0 h1:EsRrnYcQiGH+5FfbgvV4AP7qEZstoyrHB0DzarOQ4ZY=
|
||||
golang.org/x/time v0.9.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||
golang.org/x/time v0.10.0 h1:3usCWA8tQn0L8+hFJQNgzpWbd89begxN66o1Ojdn5L4=
|
||||
golang.org/x/time v0.10.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
@@ -388,22 +307,14 @@ golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
|
||||
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/api v0.220.0 h1:3oMI4gdBgB72WFVwE1nerDD8W3HUOS4kypK6rRLbGns=
|
||||
google.golang.org/api v0.220.0/go.mod h1:26ZAlY6aN/8WgpCzjPNy18QpYaz7Zgg1h0qe1GkZEmY=
|
||||
google.golang.org/api v0.223.0 h1:JUTaWEriXmEy5AhvdMgksGGPEFsYfUKaPEYXd4c3Wvc=
|
||||
google.golang.org/api v0.223.0/go.mod h1:C+RS7Z+dDwds2b+zoAk5hN/eSfsiCn0UDrYof/M4d2M=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20241209162323-e6fa225c2576 h1:CkkIfIt50+lT6NHAVoRYEyAvQGFM7xEwXUUywFvEb3Q=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20241209162323-e6fa225c2576/go.mod h1:1R3kvZ1dtP3+4p4d3G8uJ8rFk/fWlScl38vanWACI08=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20250224174004-546df14abb99 h1:ilJhrCga0AptpJZXmUYG4MCrx/zf3l1okuYz7YK9PPw=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20250224174004-546df14abb99/go.mod h1:Xsh8gBVxGCcbV8ZeTB9wI5XPyZ5RvC6V3CTeeplHbiA=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250127172529-29210b9bc287 h1:J1H9f+LEdWAfHcez/4cvaVBox7cOYT+IU6rgqj5x++8=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250127172529-29210b9bc287/go.mod h1:8BS3B93F/U1juMFq9+EDk+qOT5CO1R9IzXxG3PTqiRk=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250224174004-546df14abb99 h1:ZSlhAUqC4r8TPzqLXQ0m3upBNZeF+Y8jQ3c4CR3Ujms=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250224174004-546df14abb99/go.mod h1:LuRYeWDFV6WOn90g357N17oMCaxpgCnbi/44qJvDn2I=
|
||||
google.golang.org/grpc v1.70.0 h1:pWFv03aZoHzlRKHWicjsZytKAiYCtNS0dHbXnIdq7jQ=
|
||||
google.golang.org/grpc v1.70.0/go.mod h1:ofIJqVKDXx/JiXrwr2IG4/zwdH9txy3IlF40RmcJSQw=
|
||||
google.golang.org/protobuf v1.36.4 h1:6A3ZDJHn/eNqc1i+IdefRzy/9PokBTPvcqMySR7NNIM=
|
||||
google.golang.org/protobuf v1.36.4/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
|
||||
google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM=
|
||||
google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
|
||||
@@ -55,9 +55,6 @@ schema = 3
|
||||
[mod."github.com/cloudwego/base64x"]
|
||||
version = "v0.1.5"
|
||||
hash = "sha256-MyUYTveN48DhnL8mwAgCRuMExLct98uzSPsmYlfaa4I="
|
||||
[mod."github.com/cloudwego/iasm"]
|
||||
version = "v0.2.0"
|
||||
hash = "sha256-TzIP2N3HOesXrKACsRr/ShcoqttwPGZPckIepsTyHOA="
|
||||
[mod."github.com/cyphar/filepath-securejoin"]
|
||||
version = "v0.4.1"
|
||||
hash = "sha256-NOV6MfbkcQbfhNmfADQw2SJmZ6q1nw0wwg8Pm2tf2DM="
|
||||
@@ -245,8 +242,8 @@ schema = 3
|
||||
version = "v0.35.0"
|
||||
hash = "sha256-XT1VU0+m1nZbhrMYXN2+eaKBlScfiT4bCBgXu4mfa1Q="
|
||||
[mod."golang.org/x/net"]
|
||||
version = "v0.35.0"
|
||||
hash = "sha256-kCLhhvzHQCvUqC8kGhgMbVLUROG4ZeZNVGOVVv6tSAE="
|
||||
version = "v0.36.0"
|
||||
hash = "sha256-2c9AvnizBMCaqKPzwF2IMlRfvjRNDFmxIYhrgZjhOm0="
|
||||
[mod."golang.org/x/oauth2"]
|
||||
version = "v0.27.0"
|
||||
hash = "sha256-TBKV2c/m0SgPqrJSE0ltJXfImrYPafNuziLN25jgsYY="
|
||||
|
||||
@@ -1 +1 @@
|
||||
"1.4.145"
|
||||
"1.4.168"
|
||||
|
||||
@@ -24,7 +24,7 @@ Extract at least basic information about the malware.
|
||||
Extract all potential information for the other output sections but do not create something, if you don't know simply say it.
|
||||
Do not give warnings or notes; only output the requested sections.
|
||||
You use bulleted lists for output, not numbered lists.
|
||||
Do not repeat ideas, facts, or resources.
|
||||
Do not repeat references.
|
||||
Do not start items with the same opening words.
|
||||
Ensure you follow ALL these instructions when creating your output.
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@ Take a step back and think step-by-step about how to achieve the best possible r
|
||||
- Extract at least 10 items for the other output sections.
|
||||
- Do not give warnings or notes; only output the requested sections.
|
||||
- You use bulleted lists for output, not numbered lists.
|
||||
- Do not repeat ideas, quotes, facts, or resources.
|
||||
- Do not repeat trends, statistics, quotes, or references.
|
||||
- Do not start items with the same opening words.
|
||||
- Ensure you follow ALL these instructions when creating your output.
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ Take a step back and think step-by-step about how to achieve the best possible r
|
||||
- Extract at least 20 TRENDS from the content.
|
||||
- Do not give warnings or notes; only output the requested sections.
|
||||
- You use bulleted lists for output, not numbered lists.
|
||||
- Do not repeat ideas, quotes, facts, or resources.
|
||||
- Do not repeat trends.
|
||||
- Do not start items with the same opening words.
|
||||
- Ensure you follow ALL these instructions when creating your output.
|
||||
|
||||
|
||||
@@ -44,7 +44,7 @@ Do not give warnings or notes; only output the requested sections.
|
||||
|
||||
You use bulleted lists for output, not numbered lists.
|
||||
|
||||
Do not repeat ideas, quotes, facts, or resources.
|
||||
Do not repeat ideas, habits, facts, or insights.
|
||||
|
||||
Do not start items with the same opening words.
|
||||
|
||||
|
||||
85
patterns/create_coding_feature/README.md
Normal file
85
patterns/create_coding_feature/README.md
Normal file
@@ -0,0 +1,85 @@
|
||||
# Create Coding Feature
|
||||
|
||||
Generate code changes to an existing coding project using AI.
|
||||
|
||||
## Installation
|
||||
|
||||
After installing the `code_helper` binary:
|
||||
|
||||
```bash
|
||||
go install github.com/danielmiessler/fabric/plugins/tools/code_helper@latest
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
The create_coding_feature allows you to apply AI-suggested code changes directly to your project files. Use it like this:
|
||||
|
||||
```bash
|
||||
code_helper [project_directory] "[instructions for code changes]" | fabric --pattern create_coding_feature
|
||||
```
|
||||
|
||||
For example:
|
||||
|
||||
```bash
|
||||
code_helper . "Create a simple Hello World C program in file main.c" | fabric --pattern create_coding_feature
|
||||
```
|
||||
|
||||
## How It Works
|
||||
|
||||
1. `code_helper` scans your project directory and creates a JSON representation
|
||||
2. The AI model analyzes your project structure and instructions
|
||||
3. AI generates file changes in a standard format
|
||||
4. Fabric parses these changes and prompts you to confirm
|
||||
5. If confirmed, changes are applied to your project files
|
||||
|
||||
## Example Workflow
|
||||
|
||||
```bash
|
||||
# Request AI to create a Hello World program
|
||||
code_helper . "Create a simple Hello World C program in file main.c" | fabric --pattern create_coding_feature
|
||||
|
||||
# Review the changes made to your project
|
||||
git diff
|
||||
|
||||
# Run/test the code
|
||||
make check
|
||||
|
||||
# If satisfied, commit the changes
|
||||
git add <changed files>
|
||||
git commit -s -m "Add Hello World program"
|
||||
```
|
||||
|
||||
### Security Enhancement Example
|
||||
|
||||
```bash
|
||||
code_helper . "Ensure that all user input is validated and sanitized before being used in the program." | fabric --pattern create_coding_feature
|
||||
git diff
|
||||
make check
|
||||
git add <changed files>
|
||||
git commit -s -m "Security fixes: Input validation"
|
||||
```
|
||||
|
||||
## Important Notes
|
||||
|
||||
- **Always run from project root**: File changes are applied relative to your current directory
|
||||
- **Use with version control**: It's highly recommended to use this feature in a clean git repository so you can review and revert
|
||||
changes. You will *not* be asked to approve each change.
|
||||
|
||||
## Security Features
|
||||
|
||||
- Path validation to prevent directory traversal attempts
|
||||
- File size limits to prevent excessive file generation
|
||||
- Operation validation (only create/update operations allowed)
|
||||
- User confirmation required before applying changes
|
||||
|
||||
## Suggestions for Future Improvements
|
||||
|
||||
- Add a dry-run mode to show changes without applying them
|
||||
- Enhance reporting with detailed change summaries
|
||||
- Support for file deletions with safety checks
|
||||
- Add configuration options for project-specific rules
|
||||
- Provide rollback capability for applied changes
|
||||
- Add support for project-specific validation rules
|
||||
- Enhance script generation with conditional logic
|
||||
- Include detailed logging for API responses
|
||||
- Consider adding a GUI for ease of use
|
||||
117
patterns/create_coding_feature/system.md
Normal file
117
patterns/create_coding_feature/system.md
Normal file
@@ -0,0 +1,117 @@
|
||||
# IDENTITY and PURPOSE
|
||||
|
||||
You are an elite programmer. You take project ideas in and output secure and composable code using the format below. You always use the latest technology and best practices.
|
||||
|
||||
Take a deep breath and think step by step about how to best accomplish this goal using the following steps.
|
||||
|
||||
Input is a JSON file with the following format:
|
||||
|
||||
Example input:
|
||||
|
||||
```json
|
||||
[
|
||||
{
|
||||
"type": "directory",
|
||||
"name": ".",
|
||||
"contents": [
|
||||
{
|
||||
"type": "file",
|
||||
"name": "README.md",
|
||||
"content": "This is the README.md file content"
|
||||
},
|
||||
{
|
||||
"type": "file",
|
||||
"name": "system.md",
|
||||
"content": "This is the system.md file contents"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "report",
|
||||
"directories": 1,
|
||||
"files": 5
|
||||
},
|
||||
{
|
||||
"type": "instructions",
|
||||
"name": "code_change_instructions",
|
||||
"details": "Update README and refactor main.py"
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
The object with `"type": "instructions"`, and field `"details"` contains the
|
||||
for the instructions for the suggested code changes. The `"name"` field is always
|
||||
`"code_change_instructions"`
|
||||
|
||||
The `"details"` field above, with type `"instructions"` contains the instructions for the suggested code changes.
|
||||
|
||||
## File Management Interface Instructions
|
||||
|
||||
You have access to a powerful file management system with the following capabilities:
|
||||
|
||||
### File Creation and Modification
|
||||
|
||||
- Use the **EXACT** JSON format below to define files that you want to be changed
|
||||
- If the file listed does not exist, it will be created
|
||||
- If a directory listed does not exist, it will be created
|
||||
- If the file already exists, it will be overwritten
|
||||
- It is **not possible** to delete files
|
||||
|
||||
```plaintext
|
||||
__CREATE_CODING_FEATURE_FILE_CHANGES__
|
||||
[
|
||||
{
|
||||
"operation": "create",
|
||||
"path": "README.md",
|
||||
"content": "This is the new README.md file content"
|
||||
},
|
||||
{
|
||||
"operation": "update",
|
||||
"path": "src/main.c",
|
||||
"content": "int main(){return 0;}"
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
### Important Guidelines
|
||||
|
||||
- Always use relative paths from the project root
|
||||
- Provide complete, functional code when creating or modifying files
|
||||
- Be precise and concise in your file operations
|
||||
- Never create files outside of the project root
|
||||
|
||||
### Constraints
|
||||
|
||||
- Do not attempt to read or modify files outside the project root directory.
|
||||
- Ensure code follows best practices and is production-ready.
|
||||
- Handle potential errors gracefully in your code suggestions.
|
||||
- Do not trust external input to applications, assume users are malicious.
|
||||
|
||||
### Workflow
|
||||
|
||||
1. Analyze the user's request
|
||||
2. Determine necessary file operations
|
||||
3. Provide clear, executable file creation/modification instructions
|
||||
4. Explain the purpose and functionality of proposed changes
|
||||
|
||||
## Output Sections
|
||||
|
||||
- Output a summary of the file changes
|
||||
- Output directory and file changes according to File Management Interface Instructions, in a json array marked by `__CREATE_CODING_FEATURE_FILE_CHANGES__`
|
||||
- Be exact in the `__CREATE_CODING_FEATURE_FILE_CHANGES__` section, and do not deviate from the proposed JSON format.
|
||||
- **never** omit the `__CREATE_CODING_FEATURE_FILE_CHANGES__` section.
|
||||
- If the proposed changes change how the project is built and installed, document these changes in the projects README.md
|
||||
- Implement build configurations changes if needed, prefer ninja if nothing already exists in the project, or is otherwise specified.
|
||||
- Document new dependencies according to best practices for the language used in the project.
|
||||
- Do not output sections that were not explicitly requested.
|
||||
|
||||
## Output Instructions
|
||||
|
||||
- Create the output using the formatting above
|
||||
- Do not output warnings or notes—just the requested sections.
|
||||
- Do not repeat items in the output sections
|
||||
- Be open to suggestions and output file system changes according to the JSON API described above
|
||||
- Output code that has comments for every step
|
||||
- Do not use deprecated features
|
||||
|
||||
## INPUT
|
||||
14
patterns/create_flash_cards/system.md
Normal file
14
patterns/create_flash_cards/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).
|
||||
@@ -27,7 +27,7 @@ Take a step back and think step-by-step about how to achieve the best possible r
|
||||
- Extract at least 10 items for the other output sections.
|
||||
- Do not give warnings or notes; only output the requested sections.
|
||||
- You use bulleted lists for output, not numbered lists.
|
||||
- Do not repeat ideas, quotes, facts, or resources.
|
||||
- Do not repeat insights, trends, or quotes.
|
||||
- Do not start items with the same opening words.
|
||||
- Ensure you follow ALL these instructions when creating your output.
|
||||
|
||||
|
||||
@@ -33,7 +33,7 @@ Take a step back and think step-by-step about how to achieve the best possible r
|
||||
- Extract at least 10 items for the other output sections.
|
||||
- Do not give warnings or notes; only output the requested sections.
|
||||
- You use bulleted lists for output, not numbered lists.
|
||||
- Do not repeat ideas, quotes, facts, or resources.
|
||||
- Do not repeat quotes, or references.
|
||||
- Do not start items with the same opening words.
|
||||
- Ensure you follow ALL these instructions when creating your output.
|
||||
|
||||
|
||||
@@ -25,5 +25,5 @@ Take a step back and think step by step about how to achieve the best result pos
|
||||
1. You only output Markdown.
|
||||
2. Do not give warnings or notes; only output the requested sections.
|
||||
3. You use numbered lists, not bullets.
|
||||
4. Do not repeat ideas, quotes, facts, or resources.
|
||||
4. Do not repeat ideas, quotes, habits, facts, or references.
|
||||
5. Do not start items with the same opening words.
|
||||
|
||||
@@ -24,7 +24,7 @@ Take a step back and think step-by-step about how to achieve the best possible r
|
||||
- Extract at least 10 items for the other output sections.
|
||||
- Do not give warnings or notes; only output the requested sections.
|
||||
- You use bulleted lists for output, not numbered lists.
|
||||
- Do not repeat ideas, quotes, facts, or resources.
|
||||
- Do not repeat ideas, quotes, facts, or references.
|
||||
- Do not start items with the same opening words.
|
||||
- Ensure you follow ALL these instructions when creating your output.
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ Take a deep breath and think step by step about how to achieve the best result p
|
||||
1. You only output Markdown.
|
||||
2. Do not give warnings or notes; only output the requested sections.
|
||||
3. You use numbered lists, not bullets.
|
||||
4. Do not repeat ideas, quotes, facts, or resources.
|
||||
4. Do not repeat ideas.
|
||||
5. Do not start items in the lists with the same opening words.
|
||||
|
||||
# INPUT:
|
||||
|
||||
@@ -24,7 +24,7 @@ Take a step back and think step-by-step about how to achieve the best possible r
|
||||
|
||||
- You use bulleted lists for output, not numbered lists.
|
||||
|
||||
- Do not repeat ideas, quotes, facts, or resources.
|
||||
- Do not repeat vulnerabilities, or references.
|
||||
|
||||
- Do not start items with the same opening words.
|
||||
|
||||
|
||||
@@ -175,7 +175,7 @@ END OUTPUT EXAMPLE
|
||||
|
||||
- You use bulleted lists for output, not numbered lists.
|
||||
|
||||
- Do not repeat ideas, quotes, facts, or resources.
|
||||
- Do not repeat insights.
|
||||
|
||||
- Do not start items with the same opening words.
|
||||
|
||||
|
||||
@@ -16,11 +16,10 @@ You create bullet points that capture the joke and punchline.
|
||||
|
||||
- You use bulleted lists for output, not numbered lists.
|
||||
|
||||
- Do not repeat jokes, quotes, facts, or resources.
|
||||
- Do not repeat jokes.
|
||||
|
||||
- Ensure you follow ALL these instructions when creating your output.
|
||||
|
||||
|
||||
# INPUT
|
||||
|
||||
INPUT:
|
||||
|
||||
21
patterns/extract_main_activities/system.md
Normal file
21
patterns/extract_main_activities/system.md
Normal file
@@ -0,0 +1,21 @@
|
||||
# IDENTITY
|
||||
|
||||
You are an expert activity extracting AI with a 24,221 IQ. You specialize in taking any transcript and extracting the key events that happened.
|
||||
|
||||
# STEPS
|
||||
|
||||
- Fully understand the input transcript or log.
|
||||
|
||||
- Extract the key events and map them on a 24KM x 24KM virtual whiteboard.
|
||||
|
||||
- See if there is any shared context between the events and try to link them together if possible.
|
||||
|
||||
# OUTPUT
|
||||
|
||||
- Write a 16 word summary sentence of the activity.
|
||||
|
||||
- Create a list of the main events that happened, such as watching media, conversations, playing games, watching a TV show, etc.
|
||||
|
||||
# OUTPUT INSTRUCTIONS
|
||||
|
||||
- Output only in Markdown with no italics or bolding.
|
||||
@@ -18,7 +18,6 @@ Take a step back and think step-by-step about how to achieve the best possible r
|
||||
|
||||
- Only output Markdown.
|
||||
- Do not give warnings or notes; only output the requested sections.
|
||||
- Do not repeat ideas, quotes, facts, or resources.
|
||||
- Do not start items with the same opening words.
|
||||
- Ensure you follow ALL these instructions when creating your output.
|
||||
|
||||
|
||||
@@ -34,7 +34,7 @@ Take a step back and think step-by-step about how to achieve the best possible r
|
||||
- Write in the style of someone giving helpful analysis finding patterns
|
||||
- Do not give warnings or notes; only output the requested sections.
|
||||
- You use bulleted lists for output, not numbered lists.
|
||||
- Do not repeat ideas, quotes, facts, or resources.
|
||||
- Do not repeat patterns.
|
||||
- Do not start items with the same opening words.
|
||||
- Ensure you follow ALL these instructions when creating your output.
|
||||
|
||||
|
||||
@@ -25,5 +25,5 @@ Take a step back and think step by step about how to achieve the best result pos
|
||||
1. You only output Markdown.
|
||||
2. Do not give warnings or notes; only output the requested sections.
|
||||
3. You use numbered lists, not bullets.
|
||||
4. Do not repeat ideas, quotes, facts, or resources.
|
||||
4. Do not repeat ideas, quotes, habits, facts, or references.
|
||||
5. Do not start items with the same opening words.
|
||||
|
||||
@@ -20,7 +20,7 @@ Take a step back and think step-by-step about how to achieve the best possible r
|
||||
|
||||
- You use bulleted lists for output, not numbered lists.
|
||||
|
||||
- Do not repeat ideas, quotes, facts, or resources.
|
||||
- Do not features.
|
||||
|
||||
- Do not start items with the same opening words.
|
||||
|
||||
|
||||
@@ -23,10 +23,10 @@ Take a step back and think step-by-step about how to achieve the best possible r
|
||||
|
||||
- You use bulleted lists for output, not numbered lists.
|
||||
|
||||
- Do not repeat ideas, quotes, facts, or resources.
|
||||
|
||||
- Do not start items with the same opening words.
|
||||
|
||||
- Do not repeat ingredients.
|
||||
|
||||
- Stick to the measurements, do not alter it.
|
||||
|
||||
- Ensure you follow ALL these instructions when creating your output.
|
||||
|
||||
@@ -25,5 +25,5 @@ Take a step back and think step by step about how to achieve the best result pos
|
||||
1. You only output Markdown.
|
||||
2. Do not give warnings or notes; only output the requested sections.
|
||||
3. You use numbered lists, not bullets.
|
||||
4. Do not repeat ideas, quotes, facts, or resources.
|
||||
4. Do not repeat ideas, quotes, habits, facts, or references.
|
||||
5. Do not start items with the same opening words.
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -48,7 +48,7 @@ Take a step back and think step-by-step about how to achieve the best possible r
|
||||
|
||||
- You use bulleted lists for output, not numbered lists.
|
||||
|
||||
- Do not repeat ideas, quotes, facts, or resources.
|
||||
- Do not repeat ideas, insights, quotes, habits, facts, or references.
|
||||
|
||||
- Do not start items with the same opening words.
|
||||
|
||||
|
||||
@@ -42,7 +42,7 @@ You are an advanced AI system that coordinates multiple teams of AI agents that
|
||||
|
||||
- All GENERALIST output agents should use bullets for their output, and sentences of 15-words.
|
||||
|
||||
- Agents should not repeat ideas, quotes, facts, or resources.
|
||||
- Agents should not repeat ideas, insights, quotes, habits, facts, or references.
|
||||
|
||||
- Agents should not start items with the same opening words.
|
||||
|
||||
|
||||
@@ -82,7 +82,7 @@ Think about the most interesting facts related to the content
|
||||
|
||||
- You use bulleted lists for output, not numbered lists.
|
||||
|
||||
- Do not repeat ideas, quotes, facts, or resources.
|
||||
- Do not repeat ideas, insights, quotes, habits, facts, or references.
|
||||
|
||||
- Do not start items with the same opening words.
|
||||
|
||||
|
||||
@@ -44,7 +44,7 @@ You extract surprising, insightful, and interesting information from text conten
|
||||
|
||||
- You use bulleted lists for output, not numbered lists.
|
||||
|
||||
- Do not repeat ideas, quotes, facts, or resources.
|
||||
- Do not repeat ideas, insights, quotes, habits, facts, or references.
|
||||
|
||||
- Do not start items with the same opening words.
|
||||
|
||||
|
||||
25
patterns/find_female_life_partner/system.md
Normal file
25
patterns/find_female_life_partner/system.md
Normal file
@@ -0,0 +1,25 @@
|
||||
# IDENTITY AND PURPOSE
|
||||
|
||||
You are a relationship and marriage and life happiness expert AI with a 4,227 IQ. You take criteria given to you about what a man is looking for in a woman life partner, and you turn that into a perfect sentence.
|
||||
|
||||
# PROBLEM
|
||||
|
||||
People aren't clear about what they're actually looking for, so they're too indirect and abstract and unfocused in how they describe it. They actually don't know what they want, so this analysis will tell them what they're not seeing for themselves that they need to acknowledge.
|
||||
|
||||
# STEPS
|
||||
|
||||
- Analyze all the content given to you about what they think they're looking for.
|
||||
|
||||
- Figure out what they're skirting around and not saying directly.
|
||||
|
||||
- Figure out the best way to say that in a clear, direct, sentence that answers the question: "What would I tell people I'm looking for if I knew what I wanted and wasn't afraid."
|
||||
|
||||
- Write the perfect 24-word sentence in these versions:
|
||||
|
||||
1. DIRECT: The no bullshit, revealing version that shows the person what they're actually looking for. Only 8 words in extremely straightforward language.
|
||||
2. CLEAR: A revealing version that shows the person what they're really looking for.
|
||||
3. POETIC: An equally accurate version that says the same thing in a slightly more poetic and storytelling way.
|
||||
|
||||
# OUTPUT INSTRUCTIONS
|
||||
|
||||
- Only output those two sentences, nothing else.
|
||||
@@ -215,7 +215,7 @@ Vacuous truth – a claim that is technically true but meaningless, in the form
|
||||
|
||||
- Don't use bold or italic formatting in the Markdown.
|
||||
|
||||
- Do no complain about the input data. Just do the task.
|
||||
- Do not complain about the input data. Just do the task.
|
||||
|
||||
# INPUT:
|
||||
|
||||
|
||||
@@ -31,7 +31,7 @@ Take a step back and think step-by-step about how to achieve the best possible r
|
||||
- Extract at least 10 items for the other output sections.
|
||||
- Do not give warnings or notes; only output the requested sections.
|
||||
- You use bulleted lists for output, not numbered lists.
|
||||
- Do not repeat ideas, quotes, facts, or resources.
|
||||
- Do not repeat quotes, or references.
|
||||
- Do not start items with the same opening words.
|
||||
- Ensure you follow ALL these instructions when creating your output.
|
||||
|
||||
|
||||
@@ -207,3 +207,4 @@ Brief one-line summary from AI analysis of what each pattern does.
|
||||
203. **write_nuclei_template_rule**: Generates Nuclei YAML templates for detecting vulnerabilities using HTTP requests, matchers, extractors, and dynamic data extraction.
|
||||
204. **write_pull-request**: Drafts detailed pull request descriptions, explaining changes, providing reasoning, and identifying potential bugs from the git diff command output.
|
||||
205. **write_semgrep_rule**: Creates accurate and working Semgrep rules based on input, following syntax guidelines and specific language considerations.
|
||||
206. **youtube_summary**: Create concise, timestamped Youtube video summaries that highlight key points.
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
|
||||
# Optional parameters:
|
||||
# @raycast.icon 🧠
|
||||
# @raycast.argument1 { "type": "text", "placeholder": "Input text", "optional": false, "percentEncoded": true}
|
||||
# @raycast.argument1 { "type": "text", "placeholder": "Input text", "optional": false, "percentEncoded": false}
|
||||
|
||||
# Documentation:
|
||||
# @raycast.description Run fabric -y on the input text of a YouTube video to get the transcript from.
|
||||
|
||||
@@ -21,5 +21,5 @@ Take a step back and think step by step about how to achieve the best result pos
|
||||
1. You only output Markdown.
|
||||
2. Do not give warnings or notes; only output the requested sections.
|
||||
3. You use numbered lists, not bullets.
|
||||
4. Do not repeat ideas, quotes, facts, or resources.
|
||||
4. Do not repeat ideas, or quotes.
|
||||
5. Do not start items with the same opening words.
|
||||
|
||||
@@ -60,13 +60,10 @@ Find the evidence each party would accept to change their mind.
|
||||
|
||||
- Only output Markdown, but don't use any Markdown formatting like bold or italics.
|
||||
|
||||
|
||||
- Do not give warnings or notes; only output the requested sections.
|
||||
|
||||
- You use bulleted lists for output, not numbered lists.
|
||||
|
||||
- Do not repeat ideas, quotes, facts, or resources.
|
||||
|
||||
- Do not start items with the same opening words.
|
||||
|
||||
- Ensure you follow ALL these instructions when creating your output.
|
||||
|
||||
@@ -35,7 +35,7 @@ Take a step back and think step-by-step about how to achieve the best possible r
|
||||
- Write CHALLENGES as 2-3 sentences.
|
||||
- Write NEXT STEPS as 2-3 sentences.
|
||||
- Do not give warnings or notes; only output the requested sections.
|
||||
- Do not repeat ideas, quotes, facts, or resources.
|
||||
- Do not repeat actionables, decisions, or challenges.
|
||||
- You use bulleted lists for output, not numbered lists.
|
||||
- Do not start items with the same opening words.
|
||||
- Ensure you follow ALL these instructions when creating your output.
|
||||
|
||||
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:
|
||||
@@ -13,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
|
||||
}
|
||||
@@ -20,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
|
||||
|
||||
15
plugins/ai/litellm/litellm.go
Normal file
15
plugins/ai/litellm/litellm.go
Normal file
@@ -0,0 +1,15 @@
|
||||
package litellm
|
||||
|
||||
import (
|
||||
"github.com/danielmiessler/fabric/plugins/ai/openai"
|
||||
)
|
||||
|
||||
func NewClient() (ret *Client) {
|
||||
ret = &Client{}
|
||||
ret.Client = openai.NewClientCompatible("LiteLLM", "http://localhost:4000", nil)
|
||||
return
|
||||
}
|
||||
|
||||
type Client struct {
|
||||
*openai.Client
|
||||
}
|
||||
@@ -32,15 +32,15 @@ func NewClientCompatible(vendorName string, defaultBaseUrl string, configureCust
|
||||
EnvNamePrefix: plugins.BuildEnvVariablePrefix(vendorName),
|
||||
ConfigureCustom: configureCustom,
|
||||
}
|
||||
ret.ApiBaseURL = ret.AddSetupQuestion("API Base URL", false)
|
||||
ret.ApiBaseURL.Value = defaultBaseUrl
|
||||
ret.ApiUrl = ret.AddSetupQuestionCustom("API URL", true,
|
||||
fmt.Sprintf("Enter your %v URL (as a reminder, it is usually %v')", vendorName, defaultBaseUrl))
|
||||
return
|
||||
}
|
||||
|
||||
// Client represents the LM Studio client.
|
||||
type Client struct {
|
||||
*plugins.PluginBase
|
||||
ApiBaseURL *plugins.SetupQuestion
|
||||
ApiUrl *plugins.SetupQuestion
|
||||
HttpClient *http.Client
|
||||
}
|
||||
|
||||
@@ -50,14 +50,9 @@ func (c *Client) configure() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Configure sets up the client configuration.
|
||||
func (c *Client) Configure() error {
|
||||
return c.ConfigureCustom()
|
||||
}
|
||||
|
||||
// ListModels returns a list of available models.
|
||||
func (c *Client) ListModels() ([]string, error) {
|
||||
url := fmt.Sprintf("%s/models", c.ApiBaseURL.Value)
|
||||
url := fmt.Sprintf("%s/models", c.ApiUrl.Value)
|
||||
|
||||
req, err := http.NewRequest("GET", url, nil)
|
||||
if err != nil {
|
||||
@@ -92,13 +87,8 @@ func (c *Client) ListModels() ([]string, error) {
|
||||
return models, nil
|
||||
}
|
||||
|
||||
// // SendStream sends a stream of messages (not implemented for LM Studio).
|
||||
// func (c *Client) SendStream(msgs []*goopenai.ChatCompletionMessage, opts *common.ChatOptions, channel chan string) error {
|
||||
// return fmt.Errorf("streaming is not currently supported for LM Studio")
|
||||
// }
|
||||
|
||||
func (c *Client) SendStream(msgs []*goopenai.ChatCompletionMessage, opts *common.ChatOptions, channel chan string) error {
|
||||
url := fmt.Sprintf("%s/chat/completions", c.ApiBaseURL.Value)
|
||||
func (c *Client) SendStream(msgs []*goopenai.ChatCompletionMessage, opts *common.ChatOptions, channel chan string) (err error) {
|
||||
url := fmt.Sprintf("%s/chat/completions", c.ApiUrl.Value)
|
||||
|
||||
payload := map[string]interface{}{
|
||||
"messages": msgs,
|
||||
@@ -106,85 +96,85 @@ func (c *Client) SendStream(msgs []*goopenai.ChatCompletionMessage, opts *common
|
||||
"stream": true, // Enable streaming
|
||||
}
|
||||
|
||||
jsonPayload, err := json.Marshal(payload)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to marshal payload: %w", err)
|
||||
var jsonPayload []byte
|
||||
if jsonPayload, err = json.Marshal(payload); err != nil {
|
||||
err = fmt.Errorf("failed to marshal payload: %w", err)
|
||||
return
|
||||
}
|
||||
|
||||
req, err := http.NewRequest("POST", url, bytes.NewBuffer(jsonPayload))
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create request: %w", err)
|
||||
var req *http.Request
|
||||
if req, err = http.NewRequest("POST", url, bytes.NewBuffer(jsonPayload)); err != nil {
|
||||
err = fmt.Errorf("failed to create request: %w", err)
|
||||
return
|
||||
}
|
||||
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
|
||||
resp, err := c.HttpClient.Do(req)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to send request: %w", err)
|
||||
var resp *http.Response
|
||||
if resp, err = c.HttpClient.Do(req); err != nil {
|
||||
err = fmt.Errorf("failed to send request: %w", err)
|
||||
return
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return fmt.Errorf("unexpected status code: %d", resp.StatusCode)
|
||||
err = fmt.Errorf("unexpected status code: %d", resp.StatusCode)
|
||||
return
|
||||
}
|
||||
|
||||
// Close channel when function exits
|
||||
defer close(channel)
|
||||
|
||||
reader := bufio.NewReader(resp.Body)
|
||||
for {
|
||||
line, err := reader.ReadBytes('\n')
|
||||
if err != nil {
|
||||
var line []byte
|
||||
if line, err = reader.ReadBytes('\n'); err != nil {
|
||||
if err == io.EOF {
|
||||
err = nil
|
||||
break
|
||||
}
|
||||
return fmt.Errorf("error reading response: %w", err)
|
||||
err = fmt.Errorf("error reading response: %w", err)
|
||||
return
|
||||
}
|
||||
|
||||
// Ignore empty lines
|
||||
if len(line) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
// Remove OpenAI-style prefix
|
||||
if bytes.HasPrefix(line, []byte("data: ")) {
|
||||
line = bytes.TrimPrefix(line, []byte("data: "))
|
||||
}
|
||||
|
||||
// Handle [DONE] signal
|
||||
if string(line) == "[DONE]" {
|
||||
break
|
||||
}
|
||||
|
||||
// Parse JSON response
|
||||
var result map[string]interface{}
|
||||
if err := json.Unmarshal(line, &result); err != nil {
|
||||
if err = json.Unmarshal(line, &result); err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
// Extract content from streaming chunks
|
||||
choices, ok := result["choices"].([]interface{})
|
||||
if !ok || len(choices) == 0 {
|
||||
var choices []interface{}
|
||||
var ok bool
|
||||
if choices, ok = result["choices"].([]interface{}); !ok || len(choices) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
delta, ok := choices[0].(map[string]interface{})["delta"].(map[string]interface{})
|
||||
if !ok {
|
||||
var delta map[string]interface{}
|
||||
if delta, ok = choices[0].(map[string]interface{})["delta"].(map[string]interface{}); !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
content, _ := delta["content"].(string)
|
||||
|
||||
// Send data to channel
|
||||
channel <- content
|
||||
var content string
|
||||
if content, _ = delta["content"].(string); content != "" {
|
||||
channel <- content
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
return
|
||||
}
|
||||
|
||||
// Send sends a single message and returns the response.
|
||||
func (c *Client) Send(ctx context.Context, msgs []*goopenai.ChatCompletionMessage, opts *common.ChatOptions) (string, error) {
|
||||
url := fmt.Sprintf("%s/chat/completions", c.ApiBaseURL.Value)
|
||||
func (c *Client) Send(ctx context.Context, msgs []*goopenai.ChatCompletionMessage, opts *common.ChatOptions) (content string, err error) {
|
||||
url := fmt.Sprintf("%s/chat/completions", c.ApiUrl.Value)
|
||||
|
||||
payload := map[string]interface{}{
|
||||
"messages": msgs,
|
||||
@@ -192,54 +182,61 @@ func (c *Client) Send(ctx context.Context, msgs []*goopenai.ChatCompletionMessag
|
||||
// Add other options from opts if supported by LM Studio
|
||||
}
|
||||
|
||||
jsonPayload, err := json.Marshal(payload)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to marshal payload: %w", err)
|
||||
var jsonPayload []byte
|
||||
if jsonPayload, err = json.Marshal(payload); err != nil {
|
||||
err = fmt.Errorf("failed to marshal payload: %w", err)
|
||||
return
|
||||
}
|
||||
|
||||
req, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewBuffer(jsonPayload))
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to create request: %w", err)
|
||||
var req *http.Request
|
||||
if req, err = http.NewRequestWithContext(ctx, "POST", url, bytes.NewBuffer(jsonPayload)); err != nil {
|
||||
err = fmt.Errorf("failed to create request: %w", err)
|
||||
return
|
||||
}
|
||||
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
|
||||
resp, err := c.HttpClient.Do(req)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to send request: %w", err)
|
||||
var resp *http.Response
|
||||
if resp, err = c.HttpClient.Do(req); err != nil {
|
||||
err = fmt.Errorf("failed to send request: %w", err)
|
||||
return
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return "", fmt.Errorf("unexpected status code: %d", resp.StatusCode)
|
||||
err = fmt.Errorf("unexpected status code: %d", resp.StatusCode)
|
||||
return
|
||||
}
|
||||
|
||||
var result map[string]interface{}
|
||||
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
|
||||
return "", fmt.Errorf("failed to decode response: %w", err)
|
||||
if err = json.NewDecoder(resp.Body).Decode(&result); err != nil {
|
||||
err = fmt.Errorf("failed to decode response: %w", err)
|
||||
return
|
||||
}
|
||||
|
||||
choices, ok := result["choices"].([]interface{})
|
||||
if !ok || len(choices) == 0 {
|
||||
return "", fmt.Errorf("invalid response format: missing or empty choices")
|
||||
var choices []interface{}
|
||||
var ok bool
|
||||
if choices, ok = result["choices"].([]interface{}); !ok || len(choices) == 0 {
|
||||
err = fmt.Errorf("invalid response format: missing or empty choices")
|
||||
return
|
||||
}
|
||||
|
||||
message, ok := choices[0].(map[string]interface{})["message"].(map[string]interface{})
|
||||
if !ok {
|
||||
return "", fmt.Errorf("invalid response format: missing message in first choice")
|
||||
var message map[string]interface{}
|
||||
if message, ok = choices[0].(map[string]interface{})["message"].(map[string]interface{}); !ok {
|
||||
err = fmt.Errorf("invalid response format: missing message in first choice")
|
||||
return
|
||||
}
|
||||
|
||||
content, ok := message["content"].(string)
|
||||
if !ok {
|
||||
return "", fmt.Errorf("invalid response format: missing or non-string content in message")
|
||||
if content, ok = message["content"].(string); !ok {
|
||||
err = fmt.Errorf("invalid response format: missing or non-string content in message")
|
||||
return
|
||||
}
|
||||
|
||||
return content, nil
|
||||
return
|
||||
}
|
||||
|
||||
// Complete sends a completion request and returns the response.
|
||||
func (c *Client) Complete(ctx context.Context, prompt string, opts *common.ChatOptions) (string, error) {
|
||||
url := fmt.Sprintf("%s/completions", c.ApiBaseURL.Value)
|
||||
func (c *Client) Complete(ctx context.Context, prompt string, opts *common.ChatOptions) (text string, err error) {
|
||||
url := fmt.Sprintf("%s/completions", c.ApiUrl.Value)
|
||||
|
||||
payload := map[string]interface{}{
|
||||
"prompt": prompt,
|
||||
@@ -247,49 +244,55 @@ func (c *Client) Complete(ctx context.Context, prompt string, opts *common.ChatO
|
||||
// Add other options from opts if supported by LM Studio
|
||||
}
|
||||
|
||||
jsonPayload, err := json.Marshal(payload)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to marshal payload: %w", err)
|
||||
var jsonPayload []byte
|
||||
if jsonPayload, err = json.Marshal(payload); err != nil {
|
||||
err = fmt.Errorf("failed to marshal payload: %w", err)
|
||||
return
|
||||
}
|
||||
|
||||
req, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewBuffer(jsonPayload))
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to create request: %w", err)
|
||||
var req *http.Request
|
||||
if req, err = http.NewRequestWithContext(ctx, "POST", url, bytes.NewBuffer(jsonPayload)); err != nil {
|
||||
err = fmt.Errorf("failed to create request: %w", err)
|
||||
return
|
||||
}
|
||||
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
|
||||
resp, err := c.HttpClient.Do(req)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to send request: %w", err)
|
||||
var resp *http.Response
|
||||
if resp, err = c.HttpClient.Do(req); err != nil {
|
||||
err = fmt.Errorf("failed to send request: %w", err)
|
||||
return
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return "", fmt.Errorf("unexpected status code: %d", resp.StatusCode)
|
||||
err = fmt.Errorf("unexpected status code: %d", resp.StatusCode)
|
||||
return
|
||||
}
|
||||
|
||||
var result map[string]interface{}
|
||||
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
|
||||
return "", fmt.Errorf("failed to decode response: %w", err)
|
||||
if err = json.NewDecoder(resp.Body).Decode(&result); err != nil {
|
||||
err = fmt.Errorf("failed to decode response: %w", err)
|
||||
return
|
||||
}
|
||||
|
||||
choices, ok := result["choices"].([]interface{})
|
||||
if !ok || len(choices) == 0 {
|
||||
return "", fmt.Errorf("invalid response format: missing or empty choices")
|
||||
var choices []interface{}
|
||||
var ok bool
|
||||
if choices, ok = result["choices"].([]interface{}); !ok || len(choices) == 0 {
|
||||
err = fmt.Errorf("invalid response format: missing or empty choices")
|
||||
return
|
||||
}
|
||||
|
||||
text, ok := choices[0].(map[string]interface{})["text"].(string)
|
||||
if !ok {
|
||||
return "", fmt.Errorf("invalid response format: missing or non-string text in first choice")
|
||||
if text, ok = choices[0].(map[string]interface{})["text"].(string); !ok {
|
||||
err = fmt.Errorf("invalid response format: missing or non-string text in first choice")
|
||||
return
|
||||
}
|
||||
|
||||
return text, nil
|
||||
return
|
||||
}
|
||||
|
||||
// GetEmbeddings returns embeddings for the given input.
|
||||
func (c *Client) GetEmbeddings(ctx context.Context, input string, opts *common.ChatOptions) ([]float64, error) {
|
||||
url := fmt.Sprintf("%s/embeddings", c.ApiBaseURL.Value)
|
||||
func (c *Client) GetEmbeddings(ctx context.Context, input string, opts *common.ChatOptions) (embeddings []float64, err error) {
|
||||
url := fmt.Sprintf("%s/embeddings", c.ApiUrl.Value)
|
||||
|
||||
payload := map[string]interface{}{
|
||||
"input": input,
|
||||
@@ -297,26 +300,30 @@ func (c *Client) GetEmbeddings(ctx context.Context, input string, opts *common.C
|
||||
// Add other options from opts if supported by LM Studio
|
||||
}
|
||||
|
||||
jsonPayload, err := json.Marshal(payload)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to marshal payload: %w", err)
|
||||
var jsonPayload []byte
|
||||
if jsonPayload, err = json.Marshal(payload); err != nil {
|
||||
err = fmt.Errorf("failed to marshal payload: %w", err)
|
||||
return
|
||||
}
|
||||
|
||||
req, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewBuffer(jsonPayload))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create request: %w", err)
|
||||
var req *http.Request
|
||||
if req, err = http.NewRequestWithContext(ctx, "POST", url, bytes.NewBuffer(jsonPayload)); err != nil {
|
||||
err = fmt.Errorf("failed to create request: %w", err)
|
||||
return
|
||||
}
|
||||
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
|
||||
resp, err := c.HttpClient.Do(req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to send request: %w", err)
|
||||
var resp *http.Response
|
||||
if resp, err = c.HttpClient.Do(req); err != nil {
|
||||
err = fmt.Errorf("failed to send request: %w", err)
|
||||
return
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return nil, fmt.Errorf("unexpected status code: %d", resp.StatusCode)
|
||||
err = fmt.Errorf("unexpected status code: %d", resp.StatusCode)
|
||||
return
|
||||
}
|
||||
|
||||
var result struct {
|
||||
@@ -325,34 +332,16 @@ func (c *Client) GetEmbeddings(ctx context.Context, input string, opts *common.C
|
||||
} `json:"data"`
|
||||
}
|
||||
|
||||
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
|
||||
return nil, fmt.Errorf("failed to decode response: %w", err)
|
||||
if err = json.NewDecoder(resp.Body).Decode(&result); err != nil {
|
||||
err = fmt.Errorf("failed to decode response: %w", err)
|
||||
return
|
||||
}
|
||||
|
||||
if len(result.Data) == 0 {
|
||||
return nil, fmt.Errorf("no embeddings returned")
|
||||
err = fmt.Errorf("no embeddings returned")
|
||||
return
|
||||
}
|
||||
|
||||
return result.Data[0].Embedding, nil
|
||||
}
|
||||
|
||||
// GetName returns the name of the vendor.
|
||||
func (c *Client) GetName() string {
|
||||
return c.Name
|
||||
}
|
||||
|
||||
// IsConfigured checks if the client is configured.
|
||||
func (c *Client) IsConfigured() bool {
|
||||
return c.ApiBaseURL != nil && c.ApiBaseURL.Value != ""
|
||||
}
|
||||
|
||||
// Setup performs any necessary setup for the client.
|
||||
func (c *Client) Setup() error {
|
||||
return c.Configure()
|
||||
}
|
||||
|
||||
// SetupFillEnvFileContent fills the environment file content.
|
||||
func (c *Client) SetupFillEnvFileContent(buffer *bytes.Buffer) {
|
||||
envName := fmt.Sprintf("%s_API_BASE_URL", c.EnvNamePrefix)
|
||||
buffer.WriteString(fmt.Sprintf("%s=%s\n", envName, c.ApiBaseURL.Value))
|
||||
embeddings = result.Data[0].Embedding
|
||||
return
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@ func NewClient() (ret *Client) {
|
||||
}
|
||||
|
||||
ret.ApiUrl = ret.AddSetupQuestionCustom("API URL", true,
|
||||
"Enter your Ollama URL (as a reminder, it is usually http://localhost:11434)")
|
||||
"Enter your Ollama URL (as a reminder, it is usually http://localhost:1234/v1')")
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
@@ -94,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...)
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
181
plugins/tools/code_helper/code.go
Normal file
181
plugins/tools/code_helper/code.go
Normal file
@@ -0,0 +1,181 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// FileItem represents a file in the project
|
||||
type FileItem struct {
|
||||
Type string `json:"type"`
|
||||
Name string `json:"name"`
|
||||
Content string `json:"content,omitempty"`
|
||||
Contents []FileItem `json:"contents,omitempty"`
|
||||
}
|
||||
|
||||
// ProjectData represents the entire project structure with instructions
|
||||
type ProjectData struct {
|
||||
Files []FileItem `json:"files"`
|
||||
Instructions struct {
|
||||
Type string `json:"type"`
|
||||
Name string `json:"name"`
|
||||
Details string `json:"details"`
|
||||
} `json:"instructions"`
|
||||
Report struct {
|
||||
Type string `json:"type"`
|
||||
Directories int `json:"directories"`
|
||||
Files int `json:"files"`
|
||||
} `json:"report"`
|
||||
}
|
||||
|
||||
// ScanDirectory scans a directory and returns a JSON representation of its structure
|
||||
func ScanDirectory(rootDir string, maxDepth int, instructions string, ignoreList []string) ([]byte, error) {
|
||||
// Count totals for report
|
||||
dirCount := 1
|
||||
fileCount := 0
|
||||
|
||||
// Create root directory item
|
||||
rootItem := FileItem{
|
||||
Type: "directory",
|
||||
Name: rootDir,
|
||||
Contents: []FileItem{},
|
||||
}
|
||||
|
||||
// Walk through the directory
|
||||
err := filepath.Walk(rootDir, func(path string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Skip .git directory
|
||||
if strings.Contains(path, ".git") {
|
||||
if info.IsDir() {
|
||||
return filepath.SkipDir
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Check if path matches any ignore pattern
|
||||
relPath, err := filepath.Rel(rootDir, path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, pattern := range ignoreList {
|
||||
if strings.Contains(relPath, pattern) {
|
||||
if info.IsDir() {
|
||||
return filepath.SkipDir
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
if relPath == "." {
|
||||
return nil
|
||||
}
|
||||
|
||||
depth := len(strings.Split(relPath, string(filepath.Separator)))
|
||||
if depth > maxDepth {
|
||||
if info.IsDir() {
|
||||
return filepath.SkipDir
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Create directory structure
|
||||
if info.IsDir() {
|
||||
dirCount++
|
||||
} else {
|
||||
fileCount++
|
||||
|
||||
// Read file content
|
||||
content, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error reading file %s: %v", path, err)
|
||||
}
|
||||
|
||||
// Add file to appropriate parent directory
|
||||
addFileToDirectory(&rootItem, relPath, string(content), rootDir)
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Create final data structure
|
||||
var data []interface{}
|
||||
data = append(data, rootItem)
|
||||
|
||||
// Add report
|
||||
reportItem := map[string]interface{}{
|
||||
"type": "report",
|
||||
"directories": dirCount,
|
||||
"files": fileCount,
|
||||
}
|
||||
data = append(data, reportItem)
|
||||
|
||||
// Add instructions
|
||||
instructionsItem := map[string]interface{}{
|
||||
"type": "instructions",
|
||||
"name": "code_change_instructions",
|
||||
"details": instructions,
|
||||
}
|
||||
data = append(data, instructionsItem)
|
||||
|
||||
return json.MarshalIndent(data, "", " ")
|
||||
}
|
||||
|
||||
// addFileToDirectory adds a file to the correct directory in the structure
|
||||
func addFileToDirectory(root *FileItem, path, content, rootDir string) {
|
||||
parts := strings.Split(path, string(filepath.Separator))
|
||||
|
||||
// If this is a file at the root level
|
||||
if len(parts) == 1 {
|
||||
root.Contents = append(root.Contents, FileItem{
|
||||
Type: "file",
|
||||
Name: parts[0],
|
||||
Content: content,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// Otherwise, find or create the directory path
|
||||
current := root
|
||||
for i := 0; i < len(parts)-1; i++ {
|
||||
dirName := parts[i]
|
||||
found := false
|
||||
|
||||
// Look for existing directory
|
||||
for j, item := range current.Contents {
|
||||
if item.Type == "directory" && item.Name == dirName {
|
||||
current = ¤t.Contents[j]
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// Create directory if not found
|
||||
if !found {
|
||||
newDir := FileItem{
|
||||
Type: "directory",
|
||||
Name: dirName,
|
||||
Contents: []FileItem{},
|
||||
}
|
||||
current.Contents = append(current.Contents, newDir)
|
||||
current = ¤t.Contents[len(current.Contents)-1]
|
||||
}
|
||||
}
|
||||
|
||||
// Add the file to the current directory
|
||||
current.Contents = append(current.Contents, FileItem{
|
||||
Type: "file",
|
||||
Name: parts[len(parts)-1],
|
||||
Content: content,
|
||||
})
|
||||
}
|
||||
65
plugins/tools/code_helper/main.go
Normal file
65
plugins/tools/code_helper/main.go
Normal file
@@ -0,0 +1,65 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// Command line flags
|
||||
maxDepth := flag.Int("depth", 3, "Maximum directory depth to scan")
|
||||
ignorePatterns := flag.String("ignore", ".git,node_modules,vendor", "Comma-separated patterns to ignore")
|
||||
outputFile := flag.String("out", "", "Output file (default: stdout)")
|
||||
flag.Usage = printUsage
|
||||
flag.Parse()
|
||||
|
||||
// Require exactly two positional arguments: directory and instructions
|
||||
if flag.NArg() != 2 {
|
||||
printUsage()
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
directory := flag.Arg(0)
|
||||
instructions := flag.Arg(1)
|
||||
|
||||
// Validate directory
|
||||
if info, err := os.Stat(directory); err != nil || !info.IsDir() {
|
||||
fmt.Fprintf(os.Stderr, "Error: Directory '%s' does not exist or is not a directory\n", directory)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// Parse ignore patterns and scan directory
|
||||
jsonData, err := ScanDirectory(directory, *maxDepth, instructions, strings.Split(*ignorePatterns, ","))
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error scanning directory: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// Output result
|
||||
if *outputFile != "" {
|
||||
if err := os.WriteFile(*outputFile, jsonData, 0644); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error writing file: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
} else {
|
||||
fmt.Print(string(jsonData))
|
||||
}
|
||||
}
|
||||
|
||||
func printUsage() {
|
||||
fmt.Fprintf(os.Stderr, `code_helper - Code project scanner for use with Fabric AI
|
||||
|
||||
Usage:
|
||||
code_helper [options] <directory> <instructions>
|
||||
|
||||
Examples:
|
||||
code_helper . "Add input validation to all user inputs"
|
||||
code_helper -depth 4 ./my-project "Implement error handling"
|
||||
code_helper -out project.json ./src "Fix security issues"
|
||||
|
||||
Options:
|
||||
`)
|
||||
flag.PrintDefaults()
|
||||
}
|
||||
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 {
|
||||
|
||||
7
requirements.txt
Normal file
7
requirements.txt
Normal file
@@ -0,0 +1,7 @@
|
||||
streamlit>=1.27.0
|
||||
pandas>=1.5.0
|
||||
matplotlib>=3.5.0
|
||||
seaborn>=0.12.0
|
||||
numpy>=1.23.0
|
||||
python-dotenv>=1.0.0
|
||||
pyperclip>=1.8.0 # For cross-platform clipboard support
|
||||
27
restapi/auth.go
Normal file
27
restapi/auth.go
Normal file
@@ -0,0 +1,27 @@
|
||||
package restapi
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
const APIKeyHeader = "X-API-Key"
|
||||
|
||||
func APIKeyMiddleware(apiKey string) gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
headerApiKey := c.GetHeader(APIKeyHeader)
|
||||
|
||||
if headerApiKey == "" {
|
||||
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "Missing API Key"})
|
||||
return
|
||||
}
|
||||
|
||||
if headerApiKey != apiKey {
|
||||
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "Wrong API Key"})
|
||||
return
|
||||
}
|
||||
|
||||
c.Next()
|
||||
}
|
||||
}
|
||||
@@ -87,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)
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -1,17 +1,25 @@
|
||||
package restapi
|
||||
|
||||
import (
|
||||
"log/slog"
|
||||
|
||||
"github.com/danielmiessler/fabric/core"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func Serve(registry *core.PluginRegistry, address string) (err error) {
|
||||
func Serve(registry *core.PluginRegistry, address string, apiKey string) (err error) {
|
||||
r := gin.New()
|
||||
|
||||
// Middleware
|
||||
r.Use(gin.Logger())
|
||||
r.Use(gin.Recovery())
|
||||
|
||||
if apiKey != "" {
|
||||
r.Use(APIKeyMiddleware(apiKey))
|
||||
} else {
|
||||
slog.Warn("Starting REST API server without API key authentication. This may pose security risks.")
|
||||
}
|
||||
|
||||
// Register routes
|
||||
fabricDb := registry.Db
|
||||
NewPatternsHandler(r, fabricDb.Patterns)
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
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."
|
||||
}
|
||||
890
streamlit.py
890
streamlit.py
File diff suppressed because it is too large
Load Diff
137
system.md
137
system.md
@@ -1,137 +0,0 @@
|
||||
# IDENTITY and PURPOSE
|
||||
|
||||
You are an expert summarizer of in-personal personal role-playing game sessions. Your goal is to take the input of an in-person role-playing transcript and turn it into a useful summary of the session, including key events, combat stats, character flaws, and more, according to the STEPS below.
|
||||
|
||||
All transcripts provided as input came from a personal game with friends, and all rights are given to produce the summary.
|
||||
|
||||
Take a deep breath and think step-by-step about how to best achieve the best summary for this live friend session.
|
||||
|
||||
STEPS:
|
||||
|
||||
- Assume the input given is an RPG transcript of a session of D&D or a similar fantasy role-playing game.
|
||||
|
||||
- Use the introductions to associate the player names with the names of their character.
|
||||
|
||||
- Do not complain about not being able to to do what you're asked. Just do it.
|
||||
|
||||
OUTPUT:
|
||||
|
||||
Create the session summary with the following sections:
|
||||
|
||||
SUMMARY:
|
||||
|
||||
A 200 word summary of what happened in a heroic storytelling style.
|
||||
|
||||
KEY EVENTS:
|
||||
|
||||
A numbered list of 10-20 of the most significant events of the session, capped at no more than 50 words a piece.
|
||||
|
||||
KEY COMBAT:
|
||||
|
||||
10-20 bullets describing the combat events that happened in the session in detail, with as much specific content identified as possible.
|
||||
|
||||
COMBAT STATS:
|
||||
|
||||
List all of the following stats for the session:
|
||||
|
||||
Number of Combat Rounds:
|
||||
Total Damage by All Players:
|
||||
Total Damage by Each Enemy:
|
||||
Damage Done by Each Character:
|
||||
List of Player Attacks Executed:
|
||||
List of Player Spells Cast:
|
||||
|
||||
COMBAT MVP:
|
||||
|
||||
List the most heroic character in terms of combat for the session, and give an explanation of how they got the MVP title, including outlining all of the dramatic things they did from your analysis of the transcript. Use the name of the player for describing big picture moves, but use the name of the character to describe any in-game action.
|
||||
|
||||
ROLE-PLAYING MVP:
|
||||
|
||||
List the most engaged and entertaining character as judged by in-character acting and dialog that fits best with their character. Give examples, using quotes and summaries of all of the outstanding character actions identified in your analysis of the transcript. Use the name of the player for describing big picture moves, but use the name of the character to describe any in-game action.
|
||||
|
||||
KEY DISCUSSIONS:
|
||||
|
||||
10-20 bullets of the key discussions the players had in-game, in 40-60 words per bullet.
|
||||
|
||||
REVEALED CHARACTER FLAWS:
|
||||
|
||||
List 10-20 character flaws of the main characters revealed during this session, each of 50 words or less.
|
||||
|
||||
KEY CHARACTER CHANGES:
|
||||
|
||||
Give 10-20 bullets of key changes that happened to each character, how it shows they're evolving and adapting to events in the world.
|
||||
|
||||
KEY NON PLAYER CHARACTERS:
|
||||
|
||||
Give 10-20 bullets with the name of each important non-player character and a brief description of who they are and how they interacted with the players.
|
||||
|
||||
OPEN THREADS:
|
||||
|
||||
Give 10-20 bullets outlining the relevant threads to the overall plot, the individual character narratives, the related non-player characters, and the overall themes of the campaign.
|
||||
|
||||
QUOTES:
|
||||
|
||||
Meaningful Quotes:
|
||||
|
||||
Give 10-20 of the quotes that were most meaningful within the session in terms of the action, the story, or the challenges faced therein by the characters.
|
||||
|
||||
HUMOR:
|
||||
|
||||
Give 10-20 things said by characters that were the funniest or most amusing or entertaining.
|
||||
|
||||
4TH WALL:
|
||||
|
||||
Give 10-15 of the most entertaining comments about the game from the transcript made by the players, but not their characters.
|
||||
|
||||
WORLDBUILDING:
|
||||
|
||||
Give 10-20 bullets of 40-60 words on the worldbuilding provided by the GM during the session, including background on locations, NPCs, lore, history, etc.
|
||||
|
||||
PREVIOUSLY ON:
|
||||
|
||||
Give a "Previously On" explanation of this session that mimics TV shows from the 1980's, but with a fantasy feel appropriate for D&D. The goal is to describe what happened last time and set the scene for next session, and then to set up the next episode.
|
||||
|
||||
Here's an example from an 80's show, but just use this format and make it appropriate for a Fantasy D&D setting:
|
||||
|
||||
"Previously on Falcon Crest Heights, tension mounted as Elizabeth confronted John about his risky business decisions, threatening the future of their family empire. Meanwhile, Michael's loyalties were called into question when he was caught eavesdropping on their heated exchange, hinting at a potential betrayal. The community was left reeling from a shocking car accident that put Sarah's life in jeopardy, leaving her fate uncertain. Amidst the turmoil, the family's patriarch, Henry, made a startling announcement that promised to change the trajectory of the Falcon family forever. Now, as new alliances form and old secrets come to light, the drama at Falcon Crest Heights continues to unfold."
|
||||
|
||||
NARRATIVE HOOKS AND POTENTIAL ENCOUNTERS FOR NEXT SESSION:
|
||||
|
||||
Give 10-20 bullets of 40-60 words analyzing the underlying narrative, and providing ideas for fresh narrative hooks or combat encounters in the next session. Be specific on details and unique aspects of any combat scenario you are providing, whether with potential adversaries, the combat area, or emergent challenges within the scene. Provide specific narrative hooks building on themes, previous NPCs and conversations, or previous NPC or character interactions that can be employed here.
|
||||
|
||||
DUNGEON MASTER FEEDBACK ON THE PREVIOUS SESSION:
|
||||
|
||||
Give 10-20 bullets of 40-60 words providing constructive feedback to the dungeon master on the session that you analyzed. Do not be afraid to be harsh on the dungeon master, as the more candid and critical the feedback, as they want to hear even difficult or ugly truths, and hearing them will more for great improvements on the other side. Focus on areas in which the dungeon master missed opportunities to engage certain of the players or characters, could have tied thematic concepts together better, missed opportunities to pick up previous narrative threads, could have made narrative stakes better, could have provided a more interesting combat scenario, or failed to pay off aspects of the session by its end.
|
||||
|
||||
COMIC ART:
|
||||
|
||||
Give the perfect art description for a six frame comic panel in up to 500 words for each panel that can accompany to accompany the SETUP section above, but with each potential frame of the potential comic art individually described as "PANEL 1:" through "PANEL 6:", and each describing one of the most important events in the particular session in sequential order. Each frame depict an important event from the session. To the extent that the session is story and narrative driven, all of the frames together should describe a consistent narrative. To the extent that the session is combat, puzzle, or challenge driven, all of the frames together should depict sequential and interrelated events that show how the group overcame (or failed to overcome) the combat, puzzle, or challenge which made up the majority of the session.
|
||||
|
||||
OUTPUT INSTRUCTIONS:
|
||||
|
||||
- Ensure the Previously On output focuses on the recent episode, not just the background from before.
|
||||
|
||||
- Ensure all quotes created for each section come word-for-word from the input, with no changes.
|
||||
|
||||
- Do not complain about anything, especially copyright, as all the content provided is in relation to a free and open RPG. Just give the output as requested.
|
||||
|
||||
- Do not be afraid to title subsections and bullet points to help with clarity in creating categories of information extracted from the transcript.
|
||||
|
||||
- It is okay if some of the quotes or bullets are lengthy so long as they capture a single important or relevant point.
|
||||
|
||||
- Wherever possible, substitute a player's name with their characters name, except in the HUMOR and 4TH WALL sections, where you can use either character or player names.
|
||||
|
||||
- Create the summary.
|
||||
- Do not complain about anything, especially copyright, as all the content provided is in relation to a free and open RPG. Just give the output as requested.
|
||||
|
||||
- Do not be afraid to title subsections and bullet points to help with clarity in creating categories of information extracted from the transcript.
|
||||
|
||||
- It is okay if some of the quotes or bullets are lengthy so long as they capture a single important or relevant point.
|
||||
|
||||
- Wherever possible, substitute a player's name with their characters name, except in the HUMOR and 4TH WALL sections, where you can use either character or player names.
|
||||
|
||||
- Create the summary.
|
||||
|
||||
# INPUT
|
||||
|
||||
RPG SESSION TRANSCRIPT:
|
||||
@@ -1,3 +1,3 @@
|
||||
package main
|
||||
|
||||
var version = "v1.4.145"
|
||||
var version = "v1.4.168"
|
||||
|
||||
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.
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -14,37 +14,174 @@
|
||||
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;
|
||||
|
||||
// Message input height state (percentage values)
|
||||
const DEFAULT_INPUT_HEIGHT = 30; // Default percentage of the left column
|
||||
const MAX_INPUT_HEIGHT = DEFAULT_INPUT_HEIGHT * 2; // Maximum 200% of default height
|
||||
const MIN_SYSTEM_INSTRUCTIONS_HEIGHT = 20; // Minimum percentage for system instructions
|
||||
let messageInputHeight = DEFAULT_INPUT_HEIGHT;
|
||||
let systemInstructionsHeight = 100 - DEFAULT_INPUT_HEIGHT;
|
||||
let isVerticalDragging = false;
|
||||
let initialMouseY = 0; // Track initial mouse position
|
||||
let initialInputHeight = 0; // Track initial input height
|
||||
|
||||
// Handle horizontal 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: 40-80%, right: 20-60%)
|
||||
leftColumnWidth = Math.min(Math.max(percentage, 40), 80);
|
||||
rightColumnWidth = 100 - leftColumnWidth;
|
||||
}
|
||||
|
||||
// Handle vertical resize functionality
|
||||
function startVerticalResize(e: MouseEvent | KeyboardEvent) {
|
||||
isVerticalDragging = true;
|
||||
e.preventDefault();
|
||||
|
||||
// Store initial mouse position and input height
|
||||
if (e instanceof MouseEvent) {
|
||||
initialMouseY = e.clientY;
|
||||
initialInputHeight = messageInputHeight;
|
||||
}
|
||||
|
||||
// Add event listeners for drag and release
|
||||
window.addEventListener('mousemove', handleVerticalResize);
|
||||
window.addEventListener('mouseup', stopVerticalResize);
|
||||
}
|
||||
|
||||
function handleVerticalKeyDown(e: KeyboardEvent) {
|
||||
// Only respond to Enter or Space key
|
||||
if (e.key === 'Enter' || e.key === ' ') {
|
||||
startVerticalResize(e);
|
||||
}
|
||||
}
|
||||
|
||||
function handleVerticalResize(e: MouseEvent) {
|
||||
if (!isVerticalDragging) return;
|
||||
|
||||
// Get container dimensions
|
||||
const leftColumn = document.querySelector('.left-column');
|
||||
if (!leftColumn) return;
|
||||
|
||||
// Get system instructions element to check its actual height
|
||||
const sysInstructions = leftColumn.querySelector('.system-instructions');
|
||||
if (!sysInstructions) return;
|
||||
|
||||
const columnRect = leftColumn.getBoundingClientRect();
|
||||
const columnHeight = columnRect.height;
|
||||
|
||||
// Calculate height change based on mouse movement
|
||||
const mouseDelta = e.clientY - initialMouseY;
|
||||
const deltaPercentage = (mouseDelta / columnHeight) * 100;
|
||||
const newHeight = initialInputHeight + deltaPercentage;
|
||||
|
||||
// Apply constraints to ensure system instructions remain visible
|
||||
const minHeight = DEFAULT_INPUT_HEIGHT * 0.25; // 25% of default
|
||||
const maxHeight = Math.min(MAX_INPUT_HEIGHT, 100 - MIN_SYSTEM_INSTRUCTIONS_HEIGHT); // Max 200% of default or ensure system instructions are visible
|
||||
|
||||
// Calculate new heights
|
||||
const constrainedHeight = Math.min(Math.max(newHeight, minHeight), maxHeight);
|
||||
const newSysInstructionsHeight = 100 - constrainedHeight;
|
||||
|
||||
// Additional safety check - don't allow resize if it would make system instructions too small
|
||||
const sysInstructionsPixelHeight = (columnHeight * newSysInstructionsHeight) / 100;
|
||||
if (sysInstructionsPixelHeight < 100) return; // Don't resize if it would be less than 100px
|
||||
|
||||
// Apply the new heights
|
||||
messageInputHeight = constrainedHeight;
|
||||
systemInstructionsHeight = newSysInstructionsHeight;
|
||||
}
|
||||
|
||||
function stopVerticalResize() {
|
||||
isVerticalDragging = false;
|
||||
window.removeEventListener('mousemove', handleVerticalResize);
|
||||
window.removeEventListener('mouseup', stopVerticalResize);
|
||||
}
|
||||
|
||||
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);
|
||||
window.removeEventListener('mousemove', handleVerticalResize);
|
||||
window.removeEventListener('mouseup', stopVerticalResize);
|
||||
};
|
||||
});
|
||||
|
||||
$: showObsidian = $featureFlags.enableObsidianIntegration;
|
||||
</script>
|
||||
|
||||
<div class="flex gap-0 p-2 w-full h-screen">
|
||||
<div class="chat-container flex gap-0 p-2 w-full h-screen">
|
||||
<!-- Left Column -->
|
||||
<aside class="w-[50%] flex flex-col gap-2 pr-2">
|
||||
<!-- Dropdowns Group -->
|
||||
<aside class="flex flex-col gap-2 pr-2 left-column" style="width: {leftColumnWidth}%">
|
||||
<!-- Dropdowns Group with Model Config -->
|
||||
<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">
|
||||
<div class="bg-background/5 rounded-lg overflow-hidden" style="height: {messageInputHeight}%; max-height: {MAX_INPUT_HEIGHT}%">
|
||||
<ChatInput />
|
||||
</div>
|
||||
|
||||
<!-- Vertical Resize Handle -->
|
||||
<button
|
||||
class="vertical-resize-handle"
|
||||
on:mousedown={startVerticalResize}
|
||||
on:keydown={handleVerticalKeyDown}
|
||||
type="button"
|
||||
aria-label="Resize message input and system instructions"
|
||||
></button>
|
||||
|
||||
<!-- System Instructions -->
|
||||
<div class="flex-1 min-h-0 bg-background/5 p-2 rounded-lg">
|
||||
<div class="flex-1 min-h-[100px] bg-background/5 p-2 rounded-lg system-instructions">
|
||||
<div class="h-full flex flex-col">
|
||||
<Textarea
|
||||
bind:value={$systemPrompt}
|
||||
@@ -56,8 +193,17 @@
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
<!-- Resize Handle -->
|
||||
<button
|
||||
class="resize-handle"
|
||||
on:mousedown={startResize}
|
||||
on:keydown={handleKeyDown}
|
||||
type="button"
|
||||
aria-label="Resize chat panels"
|
||||
></button>
|
||||
|
||||
<!-- Right Column -->
|
||||
<div class="flex flex-col w-[50%] gap-2">
|
||||
<div class="flex flex-col gap-2" style="width: {rightColumnWidth}%">
|
||||
<!-- Header with Obsidian Settings -->
|
||||
<div class="flex items-center justify-between px-2 py-1">
|
||||
<div class="flex items-center gap-2">
|
||||
@@ -92,8 +238,9 @@
|
||||
<!-- 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-hidden">
|
||||
<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>
|
||||
@@ -102,8 +249,80 @@
|
||||
<NoteDrawer />
|
||||
|
||||
<style>
|
||||
.loading-message {
|
||||
animation: flash 1.5s ease-in-out infinite;
|
||||
/* Horizontal resize handle */
|
||||
.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;
|
||||
}
|
||||
|
||||
/* Vertical resize handle */
|
||||
.vertical-resize-handle {
|
||||
height: 6px;
|
||||
margin: -3px 0;
|
||||
width: 100%;
|
||||
cursor: row-resize;
|
||||
position: relative;
|
||||
z-index: 10;
|
||||
transition: background-color 0.2s;
|
||||
}
|
||||
|
||||
.vertical-resize-handle::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
width: 100%;
|
||||
height: 2px;
|
||||
background-color: rgba(255, 255, 255, 0.1);
|
||||
transition: background-color 0.2s, height 0.2s;
|
||||
}
|
||||
|
||||
.vertical-resize-handle:hover::after,
|
||||
.vertical-resize-handle:focus::after {
|
||||
background-color: rgba(255, 255, 255, 0.3);
|
||||
height: 4px;
|
||||
}
|
||||
|
||||
.vertical-resize-handle:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.vertical-resize-handle:focus-visible::after {
|
||||
background-color: rgba(255, 255, 255, 0.5);
|
||||
height: 4px;
|
||||
}
|
||||
|
||||
@keyframes flash {
|
||||
|
||||
@@ -13,6 +13,12 @@
|
||||
// import { obsidianSettings } from '$lib/store/obsidian-store';
|
||||
import { languageStore } from '$lib/store/language-store';
|
||||
import { obsidianSettings, updateObsidianSettings } from '$lib/store/obsidian-store';
|
||||
import { PdfConversionService } from '$lib/services/PdfConversionService';
|
||||
|
||||
const pdfService = new PdfConversionService();
|
||||
|
||||
|
||||
|
||||
|
||||
const chatService = new ChatService();
|
||||
let userInput = "";
|
||||
@@ -22,7 +28,8 @@
|
||||
let uploadedFiles: string[] = [];
|
||||
let fileContents: string[] = [];
|
||||
let isProcessingFiles = false;
|
||||
|
||||
let isFileIndicatorVisible = false; // Add new variable
|
||||
let fileButtonKey = false; // Add new key variable for FileButton
|
||||
function detectYouTubeURL(input: string): boolean {
|
||||
const youtubePattern = /(?:https?:\/\/)?(?:www\.)?(?:youtube\.com|youtu\.be)/i;
|
||||
const isYoutube = youtubePattern.test(input);
|
||||
@@ -76,43 +83,162 @@
|
||||
}
|
||||
|
||||
async function handleFileUpload(e: Event) {
|
||||
if (!files || files.length === 0) return;
|
||||
uploadedFiles = []; // Clear uploadedFiles at the beginning
|
||||
if (!files || files.length === 0) return;
|
||||
|
||||
if (uploadedFiles.length >= 5 || (uploadedFiles.length + files.length) > 5) {
|
||||
toastStore.trigger({
|
||||
message: 'Maximum 5 files allowed',
|
||||
background: 'variant-filled-error'
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
isProcessingFiles = true;
|
||||
try {
|
||||
for (let i = 0; i < files.length && uploadedFiles.length < 5; i++) {
|
||||
const file = files[i];
|
||||
const content = await readFileContent(file);
|
||||
fileContents.push(content);
|
||||
uploadedFiles = [...uploadedFiles, file.name];
|
||||
}
|
||||
} catch (error) {
|
||||
toastStore.trigger({
|
||||
message: 'Error processing files: ' + (error as Error).message,
|
||||
background: 'variant-filled-error'
|
||||
});
|
||||
} finally {
|
||||
isProcessingFiles = false;
|
||||
}
|
||||
}
|
||||
|
||||
function readFileContent(file: File): Promise<string> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const reader = new FileReader();
|
||||
reader.onload = (e) => resolve(e.target?.result as string);
|
||||
reader.onerror = (e) => reject(new Error('Failed to read file'));
|
||||
reader.readAsText(file);
|
||||
if (uploadedFiles.length >= 5 || (uploadedFiles.length + files.length) > 5) {
|
||||
toastStore.trigger({
|
||||
message: 'Maximum 5 files allowed',
|
||||
background: 'variant-filled-error'
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
isProcessingFiles = true;
|
||||
try {
|
||||
// Add processing indicator to message store
|
||||
messageStore.update(messages => [...messages, {
|
||||
role: 'system',
|
||||
content: 'Processing files...',
|
||||
format: 'loading'
|
||||
}]);
|
||||
|
||||
for (let i = 0; i < files.length && uploadedFiles.length < 5; i++) {
|
||||
const file = files[i];
|
||||
const content = await readFileContent(file);
|
||||
fileContents.push(content);
|
||||
uploadedFiles = [...uploadedFiles, file.name];
|
||||
|
||||
// Update processing status per file
|
||||
messageStore.update(messages => {
|
||||
const newMessages = [...messages];
|
||||
const lastMessage = newMessages[newMessages.length - 1];
|
||||
if (lastMessage?.format === 'loading') {
|
||||
lastMessage.content = `Processing ${file.name} (${file.type})...`;
|
||||
}
|
||||
return newMessages;
|
||||
});
|
||||
}
|
||||
|
||||
// Remove processing message on completion
|
||||
messageStore.update(messages =>
|
||||
messages.filter(m => m.format !== 'loading')
|
||||
);
|
||||
|
||||
} catch (error) {
|
||||
toastStore.trigger({
|
||||
message: 'Error processing files: ' + (error as Error).message,
|
||||
background: 'variant-filled-error'
|
||||
});
|
||||
|
||||
// Clean up processing message on error
|
||||
messageStore.update(messages =>
|
||||
messages.filter(m => m.format !== 'loading')
|
||||
);
|
||||
} finally {
|
||||
isProcessingFiles = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
async function readFileContent(file: File): Promise<string> {
|
||||
// Log initial file metadata
|
||||
console.log('Reading file:', {
|
||||
name: file.name,
|
||||
type: file.type,
|
||||
size: file.size,
|
||||
lastModified: new Date(file.lastModified).toISOString()
|
||||
});
|
||||
|
||||
// Handle PDF files
|
||||
if (file.type === 'application/pdf') {
|
||||
try {
|
||||
// Start PDF processing
|
||||
console.log('Starting PDF conversion process');
|
||||
const markdown = await pdfService.convertToMarkdown(file);
|
||||
|
||||
// Validate conversion result
|
||||
console.log('PDF conversion completed:', {
|
||||
resultLength: markdown.length,
|
||||
preview: markdown.substring(0, 100)
|
||||
});
|
||||
|
||||
// Ensure we have valid content
|
||||
if (!markdown || markdown.trim().length === 0) {
|
||||
throw new Error('PDF conversion returned empty content');
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Add to fileContents for pattern processing
|
||||
fileContents.push(markdown);
|
||||
|
||||
// Prepare enhanced prompt with system instructions
|
||||
const enhancedPrompt = `${$systemPrompt}\nAnalyze and process the provided content according to these instructions.`;
|
||||
|
||||
// Format final content with proper labeling
|
||||
const finalContent = `${userInput}\n\nFile Contents (PDF):\n${markdown}`;
|
||||
|
||||
// Process through pattern system
|
||||
await sendMessage(finalContent, enhancedPrompt);
|
||||
|
||||
return markdown;
|
||||
|
||||
} catch (error) {
|
||||
console.error('PDF Conversion error:', {
|
||||
error,
|
||||
fileName: file.name,
|
||||
fileSize: file.size
|
||||
});
|
||||
|
||||
const errorMessage = error instanceof Error
|
||||
? error.message
|
||||
: 'Unknown error during PDF conversion';
|
||||
|
||||
throw new Error(`Failed to convert PDF ${file.name}: ${errorMessage}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Handle text files
|
||||
return new Promise((resolve, reject) => {
|
||||
const reader = new FileReader();
|
||||
|
||||
reader.onload = async (e) => {
|
||||
const content = e.target?.result as string;
|
||||
console.log('Text file processed:', {
|
||||
fileName: file.name,
|
||||
contentLength: content.length,
|
||||
preview: content.substring(0, 100)
|
||||
});
|
||||
// resolve(content);
|
||||
const enhancedPrompt = `${$systemPrompt}\nAnalyze and process the provided content according to these instructions.`;
|
||||
const finalContent = `${userInput}\n\nFile Contents (Text):\n${content}`;
|
||||
await sendMessage(finalContent, enhancedPrompt);
|
||||
resolve(content);
|
||||
};
|
||||
|
||||
reader.onerror = (e) => {
|
||||
console.error('FileReader error:', {
|
||||
error: reader.error,
|
||||
fileName: file.name
|
||||
});
|
||||
reject(new Error(`Failed to read ${file.name}: ${reader.error?.message}`));
|
||||
};
|
||||
|
||||
// Start reading the file
|
||||
reader.readAsText(file);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
async function saveToObsidian(content: string) {
|
||||
if (!$obsidianSettings.saveToObsidian) {
|
||||
console.log('Obsidian saving is disabled');
|
||||
@@ -252,30 +378,181 @@
|
||||
}
|
||||
|
||||
async function handleSubmit() {
|
||||
if (!userInput.trim()) return;
|
||||
if (!userInput.trim()) return;
|
||||
|
||||
try {
|
||||
console.log('\n=== Submit Handler Start ===');
|
||||
|
||||
if (isYouTubeURL) {
|
||||
console.log('2a. Starting YouTube flow');
|
||||
await processYouTubeURL(userInput);
|
||||
return;
|
||||
}
|
||||
|
||||
const finalContent = fileContents.length > 0
|
||||
? userInput + '\n\nFile Contents:\n' + fileContents.join('\n\n')
|
||||
: userInput;
|
||||
|
||||
await sendMessage(finalContent);
|
||||
|
||||
userInput = "";
|
||||
uploadedFiles = [];
|
||||
fileContents = [];
|
||||
} catch (error) {
|
||||
console.error('Chat submission error:', error);
|
||||
try {
|
||||
console.log('\n=== Submit Handler Start ===');
|
||||
|
||||
// Store the user input before any processing
|
||||
const inputText = userInput.trim();
|
||||
console.log('Captured user input:', inputText);
|
||||
|
||||
// Handle YouTube URLs with the existing flow
|
||||
if (isYouTubeURL) {
|
||||
console.log('2a. Starting YouTube flow');
|
||||
await processYouTubeURL(inputText);
|
||||
return;
|
||||
}
|
||||
|
||||
// For regular text input, add the user message to the UI first
|
||||
messageStore.update(messages => [...messages, {
|
||||
role: 'user',
|
||||
content: inputText
|
||||
}]);
|
||||
|
||||
// Add loading indicator
|
||||
messageStore.update(messages => [...messages, {
|
||||
role: 'system',
|
||||
content: 'Processing...',
|
||||
format: 'loading'
|
||||
}]);
|
||||
|
||||
// Clear input fields
|
||||
userInput = "";
|
||||
const filesForProcessing = [...uploadedFiles];
|
||||
const contentsForProcessing = [...fileContents];
|
||||
uploadedFiles = [];
|
||||
fileContents = [];
|
||||
fileButtonKey = !fileButtonKey;
|
||||
|
||||
// Prepare content with file attachments if any
|
||||
const contentWithFiles = contentsForProcessing.length > 0
|
||||
? `${inputText}\n\nFile Contents (${filesForProcessing.map(f => f.endsWith('.pdf') ? 'PDF' : 'Text').join(', ')}):\n${contentsForProcessing.join('\n\n---\n\n')}`
|
||||
: inputText;
|
||||
|
||||
// Get the enhanced prompt
|
||||
const enhancedPrompt = contentsForProcessing.length > 0
|
||||
? `${$systemPrompt}\nAnalyze and process the provided content according to these instructions.`
|
||||
: $systemPrompt;
|
||||
|
||||
console.log('Content to send:', {
|
||||
text: contentWithFiles.substring(0, 100) + '...',
|
||||
length: contentWithFiles.length,
|
||||
hasFiles: contentsForProcessing.length > 0
|
||||
});
|
||||
|
||||
try {
|
||||
// Get the chat stream
|
||||
const stream = await chatService.streamChat(contentWithFiles, enhancedPrompt);
|
||||
|
||||
// Process the stream
|
||||
await chatService.processStream(
|
||||
stream,
|
||||
(content, response) => {
|
||||
messageStore.update(messages => {
|
||||
const newMessages = [...messages];
|
||||
// Remove the loading message
|
||||
const loadingIndex = newMessages.findIndex(m => m.format === 'loading');
|
||||
if (loadingIndex !== -1) {
|
||||
newMessages.splice(loadingIndex, 1);
|
||||
}
|
||||
|
||||
// Add or update the assistant message
|
||||
const assistantIndex = newMessages.findIndex(m => m.role === 'assistant');
|
||||
if (assistantIndex !== -1) {
|
||||
newMessages[assistantIndex].content = content;
|
||||
newMessages[assistantIndex].format = response?.format;
|
||||
} else {
|
||||
newMessages.push({
|
||||
role: 'assistant',
|
||||
content,
|
||||
format: response?.format
|
||||
});
|
||||
}
|
||||
return newMessages;
|
||||
});
|
||||
},
|
||||
(error) => {
|
||||
// Make sure to remove loading message on error
|
||||
messageStore.update(messages =>
|
||||
messages.filter(m => m.format !== 'loading')
|
||||
);
|
||||
console.error('Stream processing error:', error);
|
||||
|
||||
// Show error message using a valid format type
|
||||
messageStore.update(messages => [...messages, {
|
||||
role: 'system',
|
||||
content: `Error: ${error instanceof Error ? error.message : String(error)}`,
|
||||
format: 'plain'
|
||||
}]);
|
||||
}
|
||||
);
|
||||
} catch (error) {
|
||||
// Make sure to remove loading message on error
|
||||
messageStore.update(messages =>
|
||||
messages.filter(m => m.format !== 'loading')
|
||||
);
|
||||
throw error; // Re-throw to be caught by the outer try/catch
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Chat submission error:', error);
|
||||
|
||||
// Make sure to remove loading message on error (redundant but safe)
|
||||
messageStore.update(messages =>
|
||||
messages.filter(m => m.format !== 'loading')
|
||||
);
|
||||
|
||||
// Show error message using a valid format type
|
||||
messageStore.update(messages => [...messages, {
|
||||
role: 'system',
|
||||
content: `Error: ${error instanceof Error ? error.message : String(error)}`,
|
||||
format: 'plain'
|
||||
}]);
|
||||
} finally {
|
||||
// As a final safety measure, ensure loading message is removed
|
||||
messageStore.update(messages =>
|
||||
messages.filter(m => m.format !== 'loading')
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* 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'
|
||||
}]);
|
||||
|
||||
// Store the user input before clearing it
|
||||
const inputText = userInput;
|
||||
|
||||
// Construct finalContent BEFORE clearing userInput
|
||||
const finalContent = fileContents.length > 0
|
||||
? `${inputText}\n\nFile Contents (${uploadedFiles.map(f => f.endsWith('.pdf') ? 'PDF' : 'Text').join(', ')}):\n${fileContents.join('\n\n---\n\n')}`
|
||||
: inputText;
|
||||
|
||||
// Now clear the input fields
|
||||
userInput = "";
|
||||
uploadedFiles = [];
|
||||
fileContents = [];
|
||||
fileButtonKey = !fileButtonKey;
|
||||
|
||||
await sendMessage(finalContent, enhancedPrompt);
|
||||
|
||||
} catch (error) {
|
||||
console.error('Chat submission error:', error);
|
||||
}
|
||||
} */
|
||||
|
||||
|
||||
|
||||
|
||||
function handleKeydown(event: KeyboardEvent) {
|
||||
if (event.key === 'Enter' && !event.shiftKey) {
|
||||
@@ -300,11 +577,12 @@
|
||||
/>
|
||||
<div class="absolute bottom-3 right-3 flex items-center gap-2">
|
||||
<div class="flex items-center gap-2">
|
||||
{#if uploadedFiles.length > 0}
|
||||
{#if isFileIndicatorVisible}
|
||||
<span class="text-xs text-white/70">
|
||||
{uploadedFiles.length} file{uploadedFiles.length > 1 ? 's' : ''} attached
|
||||
</span>
|
||||
{/if}
|
||||
{#key fileButtonKey}
|
||||
<FileButton
|
||||
name="file-upload"
|
||||
button="btn-icon variant-ghost"
|
||||
@@ -313,12 +591,10 @@
|
||||
disabled={isProcessingFiles || uploadedFiles.length >= 5}
|
||||
class="h-10 w-10 bg-primary-800/30 hover:bg-primary-800/50 rounded-full transition-colors"
|
||||
>
|
||||
{#if uploadedFiles.length > 0}
|
||||
<FileCheck class="w-5 h-5" />
|
||||
{:else}
|
||||
<Paperclip class="w-5 h-5" />
|
||||
{/if}
|
||||
<Paperclip class="w-5 h-5" />
|
||||
|
||||
</FileButton>
|
||||
{/key}
|
||||
<Button
|
||||
type="button"
|
||||
variant="ghost"
|
||||
|
||||
@@ -35,10 +35,13 @@
|
||||
$: 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);
|
||||
}
|
||||
// Auto-scroll on both user messages and assistant messages
|
||||
setTimeout(scrollToBottom, 100);
|
||||
}
|
||||
|
||||
// Also watch for streaming state changes to ensure scrolling when streaming completes
|
||||
$: if ($streamingStore === false) {
|
||||
setTimeout(scrollToBottom, 100);
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<script lang="ts">
|
||||
import Patterns from "./Patterns.svelte";
|
||||
import Models from "./Models.svelte";
|
||||
import ModelConfig from "./ModelConfig.svelte";
|
||||
import { Select } from "$lib/components/ui/select";
|
||||
import { languageStore } from '$lib/store/language-store';
|
||||
|
||||
@@ -11,25 +12,34 @@
|
||||
{ code: 'es', name: 'Spanish' },
|
||||
{ code: 'de', name: 'German' },
|
||||
{ code: 'zh', name: 'Chinese' },
|
||||
{ code: 'ja', name: 'Japanese' }
|
||||
{ code: 'ja', name: 'Japanese' },
|
||||
{ code: 'it', name: 'Italian' }
|
||||
];
|
||||
</script>
|
||||
|
||||
<div class="flex flex-col gap-3">
|
||||
<div class="w-[50%]">
|
||||
<Patterns />
|
||||
<div class="flex gap-4">
|
||||
<!-- Left side - Dropdowns -->
|
||||
<div class="w-[35%] flex flex-col gap-3">
|
||||
<div>
|
||||
<Patterns />
|
||||
</div>
|
||||
<div>
|
||||
<Models />
|
||||
</div>
|
||||
<div>
|
||||
<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>
|
||||
<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>
|
||||
|
||||
<!-- Right side - Model Config -->
|
||||
<div class="w-[65%]">
|
||||
<ModelConfig />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -7,11 +7,13 @@
|
||||
import { onMount } from 'svelte';
|
||||
import Modal from '$lib/components/ui/modal/Modal.svelte';
|
||||
import PatternList from '$lib/components/patterns/PatternList.svelte';
|
||||
import PatternTilesModal from '$lib/components/ui/modal/PatternTilesModal.svelte';
|
||||
import HelpModal from '$lib/components/ui/help/HelpModal.svelte';
|
||||
import { selectedPatternName } from '$lib/store/pattern-store';
|
||||
|
||||
let isMenuOpen = false;
|
||||
let showPatternModal = false;
|
||||
let showPatternTilesModal = false;
|
||||
let showHelpModal = false;
|
||||
|
||||
function goToGithub() {
|
||||
@@ -70,15 +72,33 @@
|
||||
</ul>
|
||||
</nav>
|
||||
|
||||
<div class="flex items-center gap-2">
|
||||
<button name="pattern-description"
|
||||
on:click={() => showPatternModal = true}
|
||||
class="inline-flex h-9 items-center justify-center rounded-full border bg-background px-3 text-sm font-medium transition-colors hover:bg-accent hover:text-accent-foreground gap-2"
|
||||
aria-label="Pattern Description"
|
||||
>
|
||||
<FileText class="h-4 w-4" />
|
||||
<span>Pattern Description</span>
|
||||
</button>
|
||||
<div class="flex items-center gap-4">
|
||||
<!-- Pattern Buttons Group -->
|
||||
<div class="flex items-center gap-3 mr-4">
|
||||
<!-- Pattern Tiles Button -->
|
||||
<button name="pattern-tiles"
|
||||
on:click={() => showPatternTilesModal = true}
|
||||
class="inline-flex h-10 items-center justify-center rounded-full border bg-background px-4 text-sm font-medium transition-colors hover:bg-accent hover:text-accent-foreground gap-2"
|
||||
aria-label="Pattern Tiles"
|
||||
>
|
||||
<FileText class="h-4 w-4" />
|
||||
<span>Pattern Tiles</span>
|
||||
</button>
|
||||
|
||||
<!-- Or text -->
|
||||
<span class="text-sm text-foreground/60 mx-1">or</span>
|
||||
|
||||
<!-- Pattern List Button -->
|
||||
<button name="pattern-list"
|
||||
on:click={() => showPatternModal = true}
|
||||
class="inline-flex h-10 items-center justify-center rounded-full border bg-background px-4 text-sm font-medium transition-colors hover:bg-accent hover:text-accent-foreground gap-2"
|
||||
aria-label="Pattern List"
|
||||
>
|
||||
<FileText class="h-4 w-4" />
|
||||
<span>Pattern List</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
|
||||
<button name="github"
|
||||
on:click={goToGithub}
|
||||
@@ -166,3 +186,16 @@
|
||||
on:close={() => showHelpModal = false}
|
||||
/>
|
||||
</Modal>
|
||||
|
||||
<Modal
|
||||
show={showPatternTilesModal}
|
||||
on:close={() => showPatternTilesModal = false}
|
||||
>
|
||||
<PatternTilesModal
|
||||
on:close={() => showPatternTilesModal = false}
|
||||
on:select={(e) => {
|
||||
selectedPatternName.set(e.detail);
|
||||
showPatternTilesModal = false;
|
||||
}}
|
||||
/>
|
||||
</Modal>
|
||||
|
||||
@@ -1,52 +1,19 @@
|
||||
|
||||
<script lang="ts">
|
||||
import { onMount, createEventDispatcher } from 'svelte';
|
||||
import { get } from 'svelte/store';
|
||||
import type { Pattern } from '$lib/interfaces/pattern-interface';
|
||||
import { favorites } from '$lib/store/favorites-store';
|
||||
import { patterns, patternAPI, systemPrompt, selectedPatternName } from '$lib/store/pattern-store';
|
||||
import { Input } from "$lib/components/ui/input";
|
||||
import TagFilterPanel from './TagFilterPanel.svelte';
|
||||
|
||||
import { createEventDispatcher, onMount } from 'svelte';
|
||||
import TagFilterPanel from '$lib/components/patterns/TagFilterPanel.svelte';
|
||||
let tagFilterRef: TagFilterPanel;
|
||||
|
||||
|
||||
const dispatch = createEventDispatcher<{
|
||||
close: void;
|
||||
select: string;
|
||||
tagsChanged: string[]; // Add this line
|
||||
}>();
|
||||
|
||||
|
||||
let patternsContainer: HTMLDivElement;
|
||||
let sortBy: 'alphabetical' | 'favorites' = 'alphabetical';
|
||||
let searchText = ""; // For pattern filtering
|
||||
let selectedTags: string[] = [];
|
||||
|
||||
// First filter patterns by both text and tags
|
||||
// First filter patterns by both text and tags
|
||||
$: filteredPatterns = $patterns
|
||||
.filter((p: Pattern) =>
|
||||
p.Name.toLowerCase().includes(searchText.toLowerCase())
|
||||
)
|
||||
.filter((p: Pattern) =>
|
||||
selectedTags.length === 0 ||
|
||||
(p.tags && selectedTags.every(tag => p.tags.includes(tag)))
|
||||
);
|
||||
|
||||
// Then sort the filtered patterns
|
||||
$: sortedPatterns = sortBy === 'alphabetical'
|
||||
? [...filteredPatterns].sort((a: Pattern, b: Pattern) => a.Name.localeCompare(b.Name))
|
||||
: [
|
||||
...filteredPatterns.filter((p: Pattern) => $favorites.includes(p.Name)).sort((a: Pattern, b: Pattern) => a.Name.localeCompare(b.Name)),
|
||||
...filteredPatterns.filter((p: Pattern) => !$favorites.includes(p.Name)).sort((a: Pattern, b: Pattern) => a.Name.localeCompare(b.Name))
|
||||
];
|
||||
|
||||
|
||||
function handleTagFilter(event: CustomEvent<string[]>) {
|
||||
selectedTags = event.detail;
|
||||
}
|
||||
|
||||
|
||||
let selectedTags: string[] = [];
|
||||
import { cn } from "$lib/utils/utils";
|
||||
import type { Pattern } from '$lib/interfaces/pattern-interface';
|
||||
import { patterns, patternAPI, selectedPatternName } from '$lib/store/pattern-store';
|
||||
import { favorites } from '$lib/store/favorites-store';
|
||||
import { Input } from "$lib/components/ui/input";
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
let searchQuery = '';
|
||||
let showOnlyFavorites = false;
|
||||
|
||||
onMount(async () => {
|
||||
try {
|
||||
await patternAPI.loadPatterns();
|
||||
@@ -54,156 +21,231 @@ function handleTagFilter(event: CustomEvent<string[]>) {
|
||||
console.error('Error loading patterns:', error);
|
||||
}
|
||||
});
|
||||
|
||||
function toggleFavorite(name: string) {
|
||||
favorites.toggleFavorite(name);
|
||||
}
|
||||
|
||||
|
||||
|
||||
function toggleFavorite(patternName: string) {
|
||||
favorites.toggleFavorite(patternName);
|
||||
}
|
||||
|
||||
function selectPattern(patternName: string) {
|
||||
patternAPI.selectPattern(patternName);
|
||||
dispatch('select', patternName);
|
||||
}
|
||||
|
||||
function closeModal() {
|
||||
dispatch('close');
|
||||
}
|
||||
|
||||
function handleTagFilter(event: CustomEvent<string[]>) {
|
||||
selectedTags = event.detail;
|
||||
}
|
||||
|
||||
function toggleFavoritesFilter() {
|
||||
showOnlyFavorites = !showOnlyFavorites;
|
||||
}
|
||||
|
||||
// Apply filtering based on search query, favorites filter, and tag selection
|
||||
$: filteredPatterns = $patterns
|
||||
.filter(p => {
|
||||
// Apply favorites filter if enabled
|
||||
if (showOnlyFavorites && !$favorites.includes(p.Name)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Apply tag filter if any tags are selected
|
||||
if (selectedTags.length > 0) {
|
||||
if (!p.tags || !selectedTags.every(tag => p.tags.includes(tag))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Apply search filter if query exists
|
||||
if (searchQuery.trim()) {
|
||||
return (
|
||||
p.Name.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
||||
p.Description.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
||||
(p.tags && p.tags.some(tag => tag.toLowerCase().includes(searchQuery.toLowerCase())))
|
||||
);
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
</script>
|
||||
|
||||
<div class="bg-primary-800 rounded-lg flex flex-col h-[85vh] w-[600px] shadow-lg relative">
|
||||
|
||||
<div class="flex flex-col border-b border-primary-700/30">
|
||||
<div class="flex justify-between items-center p-4">
|
||||
<b class="text-lg text-muted-foreground font-bold">Pattern Descriptions</b>
|
||||
<button
|
||||
on:click={() => dispatch('close')}
|
||||
class="text-muted-foreground hover:text-primary-300 transition-colors"
|
||||
>
|
||||
✕
|
||||
</button>
|
||||
<b class="text-lg text-muted-foreground font-bold">Pattern Descriptions</b>
|
||||
<button
|
||||
on:click={closeModal}
|
||||
class="text-muted-foreground hover:text-primary-300 transition-colors"
|
||||
>
|
||||
✕
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="px-4 pb-4 flex items-center justify-between">
|
||||
<div class="flex gap-4">
|
||||
<label class="flex items-center gap-2 text-sm text-muted-foreground">
|
||||
<input
|
||||
type="radio"
|
||||
bind:group={sortBy}
|
||||
value="alphabetical"
|
||||
class="radio"
|
||||
>
|
||||
Alphabetical
|
||||
</label>
|
||||
<label class="flex items-center gap-2 text-sm text-muted-foreground">
|
||||
<input
|
||||
type="radio"
|
||||
bind:group={sortBy}
|
||||
value="favorites"
|
||||
class="radio"
|
||||
>
|
||||
Favorites First
|
||||
</label>
|
||||
</div>
|
||||
<div class="w-64 mr-4">
|
||||
<Input
|
||||
bind:value={searchText}
|
||||
placeholder="Search patterns..."
|
||||
class="text-emerald-900"
|
||||
/>
|
||||
<div class="flex-1 flex items-center">
|
||||
<div class="flex-1 mr-2">
|
||||
<Input
|
||||
bind:value={searchQuery}
|
||||
placeholder="Search patterns..."
|
||||
class="text-emerald-900"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Favorites button similar to PatternTilesModal -->
|
||||
<button
|
||||
on:click={toggleFavoritesFilter}
|
||||
class={cn(
|
||||
"px-3 py-1.5 rounded-md text-sm font-medium transition-all",
|
||||
showOnlyFavorites
|
||||
? "bg-yellow-500/20 text-yellow-300 border border-yellow-500/30"
|
||||
: "bg-primary-700/30 text-primary-300 border border-primary-600/20 hover:bg-primary-700/50"
|
||||
)}
|
||||
>
|
||||
<span class="mr-1">{showOnlyFavorites ? "★" : "☆"}</span>
|
||||
Favorites
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- New tag display section -->
|
||||
<!-- Selected tags display -->
|
||||
<div class="px-4 pb-2">
|
||||
<div class="text-sm text-white/70 bg-primary-700/30 rounded-md p-2 flex justify-between items-center">
|
||||
<div>Tags: {selectedTags.length ? selectedTags.join(', ') : 'none'}</div>
|
||||
<button
|
||||
class="px-2 py-1 text-xs text-white/70 bg-primary-600/30 rounded hover:bg-primary-600/50 transition-colors"
|
||||
on:click={() => {
|
||||
selectedTags = [];
|
||||
dispatch('tagsChanged', selectedTags);
|
||||
}}
|
||||
>
|
||||
reset
|
||||
</button>
|
||||
<div class="flex flex-wrap gap-1 items-center">
|
||||
<span class="mr-1">Tags:</span>
|
||||
{#if selectedTags.length > 0}
|
||||
{#each selectedTags as tag}
|
||||
<div class="inline-flex items-center px-2 py-1 rounded-full text-xs font-medium bg-primary-600/40 text-primary-200 border border-primary-500/30">
|
||||
{tag}
|
||||
<button
|
||||
class="ml-1 text-xs text-primary-300 hover:text-primary-100"
|
||||
on:click={() => {
|
||||
selectedTags = selectedTags.filter(t => t !== tag);
|
||||
}}
|
||||
>
|
||||
×
|
||||
</button>
|
||||
</div>
|
||||
{/each}
|
||||
{:else}
|
||||
<span class="text-primary-300/50">none</span>
|
||||
{/if}
|
||||
</div>
|
||||
<button
|
||||
class="px-2 py-1 text-xs text-white/70 bg-primary-600/30 rounded hover:bg-primary-600/50 transition-colors"
|
||||
on:click={() => {
|
||||
selectedTags = [];
|
||||
if (tagFilterRef && typeof tagFilterRef.reset === 'function') {
|
||||
tagFilterRef.reset();
|
||||
}
|
||||
}}
|
||||
>
|
||||
reset
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
<div class="patterns-container p-4 flex-1 overflow-y-auto">
|
||||
{#if filteredPatterns.length === 0}
|
||||
<div class="flex justify-center items-center h-full">
|
||||
<p class="text-primary-300">
|
||||
{showOnlyFavorites
|
||||
? "No favorite patterns found. Add some favorites first!"
|
||||
: "No patterns found matching your search."}
|
||||
</p>
|
||||
</div>
|
||||
{:else}
|
||||
<div class="patterns-list space-y-2">
|
||||
{#each filteredPatterns as pattern}
|
||||
<div class="pattern-item bg-primary/10 rounded-lg p-3">
|
||||
<div class="flex justify-between items-start gap-4 mb-2">
|
||||
<button
|
||||
class="text-xl font-bold text-primary-300 hover:text-primary-100 cursor-pointer transition-colors text-left w-full"
|
||||
on:click={() => selectPattern(pattern.Name)}
|
||||
>
|
||||
{pattern.Name}
|
||||
</button>
|
||||
<button
|
||||
class="text-muted-foreground hover:text-primary-300 transition-colors"
|
||||
on:click|stopPropagation={() => toggleFavorite(pattern.Name)}
|
||||
>
|
||||
{#if $favorites.includes(pattern.Name)}
|
||||
<span class="text-yellow-400">★</span>
|
||||
{:else}
|
||||
<span class="text-primary-400 hover:text-yellow-300">☆</span>
|
||||
{/if}
|
||||
</button>
|
||||
</div>
|
||||
<p class="text-sm text-muted-foreground break-words leading-relaxed">{pattern.Description}</p>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<TagFilterPanel
|
||||
patterns={$patterns}
|
||||
on:tagsChanged={handleTagFilter}
|
||||
bind:this={tagFilterRef}
|
||||
/>
|
||||
|
||||
<div
|
||||
class="patterns-container p-4 flex-1 overflow-y-auto"
|
||||
bind:this={patternsContainer}
|
||||
>
|
||||
<div class="patterns-list space-y-2">
|
||||
{#each sortedPatterns as pattern}
|
||||
<div class="pattern-item bg-primary/10 rounded-lg p-3">
|
||||
<div class="flex justify-between items-start gap-4 mb-2">
|
||||
<button
|
||||
class="text-xl font-bold text-primary-300 hover:text-primary-100 cursor-pointer transition-colors text-left w-full"
|
||||
on:click={() => {
|
||||
console.log('Selecting pattern:', pattern.Name);
|
||||
patternAPI.selectPattern(pattern.Name);
|
||||
searchText = "";
|
||||
tagFilterRef.reset();
|
||||
dispatch('select', pattern.Name);
|
||||
dispatch('close');
|
||||
}}
|
||||
on:keydown={(e) => {
|
||||
if (e.key === 'Enter' || e.key === ' ') {
|
||||
e.preventDefault();
|
||||
e.currentTarget.click();
|
||||
}
|
||||
}}
|
||||
>
|
||||
{pattern.Name}
|
||||
</button>
|
||||
<button
|
||||
class="text-muted-foreground hover:text-primary-300 transition-colors"
|
||||
on:click={() => toggleFavorite(pattern.Name)}
|
||||
>
|
||||
{#if $favorites.includes(pattern.Name)}
|
||||
★
|
||||
{:else}
|
||||
☆
|
||||
{/if}
|
||||
</button>
|
||||
</div>
|
||||
<p class="text-sm text-muted-foreground break-words leading-relaxed">{pattern.Description}</p>
|
||||
</div>
|
||||
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
patterns={$patterns}
|
||||
on:tagsChanged={handleTagFilter}
|
||||
bind:this={tagFilterRef}
|
||||
hideToggleButton={false}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.patterns-container {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
scrollbar-width: thin;
|
||||
-ms-overflow-style: thin;
|
||||
}
|
||||
/* Custom scrollbar styling */
|
||||
.custom-scrollbar::-webkit-scrollbar {
|
||||
width: 4px;
|
||||
}
|
||||
|
||||
.patterns-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
max-width: 560px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
.custom-scrollbar::-webkit-scrollbar-track {
|
||||
background: rgba(31, 41, 55, 0.2);
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.pattern-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
.custom-scrollbar::-webkit-scrollbar-thumb {
|
||||
background: rgba(156, 163, 175, 0.3);
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.pattern-item:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
.custom-scrollbar::-webkit-scrollbar-thumb:hover {
|
||||
background: rgba(156, 163, 175, 0.5);
|
||||
}
|
||||
|
||||
/* h3.pattern-name {
|
||||
word-break: break-all;
|
||||
hyphens: auto;
|
||||
overflow-wrap: break-word;
|
||||
} */
|
||||
|
||||
.custom-scrollbar {
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: rgba(156, 163, 175, 0.3) rgba(31, 41, 55, 0.2);
|
||||
}
|
||||
|
||||
.patterns-container {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
scrollbar-width: thin;
|
||||
-ms-overflow-style: thin;
|
||||
}
|
||||
|
||||
.patterns-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
max-width: 560px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.pattern-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
.pattern-item:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -7,17 +7,10 @@
|
||||
}>();
|
||||
|
||||
export let patterns: Pattern[];
|
||||
export let hideToggleButton = false; // New prop to hide the toggle button when used in modal
|
||||
let selectedTags: string[] = [];
|
||||
let isExpanded = false;
|
||||
|
||||
// Add console log to see what tags we're getting
|
||||
$: console.log('Available tags:', Array.from(new Set(patterns.flatMap(p => p.tags))));
|
||||
|
||||
// Add these debug logs
|
||||
$: console.log('Patterns received:', patterns);
|
||||
$: console.log('Tags extracted:', patterns.map(p => p.tags));
|
||||
$: console.log('Panel expanded:', isExpanded);
|
||||
|
||||
function toggleTag(tag: string) {
|
||||
selectedTags = selectedTags.includes(tag)
|
||||
? selectedTags.filter(t => t !== tag)
|
||||
@@ -36,15 +29,16 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="tag-panel {isExpanded ? 'expanded' : ''}" style="z-index: 50">
|
||||
<div class="tag-panel {isExpanded ? 'expanded' : ''} {hideToggleButton ? 'embedded' : ''}" style="z-index: 50">
|
||||
{#if !hideToggleButton}
|
||||
<div class="panel-header">
|
||||
<button class="close-btn" on:click={togglePanel}>
|
||||
{isExpanded ? 'Close Filter Tags ◀' : 'Open Filter Tags ▶'}
|
||||
</button>
|
||||
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<div class="panel-content">
|
||||
<div class="panel-content {hideToggleButton ? 'always-visible' : ''}">
|
||||
<div class="reset-container">
|
||||
<button
|
||||
class="reset-btn"
|
||||
@@ -56,7 +50,7 @@
|
||||
Reset All Tags
|
||||
</button>
|
||||
</div>
|
||||
{#each Array.from(new Set(patterns.flatMap(p => p.tags))).sort() as tag}
|
||||
{#each Array.from(new Set(patterns.flatMap(p => p.tags || []))).sort() as tag}
|
||||
<button
|
||||
class="tag-brick {selectedTags.includes(tag) ? 'selected' : ''}"
|
||||
on:click={() => toggleTag(tag)}
|
||||
@@ -67,6 +61,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<style>
|
||||
/* Default positioning for standalone mode */
|
||||
.tag-panel {
|
||||
position: fixed; /* Change to fixed positioning */
|
||||
left: calc(50% + 300px); /* Position starts after modal's right edge */
|
||||
@@ -76,19 +71,37 @@
|
||||
transition: left 0.3s ease;
|
||||
}
|
||||
|
||||
/* When embedded in another component, use relative positioning */
|
||||
.tag-panel.embedded {
|
||||
position: relative;
|
||||
left: auto;
|
||||
top: auto;
|
||||
transform: none;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.tag-panel.expanded {
|
||||
left: calc(50% + 360px); /* Final position just to the right of modal */
|
||||
}
|
||||
|
||||
|
||||
.panel-content {
|
||||
.panel-content {
|
||||
display: none;
|
||||
padding: 12px;
|
||||
flex-wrap: wrap;
|
||||
gap: 6px;
|
||||
max-height: 80vh;
|
||||
overflow-y: auto;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
}
|
||||
|
||||
/* Adjust max-height when embedded */
|
||||
.embedded .panel-content {
|
||||
max-height: 100%;
|
||||
}
|
||||
|
||||
/* When used in modal, always show content */
|
||||
.panel-content.always-visible {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.tag-brick {
|
||||
@@ -102,7 +115,6 @@
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
|
||||
.reset-container {
|
||||
width: 100%;
|
||||
padding-bottom: 8px;
|
||||
@@ -124,29 +136,9 @@
|
||||
background: rgba(255,255,255,0.1);
|
||||
}
|
||||
|
||||
|
||||
|
||||
.expanded .panel-content {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
|
||||
/* .toggle-btn {
|
||||
position: absolute;
|
||||
left: -30px;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
padding: 8px;
|
||||
background: var(--primary-800);
|
||||
border-radius: 4px 0 0 4px;
|
||||
cursor: pointer;
|
||||
.expanded .panel-content {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
font-size: 0.9rem;
|
||||
box-shadow: -2px 0 5px rgba(0,0,0,0.2);
|
||||
} */
|
||||
|
||||
}
|
||||
|
||||
.panel-header {
|
||||
padding: 8px;
|
||||
@@ -178,17 +170,11 @@
|
||||
margin-left: -50px;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
.close-btn:hover {
|
||||
background: rgba(255,255,255,0.1);
|
||||
}
|
||||
|
||||
|
||||
|
||||
.tag-brick.selected {
|
||||
background: var(--primary-300);
|
||||
}
|
||||
.tag-brick.selected {
|
||||
background: var(--primary-300);
|
||||
}
|
||||
</style>
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user