mirror of
https://github.com/danielmiessler/Fabric.git
synced 2026-01-09 22:38:10 -05:00
Compare commits
82 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ba2e178e03 | ||
|
|
ed298bcedd | ||
|
|
6b04e6e674 | ||
|
|
04c0f6a0a5 | ||
|
|
f7ab484510 | ||
|
|
f50a14305a | ||
|
|
d5f0cd7616 | ||
|
|
67c658f5b4 | ||
|
|
ef3bc03343 | ||
|
|
e31cb2b46a | ||
|
|
6ca7142ea4 | ||
|
|
8b2174897a | ||
|
|
ac5eab0563 | ||
|
|
65414dcc1c | ||
|
|
5db352f5be | ||
|
|
9e7830ff77 | ||
|
|
5945c0e16b | ||
|
|
29ee141822 | ||
|
|
8a69621e87 | ||
|
|
1645b0c4ea | ||
|
|
45205574d5 | ||
|
|
71a5e0394a | ||
|
|
f286936c23 | ||
|
|
9000f92a55 | ||
|
|
8c84d4b3c8 | ||
|
|
1d77afcc44 | ||
|
|
835bc6044b | ||
|
|
ef895a1ab9 | ||
|
|
82039cedaf | ||
|
|
973df61dfd | ||
|
|
661c85d7a6 | ||
|
|
4638f67fb7 | ||
|
|
ab71dbcd4f | ||
|
|
2abdabc100 | ||
|
|
9f78a2c8e1 | ||
|
|
76f78601f2 | ||
|
|
4eaba2dc56 | ||
|
|
2dcd9cb5f7 | ||
|
|
2943872bde | ||
|
|
b901542a48 | ||
|
|
c122ff8960 | ||
|
|
e128d818c4 | ||
|
|
5e9d6d0a91 | ||
|
|
70edf9cbe3 | ||
|
|
e61a0a9391 | ||
|
|
f8ddf98404 | ||
|
|
55219467f3 | ||
|
|
74d4be1ac6 | ||
|
|
9e57f8c6f1 | ||
|
|
3d2903cb47 | ||
|
|
13e9d22ec6 | ||
|
|
01d12c47cf | ||
|
|
c3258a2c3f | ||
|
|
746885e263 | ||
|
|
b25895c1d2 | ||
|
|
e40b1c1f66 | ||
|
|
ef2ec8bffe | ||
|
|
589991e6a6 | ||
|
|
965392ebbd | ||
|
|
6f615baf53 | ||
|
|
b60bad7799 | ||
|
|
234d1303ad | ||
|
|
cd74a96be2 | ||
|
|
ceaa90a7c7 | ||
|
|
15a2eeadc9 | ||
|
|
8d02f5b21d | ||
|
|
0f8f0b6b39 | ||
|
|
fd58b6d410 | ||
|
|
2579d37c16 | ||
|
|
4f28d85e96 | ||
|
|
f529b8bb80 | ||
|
|
71437605e1 | ||
|
|
cf5753a186 | ||
|
|
433c83fe2c | ||
|
|
01770cc6e3 | ||
|
|
55fda5e025 | ||
|
|
daad5f986e | ||
|
|
3785d0a5fa | ||
|
|
8a326e9cfb | ||
|
|
5f5822f1c6 | ||
|
|
111482e46e | ||
|
|
9b56e0e996 |
@@ -1,5 +1,5 @@
|
||||
# Use official golang image as builder
|
||||
FROM golang:1.23.3-alpine AS builder
|
||||
FROM golang:1.23.4-alpine AS builder
|
||||
|
||||
# Set working directory
|
||||
WORKDIR /app
|
||||
|
||||
@@ -1693,8 +1693,87 @@
|
||||
},
|
||||
{
|
||||
"patternName": "extract_wisdom_short",
|
||||
"description": "Extract condensed insightful ideas and recommendations focusing on life wisdom.",
|
||||
"tags": [
|
||||
"EXTRACT",
|
||||
"WISDOM",
|
||||
"SELF"
|
||||
]
|
||||
},
|
||||
{
|
||||
"patternName": "analyze_bill",
|
||||
"description": "Analyze a legislative bill and implications.",
|
||||
"tags": [
|
||||
"ANALYSIS",
|
||||
"BILL"
|
||||
]
|
||||
},
|
||||
{
|
||||
"patternName": "analyze_bill_short",
|
||||
"description": "Consended - Analyze a legislative bill and implications.",
|
||||
"tags": [
|
||||
"ANALYSIS",
|
||||
"BILL"
|
||||
]
|
||||
},
|
||||
{
|
||||
"patternName": "create_coding_feature",
|
||||
"description": "[Description pending]",
|
||||
"tags": []
|
||||
"tags": [
|
||||
"DEVELOPMENT"
|
||||
]
|
||||
},
|
||||
{
|
||||
"patternName": "create_excalidraw_visualization",
|
||||
"description": "Create visualizations using Excalidraw.",
|
||||
"tags": [
|
||||
"VISUALIZATION"
|
||||
]
|
||||
},
|
||||
{
|
||||
"patternName": "create_flash_cards",
|
||||
"description": "Generate flashcards for key concepts and definitions.",
|
||||
"tags": [
|
||||
"LEARNING"
|
||||
]
|
||||
},
|
||||
{
|
||||
"patternName": "create_loe_document",
|
||||
"description": "Create detailed Level of Effort (LOE) estimation documents.",
|
||||
"tags": [
|
||||
"DEVELOPMENT",
|
||||
"BUSINESS"
|
||||
]
|
||||
},
|
||||
{
|
||||
"patternName": "extract_domains",
|
||||
"description": "Extract key content and source.",
|
||||
"tags": [
|
||||
"EXTRACT",
|
||||
"ANALYSIS"
|
||||
]
|
||||
},
|
||||
{
|
||||
"patternName": "extract_main_activities",
|
||||
"description": "Extract and list main events from transcripts.",
|
||||
"tags": [
|
||||
"EXTRACT",
|
||||
"ANALYSIS"
|
||||
]
|
||||
},
|
||||
{
|
||||
"patternName": "find_female_life_partner",
|
||||
"description": "Clarify and summarize partner criteria in direct language.",
|
||||
"tags": [
|
||||
"SELF"
|
||||
]
|
||||
},
|
||||
{
|
||||
"patternName": "youtube_summary",
|
||||
"description": "Summarize YouTube videos with key points and timestamps.",
|
||||
"tags": [
|
||||
"SUMMARIZE"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -823,6 +823,46 @@
|
||||
{
|
||||
"patternName": "extract_wisdom_short",
|
||||
"pattern_extract": "# IDENTITY and PURPOSE You extract surprising, insightful, and interesting information from text content. You are interested in insights related to the purpose and meaning of life, human flourishing, the role of technology in the future of humanity, artificial intelligence and its affect on humans, memes, learning, reading, books, continuous improvement, and similar topics. Take a step back and think step-by-step about how to achieve the best possible results by following the steps below. # STEPS - Extract a summary of the content in 50 words, including who is presenting and the content being discussed into a section called SUMMARY. - Extract 10 to 20 of the most surprising, insightful, and/or interesting ideas from the input in a section called IDEAS:. If there are less than 50 then collect all of them. Make sure you extract at least 20. - Extract 5 to 10 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 10 TO 15 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 5 to 10 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. - Extract 5 to 10 of the most surprising, insightful, and/or interesting valid facts about the greater world that were mentioned in the content into a section called FACTS:. - Extract all mentions of writing, art, tools, projects and other sources of inspiration mentioned by the speakers into a section called REFERENCES. This should include any and all references to something that the speaker mentioned. - Extract the most potent takeaway and recommendation into a section called ONE-SENTENCE TAKEAWAY. This should be a 15-word sentence that captures the most important essence of the content. - Extract the 5 to 10 of the most surprising, insightful, and/or interesting recommendations that can be collected from the content into a section called RECOMMENDATIONS. # OUTPUT INSTRUCTIONS - Only output Markdown. - Write the IDEAS bullets as exactly 16 words. - Write the RECOMMENDATIONS bullets as exactly 16 words. - Write the HABITS bullets as exactly 16 words. - Write the FACTS bullets as exactly 16 words. - Write the INSIGHTS bullets as exactly 16 words. - Extract at least 25 IDEAS from the content. - Extract at least 5 INSIGHTS from the content. - 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"
|
||||
},
|
||||
{
|
||||
"patternName": "analyze_bill",
|
||||
"pattern_extract": "# IDENTITY You are an AI with a 3,129 IQ that specializes in discerning the true nature and goals of a piece of legislation. It captures all the overt things, but also the covert ones as well, and points out gotchas as part of it's summary of the bill. # STEPS 1. Read the entire bill 37 times using different perspectives. 2. Map out all the stuff it's trying to do on a 10 KM by 10K mental whiteboard. 3. Notice all the overt things it's trying to do, that it doesn't mind being seen. 4. Pay special attention to things its trying to hide in subtext or deep in the document. # OUTPUT 1. Give the metadata for the bill, such as who proposed it, when, etc. 2. Create a 24-word summary of the bill and what it's trying to accomplish. 3. Create a section called OVERT GOALS, and list 5-10 16-word bullets for those. 4. Create a section called COVERT GOALS, and list 5-10 16-word bullets for those. 5. Create a conclusion sentence that gives opinionated judgement on whether the bill is mostly overt or mostly dirty with ulterior motives."
|
||||
},
|
||||
{
|
||||
"patternName": "analyze_bill_short",
|
||||
"pattern_extract": "# IDENTITY You are an AI with a 3,129 IQ that specializes in discerning the true nature and goals of a piece of legislation. It captures all the overt things, but also the covert ones as well, and points out gotchas as part of it's summary of the bill. # STEPS 1. Read the entire bill 37 times using different perspectives. 2. Map out all the stuff it's trying to do on a 10 KM by 10K mental whiteboard. 3. Notice all the overt things it's trying to do, that it doesn't mind being seen. 4. Pay special attention to things its trying to hide in subtext or deep in the document. # OUTPUT 1. Give the metadata for the bill, such as who proposed it, when, etc. 2. Create a 16-word summary of the bill and what it's trying to accomplish. 3. Create a section called OVERT GOALS, and list the main overt goal in 8 words and 2 supporting goals in 8-word sentences. 3. Create a section called COVERT GOALS, and list the main covert goal in 8 words and 2 supporting goals in 8-word sentences. 5. Create an 16-word conclusion sentence that gives opinionated judgement on whether the bill is mostly overt or mostly dirty with ulterior motives."
|
||||
},
|
||||
{
|
||||
"patternName": "create_coding_feature",
|
||||
"pattern_extract": "# 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"
|
||||
},
|
||||
{
|
||||
"patternName": "create_excalidraw_visualization",
|
||||
"pattern_extract": "# IDENTITY You are an expert AI with a 1,222 IQ that deeply understands the relationships between complex ideas and concepts. You are also an expert in the Excalidraw tool and schema. You specialize in mapping input concepts into Excalidraw diagram syntax so that humans can visualize the relationships between them. # STEPS 1. Deeply study the input. 2. Think for 47 minutes about each of the sections in the input. 3. Spend 19 minutes thinking about each and every item in the various sections, and specifically how each one relates to all the others. E.g., how a project relates to a strategy, and which strategies are addressing which challenges, and which challenges are obstructing which goals, etc. 4. Build out this full mapping in on a 9KM x 9KM whiteboard in your mind. 5. Analyze and improve this mapping for 13 minutes. # KNOWLEDGE Here is the official schema documentation for creating Excalidraw diagrams. Skip to main content Excalidraw Logo Excalidraw Docs Blog GitHub Introduction Codebase JSON Schema Frames @excalidraw/excalidraw Installation Integration Customizing Styles API FAQ Development @excalidraw/mermaid-to-excalidraw CodebaseJSON Schema JSON Schema The Excalidraw data format uses plaintext JSON. Excalidraw files When saving an Excalidraw scene locally to a file, the JSON file (.excalidraw) is using the below format. Attributes Attribute Description Value type The type of the Excalidraw schema \"excalidraw\" version The version of the Excalidraw schema number source The source URL of the Excalidraw application \"https://excalidraw.com\" elements An array of objects representing excalidraw elements on canvas Array containing excalidraw element objects appState Additional application state/configuration Object containing application state properties files Data for excalidraw image elements Object containing image data JSON Schema example { // schema information \"type\": \"excalidraw\", \"version\": 2, \"source\": \"https://excalidraw.com\", // elements on canvas \"elements\": [ // example element { \"id\": \"pologsyG-tAraPgiN9xP9b\", \"type\": \"rectangle\", \"x\": 928, \"y\": 319, \"width\": 134, \"height\": 90 /* ...other element properties */ } /* other elements */ ], // editor state (canvas config, preferences, ...) \"appState\": { \"gridSize\": 20, \"viewBackgroundColor\": \"#ffffff\" }, // files data for \"image\" elements, using format `{ [fileId]: fileData }` \"files\": { // example of an image data object \"3cebd7720911620a3938ce77243696149da03861\": { \"mimeType\": \"image/png\", \"id\": \"3cebd7720911620a3938c.77243626149da03861\", \"dataURL\": \"data:image/png;base64,iVBORWOKGgoAAAANSUhEUgA=\", \"created\": 1690295874454, \"lastRetrieved\": 1690295874454 } /* ...other image data objects */ } } Excalidraw clipboard format When copying selected excalidraw elements to clipboard, the JSON schema is similar to .excalidraw format, except it differs in attributes. Attributes Attribute Description Example Value type The type of the Excalidraw document. \"excalidraw/clipboard\" elements An array of objects representing excalidraw elements on canvas. Array containing excalidraw element objects (see example below) files Data for excalidraw image elements. Object containing image data Edit this page Previous Contributing Next Frames Excalidraw files Attributes JSON Schema example Excalidraw clipboard format Attributes Docs Get Started Community Discord Twitter Linkedin More Blog GitHub Copyright © 2023 Excalidraw community. Built with Docusaurus ❤️ # OUTPUT 1. Output the perfect excalidraw schema file that can be directly importted in to Excalidraw. This should have no preamble or follow-on text"
|
||||
},
|
||||
{
|
||||
"patternName": "create_flash_cards",
|
||||
"pattern_extract": "# 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)."
|
||||
},
|
||||
{
|
||||
"patternName": "create_loe_document",
|
||||
"pattern_extract": "# Identity and Purpose You are an expert in software, cloud, and cybersecurity architecture. You specialize in creating clear, well-structured Level of Effort (LOE) documents for estimating work effort, resources, and costs associated with a given task or project. # Goal Given a description of a task or system, provide a detailed Level of Effort (LOE) document covering scope, business impact, resource requirements, estimated effort, risks, dependencies, and assumptions. # Steps 1. Analyze the input task thoroughly to ensure full comprehension. 2. Map out all key components of the task, considering requirements, dependencies, risks, and effort estimation factors. 3. Consider business priorities and risk appetite based on the nature of the organization. 4. Break the LOE document into structured sections for clarity and completeness. --- # Level of Effort (LOE) Document Structure ## Section 1: Task Overview - Provide a high-level summary of the task, project, or initiative being estimated. - Define objectives and expected outcomes. - Identify key stakeholders and beneficiaries. ## Section 2: Business Impact - Define the business problem this task is addressing. - List the expected benefits and value to the organization. - Highlight any business risks or regulatory considerations. ## Section 3: Scope & Deliverables - Outline in-scope and out-of-scope work. - Break down major deliverables and milestones. - Specify acceptance criteria for successful completion. ## Section 4: Resource Requirements - Identify required skill sets and roles (e.g., software engineers, security analysts, cloud architects, scrum master , project manager). - Estimate the number of personnel needed , in tabular format. - List tooling, infrastructure, or licenses required. ## Section 5: Estimated Effort - Break down tasks into granular units (e.g., design, development, testing, deployment). - Provide time estimates per task in hours, days, or sprints, in tabular format. - Aggregate total effort for the entire task or project. - Include buffer time for unforeseen issues or delays. - Use T-shirt sizing (S/M/L/XL) or effort points to classify work complexity. ## Section 6: Dependencies - List external dependencies (e.g., APIs, third-party vendors, internal teams). - Specify hardware/software requirements that may impact effort. ## Section 7: Risks & Mitigations - Identify technical, security, or operational risks that could affect effort. - Propose mitigation strategies to address risks. - Indicate if risks could lead to effort overruns. ## Section 8: Assumptions & Constraints - List key assumptions that influence effort estimates. - Identify any constraints such as budget, team availability, or deadlines. ## Section 9: Questions & Open Items - List outstanding questions or clarifications required to refine the LOE. - Highlight areas needing further input from stakeholders. --- # Output Instructions - Output the LOE document in valid Markdown format. - Do not use bold or italic formatting. - Do not provide commentary or disclaimers, just execute the request. # Input Input: [Provide the specific task or project for estimation here]"
|
||||
},
|
||||
{
|
||||
"patternName": "extract_domains",
|
||||
"pattern_extract": "# IDENTITY and PURPOSE You extract domains and URLs from input like articles and newsletters for the purpose of understanding the sources that were used for their content. # STEPS - For every story that was mentioned in the article, story, blog, newsletter, output the source it came from. - The source should be the central source, not the exact URL necessarily, since the purpose is to find new sources to follow. - As such, if it's a person, link their profile that was in the input. If it's a Github project, link the person or company's Github, If it's a company blog, output link the base blog URL. If it's a paper, link the publication site. Etc. - Only output each source once. - Only output the source, nothing else, one per line # INPUT INPUT:"
|
||||
},
|
||||
{
|
||||
"patternName": "extract_main_activities",
|
||||
"pattern_extract": "# 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."
|
||||
},
|
||||
{
|
||||
"patternName": "find_female_life_partner",
|
||||
"pattern_extract": "# 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."
|
||||
},
|
||||
{
|
||||
"patternName": "youtube_summary",
|
||||
"pattern_extract": "# 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:"
|
||||
}
|
||||
]
|
||||
}
|
||||
34
README.md
34
README.md
@@ -1,4 +1,7 @@
|
||||
<div align="center">
|
||||
Fabric is graciously supported by…
|
||||
|
||||
[](https://warp.dev/fabric)
|
||||
|
||||
<img src="./images/fabric-logo-gif.gif" alt="fabriclogo" width="400" height="400"/>
|
||||
|
||||
@@ -15,7 +18,7 @@
|
||||
</p>
|
||||
|
||||
[Updates](#updates) •
|
||||
[What and Why](#whatandwhy) •
|
||||
[What and Why](#what-and-why) •
|
||||
[Philosophy](#philosophy) •
|
||||
[Installation](#Installation) •
|
||||
[Usage](#Usage) •
|
||||
@@ -65,6 +68,7 @@
|
||||
- [Helper Apps](#helper-apps)
|
||||
- [`to_pdf`](#to_pdf)
|
||||
- [`to_pdf` Installation](#to_pdf-installation)
|
||||
- [`code_helper`](#code_helper)
|
||||
- [pbpaste](#pbpaste)
|
||||
- [Web Interface](#web-interface)
|
||||
- [Installing](#installing)
|
||||
@@ -72,15 +76,16 @@
|
||||
- [Clipboard Support](#clipboard-support)
|
||||
- [Meta](#meta)
|
||||
- [Primary contributors](#primary-contributors)
|
||||
- [Contributors](#contributors)
|
||||
|
||||
<br />
|
||||
|
||||
## Updates
|
||||
|
||||
> [!NOTE]
|
||||
> February 24, 2025
|
||||
> April 16, 2025
|
||||
>
|
||||
> - Fabric now supports Sonnet 3.7! Update and use `-S` to select it as your default if you want, or just use the shortcut `-m claude-3-7-sonnet-latest`. Enjoy!
|
||||
> - Fabric now supports Grok (from XAI)! Update and use `-S` to select it as your default if you want, or just use the shortcut `-m grok-3-beta`. Enjoy!
|
||||
|
||||
## What and why
|
||||
|
||||
@@ -471,6 +476,7 @@ Application Options:
|
||||
--rmextension= Remove a registered extension by name
|
||||
--strategy= Choose a strategy from the available strategies
|
||||
--liststrategies List all strategies
|
||||
--listvendors List all vendors
|
||||
|
||||
Help Options:
|
||||
-h, --help Show this help message
|
||||
@@ -599,6 +605,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.
|
||||
@@ -698,6 +718,14 @@ The Streamlit UI supports clipboard operations across different platforms:
|
||||
<a href="https://github.com/sbehrens"><img src="https://avatars.githubusercontent.com/u/688589?v=4" title="Scott Behrens" width="50" height="50"></a>
|
||||
<a href="https://github.com/agu3rra"><img src="https://avatars.githubusercontent.com/u/10410523?v=4" title="Andre Guerra" width="50" height="50"></a>
|
||||
|
||||
### Contributors
|
||||
|
||||
<a href="https://github.com/danielmiessler/fabric/graphs/contributors">
|
||||
<img src="https://contrib.rocks/image?repo=danielmiessler/fabric" />
|
||||
</a>
|
||||
|
||||
Made with [contrib.rocks](https://contrib.rocks).
|
||||
|
||||
`fabric` was created by <a href="https://danielmiessler.com/subscribe" target="_blank">Daniel Miessler</a> in January of 2024.
|
||||
<br /><br />
|
||||
<a href="https://twitter.com/intent/user?screen_name=danielmiessler"></a>
|
||||
|
||||
@@ -1,20 +1,25 @@
|
||||
# YAML Configuration Support
|
||||
|
||||
## Overview
|
||||
|
||||
Fabric now supports YAML configuration files for commonly used options. This allows users to persist settings and share configurations across multiple runs.
|
||||
|
||||
## Usage
|
||||
|
||||
Use the `--config` flag to specify a YAML configuration file:
|
||||
|
||||
```bash
|
||||
fabric --config ~/.config/fabric/config.yaml "Tell me about APIs"
|
||||
```
|
||||
|
||||
## Configuration Precedence
|
||||
|
||||
1. CLI flags (highest priority)
|
||||
2. YAML config values
|
||||
3. Default values (lowest priority)
|
||||
|
||||
## Supported Configuration Options
|
||||
|
||||
```yaml
|
||||
# Model selection
|
||||
model: gpt-4
|
||||
@@ -36,6 +41,7 @@ raw: false
|
||||
```
|
||||
|
||||
## Rules and Behavior
|
||||
|
||||
- Only long flag names are supported in YAML (e.g., `temperature` not `-t`)
|
||||
- CLI flags always override YAML values
|
||||
- Unknown YAML declarations are ignored
|
||||
@@ -43,12 +49,15 @@ raw: false
|
||||
- The order of YAML declarations doesn't matter
|
||||
|
||||
## Type Conversions
|
||||
|
||||
The following string-to-type conversions are supported:
|
||||
|
||||
- String to number: `"42"` → `42`
|
||||
- String to float: `"42.5"` → `42.5`
|
||||
- String to boolean: `"true"` → `true`
|
||||
|
||||
## Example Config
|
||||
|
||||
```yaml
|
||||
# ~/.config/fabric/config.yaml
|
||||
model: gpt-4
|
||||
@@ -61,8 +70,8 @@ frequencypenalty: 0.2
|
||||
```
|
||||
|
||||
## CLI Override Example
|
||||
|
||||
```bash
|
||||
# Override temperature from config
|
||||
fabric --config ~/.config/fabric/config.yaml --temperature 0.9 "Query"
|
||||
```
|
||||
|
||||
|
||||
11
cli/cli.go
11
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
|
||||
}
|
||||
|
||||
@@ -164,6 +164,11 @@ func Cli(version string) (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
if currentFlags.ListVendors {
|
||||
err = registry.ListVendors(os.Stdout)
|
||||
return
|
||||
}
|
||||
|
||||
// if the interactive flag is set, run the interactive function
|
||||
// if currentFlags.Interactive {
|
||||
// interactive.Interactive()
|
||||
@@ -211,7 +216,9 @@ func Cli(version string) (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
messageTools, err = processYoutubeVideo(currentFlags, registry, videoId)
|
||||
if messageTools, err = processYoutubeVideo(currentFlags, registry, videoId); err != nil {
|
||||
return
|
||||
}
|
||||
if !currentFlags.IsChatRequest() {
|
||||
err = currentFlags.WriteOutput(messageTools)
|
||||
return
|
||||
|
||||
@@ -64,6 +64,7 @@ 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"`
|
||||
@@ -71,6 +72,7 @@ type Flags struct {
|
||||
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"`
|
||||
ListVendors bool `long:"listvendors" description:"List all vendors"`
|
||||
}
|
||||
|
||||
var debug = false
|
||||
@@ -333,7 +335,6 @@ func (o *Flags) BuildChatRequest(Meta string) (ret *common.ChatRequest, err erro
|
||||
|
||||
func (o *Flags) AppendMessage(message string) {
|
||||
o.Message = AppendMessage(o.Message, message)
|
||||
return
|
||||
}
|
||||
|
||||
func (o *Flags) IsChatRequest() (ret bool) {
|
||||
|
||||
@@ -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
|
||||
|
||||
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,6 +2,8 @@ package common
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/samber/lo"
|
||||
)
|
||||
@@ -71,15 +73,31 @@ func (o *GroupsItemsSelector[I]) Print() {
|
||||
fmt.Printf("\n%v:\n", o.SelectionLabel)
|
||||
|
||||
var currentItemIndex int
|
||||
for _, groupItems := range o.GroupsItems {
|
||||
// Create a copy of groups to sort
|
||||
sortedGroupsItems := make([]*GroupItems[I], len(o.GroupsItems))
|
||||
copy(sortedGroupsItems, o.GroupsItems)
|
||||
|
||||
// Sort groups alphabetically case-insensitive
|
||||
sort.SliceStable(sortedGroupsItems, func(i, j int) bool {
|
||||
return strings.ToLower(sortedGroupsItems[i].Group) < strings.ToLower(sortedGroupsItems[j].Group)
|
||||
})
|
||||
|
||||
for _, groupItems := range sortedGroupsItems {
|
||||
fmt.Println()
|
||||
fmt.Printf("%s\n", groupItems.Group)
|
||||
fmt.Println()
|
||||
|
||||
for _, item := range groupItems.Items {
|
||||
// Create a copy of items to sort
|
||||
sortedItems := make([]I, len(groupItems.Items))
|
||||
copy(sortedItems, groupItems.Items)
|
||||
// Sort items alphabetically case-insensitive
|
||||
sort.SliceStable(sortedItems, func(i, j int) bool {
|
||||
return strings.ToLower(o.GetItemKey(sortedItems[i])) < strings.ToLower(o.GetItemKey(sortedItems[j]))
|
||||
})
|
||||
|
||||
for _, item := range sortedItems {
|
||||
currentItemIndex++
|
||||
fmt.Printf("\t[%d]\t%s\n", currentItemIndex, o.GetItemKey(item))
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,9 @@ package core
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
goopenai "github.com/sashabaranov/go-openai"
|
||||
@@ -28,6 +30,7 @@ type Chatter struct {
|
||||
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
|
||||
@@ -79,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 != "" {
|
||||
@@ -158,8 +185,10 @@ func (o *Chatter) BuildSession(request *common.ChatRequest, raw bool) (session *
|
||||
}
|
||||
}
|
||||
|
||||
if request.Language != "" {
|
||||
systemMessage = fmt.Sprintf("%s. Please use the language '%s' for the output.", systemMessage, request.Language)
|
||||
// Apply refined language instruction if specified
|
||||
if request.Language != "" && request.Language != "en" {
|
||||
// Refined instruction: Execute pattern using user input, then translate the entire response.
|
||||
systemMessage = fmt.Sprintf("%s\n\nIMPORTANT: First, execute the instructions provided in this prompt using the user's input. Second, ensure your entire final response, including any section headers or titles generated as part of executing the instructions, is written ONLY in the %s language.", systemMessage, request.Language)
|
||||
}
|
||||
|
||||
if raw {
|
||||
@@ -185,7 +214,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
|
||||
}
|
||||
|
||||
@@ -3,9 +3,12 @@ package core
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/danielmiessler/fabric/plugins/ai/exolab"
|
||||
"github.com/danielmiessler/fabric/plugins/strategy"
|
||||
@@ -17,17 +20,12 @@ import (
|
||||
"github.com/danielmiessler/fabric/plugins/ai"
|
||||
"github.com/danielmiessler/fabric/plugins/ai/anthropic"
|
||||
"github.com/danielmiessler/fabric/plugins/ai/azure"
|
||||
"github.com/danielmiessler/fabric/plugins/ai/deepseek"
|
||||
"github.com/danielmiessler/fabric/plugins/ai/dryrun"
|
||||
"github.com/danielmiessler/fabric/plugins/ai/gemini"
|
||||
"github.com/danielmiessler/fabric/plugins/ai/groq"
|
||||
"github.com/danielmiessler/fabric/plugins/ai/litellm"
|
||||
"github.com/danielmiessler/fabric/plugins/ai/lmstudio"
|
||||
"github.com/danielmiessler/fabric/plugins/ai/mistral"
|
||||
"github.com/danielmiessler/fabric/plugins/ai/ollama"
|
||||
"github.com/danielmiessler/fabric/plugins/ai/openai"
|
||||
"github.com/danielmiessler/fabric/plugins/ai/openrouter"
|
||||
"github.com/danielmiessler/fabric/plugins/ai/siliconcloud"
|
||||
"github.com/danielmiessler/fabric/plugins/ai/openai_compatible"
|
||||
"github.com/danielmiessler/fabric/plugins/db/fsdb"
|
||||
"github.com/danielmiessler/fabric/plugins/template"
|
||||
"github.com/danielmiessler/fabric/plugins/tools"
|
||||
@@ -56,27 +54,49 @@ func NewPluginRegistry(db *fsdb.Db) (ret *PluginRegistry, err error) {
|
||||
|
||||
ret.Defaults = tools.NeeDefaults(ret.GetModels)
|
||||
|
||||
ret.VendorsAll.AddVendors(
|
||||
// Create a vendors slice to hold all vendors (order doesn't matter initially)
|
||||
vendors := []ai.Vendor{}
|
||||
|
||||
// Add non-OpenAI compatible clients
|
||||
vendors = append(vendors,
|
||||
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(),
|
||||
litellm.NewClient(),
|
||||
)
|
||||
|
||||
// Add all OpenAI-compatible providers
|
||||
for providerName := range openai_compatible.ProviderMap {
|
||||
provider, _ := openai_compatible.GetProviderByName(providerName)
|
||||
vendors = append(vendors, openai_compatible.NewClient(provider))
|
||||
}
|
||||
|
||||
// Sort vendors by name for consistent ordering (case-insensitive)
|
||||
sort.Slice(vendors, func(i, j int) bool {
|
||||
return strings.ToLower(vendors[i].GetName()) < strings.ToLower(vendors[j].GetName())
|
||||
})
|
||||
|
||||
// Add all sorted vendors to VendorsAll
|
||||
ret.VendorsAll.AddVendors(vendors...)
|
||||
_ = ret.Configure()
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (o *PluginRegistry) ListVendors(out io.Writer) error {
|
||||
vendors := lo.Map(o.VendorsAll.Vendors, func(vendor ai.Vendor, _ int) string {
|
||||
return vendor.GetName()
|
||||
})
|
||||
fmt.Fprint(out, "Available Vendors:\n\n")
|
||||
for _, vendor := range vendors {
|
||||
fmt.Fprintf(out, "%s\n", vendor)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type PluginRegistry struct {
|
||||
Db *fsdb.Db
|
||||
|
||||
@@ -129,7 +149,7 @@ func (o *PluginRegistry) Setup() (err error) {
|
||||
return vendor
|
||||
})...)
|
||||
|
||||
groupsPlugins.AddGroupItems("Tools", o.Defaults, o.PatternsLoader, o.YouTube, o.Language, o.Jina, o.Strategies)
|
||||
groupsPlugins.AddGroupItems("Tools", o.Defaults, o.Jina, o.Language, o.PatternsLoader, o.Strategies, o.YouTube)
|
||||
|
||||
for {
|
||||
groupsPlugins.Print()
|
||||
|
||||
10
go.mod
10
go.mod
@@ -19,7 +19,7 @@ require (
|
||||
github.com/samber/lo v1.49.1
|
||||
github.com/sashabaranov/go-openai v1.38.0
|
||||
github.com/stretchr/testify v1.10.0
|
||||
golang.org/x/text v0.22.0
|
||||
golang.org/x/text v0.23.0
|
||||
google.golang.org/api v0.223.0
|
||||
gopkg.in/yaml.v2 v2.4.0
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
@@ -89,11 +89,11 @@ require (
|
||||
go.opentelemetry.io/otel/metric v1.34.0 // indirect
|
||||
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.36.0 // indirect
|
||||
golang.org/x/crypto v0.36.0 // indirect
|
||||
golang.org/x/net v0.38.0 // indirect
|
||||
golang.org/x/oauth2 v0.27.0 // indirect
|
||||
golang.org/x/sync v0.11.0 // indirect
|
||||
golang.org/x/sys v0.30.0 // indirect
|
||||
golang.org/x/sync v0.12.0 // indirect
|
||||
golang.org/x/sys v0.31.0 // indirect
|
||||
golang.org/x/time v0.10.0 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20250224174004-546df14abb99 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250224174004-546df14abb99 // indirect
|
||||
|
||||
24
go.sum
24
go.sum
@@ -224,8 +224,8 @@ 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.35.0 h1:b15kiHdrGCHrP6LvwaQ3c03kgNhhiMgvlhxHQhmg2Xs=
|
||||
golang.org/x/crypto v0.35.0/go.mod h1:dy7dXNW32cAb/6/PRuTNsix8T+vJAqvuIy5Bli/x0YQ=
|
||||
golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34=
|
||||
golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc=
|
||||
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8=
|
||||
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
@@ -244,8 +244,8 @@ 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.36.0 h1:vWF2fRbw4qslQsQzgFqZff+BItCvGFQqKzKIzx1rmoA=
|
||||
golang.org/x/net v0.36.0/go.mod h1:bFmbeoIPfrw4sMHNhb4J9f6+tPziuGjq7Jk/38fxi1I=
|
||||
golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8=
|
||||
golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=
|
||||
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=
|
||||
@@ -255,8 +255,8 @@ golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
|
||||
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w=
|
||||
golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw=
|
||||
golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
@@ -273,8 +273,8 @@ golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
|
||||
golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik=
|
||||
golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
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=
|
||||
@@ -284,8 +284,8 @@ golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
|
||||
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
|
||||
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
|
||||
golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM=
|
||||
golang.org/x/term v0.29.0 h1:L6pJp37ocefwRRtYPKSWOWzOtWSxVajvz2ldH/xi3iU=
|
||||
golang.org/x/term v0.29.0/go.mod h1:6bl4lRlvVuDgSf3179VpIxBF0o10JUpXWOnI7nErv7s=
|
||||
golang.org/x/term v0.30.0 h1:PQ39fJZ+mfadBm0y5WlL4vlM7Sx1Hgf13sMIY2+QS9Y=
|
||||
golang.org/x/term v0.30.0/go.mod h1:NYYFdzHoI5wRh/h5tDMdMqCqPJZEuNqVR5xJLd/n67g=
|
||||
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=
|
||||
@@ -296,8 +296,8 @@ golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
|
||||
golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM=
|
||||
golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY=
|
||||
golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY=
|
||||
golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4=
|
||||
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=
|
||||
|
||||
@@ -239,23 +239,23 @@ schema = 3
|
||||
version = "v0.14.0"
|
||||
hash = "sha256-9akWthLBB+Au/JIg3WKcSx1YAfHEHOCnQF62sJoMJG4="
|
||||
[mod."golang.org/x/crypto"]
|
||||
version = "v0.35.0"
|
||||
hash = "sha256-XT1VU0+m1nZbhrMYXN2+eaKBlScfiT4bCBgXu4mfa1Q="
|
||||
[mod."golang.org/x/net"]
|
||||
version = "v0.36.0"
|
||||
hash = "sha256-2c9AvnizBMCaqKPzwF2IMlRfvjRNDFmxIYhrgZjhOm0="
|
||||
hash = "sha256-Np+suvZdMOXALDm4m8nDNa+QsvUV0rE0PEINuxCoKGM="
|
||||
[mod."golang.org/x/net"]
|
||||
version = "v0.38.0"
|
||||
hash = "sha256-iHmyxkZQLw1PsUZaXHMt84GrjbXPvkEc92sLoK3W++c="
|
||||
[mod."golang.org/x/oauth2"]
|
||||
version = "v0.27.0"
|
||||
hash = "sha256-TBKV2c/m0SgPqrJSE0ltJXfImrYPafNuziLN25jgsYY="
|
||||
[mod."golang.org/x/sync"]
|
||||
version = "v0.11.0"
|
||||
hash = "sha256-5ZBfDJvNaUBM4Vhk0fgYblCGL3eBxiJL85nIE8LiKl0="
|
||||
version = "v0.12.0"
|
||||
hash = "sha256-lPIbI6aXx+iKtFjPaXKNLEnuNfhHCVl7EQiE7alUvlM="
|
||||
[mod."golang.org/x/sys"]
|
||||
version = "v0.30.0"
|
||||
hash = "sha256-BuhWtwDkciVioc03rxty6G2vcZVnPX85lI7tgQOFVP8="
|
||||
version = "v0.31.0"
|
||||
hash = "sha256-aulv5obCrhheMlSq7seUgP3C29nfZABwiQ4IBNisgME="
|
||||
[mod."golang.org/x/text"]
|
||||
version = "v0.22.0"
|
||||
hash = "sha256-kUwLNFk9K/YuWmO5/u2IshrmhT2CCuk+mAShSlTTeZo="
|
||||
version = "v0.23.0"
|
||||
hash = "sha256-TiYX1K4DYpP1dEV06whOm43xyOntjrPFi+VAdncoeCY="
|
||||
[mod."golang.org/x/time"]
|
||||
version = "v0.10.0"
|
||||
hash = "sha256-vnlAME3gDR6R4cbCmSYAlR1Rjc0yUpkufTOPNvCdf6Q="
|
||||
|
||||
@@ -1 +1 @@
|
||||
"1.4.164"
|
||||
"1.4.179"
|
||||
|
||||
20
patterns/analyze_bill/system.md
Normal file
20
patterns/analyze_bill/system.md
Normal file
@@ -0,0 +1,20 @@
|
||||
# IDENTITY
|
||||
|
||||
You are an AI with a 3,129 IQ that specializes in discerning the true nature and goals of a piece of legislation.
|
||||
|
||||
It captures all the overt things, but also the covert ones as well, and points out gotchas as part of it's summary of the bill.
|
||||
|
||||
# STEPS
|
||||
|
||||
1. Read the entire bill 37 times using different perspectives.
|
||||
2. Map out all the stuff it's trying to do on a 10 KM by 10K mental whiteboard.
|
||||
3. Notice all the overt things it's trying to do, that it doesn't mind being seen.
|
||||
4. Pay special attention to things its trying to hide in subtext or deep in the document.
|
||||
|
||||
# OUTPUT
|
||||
|
||||
1. Give the metadata for the bill, such as who proposed it, when, etc.
|
||||
2. Create a 24-word summary of the bill and what it's trying to accomplish.
|
||||
3. Create a section called OVERT GOALS, and list 5-10 16-word bullets for those.
|
||||
4. Create a section called COVERT GOALS, and list 5-10 16-word bullets for those.
|
||||
5. Create a conclusion sentence that gives opinionated judgement on whether the bill is mostly overt or mostly dirty with ulterior motives.
|
||||
20
patterns/analyze_bill_short/system.md
Normal file
20
patterns/analyze_bill_short/system.md
Normal file
@@ -0,0 +1,20 @@
|
||||
# IDENTITY
|
||||
|
||||
You are an AI with a 3,129 IQ that specializes in discerning the true nature and goals of a piece of legislation.
|
||||
|
||||
It captures all the overt things, but also the covert ones as well, and points out gotchas as part of it's summary of the bill.
|
||||
|
||||
# STEPS
|
||||
|
||||
1. Read the entire bill 37 times using different perspectives.
|
||||
2. Map out all the stuff it's trying to do on a 10 KM by 10K mental whiteboard.
|
||||
3. Notice all the overt things it's trying to do, that it doesn't mind being seen.
|
||||
4. Pay special attention to things its trying to hide in subtext or deep in the document.
|
||||
|
||||
# OUTPUT
|
||||
|
||||
1. Give the metadata for the bill, such as who proposed it, when, etc.
|
||||
2. Create a 16-word summary of the bill and what it's trying to accomplish.
|
||||
3. Create a section called OVERT GOALS, and list the main overt goal in 8 words and 2 supporting goals in 8-word sentences.
|
||||
3. Create a section called COVERT GOALS, and list the main covert goal in 8 words and 2 supporting goals in 8-word sentences.
|
||||
5. Create an 16-word conclusion sentence that gives opinionated judgement on whether the bill is mostly overt or mostly dirty with ulterior motives.
|
||||
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
|
||||
131
patterns/create_excalidraw_visualization/system.md
Normal file
131
patterns/create_excalidraw_visualization/system.md
Normal file
@@ -0,0 +1,131 @@
|
||||
# IDENTITY
|
||||
|
||||
You are an expert AI with a 1,222 IQ that deeply understands the relationships between complex ideas and concepts. You are also an expert in the Excalidraw tool and schema.
|
||||
|
||||
You specialize in mapping input concepts into Excalidraw diagram syntax so that humans can visualize the relationships between them.
|
||||
|
||||
# STEPS
|
||||
|
||||
1. Deeply study the input.
|
||||
2. Think for 47 minutes about each of the sections in the input.
|
||||
3. Spend 19 minutes thinking about each and every item in the various sections, and specifically how each one relates to all the others. E.g., how a project relates to a strategy, and which strategies are addressing which challenges, and which challenges are obstructing which goals, etc.
|
||||
4. Build out this full mapping in on a 9KM x 9KM whiteboard in your mind.
|
||||
5. Analyze and improve this mapping for 13 minutes.
|
||||
|
||||
# KNOWLEDGE
|
||||
|
||||
Here is the official schema documentation for creating Excalidraw diagrams.
|
||||
|
||||
Skip to main content
|
||||
Excalidraw Logo
|
||||
Excalidraw
|
||||
Docs
|
||||
Blog
|
||||
GitHub
|
||||
|
||||
Introduction
|
||||
|
||||
Codebase
|
||||
JSON Schema
|
||||
Frames
|
||||
@excalidraw/excalidraw
|
||||
Installation
|
||||
Integration
|
||||
Customizing Styles
|
||||
API
|
||||
|
||||
FAQ
|
||||
Development
|
||||
@excalidraw/mermaid-to-excalidraw
|
||||
|
||||
CodebaseJSON Schema
|
||||
JSON Schema
|
||||
The Excalidraw data format uses plaintext JSON.
|
||||
|
||||
Excalidraw files
|
||||
When saving an Excalidraw scene locally to a file, the JSON file (.excalidraw) is using the below format.
|
||||
|
||||
Attributes
|
||||
Attribute Description Value
|
||||
type The type of the Excalidraw schema "excalidraw"
|
||||
version The version of the Excalidraw schema number
|
||||
source The source URL of the Excalidraw application "https://excalidraw.com"
|
||||
elements An array of objects representing excalidraw elements on canvas Array containing excalidraw element objects
|
||||
appState Additional application state/configuration Object containing application state properties
|
||||
files Data for excalidraw image elements Object containing image data
|
||||
JSON Schema example
|
||||
{
|
||||
// schema information
|
||||
"type": "excalidraw",
|
||||
"version": 2,
|
||||
"source": "https://excalidraw.com",
|
||||
|
||||
// elements on canvas
|
||||
"elements": [
|
||||
// example element
|
||||
{
|
||||
"id": "pologsyG-tAraPgiN9xP9b",
|
||||
"type": "rectangle",
|
||||
"x": 928,
|
||||
"y": 319,
|
||||
"width": 134,
|
||||
"height": 90
|
||||
/* ...other element properties */
|
||||
}
|
||||
/* other elements */
|
||||
],
|
||||
|
||||
// editor state (canvas config, preferences, ...)
|
||||
"appState": {
|
||||
"gridSize": 20,
|
||||
"viewBackgroundColor": "#ffffff"
|
||||
},
|
||||
|
||||
// files data for "image" elements, using format `{ [fileId]: fileData }`
|
||||
"files": {
|
||||
// example of an image data object
|
||||
"3cebd7720911620a3938ce77243696149da03861": {
|
||||
"mimeType": "image/png",
|
||||
"id": "3cebd7720911620a3938c.77243626149da03861",
|
||||
"dataURL": "data:image/png;base64,iVBORWOKGgoAAAANSUhEUgA=",
|
||||
"created": 1690295874454,
|
||||
"lastRetrieved": 1690295874454
|
||||
}
|
||||
/* ...other image data objects */
|
||||
}
|
||||
}
|
||||
|
||||
Excalidraw clipboard format
|
||||
When copying selected excalidraw elements to clipboard, the JSON schema is similar to .excalidraw format, except it differs in attributes.
|
||||
|
||||
Attributes
|
||||
Attribute Description Example Value
|
||||
type The type of the Excalidraw document. "excalidraw/clipboard"
|
||||
elements An array of objects representing excalidraw elements on canvas. Array containing excalidraw element objects (see example below)
|
||||
files Data for excalidraw image elements. Object containing image data
|
||||
Edit this page
|
||||
Previous
|
||||
Contributing
|
||||
Next
|
||||
Frames
|
||||
Excalidraw files
|
||||
Attributes
|
||||
JSON Schema example
|
||||
Excalidraw clipboard format
|
||||
Attributes
|
||||
Docs
|
||||
Get Started
|
||||
Community
|
||||
Discord
|
||||
Twitter
|
||||
Linkedin
|
||||
More
|
||||
Blog
|
||||
GitHub
|
||||
Copyright © 2023 Excalidraw community. Built with Docusaurus ❤️
|
||||
|
||||
# OUTPUT
|
||||
|
||||
1. Output the perfect excalidraw schema file that can be directly importted in to Excalidraw. This should have no preamble or follow-on text that breaks the format. It should be pure Excalidraw schema JSON.
|
||||
2. Ensure all components are high contrast on a white background, and that you include all the arrows and appropriate relationship components that preserve the meaning of the original input.
|
||||
3. Do not output the first and last lines of the schema, , e.g., json and backticks and then ending backticks. as this is automatically added by Excalidraw when importing.
|
||||
@@ -1,15 +0,0 @@
|
||||
package deepseek
|
||||
|
||||
import (
|
||||
"github.com/danielmiessler/fabric/plugins/ai/openai"
|
||||
)
|
||||
|
||||
func NewClient() (ret *Client) {
|
||||
ret = &Client{}
|
||||
ret.Client = openai.NewClientCompatible("DeepSeek", "https://api.deepseek.com", nil)
|
||||
return
|
||||
}
|
||||
|
||||
type Client struct {
|
||||
*openai.Client
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
package deepseek
|
||||
|
||||
// Test generated using Keploy
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestNewClient_EmbeddedClientNotNil(t *testing.T) {
|
||||
client := NewClient()
|
||||
if client.Client == nil {
|
||||
t.Fatalf("Expected embedded openai.Client to be non-nil, got nil")
|
||||
}
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
package groq
|
||||
|
||||
import (
|
||||
"github.com/danielmiessler/fabric/plugins/ai/openai"
|
||||
)
|
||||
|
||||
// NewClient initializes and returns a new Groq Client.
|
||||
func NewClient() (ret *Client) {
|
||||
ret = &Client{}
|
||||
ret.Client = openai.NewClientCompatible("Groq", "https://api.groq.com/openai/v1", nil)
|
||||
return
|
||||
}
|
||||
|
||||
// Client wraps the openai.Client to provide additional functionality specific to Groq.
|
||||
type Client struct {
|
||||
*openai.Client
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
package groq
|
||||
|
||||
// Test generated using Keploy
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestNewClientEmbeddedClientNotNil(t *testing.T) {
|
||||
client := NewClient()
|
||||
if client.Client == nil {
|
||||
t.Fatalf("Expected embedded openai.Client to be non-nil, got nil")
|
||||
}
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
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
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
package mistral
|
||||
|
||||
import (
|
||||
"github.com/danielmiessler/fabric/plugins/ai/openai"
|
||||
)
|
||||
|
||||
func NewClient() (ret *Client) {
|
||||
ret = &Client{}
|
||||
ret.Client = openai.NewClientCompatible("Mistral", "https://api.mistral.ai/v1", nil)
|
||||
return
|
||||
}
|
||||
|
||||
type Client struct {
|
||||
*openai.Client
|
||||
}
|
||||
74
plugins/ai/openai_compatible/providers_config.go
Normal file
74
plugins/ai/openai_compatible/providers_config.go
Normal file
@@ -0,0 +1,74 @@
|
||||
package openai_compatible
|
||||
|
||||
import (
|
||||
"github.com/danielmiessler/fabric/plugins/ai/openai"
|
||||
)
|
||||
|
||||
// ProviderConfig defines the configuration for an OpenAI-compatible API provider
|
||||
type ProviderConfig struct {
|
||||
Name string
|
||||
BaseURL string
|
||||
}
|
||||
|
||||
// Client is the common structure for all OpenAI-compatible providers
|
||||
type Client struct {
|
||||
*openai.Client
|
||||
}
|
||||
|
||||
// NewClient creates a new OpenAI-compatible client for the specified provider
|
||||
func NewClient(providerConfig ProviderConfig) *Client {
|
||||
client := &Client{}
|
||||
client.Client = openai.NewClientCompatible(providerConfig.Name, providerConfig.BaseURL, nil)
|
||||
return client
|
||||
}
|
||||
|
||||
// ProviderMap is a map of provider name to ProviderConfig for O(1) lookup
|
||||
var ProviderMap = map[string]ProviderConfig{
|
||||
"Mistral": {
|
||||
Name: "Mistral",
|
||||
BaseURL: "https://api.mistral.ai/v1",
|
||||
},
|
||||
"LiteLLM": {
|
||||
Name: "LiteLLM",
|
||||
BaseURL: "http://localhost:4000",
|
||||
},
|
||||
"Groq": {
|
||||
Name: "Groq",
|
||||
BaseURL: "https://api.groq.com/openai/v1",
|
||||
},
|
||||
"GrokAI": {
|
||||
Name: "GrokAI",
|
||||
BaseURL: "https://api.x.ai/v1",
|
||||
},
|
||||
"DeepSeek": {
|
||||
Name: "DeepSeek",
|
||||
BaseURL: "https://api.deepseek.com",
|
||||
},
|
||||
"Cerebras": {
|
||||
Name: "Cerebras",
|
||||
BaseURL: "https://api.cerebras.ai/v1",
|
||||
},
|
||||
"OpenRouter": {
|
||||
Name: "OpenRouter",
|
||||
BaseURL: "https://openrouter.ai/api/v1",
|
||||
},
|
||||
"SiliconCloud": {
|
||||
Name: "SiliconCloud",
|
||||
BaseURL: "https://api.siliconflow.cn/v1",
|
||||
},
|
||||
}
|
||||
|
||||
// GetProviderByName returns the provider configuration for a given name with O(1) lookup
|
||||
func GetProviderByName(name string) (ProviderConfig, bool) {
|
||||
provider, found := ProviderMap[name]
|
||||
return provider, found
|
||||
}
|
||||
|
||||
// CreateClient creates a new client for a provider by name
|
||||
func CreateClient(providerName string) (*Client, bool) {
|
||||
providerConfig, found := GetProviderByName(providerName)
|
||||
if !found {
|
||||
return nil, false
|
||||
}
|
||||
return NewClient(providerConfig), true
|
||||
}
|
||||
42
plugins/ai/openai_compatible/providers_config_test.go
Normal file
42
plugins/ai/openai_compatible/providers_config_test.go
Normal file
@@ -0,0 +1,42 @@
|
||||
package openai_compatible
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestCreateClient(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
provider string
|
||||
exists bool
|
||||
}{
|
||||
{
|
||||
name: "Existing provider - Mistral",
|
||||
provider: "Mistral",
|
||||
exists: true,
|
||||
},
|
||||
{
|
||||
name: "Existing provider - Groq",
|
||||
provider: "Groq",
|
||||
exists: true,
|
||||
},
|
||||
{
|
||||
name: "Non-existent provider",
|
||||
provider: "NonExistent",
|
||||
exists: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
client, exists := CreateClient(tc.provider)
|
||||
if exists != tc.exists {
|
||||
t.Errorf("Expected exists=%v for provider %s, got %v",
|
||||
tc.exists, tc.provider, exists)
|
||||
}
|
||||
if exists && client == nil {
|
||||
t.Errorf("Expected non-nil client for provider %s", tc.provider)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
package openrouter
|
||||
|
||||
import (
|
||||
"github.com/danielmiessler/fabric/plugins/ai/openai"
|
||||
)
|
||||
|
||||
func NewClient() (ret *Client) {
|
||||
ret = &Client{}
|
||||
ret.Client = openai.NewClientCompatible("OpenRouter", "https://openrouter.ai/api/v1", nil)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
type Client struct {
|
||||
*openai.Client
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
package openrouter
|
||||
@@ -1,15 +0,0 @@
|
||||
package siliconcloud
|
||||
|
||||
import (
|
||||
"github.com/danielmiessler/fabric/plugins/ai/openai"
|
||||
)
|
||||
|
||||
func NewClient() (ret *Client) {
|
||||
ret = &Client{}
|
||||
ret.Client = openai.NewClientCompatible("SiliconCloud", "https://api.siliconflow.cn/v1", nil)
|
||||
return
|
||||
}
|
||||
|
||||
type Client struct {
|
||||
*openai.Client
|
||||
}
|
||||
@@ -4,6 +4,8 @@ import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/danielmiessler/fabric/plugins"
|
||||
@@ -95,6 +97,9 @@ func (o *VendorsManager) readModels() (err error) {
|
||||
if result.err != nil {
|
||||
fmt.Println(result.vendorName, result.err)
|
||||
} else {
|
||||
sort.Slice(result.models, func(i, j int) bool {
|
||||
return strings.ToLower(result.models[i]) < strings.ToLower(result.models[j])
|
||||
})
|
||||
o.Models.AddGroupItems(result.vendorName, result.models...)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -67,7 +67,7 @@ func (o *StorageEntity) GetNames() (ret []string, err error) {
|
||||
}
|
||||
|
||||
func (o *StorageEntity) Delete(name string) (err error) {
|
||||
if err = os.Remove(o.BuildFilePathByName(name)); err != nil {
|
||||
if err = os.RemoveAll(o.BuildFilePathByName(name)); err != nil {
|
||||
err = fmt.Errorf("could not delete %s: %v", name, err)
|
||||
}
|
||||
return
|
||||
|
||||
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()
|
||||
}
|
||||
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()
|
||||
}
|
||||
}
|
||||
@@ -3,8 +3,11 @@ package restapi
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
goopenai "github.com/sashabaranov/go-openai"
|
||||
@@ -21,15 +24,17 @@ type ChatHandler struct {
|
||||
}
|
||||
|
||||
type PromptRequest struct {
|
||||
UserInput string `json:"userInput"`
|
||||
Vendor string `json:"vendor"`
|
||||
Model string `json:"model"`
|
||||
ContextName string `json:"contextName"`
|
||||
PatternName string `json:"patternName"`
|
||||
UserInput string `json:"userInput"`
|
||||
Vendor string `json:"vendor"`
|
||||
Model string `json:"model"`
|
||||
ContextName string `json:"contextName"`
|
||||
PatternName string `json:"patternName"`
|
||||
StrategyName string `json:"strategyName"` // Optional strategy name
|
||||
}
|
||||
|
||||
type ChatRequest struct {
|
||||
Prompts []PromptRequest `json:"prompts"`
|
||||
Language string `json:"language"` // Add Language field to bind from request
|
||||
common.ChatOptions // Embed the ChatOptions from common package
|
||||
}
|
||||
|
||||
@@ -60,7 +65,8 @@ func (h *ChatHandler) HandleChat(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
log.Printf("Received chat request with %d prompts", len(request.Prompts))
|
||||
// Add log to check received language field
|
||||
log.Printf("Received chat request - Language: '%s', Prompts: %d", request.Language, len(request.Prompts))
|
||||
|
||||
// Set headers for SSE
|
||||
c.Writer.Header().Set("Content-Type", "text/readystream")
|
||||
@@ -80,13 +86,25 @@ func (h *ChatHandler) HandleChat(c *gin.Context) {
|
||||
log.Printf("Processing prompt %d: Model=%s Pattern=%s Context=%s",
|
||||
i+1, prompt.Model, prompt.PatternName, prompt.ContextName)
|
||||
|
||||
// Create chat channel for streaming
|
||||
streamChan := make(chan string)
|
||||
|
||||
// Start chat processing in goroutine
|
||||
go func(p PromptRequest) {
|
||||
defer close(streamChan)
|
||||
|
||||
// Load and prepend strategy prompt if strategyName is set
|
||||
if p.StrategyName != "" {
|
||||
strategyFile := filepath.Join(os.Getenv("HOME"), ".config", "fabric", "strategies", p.StrategyName+".json")
|
||||
data, err := ioutil.ReadFile(strategyFile)
|
||||
if err == nil {
|
||||
var s struct {
|
||||
Prompt string `json:"prompt"`
|
||||
}
|
||||
if err := json.Unmarshal(data, &s); err == nil && s.Prompt != "" {
|
||||
p.UserInput = s.Prompt + "\n" + p.UserInput
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
chatter, err := h.registry.GetChatter(p.Model, 2048, "", false, false)
|
||||
if err != nil {
|
||||
log.Printf("Error creating chatter: %v", err)
|
||||
@@ -94,6 +112,7 @@ func (h *ChatHandler) HandleChat(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
// Pass the language received in the initial request to the common.ChatRequest
|
||||
chatReq := &common.ChatRequest{
|
||||
Message: &goopenai.ChatCompletionMessage{
|
||||
Role: "user",
|
||||
@@ -101,6 +120,7 @@ func (h *ChatHandler) HandleChat(c *gin.Context) {
|
||||
},
|
||||
PatternName: p.PatternName,
|
||||
ContextName: p.ContextName,
|
||||
Language: request.Language, // Pass the language field
|
||||
}
|
||||
|
||||
opts := &common.ChatOptions{
|
||||
@@ -124,7 +144,6 @@ func (h *ChatHandler) HandleChat(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
// Get the last message from the session
|
||||
lastMsg := session.GetLastMessage()
|
||||
if lastMsg != nil {
|
||||
streamChan <- lastMsg.Content
|
||||
@@ -134,37 +153,32 @@ func (h *ChatHandler) HandleChat(c *gin.Context) {
|
||||
}
|
||||
}(prompt)
|
||||
|
||||
// Read from streamChan and write to client
|
||||
for content := range streamChan {
|
||||
select {
|
||||
case <-clientGone:
|
||||
return
|
||||
default:
|
||||
var response StreamResponse
|
||||
if strings.HasPrefix(content, "Error:") {
|
||||
response := StreamResponse{
|
||||
response = StreamResponse{
|
||||
Type: "error",
|
||||
Format: "plain",
|
||||
Content: content,
|
||||
}
|
||||
if err := writeSSEResponse(c.Writer, response); err != nil {
|
||||
log.Printf("Error writing error response: %v", err)
|
||||
return
|
||||
}
|
||||
} else {
|
||||
response := StreamResponse{
|
||||
response = StreamResponse{
|
||||
Type: "content",
|
||||
Format: detectFormat(content),
|
||||
Content: content,
|
||||
}
|
||||
if err := writeSSEResponse(c.Writer, response); err != nil {
|
||||
log.Printf("Error writing content response: %v", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
if err := writeSSEResponse(c.Writer, response); err != nil {
|
||||
log.Printf("Error writing response: %v", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Signal completion of this prompt
|
||||
completeResponse := StreamResponse{
|
||||
Type: "complete",
|
||||
Format: "plain",
|
||||
@@ -192,26 +206,6 @@ func writeSSEResponse(w gin.ResponseWriter, response StreamResponse) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
/*
|
||||
func detectFormat(content string) string {
|
||||
if strings.HasPrefix(content, "graph TD") ||
|
||||
strings.HasPrefix(content, "gantt") ||
|
||||
strings.HasPrefix(content, "flowchart") ||
|
||||
strings.HasPrefix(content, "sequenceDiagram") ||
|
||||
strings.HasPrefix(content, "classDiagram") ||
|
||||
strings.HasPrefix(content, "stateDiagram") {
|
||||
return "mermaid"
|
||||
}
|
||||
if strings.Contains(content, "```") ||
|
||||
strings.Contains(content, "#") ||
|
||||
strings.Contains(content, "*") ||
|
||||
strings.Contains(content, "_") ||
|
||||
strings.Contains(content, "-") {
|
||||
return "markdown"
|
||||
}
|
||||
return "plain"
|
||||
}
|
||||
*/
|
||||
func detectFormat(content string) string {
|
||||
if strings.HasPrefix(content, "graph TD") ||
|
||||
strings.HasPrefix(content, "gantt") ||
|
||||
|
||||
@@ -45,6 +45,7 @@ func (h *ConfigHandler) GetConfig(c *gin.Context) {
|
||||
"openrouter": "",
|
||||
"silicon": "",
|
||||
"deepseek": "",
|
||||
"grokai": "",
|
||||
})
|
||||
return
|
||||
}
|
||||
@@ -65,6 +66,7 @@ func (h *ConfigHandler) GetConfig(c *gin.Context) {
|
||||
"openrouter": os.Getenv("OPENROUTER_API_KEY"),
|
||||
"silicon": os.Getenv("SILICON_API_KEY"),
|
||||
"deepseek": os.Getenv("DEEPSEEK_API_KEY"),
|
||||
"grokai": os.Getenv("GROKAI_API_KEY"),
|
||||
"lmstudio": os.Getenv("LM_STUDIO_API_BASE_URL"),
|
||||
}
|
||||
|
||||
@@ -87,6 +89,7 @@ func (h *ConfigHandler) UpdateConfig(c *gin.Context) {
|
||||
OpenRouterApiKey string `json:"openrouter_api_key"`
|
||||
SiliconApiKey string `json:"silicon_api_key"`
|
||||
DeepSeekApiKey string `json:"deepseek_api_key"`
|
||||
GrokaiApiKey string `json:"grokai_api_key"`
|
||||
LMStudioURL string `json:"lm_studio_base_url"`
|
||||
}
|
||||
|
||||
@@ -105,6 +108,7 @@ func (h *ConfigHandler) UpdateConfig(c *gin.Context) {
|
||||
"OPENROUTER_API_KEY": config.OpenRouterApiKey,
|
||||
"SILICON_API_KEY": config.SiliconApiKey,
|
||||
"DEEPSEEK_API_KEY": config.DeepSeekApiKey,
|
||||
"GROKAI_API_KEY": config.GrokaiApiKey,
|
||||
"LM_STUDIO_API_BASE_URL": config.LMStudioURL,
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
@@ -20,6 +28,7 @@ func Serve(registry *core.PluginRegistry, address string) (err error) {
|
||||
NewChatHandler(r, registry, fabricDb)
|
||||
NewConfigHandler(r, fabricDb)
|
||||
NewModelsHandler(r, registry.VendorManager)
|
||||
NewStrategiesHandler(r)
|
||||
|
||||
// Start server
|
||||
err = r.Run(address)
|
||||
|
||||
62
restapi/strategies.go
Normal file
62
restapi/strategies.go
Normal file
@@ -0,0 +1,62 @@
|
||||
package restapi
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// StrategyMeta represents the minimal info about a strategy
|
||||
type StrategyMeta struct {
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
Prompt string `json:"prompt"`
|
||||
}
|
||||
|
||||
// NewStrategiesHandler registers the /strategies GET endpoint
|
||||
func NewStrategiesHandler(r *gin.Engine) {
|
||||
r.GET("/strategies", func(c *gin.Context) {
|
||||
strategiesDir := filepath.Join(os.Getenv("HOME"), ".config", "fabric", "strategies")
|
||||
|
||||
files, err := ioutil.ReadDir(strategiesDir)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to read strategies directory"})
|
||||
return
|
||||
}
|
||||
|
||||
var strategies []StrategyMeta
|
||||
|
||||
for _, file := range files {
|
||||
if file.IsDir() || filepath.Ext(file.Name()) != ".json" {
|
||||
continue
|
||||
}
|
||||
|
||||
fullPath := filepath.Join(strategiesDir, file.Name())
|
||||
data, err := ioutil.ReadFile(fullPath)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
var s struct {
|
||||
Description string `json:"description"`
|
||||
Prompt string `json:"prompt"`
|
||||
}
|
||||
if err := json.Unmarshal(data, &s); err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
strategies = append(strategies, StrategyMeta{
|
||||
Name: strings.TrimSuffix(file.Name(), ".json"),
|
||||
Description: s.Description,
|
||||
Prompt: s.Prompt,
|
||||
})
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, strategies)
|
||||
})
|
||||
}
|
||||
4
strategies/aot.json
Normal file
4
strategies/aot.json
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"description": "Atom-of-Thought (AoT) Prompting",
|
||||
"prompt": "To solve this problem, break it down into the smallest independent 'atomic' sub-problems. For each atomic sub-problem: 1. Label it as 'Atom X: [brief description]' 2. Solve that specific subproblem completely 3. Make sure each atom can be solved independently. After solving all atomic sub-problems, provide a synthesis that combines them into a final answer. Return the final answer in the required format."
|
||||
}
|
||||
@@ -1,3 +1,3 @@
|
||||
package main
|
||||
|
||||
var version = "v1.4.164"
|
||||
var version = "v1.4.179"
|
||||
|
||||
@@ -35,6 +35,11 @@ https://youtu.be/fcVitd4Kb98
|
||||
|
||||
The tag filtering system has been deeply integrated into the Pattern Selection interface through several UI enhancements:
|
||||
|
||||
### 5. Strategy flags
|
||||
- strategies are fetch from .config/fabric/strategies for server processing
|
||||
- for gui, they are fetched from static/strategies
|
||||
|
||||
|
||||
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
|
||||
|
||||
@@ -305,10 +305,11 @@ async function readFileContent(file: File): Promise<string> {
|
||||
}
|
||||
}
|
||||
|
||||
// Centralized language instruction logic in ChatService.ts; YouTube flow now passes plain transcript and system prompt
|
||||
async function processYouTubeURL(input: string) {
|
||||
console.log('\n=== YouTube Flow Start ===');
|
||||
const originalLanguage = get(languageStore);
|
||||
|
||||
|
||||
try {
|
||||
// Add processing message first
|
||||
messageStore.update(messages => [...messages, {
|
||||
@@ -316,16 +317,11 @@ async function readFileContent(file: File): Promise<string> {
|
||||
content: 'Processing YouTube video...',
|
||||
format: 'loading'
|
||||
}]);
|
||||
|
||||
|
||||
// Get transcript but don't display it
|
||||
const { transcript } = await getTranscript(input);
|
||||
|
||||
// Log system prompt BEFORE createChatRequest
|
||||
console.log('System prompt BEFORE createChatRequest in YouTube flow:', $systemPrompt);
|
||||
|
||||
// Log system prompt BEFORE streamChat
|
||||
console.log(`System prompt BEFORE streamChat in YouTube flow: ${$systemPrompt}`);
|
||||
|
||||
// Pass plain transcript and system prompt; ChatService will handle language instruction
|
||||
const stream = await chatService.streamChat(transcript, $systemPrompt);
|
||||
await chatService.processStream(
|
||||
stream,
|
||||
@@ -447,21 +443,16 @@ async function readFileContent(file: File): Promise<string> {
|
||||
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
|
||||
});
|
||||
}
|
||||
// Always append a new assistant message
|
||||
newMessages.push({
|
||||
role: 'assistant',
|
||||
content,
|
||||
format: response?.format
|
||||
});
|
||||
return newMessages;
|
||||
});
|
||||
},
|
||||
|
||||
(error) => {
|
||||
// Make sure to remove loading message on error
|
||||
messageStore.update(messages =>
|
||||
|
||||
@@ -4,6 +4,8 @@
|
||||
import ModelConfig from "./ModelConfig.svelte";
|
||||
import { Select } from "$lib/components/ui/select";
|
||||
import { languageStore } from '$lib/store/language-store';
|
||||
import { strategies, selectedStrategy, fetchStrategies } from '$lib/store/strategy-store';
|
||||
import { onMount } from 'svelte';
|
||||
|
||||
const languages = [
|
||||
{ code: '', name: 'Default Language' },
|
||||
@@ -12,8 +14,13 @@
|
||||
{ code: 'es', name: 'Spanish' },
|
||||
{ code: 'de', name: 'German' },
|
||||
{ code: 'zh', name: 'Chinese' },
|
||||
{ code: 'ja', name: 'Japanese' }
|
||||
{ code: 'ja', name: 'Japanese' },
|
||||
{ code: 'it', name: 'Italian' }
|
||||
];
|
||||
|
||||
onMount(() => {
|
||||
fetchStrategies();
|
||||
});
|
||||
</script>
|
||||
|
||||
<div class="flex gap-4">
|
||||
@@ -35,6 +42,17 @@
|
||||
{/each}
|
||||
</Select>
|
||||
</div>
|
||||
<div>
|
||||
<Select
|
||||
bind:value={$selectedStrategy}
|
||||
class="bg-primary-800/30 border-none hover:bg-primary-800/40 transition-colors"
|
||||
>
|
||||
<option value="">None</option>
|
||||
{#each $strategies as strategy}
|
||||
<option value={strategy.name}>{strategy.name} - {strategy.description}</option>
|
||||
{/each}
|
||||
</Select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Right side - Model Config -->
|
||||
|
||||
@@ -15,23 +15,31 @@
|
||||
});
|
||||
|
||||
// Watch selectedPreset changes
|
||||
$: if (selectedPreset) {
|
||||
console.log('Pattern selected from dropdown:', selectedPreset);
|
||||
// Always call selectPattern when the dropdown value changes.
|
||||
// The patternAPI.selectPattern function handles empty strings correctly.
|
||||
$: {
|
||||
// Log the change regardless of the value
|
||||
console.log('Dropdown selection changed to:', selectedPreset);
|
||||
try {
|
||||
// Call the function to select the pattern (or reset if selectedPreset is empty)
|
||||
patternAPI.selectPattern(selectedPreset);
|
||||
// Verify the selection
|
||||
|
||||
// Optional: Keep verification logs if helpful for debugging
|
||||
const currentSystemPrompt = get(systemPrompt);
|
||||
const currentPattern = get(selectedPatternName);
|
||||
console.log('After dropdown selection - Pattern:', currentPattern);
|
||||
console.log('After dropdown selection - System Prompt length:', currentSystemPrompt?.length);
|
||||
|
||||
if (!currentPattern || !currentSystemPrompt) {
|
||||
console.error('Pattern selection verification failed:');
|
||||
console.error('- Selected Pattern:', currentPattern);
|
||||
console.error('- System Prompt:', currentSystemPrompt);
|
||||
}
|
||||
|
||||
// Optional: Refine verification logic if needed
|
||||
// For example, only log error if a pattern was expected but not set
|
||||
// if (selectedPreset && (!currentPattern || !currentSystemPrompt)) {
|
||||
// console.error('Pattern selection verification failed:');
|
||||
// console.error('- Selected Pattern:', currentPattern);
|
||||
// console.error('- System Prompt:', currentSystemPrompt);
|
||||
// }
|
||||
} catch (error) {
|
||||
console.error('Error in pattern selection:', error);
|
||||
// Log any errors during the pattern selection process
|
||||
console.error('Error processing pattern selection:', error);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -6,7 +6,8 @@ export interface ChatPrompt {
|
||||
userInput: string;
|
||||
systemPrompt: string;
|
||||
model: string;
|
||||
patternName: string;
|
||||
patternName?: string;
|
||||
strategyName?: string; // Optional strategy name to prepend strategy prompt
|
||||
}
|
||||
|
||||
export interface ChatConfig {
|
||||
@@ -23,6 +24,7 @@ export interface ChatRequest {
|
||||
top_p: number;
|
||||
frequency_penalty: number;
|
||||
presence_penalty: number;
|
||||
language?: string;
|
||||
}
|
||||
|
||||
export interface Message {
|
||||
|
||||
@@ -10,6 +10,7 @@ import { systemPrompt, selectedPatternName } from '$lib/store/pattern-store';
|
||||
import { chatConfig } from '$lib/store/chat-config';
|
||||
import { messageStore } from '$lib/store/chat-store';
|
||||
import { languageStore } from '$lib/store/language-store';
|
||||
import { selectedStrategy } from '$lib/store/strategy-store';
|
||||
|
||||
class LanguageValidator {
|
||||
constructor(private targetLanguage: string) {}
|
||||
@@ -47,6 +48,8 @@ export class ChatService {
|
||||
promptCount: request.prompts?.length,
|
||||
messageCount: request.messages?.length
|
||||
});
|
||||
// NEW: Log the full payload before sending to backend
|
||||
console.log('Final ChatRequest payload:', JSON.stringify(request, null, 2));
|
||||
|
||||
const response = await fetch('/api/chat', {
|
||||
method: 'POST',
|
||||
@@ -179,7 +182,8 @@ export class ChatService {
|
||||
userInput: finalUserInput,
|
||||
systemPrompt: finalSystemPrompt,
|
||||
model: config.model,
|
||||
patternName: get(selectedPatternName)
|
||||
patternName: get(selectedPatternName),
|
||||
strategyName: get(selectedStrategy) // Add selected strategy to prompt
|
||||
};
|
||||
}
|
||||
|
||||
@@ -191,10 +195,12 @@ export class ChatService {
|
||||
public async createChatRequest(userInput: string, systemPromptText?: string, isPattern: boolean = false): Promise<ChatRequest> {
|
||||
const prompt = this.createChatPrompt(userInput, systemPromptText);
|
||||
const config = get(chatConfig);
|
||||
|
||||
const language = get(languageStore);
|
||||
|
||||
return {
|
||||
prompts: [prompt],
|
||||
messages: [],
|
||||
language: language, // Add language at the top level for backend compatibility
|
||||
...config
|
||||
};
|
||||
}
|
||||
|
||||
32
web/src/lib/store/strategy-store.ts
Normal file
32
web/src/lib/store/strategy-store.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
import { writable } from 'svelte/store';
|
||||
|
||||
/**
|
||||
* List of available strategies fetched from backend.
|
||||
* Each strategy has a name and description.
|
||||
*/
|
||||
export const strategies = writable<Array<{ name: string; description: string }>>([]);
|
||||
|
||||
/**
|
||||
* Currently selected strategy name.
|
||||
* Default is empty string meaning "None".
|
||||
*/
|
||||
export const selectedStrategy = writable<string>("");
|
||||
|
||||
/**
|
||||
* Fetches available strategies from the backend `/strategies` endpoint.
|
||||
* Populates the `strategies` store.
|
||||
*/
|
||||
export async function fetchStrategies() {
|
||||
try {
|
||||
const response = await fetch('/strategies/strategies.json');
|
||||
if (!response.ok) {
|
||||
console.error('Failed to fetch strategies:', response.statusText);
|
||||
return;
|
||||
}
|
||||
const data = await response.json();
|
||||
// Expecting an array of { name, description }
|
||||
strategies.set(data);
|
||||
} catch (error) {
|
||||
console.error('Error fetching strategies:', error);
|
||||
}
|
||||
}
|
||||
@@ -61,16 +61,16 @@ export const POST: RequestHandler = async ({ request }) => {
|
||||
language: body.language
|
||||
});
|
||||
|
||||
// Ensure language instruction is present
|
||||
if (body.prompts?.[0] && body.language && body.language !== 'en') {
|
||||
const languageInstruction = `. Please use the language '${body.language}' for the output.`;
|
||||
if (!body.prompts[0].userInput?.includes(languageInstruction)) {
|
||||
body.prompts[0].userInput = (body.prompts[0].userInput || '') + languageInstruction;
|
||||
}
|
||||
}
|
||||
// Removed redundant language instruction logic; Go backend handles this
|
||||
// if (body.prompts?.[0] && body.language && body.language !== 'en') {
|
||||
// const languageInstruction = `. Please use the language '${body.language}' for the output.`;
|
||||
// if (!body.prompts[0].userInput?.includes(languageInstruction)) {
|
||||
// body.prompts[0].userInput = (body.prompts[0].userInput || '') + languageInstruction;
|
||||
// }
|
||||
// }
|
||||
|
||||
console.log('2. Language analysis:', {
|
||||
input: body.prompts?.[0]?.userInput?.substring(0, 100),
|
||||
input: body.prompts?.[0]?.userInput?.substring(0, 100), // Note: This input no longer has the instruction appended here
|
||||
hasLanguageInstruction: body.prompts?.[0]?.userInput?.includes('language'),
|
||||
containsFr: body.prompts?.[0]?.userInput?.includes('fr'),
|
||||
containsEn: body.prompts?.[0]?.userInput?.includes('en'),
|
||||
|
||||
@@ -1693,8 +1693,87 @@
|
||||
},
|
||||
{
|
||||
"patternName": "extract_wisdom_short",
|
||||
"description": "Extract condensed insightful ideas and recommendations focusing on life wisdom.",
|
||||
"tags": [
|
||||
"EXTRACT",
|
||||
"WISDOM",
|
||||
"SELF"
|
||||
]
|
||||
},
|
||||
{
|
||||
"patternName": "analyze_bill",
|
||||
"description": "Analyze a legislative bill and implications.",
|
||||
"tags": [
|
||||
"ANALYSIS",
|
||||
"BILL"
|
||||
]
|
||||
},
|
||||
{
|
||||
"patternName": "analyze_bill_short",
|
||||
"description": "Consended - Analyze a legislative bill and implications.",
|
||||
"tags": [
|
||||
"ANALYSIS",
|
||||
"BILL"
|
||||
]
|
||||
},
|
||||
{
|
||||
"patternName": "create_coding_feature",
|
||||
"description": "[Description pending]",
|
||||
"tags": []
|
||||
"tags": [
|
||||
"DEVELOPMENT"
|
||||
]
|
||||
},
|
||||
{
|
||||
"patternName": "create_excalidraw_visualization",
|
||||
"description": "Create visualizations using Excalidraw.",
|
||||
"tags": [
|
||||
"VISUALIZATION"
|
||||
]
|
||||
},
|
||||
{
|
||||
"patternName": "create_flash_cards",
|
||||
"description": "Generate flashcards for key concepts and definitions.",
|
||||
"tags": [
|
||||
"LEARNING"
|
||||
]
|
||||
},
|
||||
{
|
||||
"patternName": "create_loe_document",
|
||||
"description": "Create detailed Level of Effort (LOE) estimation documents.",
|
||||
"tags": [
|
||||
"DEVELOPMENT",
|
||||
"BUSINESS"
|
||||
]
|
||||
},
|
||||
{
|
||||
"patternName": "extract_domains",
|
||||
"description": "Extract key content and source.",
|
||||
"tags": [
|
||||
"EXTRACT",
|
||||
"ANALYSIS"
|
||||
]
|
||||
},
|
||||
{
|
||||
"patternName": "extract_main_activities",
|
||||
"description": "Extract and list main events from transcripts.",
|
||||
"tags": [
|
||||
"EXTRACT",
|
||||
"ANALYSIS"
|
||||
]
|
||||
},
|
||||
{
|
||||
"patternName": "find_female_life_partner",
|
||||
"description": "Clarify and summarize partner criteria in direct language.",
|
||||
"tags": [
|
||||
"SELF"
|
||||
]
|
||||
},
|
||||
{
|
||||
"patternName": "youtube_summary",
|
||||
"description": "Summarize YouTube videos with key points and timestamps.",
|
||||
"tags": [
|
||||
"SUMMARIZE"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
11
web/static/strategies/strategies.json
Normal file
11
web/static/strategies/strategies.json
Normal file
@@ -0,0 +1,11 @@
|
||||
[
|
||||
{ "name": "aot", "description": "Atom-of-Thought (AoT)" },
|
||||
{ "name": "cod", "description": "Chain-of-Draft (CoD)" },
|
||||
{ "name": "cot", "description": "Chain-of-Thought (CoT) Prompting" },
|
||||
{ "name": "ltm", "description": "Least-to-Most Prompting" },
|
||||
{ "name": "reflexion", "description": "Reflexion Prompting" },
|
||||
{ "name": "self-consistent", "description": "Self-Consistency Prompting" },
|
||||
{ "name": "self-refine", "description": "Self-Refinement" },
|
||||
{ "name": "standard", "description": "Standard Prompting" },
|
||||
{ "name": "tot", "description": "Tree-of-Thoughts (ToT)" }
|
||||
]
|
||||
Reference in New Issue
Block a user