mirror of
https://github.com/danielmiessler/Fabric.git
synced 2026-01-09 22:38:10 -05:00
Compare commits
66 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
98038707f1 | ||
|
|
03b22a70f0 | ||
|
|
66025d516c | ||
|
|
32ef2b73c4 | ||
|
|
656ca7ee28 | ||
|
|
0025466e4e | ||
|
|
4c2b38ca53 | ||
|
|
9c7ce4a974 | ||
|
|
626c492c63 | ||
|
|
71fb3fea7e | ||
|
|
3bc1150da4 | ||
|
|
827e0aeca7 | ||
|
|
0a1e01c4ab | ||
|
|
6003bb2c86 | ||
|
|
bb896b1064 | ||
|
|
d149c62a37 | ||
|
|
3d25fbc04c | ||
|
|
4c822d2c59 | ||
|
|
f1ffd6ee29 | ||
|
|
deb59bdd21 | ||
|
|
2a1e8dcf12 | ||
|
|
b6fd81dd16 | ||
|
|
5b723c9e92 | ||
|
|
93f8978085 | ||
|
|
4d91bf837f | ||
|
|
cb29a0d606 | ||
|
|
b1eb7a82d9 | ||
|
|
bc8f5add00 | ||
|
|
c3f874f985 | ||
|
|
922df52d0c | ||
|
|
4badfecadb | ||
|
|
83139a64d5 | ||
|
|
78fd836532 | ||
|
|
894459ddec | ||
|
|
920c22c889 | ||
|
|
a0f931feb0 | ||
|
|
4b080fd6dd | ||
|
|
298abecb3f | ||
|
|
e2d4aab775 | ||
|
|
17cac13584 | ||
|
|
e4a004cf88 | ||
|
|
fcb10feadd | ||
|
|
9560537730 | ||
|
|
42fabab352 | ||
|
|
895ca1ad99 | ||
|
|
2ef7db8bb2 | ||
|
|
8491354a30 | ||
|
|
1fd5b0d27b | ||
|
|
7eb67ee82d | ||
|
|
e3df1e1c0a | ||
|
|
6e939cfff4 | ||
|
|
9e2a35e150 | ||
|
|
a3a1e616e7 | ||
|
|
98eddaf5e8 | ||
|
|
0ae20a8ccd | ||
|
|
0fbc86be17 | ||
|
|
5b1a4ab306 | ||
|
|
817e75853e | ||
|
|
659d59028d | ||
|
|
abbd7d9c53 | ||
|
|
3c728cfacb | ||
|
|
67778a6159 | ||
|
|
38e7e31ae1 | ||
|
|
95e60809fa | ||
|
|
a09686820d | ||
|
|
15c8a84b25 |
6
.vscode/settings.json
vendored
6
.vscode/settings.json
vendored
@@ -10,6 +10,7 @@
|
||||
"aplicar",
|
||||
"atotto",
|
||||
"Autonoe",
|
||||
"azureml",
|
||||
"badfile",
|
||||
"Behrens",
|
||||
"blindspots",
|
||||
@@ -23,6 +24,7 @@
|
||||
"compadd",
|
||||
"compdef",
|
||||
"compinit",
|
||||
"conceptmap",
|
||||
"creatordate",
|
||||
"curcontext",
|
||||
"custompatterns",
|
||||
@@ -87,12 +89,14 @@
|
||||
"horts",
|
||||
"HTMLURL",
|
||||
"imagetools",
|
||||
"Jamba",
|
||||
"jaredmontoya",
|
||||
"jessevdk",
|
||||
"Jina",
|
||||
"joho",
|
||||
"kballard",
|
||||
"Keploy",
|
||||
"kimi",
|
||||
"Kore",
|
||||
"ksylvan",
|
||||
"Langdock",
|
||||
@@ -148,6 +152,7 @@
|
||||
"Pulcherrima",
|
||||
"pycache",
|
||||
"pyperclip",
|
||||
"qwen",
|
||||
"readystream",
|
||||
"restapi",
|
||||
"rmextension",
|
||||
@@ -161,6 +166,7 @@
|
||||
"sess",
|
||||
"sgaunet",
|
||||
"shellquote",
|
||||
"skeletonlabs",
|
||||
"SSEHTTP",
|
||||
"storer",
|
||||
"Streamlit",
|
||||
|
||||
163
CHANGELOG.md
163
CHANGELOG.md
@@ -1,5 +1,168 @@
|
||||
# Changelog
|
||||
|
||||
## v1.4.346 (2025-12-16)
|
||||
|
||||
### PR [#1874](https://github.com/danielmiessler/Fabric/pull/1874) by [ksylvan](https://github.com/ksylvan): refactor: replace interface{} with any across codebase
|
||||
|
||||
- Part 1 of dealing with #1873 as pointed out by @philoserf
|
||||
- Replace `interface{}` with `any` in slice type declarations throughout the codebase
|
||||
- Update map types from `map[string]interface{}` to `map[string]any` for modern Go standards
|
||||
- Change variadic function parameters to use `...any` instead of `...interface{}`
|
||||
- Modernize JSON unmarshaling variables to use `any` for consistency
|
||||
- Update struct fields and method signatures to prefer the `any` alias over legacy interface syntax
|
||||
|
||||
## v1.4.345 (2025-12-15)
|
||||
|
||||
### PR [#1870](https://github.com/danielmiessler/Fabric/pull/1870) by [ksylvan](https://github.com/ksylvan): Web UI: upgrade pdfjs and add SSR-safe dynamic PDF worker init
|
||||
|
||||
- Upgrade `pdfjs-dist` to v5 with new engine requirement
|
||||
- Dynamically import PDF.js to avoid SSR import-time crashes
|
||||
- Configure PDF worker via CDN using runtime PDF.js version
|
||||
- Update PDF conversion pipeline to use lazy initialization
|
||||
- Guard chat message localStorage persistence behind browser checks
|
||||
|
||||
## v1.4.344 (2025-12-14)
|
||||
|
||||
### PR [#1867](https://github.com/danielmiessler/Fabric/pull/1867) by [jaredmontoya](https://github.com/jaredmontoya): chore: update flake
|
||||
|
||||
- Chore: update flake
|
||||
- Merge branch 'main' into update-flake
|
||||
|
||||
## v1.4.343 (2025-12-14)
|
||||
|
||||
### PR [#1829](https://github.com/danielmiessler/Fabric/pull/1829) by [dependabo](https://github.com/apps/dependabot): chore(deps): bump js-yaml from 4.1.0 to 4.1.1 in /web in the npm_and_yarn group across 1 directory
|
||||
|
||||
- Updated js-yaml dependency from version 4.1.0 to 4.1.1 in the /web directory
|
||||
|
||||
## v1.4.342 (2025-12-13)
|
||||
|
||||
### PR [#1866](https://github.com/danielmiessler/Fabric/pull/1866) by [ksylvan](https://github.com/ksylvan): fix: write CLI and streaming errors to stderr
|
||||
|
||||
- Fix: write CLI and streaming errors to stderr
|
||||
- Route CLI execution errors to standard error output
|
||||
- Print Anthropic stream errors to stderr consistently
|
||||
- Add os import to support stderr error writes
|
||||
- Preserve help-output suppression and exit behavior
|
||||
|
||||
## v1.4.341 (2025-12-10)
|
||||
|
||||
### PR [#1860](https://github.com/danielmiessler/Fabric/pull/1860) by [ksylvan](https://github.com/ksylvan): fix: allow resetting required settings without validation errors
|
||||
|
||||
- Fix: allow resetting required settings without validation errors
|
||||
- Update `Ask` to detect reset command and bypass validation
|
||||
- Refactor `OnAnswer` to support new `isReset` parameter logic
|
||||
- Invoke `ConfigureCustom` in `Setup` to avoid redundant re-validation
|
||||
- Add unit tests ensuring required fields can be reset
|
||||
|
||||
## v1.4.340 (2025-12-08)
|
||||
|
||||
### PR [#1856](https://github.com/danielmiessler/Fabric/pull/1856) by [ksylvan](https://github.com/ksylvan): Add support for new ClaudeHaiku 4.5 models
|
||||
|
||||
- Add support for new ClaudeHaiku models in client
|
||||
- Add `ModelClaudeHaiku4_5` to supported models
|
||||
- Add `ModelClaudeHaiku4_5_20251001` to supported models
|
||||
|
||||
## v1.4.339 (2025-12-08)
|
||||
|
||||
### PR [#1855](https://github.com/danielmiessler/Fabric/pull/1855) by [ksylvan](https://github.com/ksylvan): feat: add image attachment support for Ollama vision models
|
||||
|
||||
- Add multi-modal image support to Ollama client
|
||||
- Implement convertMessage to handle multi-content chat messages
|
||||
- Add loadImageBytes to fetch images from URLs
|
||||
- Support base64 data URLs for inline images
|
||||
- Handle HTTP image URLs with context propagation
|
||||
|
||||
## v1.4.338 (2025-12-04)
|
||||
|
||||
### PR [#1852](https://github.com/danielmiessler/Fabric/pull/1852) by [ksylvan](https://github.com/ksylvan): Add Abacus vendor for ChatLLM models with static model list
|
||||
|
||||
- Add static model support and register Abacus provider
|
||||
- Detect modelsURL starting with 'static:' and route appropriately
|
||||
- Implement getStaticModels returning curated Abacus model list
|
||||
- Register Abacus provider with ModelsURL 'static:abacus'
|
||||
- Extend provider tests to include Abacus existence
|
||||
|
||||
## v1.4.337 (2025-12-04)
|
||||
|
||||
### PR [#1851](https://github.com/danielmiessler/Fabric/pull/1851) by [ksylvan](https://github.com/ksylvan): Add Z AI provider and glm model support
|
||||
|
||||
- Add Z AI provider configuration to ProviderMap
|
||||
- Include BaseURL for Z AI API endpoint
|
||||
- Add test case for Z AI provider existence
|
||||
- Add glm to OpenAI model prefixes list
|
||||
- Support new Z AI provider in OpenAI compatible plugins
|
||||
|
||||
## v1.4.336 (2025-12-01)
|
||||
|
||||
### PR [#1848](https://github.com/danielmiessler/Fabric/pull/1848) by [zeddy303](https://github.com/zeddy303): Fix localStorage SSR error in favorites-store
|
||||
|
||||
- Fix localStorage SSR error in favorites-store by using SvelteKit's browser constant instead of typeof localStorage check to properly handle server-side rendering and prevent 'localStorage.getItem is not a function' error when running dev server
|
||||
|
||||
## v1.4.335 (2025-11-28)
|
||||
|
||||
### PR [#1847](https://github.com/danielmiessler/Fabric/pull/1847) by [ksylvan](https://github.com/ksylvan): Improve model name matching for NeedsRaw in Ollama plugin
|
||||
|
||||
- Improved model name matching in Ollama plugin by replacing prefix-based matching with substring matching
|
||||
- Enhanced NeedsRaw functionality to support more flexible model name detection
|
||||
- Renamed `ollamaPrefixes` variable to `ollamaSearchStrings` for better code clarity
|
||||
- Replaced `HasPrefix` function with `Contains` for more comprehensive model matching
|
||||
- Added "conceptmap" to VSCode dictionary settings
|
||||
|
||||
### Direct commits
|
||||
|
||||
- Merge branch 'danielmiessler:main' into main
|
||||
- Docs: Fix typo in README
|
||||
|
||||
## v1.4.334 (2025-11-26)
|
||||
|
||||
### PR [#1845](https://github.com/danielmiessler/Fabric/pull/1845) by [ksylvan](https://github.com/ksylvan): Add Claude Opus 4.5 Support
|
||||
|
||||
- Add Claude Opus 4.5 model variants to Anthropic client
|
||||
- Upgrade anthropic-sdk-go from v1.16.0 to v1.19.0
|
||||
- Update golang.org/x/crypto from v0.41.0 to v0.45.0
|
||||
- Upgrade golang.org/x/net from v0.43.0 to v0.47.0
|
||||
- Bump golang.org/x/text from v0.28.0 to v0.31.0
|
||||
|
||||
## v1.4.333 (2025-11-25)
|
||||
|
||||
### PR [#1833](https://github.com/danielmiessler/Fabric/pull/1833) by [junaid18183](https://github.com/junaid18183): Added concall_summary
|
||||
|
||||
- Added concall_summery pattern to extract strategic insights from earnings transcripts for investors.
|
||||
|
||||
### PR [#1844](https://github.com/danielmiessler/Fabric/pull/1844) by [ksylvan](https://github.com/ksylvan): Correct directory name from `concall_summery` to `concall_summary`
|
||||
|
||||
- Fix: correct directory name from `concall_summery` to `concall_summary`
|
||||
- Rename pattern directory to fix spelling error
|
||||
- Update suggest_pattern system with concall_summary references
|
||||
- Add concall_summary to BUSINESS and SUMMARIZE category listings
|
||||
- Add user documentation for earnings call analysis
|
||||
|
||||
## v1.4.332 (2025-11-24)
|
||||
|
||||
### PR [#1843](https://github.com/danielmiessler/Fabric/pull/1843) by [ksylvan](https://github.com/ksylvan): Implement case-insensitive vendor and model name matching
|
||||
|
||||
- Fix: implement case-insensitive vendor and model name matching across the application
|
||||
- Add case-insensitive vendor lookup in VendorsManager
|
||||
- Implement model name normalization in GetChatter method
|
||||
- Add FilterByVendor method with case-insensitive matching
|
||||
- Add FindModelNameCaseInsensitive helper for model queries
|
||||
|
||||
## v1.4.331 (2025-11-22)
|
||||
|
||||
### PR [#1839](https://github.com/danielmiessler/Fabric/pull/1839) by [ksylvan](https://github.com/ksylvan): Add GitHub Models Provider and Refactor Fetching Fallback Logic
|
||||
|
||||
- Add GitHub Models provider and refactor model fetching with direct API fallback
|
||||
- Add GitHub Models to supported OpenAI-compatible providers list
|
||||
- Implement direct HTTP fallback for non-standard model responses
|
||||
- Centralize model fetching logic in openai package
|
||||
- Upgrade openai-go SDK dependency from v1.8.2 to v1.12.0
|
||||
|
||||
## v1.4.330 (2025-11-23)
|
||||
|
||||
### PR [#1840](https://github.com/danielmiessler/Fabric/pull/1840) by [ZackaryWelch](https://github.com/ZackaryWelch): Replace deprecated bash function in completion script
|
||||
|
||||
- Replace deprecated bash function in completion script to use `_comp_get_words` instead of `__get_comp_words_by_ref`, fixing compatibility issues with latest bash versions and preventing script breakage on updated distributions like Fedora 42+
|
||||
|
||||
## v1.4.329 (2025-11-20)
|
||||
|
||||
### PR [#1838](https://github.com/danielmiessler/fabric/pull/1838) by [ksylvan](https://github.com/ksylvan): refactor: implement i18n support for YouTube tool error messages
|
||||
|
||||
@@ -73,6 +73,11 @@ Below are the **new features and capabilities** we've added (newest first):
|
||||
|
||||
### Recent Major Features
|
||||
|
||||
- [v1.4.338](https://github.com/danielmiessler/fabric/releases/tag/v1.4.338) (Dec 4, 2025) — Add Abacus vendor support for Chat-LLM
|
||||
models (see [RouteLLM APIs](https://abacus.ai/app/route-llm-apis)).
|
||||
- [v1.4.337](https://github.com/danielmiessler/fabric/releases/tag/v1.4.337) (Dec 4, 2025) — Add "Z AI" vendor support. See the [Z AI overview](https://docs.z.ai/guides/overview/overview) page for more details.
|
||||
- [v1.4.334](https://github.com/danielmiessler/fabric/releases/tag/v1.4.334) (Nov 26, 2025) — **Claude Opus 4.5**: Updates the Anthropic SDK to the latest and adds the new [Claude Opus 4.5](https://www.anthropic.com/news/claude-opus-4-5) to the available models.
|
||||
- [v1.4.331](https://github.com/danielmiessler/fabric/releases/tag/v1.4.331) (Nov 23, 2025) — **Support for GitHub Models**: Adds support for using GitHub Models.
|
||||
- [v1.4.322](https://github.com/danielmiessler/fabric/releases/tag/v1.4.322) (Nov 5, 2025) — **Interactive HTML Concept Maps and Claude Sonnet 4.5**: Adds `create_conceptmap` pattern for visual knowledge representation using Vis.js, introduces WELLNESS category with psychological analysis patterns, and upgrades to Claude Sonnet 4.5
|
||||
- [v1.4.317](https://github.com/danielmiessler/fabric/releases/tag/v1.4.317) (Sep 21, 2025) — **Portuguese Language Variants**: Adds BCP 47 locale normalization with support for Brazilian Portuguese (pt-BR) and European Portuguese (pt-PT) with intelligent fallback chains
|
||||
- [v1.4.314](https://github.com/danielmiessler/fabric/releases/tag/v1.4.314) (Sep 17, 2025) — **Azure OpenAI Migration**: Migrates to official `openai-go/azure` SDK with improved authentication and default API version support
|
||||
|
||||
@@ -109,11 +109,11 @@ func ScanDirectory(rootDir string, maxDepth int, instructions string, ignoreList
|
||||
}
|
||||
|
||||
// Create final data structure
|
||||
var data []interface{}
|
||||
var data []any
|
||||
data = append(data, rootItem)
|
||||
|
||||
// Add report
|
||||
reportItem := map[string]interface{}{
|
||||
reportItem := map[string]any{
|
||||
"type": "report",
|
||||
"directories": dirCount,
|
||||
"files": fileCount,
|
||||
@@ -121,7 +121,7 @@ func ScanDirectory(rootDir string, maxDepth int, instructions string, ignoreList
|
||||
data = append(data, reportItem)
|
||||
|
||||
// Add instructions
|
||||
instructionsItem := map[string]interface{}{
|
||||
instructionsItem := map[string]any{
|
||||
"type": "instructions",
|
||||
"name": "code_change_instructions",
|
||||
"details": instructions,
|
||||
|
||||
@@ -12,7 +12,7 @@ import (
|
||||
func main() {
|
||||
err := cli.Cli(version)
|
||||
if err != nil && !flags.WroteHelp(err) {
|
||||
fmt.Printf("%s\n", err)
|
||||
fmt.Fprintf(os.Stderr, "%s\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
package main
|
||||
|
||||
var version = "v1.4.329"
|
||||
var version = "v1.4.346"
|
||||
|
||||
Binary file not shown.
@@ -333,7 +333,7 @@ func (c *Client) FetchAllMergedPRsGraphQL(since time.Time) ([]*PR, error) {
|
||||
|
||||
for {
|
||||
// Prepare variables
|
||||
variables := map[string]interface{}{
|
||||
variables := map[string]any{
|
||||
"owner": graphql.String(c.owner),
|
||||
"repo": graphql.String(c.repo),
|
||||
"after": (*graphql.String)(after),
|
||||
|
||||
@@ -10,7 +10,11 @@
|
||||
|
||||
_fabric() {
|
||||
local cur prev words cword
|
||||
_get_comp_words_by_ref -n : cur prev words cword
|
||||
if declare -F _comp_get_words &>/dev/null; then
|
||||
_comp_get_words cur prev words cword
|
||||
else
|
||||
_get_comp_words_by_ref cur prev words cword
|
||||
fi
|
||||
|
||||
# Define all possible options/flags
|
||||
local opts="--pattern -p --variable -v --context -C --session --attachment -a --setup -S --temperature -t --topp -T --stream -s --presencepenalty -P --raw -r --frequencypenalty -F --listpatterns -l --listmodels -L --listcontexts -x --listsessions -X --updatepatterns -U --copy -c --model -m --vendor -V --modelContextLength --output -o --output-session --latest -n --changeDefaultModel -d --youtube -y --playlist --transcript --transcript-with-timestamps --comments --metadata --yt-dlp-args --language -g --scrape_url -u --scrape_question -q --seed -e --thinking --wipecontext -w --wipesession -W --printcontext --printsession --readability --input-has-vars --no-variable-replacement --dry-run --serve --serveOllama --address --api-key --config --search --search-location --image-file --image-size --image-quality --image-compression --image-background --suppress-think --think-start-tag --think-end-tag --disable-responses-api --transcribe-file --transcribe-model --split-media-file --voice --list-gemini-voices --notification --notification-command --debug --version --listextensions --addextension --rmextension --strategy --liststrategies --listvendors --shell-complete-list --help -h"
|
||||
|
||||
84
data/patterns/concall_summary/system.md
Normal file
84
data/patterns/concall_summary/system.md
Normal file
@@ -0,0 +1,84 @@
|
||||
# IDENTITY and PURPOSE
|
||||
|
||||
You are an equity research analyst specializing in earnings and conference call analysis. Your role involves carefully examining transcripts to extract actionable insights that can inform investment decisions. You need to focus on several key areas, including management commentary, analyst questions, financial and operational insights, risks and red flags, hidden signals, and an executive summary. Your task is to distill complex information into clear, concise bullet points, capturing strategic themes, growth drivers, and potential concerns. It is crucial to interpret the tone, identify contradictions, and highlight any subtle cues that may indicate future strategic shifts or risks.
|
||||
|
||||
Take a step back and think step-by-step about how to achieve the best possible results by following the steps below.
|
||||
|
||||
# STEPS
|
||||
|
||||
* Analyze the transcript to extract management commentary, focusing on strategic themes, growth drivers, margin commentary, guidance, tone analysis, and any contradictions or vague areas.
|
||||
* Extract a summary of the content in exactly **25 words**, including who is presenting and the content being discussed; place this under a **SUMMARY** section.
|
||||
* For each analyst's question, determine the underlying concern, summarize management’s exact answer, evaluate if the answers address the question fully, and identify anything the management avoided or deflected.
|
||||
* Gather financial and operational insights, including commentary on demand, pricing, capacity, market share, cost inflation, raw material trends, and supply-chain issues.
|
||||
* Identify risks and red flags by noting any negative commentary, early warning signs, unusual wording, delayed responses, repeated disclaimers, and areas where management seemed less confident.
|
||||
* Detect hidden signals such as forward-looking hints, unasked but important questions, and subtle cues about strategy shifts or stress.
|
||||
* Create an executive summary in bullet points, listing the 10 most important takeaways, 3 surprises, and 3 things to track in the next quarter.
|
||||
|
||||
# OUTPUT STRUCTURE
|
||||
|
||||
* MANAGEMENT COMMENTARY
|
||||
|
||||
* Key strategic themes
|
||||
* Growth drivers discussed
|
||||
* Margin commentary
|
||||
* Guidance (explicit + implicit)
|
||||
* Tone analysis (positive/neutral/negative)
|
||||
* Any contradictions or vague areas
|
||||
|
||||
* ANALYST QUESTIONS (Q&A)
|
||||
|
||||
* For each analyst (use bullets, one analyst per bullet-group):
|
||||
|
||||
* Underlying concern (what the question REALLY asked)
|
||||
* Management’s exact answer (concise)
|
||||
* Answer completeness (Yes/No — short explanation)
|
||||
* Items management avoided or deflected
|
||||
|
||||
* FINANCIAL & OPERATIONAL INSIGHTS
|
||||
|
||||
* Demand, pricing, capacity, market share commentary
|
||||
* Cost inflation, raw material trends, supply-chain issues
|
||||
* Segment-wise performance and commentary (if applicable)
|
||||
|
||||
* RISKS & RED FLAGS
|
||||
|
||||
* Negative commentary or early-warning signs
|
||||
* Unusual wording, delayed responses, repeated disclaimers
|
||||
* Areas where management was less confident
|
||||
|
||||
* HIDDEN SIGNALS
|
||||
|
||||
* Forward-looking hints and tone shifts
|
||||
* Important topics not asked by analysts but relevant
|
||||
* Subtle cues of strategy change, stress, or opportunity
|
||||
|
||||
* EXECUTIVE SUMMARY
|
||||
|
||||
* 10 most important takeaways (bullet points)
|
||||
* 3 surprises (bullet points)
|
||||
* 3 things to track next quarter (bullet points)
|
||||
|
||||
* SUMMARY (exactly 25 words)
|
||||
|
||||
* A single 25-word sentence summarizing who presented and what was discussed
|
||||
|
||||
# OUTPUT INSTRUCTIONS
|
||||
|
||||
* Only output Markdown.
|
||||
* Provide everything in clear, crisp bullet points.
|
||||
* Use bulleted lists only; do not use numbered lists.
|
||||
* Begin the output with the **SUMMARY** (exactly 25 words), then the sections in the order shown under **OUTPUT STRUCTURE**.
|
||||
* For **ANALYST QUESTIONS (Q&A)**, keep each analyst’s Q&A grouped and separated by a blank line for readability.
|
||||
* For **EXECUTIVE SUMMARY**, present the 10 takeaways first, then the 3 surprises, then the 3 things to track.
|
||||
* Keep each bullet concise — prefer single-sentence bullets.
|
||||
* Do not include warnings, meta-comments, or process notes in the final output.
|
||||
* Do not repeat ideas, insights, quotes, habits, facts, or references across bullets.
|
||||
* When interpreting tone or identifying a hidden signal, be explicit about the textual clue supporting that interpretation (briefly, within the same bullet).
|
||||
* If any numeric figure or explicit guidance is cited in the transcript, reproduce it verbatim in the relevant bullet and mark it as **(quoted)**.
|
||||
* If information is missing or management declined to answer, state that clearly within the relevant bullet.
|
||||
* Ensure fidelity: do not invent facts not in the transcript. If you infer, label it as an inference.
|
||||
* Ensure you follow ALL these instructions when creating your output.
|
||||
|
||||
# INPUT
|
||||
|
||||
INPUT:
|
||||
@@ -46,188 +46,189 @@
|
||||
42. **clean_text**: Fix broken or malformatted text by correcting line breaks, punctuation, capitalization, and paragraphs without altering content or spelling.
|
||||
43. **coding_master**: Explain a coding concept to a beginner, providing examples, and formatting code in markdown with specific output sections like ideas, recommendations, facts, and insights.
|
||||
44. **compare_and_contrast**: Compare and contrast a list of items in a markdown table, with items on the left and topics on top.
|
||||
45. **convert_to_markdown**: Convert content to clean, complete Markdown format, preserving all original structure, formatting, links, and code blocks without alterations.
|
||||
46. **create_5_sentence_summary**: Create concise summaries or answers to input at 5 different levels of depth, from 5 words to 1 word.
|
||||
47. **create_academic_paper**: Generate a high-quality academic paper in LaTeX format with clear concepts, structured content, and a professional layout.
|
||||
48. **create_ai_jobs_analysis**: Analyze job categories' susceptibility to automation, identify resilient roles, and provide strategies for personal adaptation to AI-driven changes in the workforce.
|
||||
49. **create_aphorisms**: Find and generate a list of brief, witty statements.
|
||||
50. **create_art_prompt**: Generates a detailed, compelling visual description of a concept, including stylistic references and direct AI instructions for creating art.
|
||||
51. **create_better_frame**: Identifies and analyzes different frames of interpreting reality, emphasizing the power of positive, productive lenses in shaping outcomes.
|
||||
52. **create_coding_feature**: Generates secure and composable code features using modern technology and best practices from project specifications.
|
||||
53. **create_coding_project**: Generate wireframes and starter code for any coding ideas that you have.
|
||||
54. **create_command**: Helps determine the correct parameters and switches for penetration testing tools based on a brief description of the objective.
|
||||
55. **create_conceptmap**: Transforms unstructured text or markdown content into an interactive HTML concept map using Vis.js by extracting key concepts and their logical relationships.
|
||||
56. **create_cyber_summary**: Summarizes cybersecurity threats, vulnerabilities, incidents, and malware with a 25-word summary and categorized bullet points, after thoroughly analyzing and mapping the provided input.
|
||||
57. **create_design_document**: Creates a detailed design document for a system using the C4 model, addressing business and security postures, and including a system context diagram.
|
||||
58. **create_diy**: Creates structured "Do It Yourself" tutorial patterns by analyzing prompts, organizing requirements, and providing step-by-step instructions in Markdown format.
|
||||
59. **create_excalidraw_visualization**: Creates complex Excalidraw diagrams to visualize relationships between concepts and ideas in structured format.
|
||||
60. **create_flash_cards**: Creates flashcards for key concepts, definitions, and terms with question-answer format for educational purposes.
|
||||
61. **create_formal_email**: Crafts professional, clear, and respectful emails by analyzing context, tone, and purpose, ensuring proper structure and formatting.
|
||||
62. **create_git_diff_commit**: Generates Git commands and commit messages for reflecting changes in a repository, using conventional commits and providing concise shell commands for updates.
|
||||
63. **create_graph_from_input**: Generates a CSV file with progress-over-time data for a security program, focusing on relevant metrics and KPIs.
|
||||
64. **create_hormozi_offer**: Creates a customized business offer based on principles from Alex Hormozi's book, "$100M Offers."
|
||||
65. **create_idea_compass**: Organizes and structures ideas by exploring their definition, evidence, sources, and related themes or consequences.
|
||||
66. **create_investigation_visualization**: Creates detailed Graphviz visualizations of complex input, highlighting key aspects and providing clear, well-annotated diagrams for investigative analysis and conclusions.
|
||||
67. **create_keynote**: Creates TED-style keynote presentations with a clear narrative, structured slides, and speaker notes, emphasizing impactful takeaways and cohesive flow.
|
||||
68. **create_loe_document**: Creates detailed Level of Effort documents for estimating work effort, resources, and costs for tasks or projects.
|
||||
69. **create_logo**: Creates simple, minimalist company logos without text, generating AI prompts for vector graphic logos based on input.
|
||||
70. **create_markmap_visualization**: Transforms complex ideas into clear visualizations using MarkMap syntax, simplifying concepts into diagrams with relationships, boxes, arrows, and labels.
|
||||
71. **create_mermaid_visualization**: Creates detailed, standalone visualizations of concepts using Mermaid (Markdown) syntax, ensuring clarity and coherence in diagrams.
|
||||
72. **create_mermaid_visualization_for_github**: Creates standalone, detailed visualizations using Mermaid (Markdown) syntax to effectively explain complex concepts, ensuring clarity and precision.
|
||||
73. **create_micro_summary**: Summarizes content into a concise, 20-word summary with main points and takeaways, formatted in Markdown.
|
||||
74. **create_mnemonic_phrases**: Creates memorable mnemonic sentences from given words to aid in memory retention and learning.
|
||||
75. **create_network_threat_landscape**: Analyzes open ports and services from a network scan and generates a comprehensive, insightful, and detailed security threat report in Markdown.
|
||||
76. **create_newsletter_entry**: Condenses provided article text into a concise, objective, newsletter-style summary with a title in the style of Frontend Weekly.
|
||||
77. **create_npc**: Generates a detailed D&D 5E NPC, including background, flaws, stats, appearance, personality, goals, and more in Markdown format.
|
||||
78. **create_pattern**: Extracts, organizes, and formats LLM/AI prompts into structured sections, detailing the AI's role, instructions, output format, and any provided examples for clarity and accuracy.
|
||||
79. **create_prd**: Creates a precise Product Requirements Document (PRD) in Markdown based on input.
|
||||
80. **create_prediction_block**: Extracts and formats predictions from input into a structured Markdown block for a blog post.
|
||||
81. **create_quiz**: Creates a three-phase reading plan based on an author or topic to help the user become significantly knowledgeable, including core, extended, and supplementary readings.
|
||||
82. **create_reading_plan**: Generates review questions based on learning objectives from the input, adapted to the specified student level, and outputs them in a clear markdown format.
|
||||
83. **create_recursive_outline**: Breaks down complex tasks or projects into manageable, hierarchical components with recursive outlining for clarity and simplicity.
|
||||
84. **create_report_finding**: Creates a detailed, structured security finding report in markdown, including sections on Description, Risk, Recommendations, References, One-Sentence-Summary, and Quotes.
|
||||
85. **create_rpg_summary**: Summarizes an in-person RPG session with key events, combat details, player stats, and role-playing highlights in a structured format.
|
||||
86. **create_security_update**: Creates concise security updates for newsletters, covering stories, threats, advisories, vulnerabilities, and a summary of key issues.
|
||||
87. **create_show_intro**: Creates compelling short intros for podcasts, summarizing key topics and themes discussed in the episode.
|
||||
88. **create_sigma_rules**: Extracts Tactics, Techniques, and Procedures (TTPs) from security news and converts them into Sigma detection rules for host-based detections.
|
||||
89. **create_story_about_people_interaction**: Analyze two personas, compare their dynamics, and craft a realistic, character-driven story from those insights.
|
||||
90. **create_story_about_person**: Creates compelling, realistic short stories based on psychological profiles, showing how characters navigate everyday problems using strategies consistent with their personality traits.
|
||||
91. **create_story_explanation**: Summarizes complex content in a clear, approachable story format that makes the concepts easy to understand.
|
||||
92. **create_stride_threat_model**: Create a STRIDE-based threat model for a system design, identifying assets, trust boundaries, data flows, and prioritizing threats with mitigations.
|
||||
93. **create_summary**: Summarizes content into a 20-word sentence, 10 main points (16 words max), and 5 key takeaways in Markdown format.
|
||||
94. **create_tags**: Identifies at least 5 tags from text content for mind mapping tools, including authors and existing tags if present.
|
||||
95. **create_threat_scenarios**: Identifies likely attack methods for any system by providing a narrative-based threat model, balancing risk and opportunity.
|
||||
96. **create_ttrc_graph**: Creates a CSV file showing the progress of Time to Remediate Critical Vulnerabilities over time using given data.
|
||||
97. **create_ttrc_narrative**: Creates a persuasive narrative highlighting progress in reducing the Time to Remediate Critical Vulnerabilities metric over time.
|
||||
98. **create_upgrade_pack**: Extracts world model and task algorithm updates from content, providing beliefs about how the world works and task performance.
|
||||
99. **create_user_story**: Writes concise and clear technical user stories for new features in complex software programs, formatted for all stakeholders.
|
||||
100. **create_video_chapters**: Extracts interesting topics and timestamps from a transcript, providing concise summaries of key moments.
|
||||
101. **create_visualization**: Transforms complex ideas into visualizations using intricate ASCII art, simplifying concepts where necessary.
|
||||
102. **dialog_with_socrates**: Engages in deep, meaningful dialogues to explore and challenge beliefs using the Socratic method.
|
||||
103. **enrich_blog_post**: Enhances Markdown blog files by applying instructions to improve structure, visuals, and readability for HTML rendering.
|
||||
104. **explain_code**: Explains code, security tool output, configuration text, and answers questions based on the provided input.
|
||||
105. **explain_docs**: Improves and restructures tool documentation into clear, concise instructions, including overviews, usage, use cases, and key features.
|
||||
106. **explain_math**: Helps you understand mathematical concepts in a clear and engaging way.
|
||||
107. **explain_project**: Summarizes project documentation into clear, concise sections covering the project, problem, solution, installation, usage, and examples.
|
||||
108. **explain_terms**: Produces a glossary of advanced terms from content, providing a definition, analogy, and explanation of why each term matters.
|
||||
109. **export_data_as_csv**: Extracts and outputs all data structures from the input in properly formatted CSV data.
|
||||
110. **extract_algorithm_update_recommendations**: Extracts concise, practical algorithm update recommendations from the input and outputs them in a bulleted list.
|
||||
111. **extract_article_wisdom**: Extracts surprising, insightful, and interesting information from content, categorizing it into sections like summary, ideas, quotes, facts, references, and recommendations.
|
||||
112. **extract_book_ideas**: Extracts and outputs 50 to 100 of the most surprising, insightful, and interesting ideas from a book's content.
|
||||
113. **extract_book_recommendations**: Extracts and outputs 50 to 100 practical, actionable recommendations from a book's content.
|
||||
114. **extract_business_ideas**: Extracts top business ideas from content and elaborates on the best 10 with unique differentiators.
|
||||
115. **extract_characters**: Identify all characters (human and non-human), resolve their aliases and pronouns into canonical names, and produce detailed descriptions of each character's role, motivations, and interactions ranked by narrative importance.
|
||||
116. **extract_controversial_ideas**: Extracts and outputs controversial statements and supporting quotes from the input in a structured Markdown list.
|
||||
117. **extract_core_message**: Extracts and outputs a clear, concise sentence that articulates the core message of a given text or body of work.
|
||||
118. **extract_ctf_writeup**: Extracts a short writeup from a warstory-like text about a cyber security engagement.
|
||||
119. **extract_domains**: Extracts domains and URLs from content to identify sources used for articles, newsletters, and other publications.
|
||||
120. **extract_extraordinary_claims**: Extracts and outputs a list of extraordinary claims from conversations, focusing on scientifically disputed or false statements.
|
||||
121. **extract_ideas**: Extracts and outputs all the key ideas from input, presented as 15-word bullet points in Markdown.
|
||||
122. **extract_insights**: Extracts and outputs the most powerful and insightful ideas from text, formatted as 16-word bullet points in the INSIGHTS section, also IDEAS section.
|
||||
123. **extract_insights_dm**: Extracts and outputs all valuable insights and a concise summary of the content, including key points and topics discussed.
|
||||
124. **extract_instructions**: Extracts clear, actionable step-by-step instructions and main objectives from instructional video transcripts, organizing them into a concise list.
|
||||
125. **extract_jokes**: Extracts jokes from text content, presenting each joke with its punchline in separate bullet points.
|
||||
126. **extract_latest_video**: Extracts the latest video URL from a YouTube RSS feed and outputs the URL only.
|
||||
127. **extract_main_activities**: Extracts key events and activities from transcripts or logs, providing a summary of what happened.
|
||||
128. **extract_main_idea**: Extracts the main idea and key recommendation from the input, summarizing them in 15-word sentences.
|
||||
129. **extract_mcp_servers**: Identify and summarize Model Context Protocol (MCP) servers referenced in the input along with their key details.
|
||||
130. **extract_most_redeeming_thing**: Extracts the most redeeming aspect from an input, summarizing it in a single 15-word sentence.
|
||||
131. **extract_patterns**: Extracts and analyzes recurring, surprising, and insightful patterns from input, providing detailed analysis and advice for builders.
|
||||
132. **extract_poc**: Extracts proof of concept URLs and validation methods from security reports, providing the URL and command to run.
|
||||
133. **extract_predictions**: Extracts predictions from input, including specific details such as date, confidence level, and verification method.
|
||||
134. **extract_primary_problem**: Extracts the primary problem with the world as presented in a given text or body of work.
|
||||
135. **extract_primary_solution**: Extracts the primary solution for the world as presented in a given text or body of work.
|
||||
136. **extract_product_features**: Extracts and outputs a list of product features from the provided input in a bulleted format.
|
||||
137. **extract_questions**: Extracts and outputs all questions asked by the interviewer in a conversation or interview.
|
||||
138. **extract_recipe**: Extracts and outputs a recipe with a short meal description, ingredients with measurements, and preparation steps.
|
||||
139. **extract_recommendations**: Extracts and outputs concise, practical recommendations from a given piece of content in a bulleted list.
|
||||
140. **extract_references**: Extracts and outputs a bulleted list of references to art, stories, books, literature, and other sources from content.
|
||||
141. **extract_skills**: Extracts and classifies skills from a job description into a table, separating each skill and classifying it as either hard or soft.
|
||||
142. **extract_song_meaning**: Analyzes a song to provide a summary of its meaning, supported by detailed evidence from lyrics, artist commentary, and fan analysis.
|
||||
143. **extract_sponsors**: Extracts and lists official sponsors and potential sponsors from a provided transcript.
|
||||
144. **extract_videoid**: Extracts and outputs the video ID from any given URL.
|
||||
145. **extract_wisdom**: Extracts surprising, insightful, and interesting information from text on topics like human flourishing, AI, learning, and more.
|
||||
146. **extract_wisdom_agents**: Extracts valuable insights, ideas, quotes, and references from content, emphasizing topics like human flourishing, AI, learning, and technology.
|
||||
147. **extract_wisdom_dm**: Extracts all valuable, insightful, and thought-provoking information from content, focusing on topics like human flourishing, AI, learning, and technology.
|
||||
148. **extract_wisdom_nometa**: Extracts insights, ideas, quotes, habits, facts, references, and recommendations from content, focusing on human flourishing, AI, technology, and related topics.
|
||||
149. **find_female_life_partner**: Analyzes criteria for finding a female life partner and provides clear, direct, and poetic descriptions.
|
||||
150. **find_hidden_message**: Extracts overt and hidden political messages, justifications, audience actions, and a cynical analysis from content.
|
||||
151. **find_logical_fallacies**: Identifies and analyzes fallacies in arguments, classifying them as formal or informal with detailed reasoning.
|
||||
152. **fix_typos**: Proofreads and corrects typos, spelling, grammar, and punctuation errors in text.
|
||||
153. **generate_code_rules**: Compile best-practice coding rules and guardrails for AI-assisted development workflows from the provided content.
|
||||
154. **get_wow_per_minute**: Determines the wow-factor of content per minute based on surprise, novelty, insight, value, and wisdom, measuring how rewarding the content is for the viewer.
|
||||
155. **heal_person**: Develops a comprehensive plan for spiritual and mental healing based on psychological profiles, providing personalized recommendations for mental health improvement and overall life enhancement.
|
||||
156. **humanize**: Rewrites AI-generated text to sound natural, conversational, and easy to understand, maintaining clarity and simplicity.
|
||||
157. **identify_dsrp_distinctions**: Encourages creative, systems-based thinking by exploring distinctions, boundaries, and their implications, drawing on insights from prominent systems thinkers.
|
||||
158. **identify_dsrp_perspectives**: Explores the concept of distinctions in systems thinking, focusing on how boundaries define ideas, influence understanding, and reveal or obscure insights.
|
||||
159. **identify_dsrp_relationships**: Encourages exploration of connections, distinctions, and boundaries between ideas, inspired by systems thinkers to reveal new insights and patterns in complex systems.
|
||||
160. **identify_dsrp_systems**: Encourages organizing ideas into systems of parts and wholes, inspired by systems thinkers to explore relationships and how changes in organization impact meaning and understanding.
|
||||
161. **identify_job_stories**: Identifies key job stories or requirements for roles.
|
||||
162. **improve_academic_writing**: Refines text into clear, concise academic language while improving grammar, coherence, and clarity, with a list of changes.
|
||||
163. **improve_prompt**: Improves an LLM/AI prompt by applying expert prompt writing strategies for better results and clarity.
|
||||
164. **improve_report_finding**: Improves a penetration test security finding by providing detailed descriptions, risks, recommendations, references, quotes, and a concise summary in markdown format.
|
||||
165. **improve_writing**: Refines text by correcting grammar, enhancing style, improving clarity, and maintaining the original meaning. skills.
|
||||
166. **judge_output**: Evaluates Honeycomb queries by judging their effectiveness, providing critiques and outcomes based on language nuances and analytics relevance.
|
||||
167. **label_and_rate**: Labels content with up to 20 single-word tags and rates it based on idea count and relevance to human meaning, AI, and other related themes, assigning a tier (S, A, B, C, D) and a quality score.
|
||||
168. **md_callout**: Classifies content and generates a markdown callout based on the provided text, selecting the most appropriate type.
|
||||
169. **model_as_sherlock_freud**: Builds psychological models using detective reasoning and psychoanalytic insight to understand human behavior.
|
||||
170. **official_pattern_template**: Template to use if you want to create new fabric patterns.
|
||||
171. **predict_person_actions**: Predicts behavioral responses based on psychological profiles and challenges.
|
||||
172. **prepare_7s_strategy**: Prepares a comprehensive briefing document from 7S's strategy capturing organizational profile, strategic elements, and market dynamics with clear, concise, and organized content.
|
||||
173. **provide_guidance**: Provides psychological and life coaching advice, including analysis, recommendations, and potential diagnoses, with a compassionate and honest tone.
|
||||
174. **rate_ai_response**: Rates the quality of AI responses by comparing them to top human expert performance, assigning a letter grade, reasoning, and providing a 1-100 score based on the evaluation.
|
||||
175. **rate_ai_result**: Assesses the quality of AI/ML/LLM work by deeply analyzing content, instructions, and output, then rates performance based on multiple dimensions, including coverage, creativity, and interdisciplinary thinking.
|
||||
176. **rate_content**: Labels content with up to 20 single-word tags and rates it based on idea count and relevance to human meaning, AI, and other related themes, assigning a tier (S, A, B, C, D) and a quality score.
|
||||
177. **rate_value**: Produces the best possible output by deeply analyzing and understanding the input and its intended purpose.
|
||||
178. **raw_query**: Fully digests and contemplates the input to produce the best possible result based on understanding the sender's intent.
|
||||
179. **recommend_artists**: Recommends a personalized festival schedule with artists aligned to your favorite styles and interests, including rationale.
|
||||
180. **recommend_pipeline_upgrades**: Optimizes vulnerability-checking pipelines by incorporating new information and improving their efficiency, with detailed explanations of changes.
|
||||
181. **recommend_talkpanel_topics**: Produces a clean set of proposed talks or panel talking points for a person based on their interests and goals, formatted for submission to a conference organizer.
|
||||
182. **recommend_yoga_practice**: Provides personalized yoga sequences, meditation guidance, and holistic lifestyle advice based on individual profiles.
|
||||
183. **refine_design_document**: Refines a design document based on a design review by analyzing, mapping concepts, and implementing changes using valid Markdown.
|
||||
184. **review_design**: Reviews and analyzes architecture design, focusing on clarity, component design, system integrations, security, performance, scalability, and data management.
|
||||
185. **sanitize_broken_html_to_markdown**: Converts messy HTML into clean, properly formatted Markdown, applying custom styling and ensuring compatibility with Vite.
|
||||
186. **suggest_pattern**: Suggests appropriate fabric patterns or commands based on user input, providing clear explanations and options for users.
|
||||
187. **summarize**: Summarizes content into a 20-word sentence, main points, and takeaways, formatted with numbered lists in Markdown.
|
||||
188. **summarize_board_meeting**: Creates formal meeting notes from board meeting transcripts for corporate governance documentation.
|
||||
189. **summarize_debate**: Summarizes debates, identifies primary disagreement, extracts arguments, and provides analysis of evidence and argument strength to predict outcomes.
|
||||
190. **summarize_git_changes**: Summarizes recent project updates from the last 7 days, focusing on key changes with enthusiasm.
|
||||
191. **summarize_git_diff**: Summarizes and organizes Git diff changes with clear, succinct commit messages and bullet points.
|
||||
192. **summarize_lecture**: Extracts relevant topics, definitions, and tools from lecture transcripts, providing structured summaries with timestamps and key takeaways.
|
||||
193. **summarize_legislation**: Summarizes complex political proposals and legislation by analyzing key points, proposed changes, and providing balanced, positive, and cynical characterizations.
|
||||
194. **summarize_meeting**: Analyzes meeting transcripts to extract a structured summary, including an overview, key points, tasks, decisions, challenges, timeline, references, and next steps.
|
||||
195. **summarize_micro**: Summarizes content into a 20-word sentence, 3 main points, and 3 takeaways, formatted in clear, concise Markdown.
|
||||
196. **summarize_newsletter**: Extracts the most meaningful, interesting, and useful content from a newsletter, summarizing key sections such as content, opinions, tools, companies, and follow-up items in clear, structured Markdown.
|
||||
197. **summarize_paper**: Summarizes an academic paper by detailing its title, authors, technical approach, distinctive features, experimental setup, results, advantages, limitations, and conclusion in a clear, structured format using human-readable Markdown.
|
||||
198. **summarize_prompt**: Summarizes AI chat prompts by describing the primary function, unique approach, and expected output in a concise paragraph. The summary is focused on the prompt's purpose without unnecessary details or formatting.
|
||||
199. **summarize_pull-requests**: Summarizes pull requests for a coding project by providing a summary and listing the top PRs with human-readable descriptions.
|
||||
200. **summarize_rpg_session**: Summarizes a role-playing game session by extracting key events, combat stats, character changes, quotes, and more.
|
||||
201. **t_analyze_challenge_handling**: Provides 8-16 word bullet points evaluating how well challenges are being addressed, calling out any lack of effort.
|
||||
202. **t_check_dunning_kruger**: Assess narratives for Dunning-Kruger patterns by contrasting self-perception with demonstrated competence and confidence cues.
|
||||
203. **t_check_metrics**: Analyzes deep context from the TELOS file and input instruction, then provides a wisdom-based output while considering metrics and KPIs to assess recent improvements.
|
||||
204. **t_create_h3_career**: Summarizes context and produces wisdom-based output by deeply analyzing both the TELOS File and the input instruction, considering the relationship between the two.
|
||||
205. **t_create_opening_sentences**: Describes from TELOS file the person's identity, goals, and actions in 4 concise, 32-word bullet points, humbly.
|
||||
206. **t_describe_life_outlook**: Describes from TELOS file a person's life outlook in 5 concise, 16-word bullet points.
|
||||
207. **t_extract_intro_sentences**: Summarizes from TELOS file a person's identity, work, and current projects in 5 concise and grounded bullet points.
|
||||
208. **t_extract_panel_topics**: Creates 5 panel ideas with titles and descriptions based on deep context from a TELOS file and input.
|
||||
209. **t_find_blindspots**: Identify potential blindspots in thinking, frames, or models that may expose the individual to error or risk.
|
||||
210. **t_find_negative_thinking**: Analyze a TELOS file and input to identify negative thinking in documents or journals, followed by tough love encouragement.
|
||||
211. **t_find_neglected_goals**: Analyze a TELOS file and input instructions to identify goals or projects that have not been worked on recently.
|
||||
212. **t_give_encouragement**: Analyze a TELOS file and input instructions to evaluate progress, provide encouragement, and offer recommendations for continued effort.
|
||||
213. **t_red_team_thinking**: Analyze a TELOS file and input instructions to red-team thinking, models, and frames, then provide recommendations for improvement.
|
||||
214. **t_threat_model_plans**: Analyze a TELOS file and input instructions to create threat models for a life plan and recommend improvements.
|
||||
215. **t_visualize_mission_goals_projects**: Analyze a TELOS file and input instructions to create an ASCII art diagram illustrating the relationship of missions, goals, and projects.
|
||||
216. **t_year_in_review**: Analyze a TELOS file to create insights about a person or entity, then summarize accomplishments and visualizations in bullet points.
|
||||
217. **to_flashcards**: Create Anki flashcards from a given text, focusing on concise, optimized questions and answers without external context.
|
||||
218. **transcribe_minutes**: Extracts (from meeting transcription) meeting minutes, identifying actionables, insightful ideas, decisions, challenges, and next steps in a structured format.
|
||||
219. **translate**: Translates sentences or documentation into the specified language code while maintaining the original formatting and tone.
|
||||
220. **tweet**: Provides a step-by-step guide on crafting engaging tweets with emojis, covering Twitter basics, account creation, features, and audience targeting.
|
||||
221. **write_essay**: Writes essays in the style of a specified author, embodying their unique voice, vocabulary, and approach. Uses `author_name` variable.
|
||||
222. **write_essay_pg**: Writes concise, clear essays in the style of Paul Graham, focusing on simplicity, clarity, and illumination of the provided topic.
|
||||
223. **write_hackerone_report**: Generates concise, clear, and reproducible bug bounty reports, detailing vulnerability impact, steps to reproduce, and exploit details for triagers.
|
||||
224. **write_latex**: Generates syntactically correct LaTeX code for a new.tex document, ensuring proper formatting and compatibility with pdflatex.
|
||||
225. **write_micro_essay**: Writes concise, clear, and illuminating essays on the given topic in the style of Paul Graham.
|
||||
226. **write_nuclei_template_rule**: Generates Nuclei YAML templates for detecting vulnerabilities using HTTP requests, matchers, extractors, and dynamic data extraction.
|
||||
227. **write_pull-request**: Drafts detailed pull request descriptions, explaining changes, providing reasoning, and identifying potential bugs from the git diff command output.
|
||||
228. **write_semgrep_rule**: Creates accurate and working Semgrep rules based on input, following syntax guidelines and specific language considerations.
|
||||
229. **youtube_summary**: Create concise, timestamped Youtube video summaries that highlight key points.
|
||||
45. **concall_summary**: Analyzes earnings and conference call transcripts to extract management commentary, analyst Q&A, financial insights, risks, and executive summaries.
|
||||
46. **convert_to_markdown**: Convert content to clean, complete Markdown format, preserving all original structure, formatting, links, and code blocks without alterations.
|
||||
47. **create_5_sentence_summary**: Create concise summaries or answers to input at 5 different levels of depth, from 5 words to 1 word.
|
||||
48. **create_academic_paper**: Generate a high-quality academic paper in LaTeX format with clear concepts, structured content, and a professional layout.
|
||||
49. **create_ai_jobs_analysis**: Analyze job categories' susceptibility to automation, identify resilient roles, and provide strategies for personal adaptation to AI-driven changes in the workforce.
|
||||
50. **create_aphorisms**: Find and generate a list of brief, witty statements.
|
||||
51. **create_art_prompt**: Generates a detailed, compelling visual description of a concept, including stylistic references and direct AI instructions for creating art.
|
||||
52. **create_better_frame**: Identifies and analyzes different frames of interpreting reality, emphasizing the power of positive, productive lenses in shaping outcomes.
|
||||
53. **create_coding_feature**: Generates secure and composable code features using modern technology and best practices from project specifications.
|
||||
54. **create_coding_project**: Generate wireframes and starter code for any coding ideas that you have.
|
||||
55. **create_command**: Helps determine the correct parameters and switches for penetration testing tools based on a brief description of the objective.
|
||||
56. **create_conceptmap**: Transforms unstructured text or markdown content into an interactive HTML concept map using Vis.js by extracting key concepts and their logical relationships.
|
||||
57. **create_cyber_summary**: Summarizes cybersecurity threats, vulnerabilities, incidents, and malware with a 25-word summary and categorized bullet points, after thoroughly analyzing and mapping the provided input.
|
||||
58. **create_design_document**: Creates a detailed design document for a system using the C4 model, addressing business and security postures, and including a system context diagram.
|
||||
59. **create_diy**: Creates structured "Do It Yourself" tutorial patterns by analyzing prompts, organizing requirements, and providing step-by-step instructions in Markdown format.
|
||||
60. **create_excalidraw_visualization**: Creates complex Excalidraw diagrams to visualize relationships between concepts and ideas in structured format.
|
||||
61. **create_flash_cards**: Creates flashcards for key concepts, definitions, and terms with question-answer format for educational purposes.
|
||||
62. **create_formal_email**: Crafts professional, clear, and respectful emails by analyzing context, tone, and purpose, ensuring proper structure and formatting.
|
||||
63. **create_git_diff_commit**: Generates Git commands and commit messages for reflecting changes in a repository, using conventional commits and providing concise shell commands for updates.
|
||||
64. **create_graph_from_input**: Generates a CSV file with progress-over-time data for a security program, focusing on relevant metrics and KPIs.
|
||||
65. **create_hormozi_offer**: Creates a customized business offer based on principles from Alex Hormozi's book, "$100M Offers."
|
||||
66. **create_idea_compass**: Organizes and structures ideas by exploring their definition, evidence, sources, and related themes or consequences.
|
||||
67. **create_investigation_visualization**: Creates detailed Graphviz visualizations of complex input, highlighting key aspects and providing clear, well-annotated diagrams for investigative analysis and conclusions.
|
||||
68. **create_keynote**: Creates TED-style keynote presentations with a clear narrative, structured slides, and speaker notes, emphasizing impactful takeaways and cohesive flow.
|
||||
69. **create_loe_document**: Creates detailed Level of Effort documents for estimating work effort, resources, and costs for tasks or projects.
|
||||
70. **create_logo**: Creates simple, minimalist company logos without text, generating AI prompts for vector graphic logos based on input.
|
||||
71. **create_markmap_visualization**: Transforms complex ideas into clear visualizations using MarkMap syntax, simplifying concepts into diagrams with relationships, boxes, arrows, and labels.
|
||||
72. **create_mermaid_visualization**: Creates detailed, standalone visualizations of concepts using Mermaid (Markdown) syntax, ensuring clarity and coherence in diagrams.
|
||||
73. **create_mermaid_visualization_for_github**: Creates standalone, detailed visualizations using Mermaid (Markdown) syntax to effectively explain complex concepts, ensuring clarity and precision.
|
||||
74. **create_micro_summary**: Summarizes content into a concise, 20-word summary with main points and takeaways, formatted in Markdown.
|
||||
75. **create_mnemonic_phrases**: Creates memorable mnemonic sentences from given words to aid in memory retention and learning.
|
||||
76. **create_network_threat_landscape**: Analyzes open ports and services from a network scan and generates a comprehensive, insightful, and detailed security threat report in Markdown.
|
||||
77. **create_newsletter_entry**: Condenses provided article text into a concise, objective, newsletter-style summary with a title in the style of Frontend Weekly.
|
||||
78. **create_npc**: Generates a detailed D&D 5E NPC, including background, flaws, stats, appearance, personality, goals, and more in Markdown format.
|
||||
79. **create_pattern**: Extracts, organizes, and formats LLM/AI prompts into structured sections, detailing the AI's role, instructions, output format, and any provided examples for clarity and accuracy.
|
||||
80. **create_prd**: Creates a precise Product Requirements Document (PRD) in Markdown based on input.
|
||||
81. **create_prediction_block**: Extracts and formats predictions from input into a structured Markdown block for a blog post.
|
||||
82. **create_quiz**: Creates a three-phase reading plan based on an author or topic to help the user become significantly knowledgeable, including core, extended, and supplementary readings.
|
||||
83. **create_reading_plan**: Generates review questions based on learning objectives from the input, adapted to the specified student level, and outputs them in a clear markdown format.
|
||||
84. **create_recursive_outline**: Breaks down complex tasks or projects into manageable, hierarchical components with recursive outlining for clarity and simplicity.
|
||||
85. **create_report_finding**: Creates a detailed, structured security finding report in markdown, including sections on Description, Risk, Recommendations, References, One-Sentence-Summary, and Quotes.
|
||||
86. **create_rpg_summary**: Summarizes an in-person RPG session with key events, combat details, player stats, and role-playing highlights in a structured format.
|
||||
87. **create_security_update**: Creates concise security updates for newsletters, covering stories, threats, advisories, vulnerabilities, and a summary of key issues.
|
||||
88. **create_show_intro**: Creates compelling short intros for podcasts, summarizing key topics and themes discussed in the episode.
|
||||
89. **create_sigma_rules**: Extracts Tactics, Techniques, and Procedures (TTPs) from security news and converts them into Sigma detection rules for host-based detections.
|
||||
90. **create_story_about_people_interaction**: Analyze two personas, compare their dynamics, and craft a realistic, character-driven story from those insights.
|
||||
91. **create_story_about_person**: Creates compelling, realistic short stories based on psychological profiles, showing how characters navigate everyday problems using strategies consistent with their personality traits.
|
||||
92. **create_story_explanation**: Summarizes complex content in a clear, approachable story format that makes the concepts easy to understand.
|
||||
93. **create_stride_threat_model**: Create a STRIDE-based threat model for a system design, identifying assets, trust boundaries, data flows, and prioritizing threats with mitigations.
|
||||
94. **create_summary**: Summarizes content into a 20-word sentence, 10 main points (16 words max), and 5 key takeaways in Markdown format.
|
||||
95. **create_tags**: Identifies at least 5 tags from text content for mind mapping tools, including authors and existing tags if present.
|
||||
96. **create_threat_scenarios**: Identifies likely attack methods for any system by providing a narrative-based threat model, balancing risk and opportunity.
|
||||
97. **create_ttrc_graph**: Creates a CSV file showing the progress of Time to Remediate Critical Vulnerabilities over time using given data.
|
||||
98. **create_ttrc_narrative**: Creates a persuasive narrative highlighting progress in reducing the Time to Remediate Critical Vulnerabilities metric over time.
|
||||
99. **create_upgrade_pack**: Extracts world model and task algorithm updates from content, providing beliefs about how the world works and task performance.
|
||||
100. **create_user_story**: Writes concise and clear technical user stories for new features in complex software programs, formatted for all stakeholders.
|
||||
101. **create_video_chapters**: Extracts interesting topics and timestamps from a transcript, providing concise summaries of key moments.
|
||||
102. **create_visualization**: Transforms complex ideas into visualizations using intricate ASCII art, simplifying concepts where necessary.
|
||||
103. **dialog_with_socrates**: Engages in deep, meaningful dialogues to explore and challenge beliefs using the Socratic method.
|
||||
104. **enrich_blog_post**: Enhances Markdown blog files by applying instructions to improve structure, visuals, and readability for HTML rendering.
|
||||
105. **explain_code**: Explains code, security tool output, configuration text, and answers questions based on the provided input.
|
||||
106. **explain_docs**: Improves and restructures tool documentation into clear, concise instructions, including overviews, usage, use cases, and key features.
|
||||
107. **explain_math**: Helps you understand mathematical concepts in a clear and engaging way.
|
||||
108. **explain_project**: Summarizes project documentation into clear, concise sections covering the project, problem, solution, installation, usage, and examples.
|
||||
109. **explain_terms**: Produces a glossary of advanced terms from content, providing a definition, analogy, and explanation of why each term matters.
|
||||
110. **export_data_as_csv**: Extracts and outputs all data structures from the input in properly formatted CSV data.
|
||||
111. **extract_algorithm_update_recommendations**: Extracts concise, practical algorithm update recommendations from the input and outputs them in a bulleted list.
|
||||
112. **extract_article_wisdom**: Extracts surprising, insightful, and interesting information from content, categorizing it into sections like summary, ideas, quotes, facts, references, and recommendations.
|
||||
113. **extract_book_ideas**: Extracts and outputs 50 to 100 of the most surprising, insightful, and interesting ideas from a book's content.
|
||||
114. **extract_book_recommendations**: Extracts and outputs 50 to 100 practical, actionable recommendations from a book's content.
|
||||
115. **extract_business_ideas**: Extracts top business ideas from content and elaborates on the best 10 with unique differentiators.
|
||||
116. **extract_characters**: Identify all characters (human and non-human), resolve their aliases and pronouns into canonical names, and produce detailed descriptions of each character's role, motivations, and interactions ranked by narrative importance.
|
||||
117. **extract_controversial_ideas**: Extracts and outputs controversial statements and supporting quotes from the input in a structured Markdown list.
|
||||
118. **extract_core_message**: Extracts and outputs a clear, concise sentence that articulates the core message of a given text or body of work.
|
||||
119. **extract_ctf_writeup**: Extracts a short writeup from a warstory-like text about a cyber security engagement.
|
||||
120. **extract_domains**: Extracts domains and URLs from content to identify sources used for articles, newsletters, and other publications.
|
||||
121. **extract_extraordinary_claims**: Extracts and outputs a list of extraordinary claims from conversations, focusing on scientifically disputed or false statements.
|
||||
122. **extract_ideas**: Extracts and outputs all the key ideas from input, presented as 15-word bullet points in Markdown.
|
||||
123. **extract_insights**: Extracts and outputs the most powerful and insightful ideas from text, formatted as 16-word bullet points in the INSIGHTS section, also IDEAS section.
|
||||
124. **extract_insights_dm**: Extracts and outputs all valuable insights and a concise summary of the content, including key points and topics discussed.
|
||||
125. **extract_instructions**: Extracts clear, actionable step-by-step instructions and main objectives from instructional video transcripts, organizing them into a concise list.
|
||||
126. **extract_jokes**: Extracts jokes from text content, presenting each joke with its punchline in separate bullet points.
|
||||
127. **extract_latest_video**: Extracts the latest video URL from a YouTube RSS feed and outputs the URL only.
|
||||
128. **extract_main_activities**: Extracts key events and activities from transcripts or logs, providing a summary of what happened.
|
||||
129. **extract_main_idea**: Extracts the main idea and key recommendation from the input, summarizing them in 15-word sentences.
|
||||
130. **extract_mcp_servers**: Identify and summarize Model Context Protocol (MCP) servers referenced in the input along with their key details.
|
||||
131. **extract_most_redeeming_thing**: Extracts the most redeeming aspect from an input, summarizing it in a single 15-word sentence.
|
||||
132. **extract_patterns**: Extracts and analyzes recurring, surprising, and insightful patterns from input, providing detailed analysis and advice for builders.
|
||||
133. **extract_poc**: Extracts proof of concept URLs and validation methods from security reports, providing the URL and command to run.
|
||||
134. **extract_predictions**: Extracts predictions from input, including specific details such as date, confidence level, and verification method.
|
||||
135. **extract_primary_problem**: Extracts the primary problem with the world as presented in a given text or body of work.
|
||||
136. **extract_primary_solution**: Extracts the primary solution for the world as presented in a given text or body of work.
|
||||
137. **extract_product_features**: Extracts and outputs a list of product features from the provided input in a bulleted format.
|
||||
138. **extract_questions**: Extracts and outputs all questions asked by the interviewer in a conversation or interview.
|
||||
139. **extract_recipe**: Extracts and outputs a recipe with a short meal description, ingredients with measurements, and preparation steps.
|
||||
140. **extract_recommendations**: Extracts and outputs concise, practical recommendations from a given piece of content in a bulleted list.
|
||||
141. **extract_references**: Extracts and outputs a bulleted list of references to art, stories, books, literature, and other sources from content.
|
||||
142. **extract_skills**: Extracts and classifies skills from a job description into a table, separating each skill and classifying it as either hard or soft.
|
||||
143. **extract_song_meaning**: Analyzes a song to provide a summary of its meaning, supported by detailed evidence from lyrics, artist commentary, and fan analysis.
|
||||
144. **extract_sponsors**: Extracts and lists official sponsors and potential sponsors from a provided transcript.
|
||||
145. **extract_videoid**: Extracts and outputs the video ID from any given URL.
|
||||
146. **extract_wisdom**: Extracts surprising, insightful, and interesting information from text on topics like human flourishing, AI, learning, and more.
|
||||
147. **extract_wisdom_agents**: Extracts valuable insights, ideas, quotes, and references from content, emphasizing topics like human flourishing, AI, learning, and technology.
|
||||
148. **extract_wisdom_dm**: Extracts all valuable, insightful, and thought-provoking information from content, focusing on topics like human flourishing, AI, learning, and technology.
|
||||
149. **extract_wisdom_nometa**: Extracts insights, ideas, quotes, habits, facts, references, and recommendations from content, focusing on human flourishing, AI, technology, and related topics.
|
||||
150. **find_female_life_partner**: Analyzes criteria for finding a female life partner and provides clear, direct, and poetic descriptions.
|
||||
151. **find_hidden_message**: Extracts overt and hidden political messages, justifications, audience actions, and a cynical analysis from content.
|
||||
152. **find_logical_fallacies**: Identifies and analyzes fallacies in arguments, classifying them as formal or informal with detailed reasoning.
|
||||
153. **fix_typos**: Proofreads and corrects typos, spelling, grammar, and punctuation errors in text.
|
||||
154. **generate_code_rules**: Compile best-practice coding rules and guardrails for AI-assisted development workflows from the provided content.
|
||||
155. **get_wow_per_minute**: Determines the wow-factor of content per minute based on surprise, novelty, insight, value, and wisdom, measuring how rewarding the content is for the viewer.
|
||||
156. **heal_person**: Develops a comprehensive plan for spiritual and mental healing based on psychological profiles, providing personalized recommendations for mental health improvement and overall life enhancement.
|
||||
157. **humanize**: Rewrites AI-generated text to sound natural, conversational, and easy to understand, maintaining clarity and simplicity.
|
||||
158. **identify_dsrp_distinctions**: Encourages creative, systems-based thinking by exploring distinctions, boundaries, and their implications, drawing on insights from prominent systems thinkers.
|
||||
159. **identify_dsrp_perspectives**: Explores the concept of distinctions in systems thinking, focusing on how boundaries define ideas, influence understanding, and reveal or obscure insights.
|
||||
160. **identify_dsrp_relationships**: Encourages exploration of connections, distinctions, and boundaries between ideas, inspired by systems thinkers to reveal new insights and patterns in complex systems.
|
||||
161. **identify_dsrp_systems**: Encourages organizing ideas into systems of parts and wholes, inspired by systems thinkers to explore relationships and how changes in organization impact meaning and understanding.
|
||||
162. **identify_job_stories**: Identifies key job stories or requirements for roles.
|
||||
163. **improve_academic_writing**: Refines text into clear, concise academic language while improving grammar, coherence, and clarity, with a list of changes.
|
||||
164. **improve_prompt**: Improves an LLM/AI prompt by applying expert prompt writing strategies for better results and clarity.
|
||||
165. **improve_report_finding**: Improves a penetration test security finding by providing detailed descriptions, risks, recommendations, references, quotes, and a concise summary in markdown format.
|
||||
166. **improve_writing**: Refines text by correcting grammar, enhancing style, improving clarity, and maintaining the original meaning. skills.
|
||||
167. **judge_output**: Evaluates Honeycomb queries by judging their effectiveness, providing critiques and outcomes based on language nuances and analytics relevance.
|
||||
168. **label_and_rate**: Labels content with up to 20 single-word tags and rates it based on idea count and relevance to human meaning, AI, and other related themes, assigning a tier (S, A, B, C, D) and a quality score.
|
||||
169. **md_callout**: Classifies content and generates a markdown callout based on the provided text, selecting the most appropriate type.
|
||||
170. **model_as_sherlock_freud**: Builds psychological models using detective reasoning and psychoanalytic insight to understand human behavior.
|
||||
171. **official_pattern_template**: Template to use if you want to create new fabric patterns.
|
||||
172. **predict_person_actions**: Predicts behavioral responses based on psychological profiles and challenges.
|
||||
173. **prepare_7s_strategy**: Prepares a comprehensive briefing document from 7S's strategy capturing organizational profile, strategic elements, and market dynamics with clear, concise, and organized content.
|
||||
174. **provide_guidance**: Provides psychological and life coaching advice, including analysis, recommendations, and potential diagnoses, with a compassionate and honest tone.
|
||||
175. **rate_ai_response**: Rates the quality of AI responses by comparing them to top human expert performance, assigning a letter grade, reasoning, and providing a 1-100 score based on the evaluation.
|
||||
176. **rate_ai_result**: Assesses the quality of AI/ML/LLM work by deeply analyzing content, instructions, and output, then rates performance based on multiple dimensions, including coverage, creativity, and interdisciplinary thinking.
|
||||
177. **rate_content**: Labels content with up to 20 single-word tags and rates it based on idea count and relevance to human meaning, AI, and other related themes, assigning a tier (S, A, B, C, D) and a quality score.
|
||||
178. **rate_value**: Produces the best possible output by deeply analyzing and understanding the input and its intended purpose.
|
||||
179. **raw_query**: Fully digests and contemplates the input to produce the best possible result based on understanding the sender's intent.
|
||||
180. **recommend_artists**: Recommends a personalized festival schedule with artists aligned to your favorite styles and interests, including rationale.
|
||||
181. **recommend_pipeline_upgrades**: Optimizes vulnerability-checking pipelines by incorporating new information and improving their efficiency, with detailed explanations of changes.
|
||||
182. **recommend_talkpanel_topics**: Produces a clean set of proposed talks or panel talking points for a person based on their interests and goals, formatted for submission to a conference organizer.
|
||||
183. **recommend_yoga_practice**: Provides personalized yoga sequences, meditation guidance, and holistic lifestyle advice based on individual profiles.
|
||||
184. **refine_design_document**: Refines a design document based on a design review by analyzing, mapping concepts, and implementing changes using valid Markdown.
|
||||
185. **review_design**: Reviews and analyzes architecture design, focusing on clarity, component design, system integrations, security, performance, scalability, and data management.
|
||||
186. **sanitize_broken_html_to_markdown**: Converts messy HTML into clean, properly formatted Markdown, applying custom styling and ensuring compatibility with Vite.
|
||||
187. **suggest_pattern**: Suggests appropriate fabric patterns or commands based on user input, providing clear explanations and options for users.
|
||||
188. **summarize**: Summarizes content into a 20-word sentence, main points, and takeaways, formatted with numbered lists in Markdown.
|
||||
189. **summarize_board_meeting**: Creates formal meeting notes from board meeting transcripts for corporate governance documentation.
|
||||
190. **summarize_debate**: Summarizes debates, identifies primary disagreement, extracts arguments, and provides analysis of evidence and argument strength to predict outcomes.
|
||||
191. **summarize_git_changes**: Summarizes recent project updates from the last 7 days, focusing on key changes with enthusiasm.
|
||||
192. **summarize_git_diff**: Summarizes and organizes Git diff changes with clear, succinct commit messages and bullet points.
|
||||
193. **summarize_lecture**: Extracts relevant topics, definitions, and tools from lecture transcripts, providing structured summaries with timestamps and key takeaways.
|
||||
194. **summarize_legislation**: Summarizes complex political proposals and legislation by analyzing key points, proposed changes, and providing balanced, positive, and cynical characterizations.
|
||||
195. **summarize_meeting**: Analyzes meeting transcripts to extract a structured summary, including an overview, key points, tasks, decisions, challenges, timeline, references, and next steps.
|
||||
196. **summarize_micro**: Summarizes content into a 20-word sentence, 3 main points, and 3 takeaways, formatted in clear, concise Markdown.
|
||||
197. **summarize_newsletter**: Extracts the most meaningful, interesting, and useful content from a newsletter, summarizing key sections such as content, opinions, tools, companies, and follow-up items in clear, structured Markdown.
|
||||
198. **summarize_paper**: Summarizes an academic paper by detailing its title, authors, technical approach, distinctive features, experimental setup, results, advantages, limitations, and conclusion in a clear, structured format using human-readable Markdown.
|
||||
199. **summarize_prompt**: Summarizes AI chat prompts by describing the primary function, unique approach, and expected output in a concise paragraph. The summary is focused on the prompt's purpose without unnecessary details or formatting.
|
||||
200. **summarize_pull-requests**: Summarizes pull requests for a coding project by providing a summary and listing the top PRs with human-readable descriptions.
|
||||
201. **summarize_rpg_session**: Summarizes a role-playing game session by extracting key events, combat stats, character changes, quotes, and more.
|
||||
202. **t_analyze_challenge_handling**: Provides 8-16 word bullet points evaluating how well challenges are being addressed, calling out any lack of effort.
|
||||
203. **t_check_dunning_kruger**: Assess narratives for Dunning-Kruger patterns by contrasting self-perception with demonstrated competence and confidence cues.
|
||||
204. **t_check_metrics**: Analyzes deep context from the TELOS file and input instruction, then provides a wisdom-based output while considering metrics and KPIs to assess recent improvements.
|
||||
205. **t_create_h3_career**: Summarizes context and produces wisdom-based output by deeply analyzing both the TELOS File and the input instruction, considering the relationship between the two.
|
||||
206. **t_create_opening_sentences**: Describes from TELOS file the person's identity, goals, and actions in 4 concise, 32-word bullet points, humbly.
|
||||
207. **t_describe_life_outlook**: Describes from TELOS file a person's life outlook in 5 concise, 16-word bullet points.
|
||||
208. **t_extract_intro_sentences**: Summarizes from TELOS file a person's identity, work, and current projects in 5 concise and grounded bullet points.
|
||||
209. **t_extract_panel_topics**: Creates 5 panel ideas with titles and descriptions based on deep context from a TELOS file and input.
|
||||
210. **t_find_blindspots**: Identify potential blindspots in thinking, frames, or models that may expose the individual to error or risk.
|
||||
211. **t_find_negative_thinking**: Analyze a TELOS file and input to identify negative thinking in documents or journals, followed by tough love encouragement.
|
||||
212. **t_find_neglected_goals**: Analyze a TELOS file and input instructions to identify goals or projects that have not been worked on recently.
|
||||
213. **t_give_encouragement**: Analyze a TELOS file and input instructions to evaluate progress, provide encouragement, and offer recommendations for continued effort.
|
||||
214. **t_red_team_thinking**: Analyze a TELOS file and input instructions to red-team thinking, models, and frames, then provide recommendations for improvement.
|
||||
215. **t_threat_model_plans**: Analyze a TELOS file and input instructions to create threat models for a life plan and recommend improvements.
|
||||
216. **t_visualize_mission_goals_projects**: Analyze a TELOS file and input instructions to create an ASCII art diagram illustrating the relationship of missions, goals, and projects.
|
||||
217. **t_year_in_review**: Analyze a TELOS file to create insights about a person or entity, then summarize accomplishments and visualizations in bullet points.
|
||||
218. **to_flashcards**: Create Anki flashcards from a given text, focusing on concise, optimized questions and answers without external context.
|
||||
219. **transcribe_minutes**: Extracts (from meeting transcription) meeting minutes, identifying actionables, insightful ideas, decisions, challenges, and next steps in a structured format.
|
||||
220. **translate**: Translates sentences or documentation into the specified language code while maintaining the original formatting and tone.
|
||||
221. **tweet**: Provides a step-by-step guide on crafting engaging tweets with emojis, covering Twitter basics, account creation, features, and audience targeting.
|
||||
222. **write_essay**: Writes essays in the style of a specified author, embodying their unique voice, vocabulary, and approach. Uses `author_name` variable.
|
||||
223. **write_essay_pg**: Writes concise, clear essays in the style of Paul Graham, focusing on simplicity, clarity, and illumination of the provided topic.
|
||||
224. **write_hackerone_report**: Generates concise, clear, and reproducible bug bounty reports, detailing vulnerability impact, steps to reproduce, and exploit details for triagers.
|
||||
225. **write_latex**: Generates syntactically correct LaTeX code for a new.tex document, ensuring proper formatting and compatibility with pdflatex.
|
||||
226. **write_micro_essay**: Writes concise, clear, and illuminating essays on the given topic in the style of Paul Graham.
|
||||
227. **write_nuclei_template_rule**: Generates Nuclei YAML templates for detecting vulnerabilities using HTTP requests, matchers, extractors, and dynamic data extraction.
|
||||
228. **write_pull-request**: Drafts detailed pull request descriptions, explaining changes, providing reasoning, and identifying potential bugs from the git diff command output.
|
||||
229. **write_semgrep_rule**: Creates accurate and working Semgrep rules based on input, following syntax guidelines and specific language considerations.
|
||||
230. **youtube_summary**: Create concise, timestamped Youtube video summaries that highlight key points.
|
||||
|
||||
@@ -73,11 +73,11 @@ Match the request to one or more of these primary categories:
|
||||
|
||||
**AI**: ai, create_ai_jobs_analysis, create_art_prompt, create_pattern, create_prediction_block, extract_mcp_servers, extract_wisdom_agents, generate_code_rules, improve_prompt, judge_output, rate_ai_response, rate_ai_result, raw_query, suggest_pattern, summarize_prompt
|
||||
|
||||
**ANALYSIS**: ai, analyze_answers, analyze_bill, analyze_bill_short, analyze_candidates, analyze_cfp_submission, analyze_claims, analyze_comments, analyze_debate, analyze_email_headers, analyze_incident, analyze_interviewer_techniques, analyze_logs, analyze_malware, analyze_military_strategy, analyze_mistakes, analyze_paper, analyze_paper_simple, analyze_patent, analyze_personality, analyze_presentation, analyze_product_feedback, analyze_proposition, analyze_prose, analyze_prose_json, analyze_prose_pinker, analyze_risk, analyze_sales_call, analyze_spiritual_text, analyze_tech_impact, analyze_terraform_plan, analyze_threat_report, analyze_threat_report_cmds, analyze_threat_report_trends, apply_ul_tags, check_agreement, compare_and_contrast, create_ai_jobs_analysis, create_idea_compass, create_investigation_visualization, create_prediction_block, create_recursive_outline, create_story_about_people_interaction, create_tags, dialog_with_socrates, extract_main_idea, extract_predictions, find_hidden_message, find_logical_fallacies, get_wow_per_minute, identify_dsrp_distinctions, identify_dsrp_perspectives, identify_dsrp_relationships, identify_dsrp_systems, identify_job_stories, label_and_rate, model_as_sherlock_freud, predict_person_actions, prepare_7s_strategy, provide_guidance, rate_content, rate_value, recommend_artists, recommend_talkpanel_topics, review_design, summarize_board_meeting, t_analyze_challenge_handling, t_check_dunning_kruger, t_check_metrics, t_describe_life_outlook, t_extract_intro_sentences, t_extract_panel_topics, t_find_blindspots, t_find_negative_thinking, t_red_team_thinking, t_threat_model_plans, t_year_in_review, write_hackerone_report
|
||||
**ANALYSIS**: ai, analyze_answers, analyze_bill, analyze_bill_short, analyze_candidates, analyze_cfp_submission, analyze_claims, analyze_comments, analyze_debate, analyze_email_headers, analyze_incident, analyze_interviewer_techniques, analyze_logs, analyze_malware, analyze_military_strategy, analyze_mistakes, analyze_paper, analyze_paper_simple, analyze_patent, analyze_personality, analyze_presentation, analyze_product_feedback, analyze_proposition, analyze_prose, analyze_prose_json, analyze_prose_pinker, analyze_risk, analyze_sales_call, analyze_spiritual_text, analyze_tech_impact, analyze_terraform_plan, analyze_threat_report, analyze_threat_report_cmds, analyze_threat_report_trends, apply_ul_tags, check_agreement, compare_and_contrast, concall_summary, create_ai_jobs_analysis, create_idea_compass, create_investigation_visualization, create_prediction_block, create_recursive_outline, create_story_about_people_interaction, create_tags, dialog_with_socrates, extract_main_idea, extract_predictions, find_hidden_message, find_logical_fallacies, get_wow_per_minute, identify_dsrp_distinctions, identify_dsrp_perspectives, identify_dsrp_relationships, identify_dsrp_systems, identify_job_stories, label_and_rate, model_as_sherlock_freud, predict_person_actions, prepare_7s_strategy, provide_guidance, rate_content, rate_value, recommend_artists, recommend_talkpanel_topics, review_design, summarize_board_meeting, t_analyze_challenge_handling, t_check_dunning_kruger, t_check_metrics, t_describe_life_outlook, t_extract_intro_sentences, t_extract_panel_topics, t_find_blindspots, t_find_negative_thinking, t_red_team_thinking, t_threat_model_plans, t_year_in_review, write_hackerone_report
|
||||
|
||||
**BILL**: analyze_bill, analyze_bill_short
|
||||
|
||||
**BUSINESS**: check_agreement, create_ai_jobs_analysis, create_formal_email, create_hormozi_offer, create_loe_document, create_logo, create_newsletter_entry, create_prd, explain_project, extract_business_ideas, extract_characters, extract_product_features, extract_skills, extract_sponsors, identify_job_stories, prepare_7s_strategy, rate_value, t_check_metrics, t_create_h3_career, t_visualize_mission_goals_projects, t_year_in_review, transcribe_minutes
|
||||
**BUSINESS**: check_agreement, concall_summary, create_ai_jobs_analysis, create_formal_email, create_hormozi_offer, create_loe_document, create_logo, create_newsletter_entry, create_prd, explain_project, extract_business_ideas, extract_characters, extract_product_features, extract_skills, extract_sponsors, identify_job_stories, prepare_7s_strategy, rate_value, t_check_metrics, t_create_h3_career, t_visualize_mission_goals_projects, t_year_in_review, transcribe_minutes
|
||||
|
||||
**CLASSIFICATION**: apply_ul_tags
|
||||
|
||||
@@ -109,7 +109,7 @@ Match the request to one or more of these primary categories:
|
||||
|
||||
**STRATEGY**: analyze_military_strategy, create_better_frame, prepare_7s_strategy, t_analyze_challenge_handling, t_find_blindspots, t_find_negative_thinking, t_find_neglected_goals, t_red_team_thinking, t_threat_model_plans, t_visualize_mission_goals_projects
|
||||
|
||||
**SUMMARIZE**: capture_thinkers_work, create_5_sentence_summary, create_micro_summary, create_newsletter_entry, create_show_intro, create_summary, extract_core_message, extract_latest_video, extract_main_idea, summarize, summarize_board_meeting, summarize_debate, summarize_git_changes, summarize_git_diff, summarize_lecture, summarize_legislation, summarize_meeting, summarize_micro, summarize_newsletter, summarize_paper, summarize_pull-requests, summarize_rpg_session, youtube_summary
|
||||
**SUMMARIZE**: capture_thinkers_work, concall_summary, create_5_sentence_summary, create_micro_summary, create_newsletter_entry, create_show_intro, create_summary, extract_core_message, extract_latest_video, extract_main_idea, summarize, summarize_board_meeting, summarize_debate, summarize_git_changes, summarize_git_diff, summarize_lecture, summarize_legislation, summarize_meeting, summarize_micro, summarize_newsletter, summarize_paper, summarize_pull-requests, summarize_rpg_session, youtube_summary
|
||||
|
||||
**VISUALIZE**: create_conceptmap, create_excalidraw_visualization, create_graph_from_input, create_idea_compass, create_investigation_visualization, create_keynote, create_logo, create_markmap_visualization, create_mermaid_visualization, create_mermaid_visualization_for_github, create_video_chapters, create_visualization, enrich_blog_post, t_visualize_mission_goals_projects
|
||||
|
||||
|
||||
@@ -196,6 +196,10 @@ Review contract to identify stipulations, issues, and changes for negotiation.
|
||||
|
||||
Create comparisons table, highlighting key differences and similarities.
|
||||
|
||||
### concall_summary
|
||||
|
||||
Analyze earnings call transcripts to extract management insights, financial metrics, and investment implications.
|
||||
|
||||
### create_ai_jobs_analysis
|
||||
|
||||
Identify automation risks and career resilience strategies.
|
||||
|
||||
@@ -51,6 +51,29 @@ docs: update installation instructions
|
||||
|
||||
## Pull Request Process
|
||||
|
||||
### Pull Request Guidelines
|
||||
|
||||
**Keep pull requests focused and minimal.**
|
||||
|
||||
PRs that touch a large number of files (50+) without clear functional justification will likely be rejected without detailed review.
|
||||
|
||||
#### Why we enforce this
|
||||
|
||||
- **Reviewability**: Large PRs are effectively un-reviewable. Studies show reviewer effectiveness drops significantly after ~200-400 lines of code. A 93-file "cleanup" PR cannot receive meaningful review.
|
||||
- **Git history**: Sweeping changes pollute `git blame`, making it harder to trace when and why functional changes were made.
|
||||
- **Merge conflicts**: Large PRs increase the likelihood of conflicts with other contributors' work.
|
||||
- **Risk**: More changed lines means more opportunities for subtle bugs, even in "safe" refactors.
|
||||
|
||||
#### What to do instead
|
||||
|
||||
If you have a large change in mind, break it into logical, independently-mergeable slices. For example:
|
||||
|
||||
- ✅ "Replace `interface{}` with `any` across codebase" (single mechanical change, easy to verify)
|
||||
- ✅ "Migrate to `strings.CutPrefix` in `internal/cli`" (scoped to one package)
|
||||
- ❌ "Modernize codebase with multiple idiom updates" (too broad, impossible to review)
|
||||
|
||||
For sweeping refactors or style changes, **open an issue first** to discuss the approach with maintainers before investing time in the work.
|
||||
|
||||
### Changelog Generation (REQUIRED)
|
||||
|
||||
After opening your PR, generate a changelog entry:
|
||||
|
||||
700
docs/GitHub-Models-Setup.md
Normal file
700
docs/GitHub-Models-Setup.md
Normal file
@@ -0,0 +1,700 @@
|
||||
# GitHub Models Setup Guide for Fabric
|
||||
|
||||
This guide will walk you through setting up and using GitHub Models with Fabric CLI. GitHub Models provides free access to multiple AI models from OpenAI, Meta, Microsoft, DeepSeek, xAI, and other providers using only your GitHub credentials.
|
||||
|
||||
## Table of Contents
|
||||
|
||||
- [What are GitHub Models?](#what-are-github-models)
|
||||
- [Getting Your GitHub Models API Key](#getting-your-github-models-api-key)
|
||||
- [Configuring Fabric for GitHub Models](#configuring-fabric-for-github-models)
|
||||
- [Testing Your Setup](#testing-your-setup)
|
||||
- [Available Models](#available-models)
|
||||
- [Rate Limits & Free Tier](#rate-limits--free-tier)
|
||||
- [Troubleshooting](#troubleshooting)
|
||||
- [Advanced Usage](#advanced-usage)
|
||||
|
||||
---
|
||||
|
||||
## What are GitHub Models?
|
||||
|
||||
**GitHub Models** is a free AI inference API platform that allows you to access multiple AI models using only your GitHub account. It's powered by Azure AI infrastructure and provides:
|
||||
|
||||
- **Unified Access**: Single API endpoint for models from multiple providers
|
||||
- **No Extra API Keys**: Uses GitHub Personal Access Tokens (no separate OpenAI, Anthropic, etc. keys needed)
|
||||
- **Free Tier**: Rate-limited free access perfect for prototyping and personal projects
|
||||
- **Web Playground**: Test models directly at [github.com/marketplace/models](https://github.com/marketplace/models)
|
||||
- **Compatible Format**: Works with OpenAI SDK standards
|
||||
|
||||
### Why Use GitHub Models with Fabric?
|
||||
|
||||
- **No Cost for Testing**: Free tier allows 50-150 requests/day depending on model
|
||||
- **Multiple Providers**: Access OpenAI, Meta Llama, Microsoft Phi, DeepSeek, and more
|
||||
- **Easy Setup**: Just one GitHub token instead of managing multiple API keys
|
||||
- **Great for Learning**: Experiment with different models without financial commitment
|
||||
|
||||
---
|
||||
|
||||
## Getting Your GitHub Models API Key
|
||||
|
||||
GitHub Models uses **Personal Access Tokens (PAT)** instead of separate API keys.
|
||||
|
||||
### Step-by-Step Instructions
|
||||
|
||||
1. **Sign in to GitHub** at [github.com](https://github.com)
|
||||
|
||||
2. **Navigate to Token Settings:**
|
||||
- Click your profile picture (upper-right corner)
|
||||
- Click **Settings**
|
||||
- Scroll down the left sidebar to **Developer settings** (at the bottom)
|
||||
- Click **Personal access tokens** → **Fine-grained tokens** (recommended)
|
||||
|
||||
3. **Generate New Token:**
|
||||
- Click **Generate new token**
|
||||
- Give it a descriptive name: `Fabric CLI - GitHub Models`
|
||||
- Set expiration (recommended: 90 days or custom)
|
||||
- **Repository access**: Select "Public Repositories (read-only)" or "All repositories" (your choice)
|
||||
- **Permissions**:
|
||||
- Scroll down to **Account permissions**
|
||||
- Find **AI Models** and set to **Read-only** ✓
|
||||
- This grants the `models:read` scope
|
||||
- Click **Generate token** at the bottom
|
||||
|
||||
4. **Save Your Token:**
|
||||
- **IMPORTANT**: Copy the token immediately (starts with `github_pat_` or `ghp_`)
|
||||
- You won't be able to see it again!
|
||||
- Store it securely - this will be your `GITHUB_TOKEN`
|
||||
|
||||
### Security Best Practices
|
||||
|
||||
- ✅ Use fine-grained tokens with minimal permissions
|
||||
- ✅ Set an expiration date (rotate tokens regularly)
|
||||
- ✅ Never commit tokens to Git repositories
|
||||
- ✅ Store in environment variables or secure credential managers
|
||||
- ❌ Don't share tokens in chat, email, or screenshots
|
||||
|
||||
---
|
||||
|
||||
## Configuring Fabric for GitHub Models
|
||||
|
||||
### Method 1: Using Fabric Setup (Recommended)
|
||||
|
||||
This is the easiest and safest method:
|
||||
|
||||
1. **Run Fabric Setup:**
|
||||
|
||||
```bash
|
||||
fabric --setup
|
||||
```
|
||||
|
||||
2. **Select GitHub from the Menu:**
|
||||
- You'll see a numbered list of AI vendors
|
||||
- Find `[8] GitHub (configured)` or similar
|
||||
- Enter the number (e.g., `8`) and press Enter
|
||||
|
||||
3. **Enter Your GitHub Token:**
|
||||
- When prompted for "API Key", paste your GitHub Personal Access Token
|
||||
- The token you created earlier (starts with `github_pat_` or `ghp_`)
|
||||
- Press Enter
|
||||
|
||||
4. **Verify Base URL (Optional):**
|
||||
- You'll be asked for "API Base URL"
|
||||
- Press Enter to use the default: `https://models.github.ai/inference`
|
||||
- Or customize if needed (advanced use only)
|
||||
|
||||
5. **Save and Exit:**
|
||||
- The setup wizard will save your configuration
|
||||
- You should see "GitHub (configured)" next time
|
||||
|
||||
### Method 2: Manual Configuration (Advanced)
|
||||
|
||||
If you prefer to manually edit the configuration file:
|
||||
|
||||
1. **Edit Environment File:**
|
||||
|
||||
```bash
|
||||
nano ~/.config/fabric/.env
|
||||
```
|
||||
|
||||
2. **Add GitHub Configuration:**
|
||||
|
||||
```bash
|
||||
# GitHub Models API Key (your Personal Access Token)
|
||||
GITHUB_API_KEY=github_pat_YOUR_TOKEN_HERE
|
||||
|
||||
# GitHub Models API Base URL (default, usually don't need to change)
|
||||
GITHUB_API_BASE_URL=https://models.github.ai/inference
|
||||
```
|
||||
|
||||
Save and exit (Ctrl+X, then Y, then Enter)
|
||||
|
||||
**Note**: The environment variable is `GITHUB_API_KEY`, not `GITHUB_TOKEN`.
|
||||
|
||||
### Verify Configuration
|
||||
|
||||
Check that your configuration is properly set:
|
||||
|
||||
```bash
|
||||
grep GITHUB_API_KEY ~/.config/fabric/.env
|
||||
```
|
||||
|
||||
You should see:
|
||||
|
||||
```text
|
||||
GITHUB_API_KEY=github_pat_...
|
||||
```
|
||||
|
||||
Or run setup again to verify:
|
||||
|
||||
```bash
|
||||
fabric --setup
|
||||
```
|
||||
|
||||
Look for `[8] GitHub (configured)` in the list.
|
||||
|
||||
---
|
||||
|
||||
## Testing Your Setup
|
||||
|
||||
### 1. List Available Models
|
||||
|
||||
Verify that Fabric can connect to GitHub Models and fetch the model list:
|
||||
|
||||
```bash
|
||||
fabric --listmodels | grep GitHub
|
||||
```
|
||||
|
||||
**Expected Output:**
|
||||
|
||||
```text
|
||||
Available models:
|
||||
...
|
||||
$ fabric -L | grep GitHub
|
||||
[65] GitHub|ai21-labs/ai21-jamba-1.5-large
|
||||
[66] GitHub|cohere/cohere-command-a
|
||||
[67] GitHub|cohere/cohere-command-r-08-2024
|
||||
[68] GitHub|cohere/cohere-command-r-plus-08-2024
|
||||
[69] GitHub|deepseek/deepseek-r1
|
||||
[70] GitHub|deepseek/deepseek-r1-0528
|
||||
[71] GitHub|deepseek/deepseek-v3-0324
|
||||
[72] GitHub|meta/llama-3.2-11b-vision-instruct
|
||||
[73] GitHub|meta/llama-3.2-90b-vision-instruct
|
||||
... (and more)
|
||||
```
|
||||
|
||||
### 2. Simple Chat Test
|
||||
|
||||
Test a basic chat completion with a small, fast model:
|
||||
|
||||
```bash
|
||||
# Use gpt-4o-mini (fast and has generous rate limits)
|
||||
fabric --vendor GitHub -m openai/gpt-4o-mini 'Why is th
|
||||
e sky blue?'
|
||||
```
|
||||
|
||||
**Expected**: You should see a response explaining Rayleigh scattering.
|
||||
|
||||
**Tip**: Model names from `--listmodels` can be used directly (e.g., `openai/gpt-4o-mini`, `openai/gpt-4o`, `meta/llama-4-maverick-17b-128e-instruct-fp8`).
|
||||
|
||||
### 3. Test with a Pattern
|
||||
|
||||
Use one of Fabric's built-in patterns:
|
||||
|
||||
```bash
|
||||
echo "Artificial intelligence is transforming how we work and live." | \
|
||||
fabric --pattern summarize --vendor GitHub --model "openai/gpt-4o-mini"
|
||||
```
|
||||
|
||||
### 4. Test Streaming
|
||||
|
||||
Verify streaming responses work:
|
||||
|
||||
```bash
|
||||
echo "Count from 1 to 100" | \
|
||||
fabric --vendor GitHub --model "openai/gpt-4o-mini" --stream
|
||||
```
|
||||
|
||||
You should see the response appear progressively, word by word.
|
||||
|
||||
### 5. Test with Different Models
|
||||
|
||||
Try a Meta Llama model:
|
||||
|
||||
```bash
|
||||
# Use a Llama model
|
||||
echo "Explain quantum computing" | \
|
||||
fabric --vendor GitHub --model "meta/Meta-Llama-3.1-8B-Instruct"
|
||||
```
|
||||
|
||||
### Quick Validation Checklist
|
||||
|
||||
- [x] `--listmodels` shows GitHub models
|
||||
- [x] Basic chat completion works
|
||||
- [x] Patterns work with GitHub vendor
|
||||
- [x] Streaming responses work
|
||||
- [x] Can switch between different models
|
||||
|
||||
---
|
||||
|
||||
## Available Models
|
||||
|
||||
GitHub Models provides access to models from multiple providers. Models use the format: `{publisher}/{model-name}`
|
||||
|
||||
### OpenAI Models
|
||||
|
||||
| Model ID | Description | Tier | Best For |
|
||||
|----------|-------------|------|----------|
|
||||
| `openai/gpt-4.1` | Latest flagship GPT-4 | High | Complex tasks, reasoning |
|
||||
| `openai/gpt-4o` | Optimized GPT-4 | High | General purpose, fast |
|
||||
| `openai/gpt-4o-mini` | Compact, cost-effective | Low | Quick tasks, high volume |
|
||||
| `openai/o1` | Advanced reasoning | High | Complex problem solving |
|
||||
| `openai/o3` | Next-gen reasoning | High | Cutting-edge reasoning |
|
||||
|
||||
### Meta Llama Models
|
||||
|
||||
| Model ID | Description | Tier | Best For |
|
||||
|----------|-------------|------|----------|
|
||||
| `meta/llama-3.1-405b` | Largest Llama model | High | Complex tasks, accuracy |
|
||||
| `meta/llama-3.1-70b` | Mid-size Llama | Low | Balanced performance |
|
||||
| `meta/llama-3.1-8b` | Compact Llama | Low | Fast, efficient tasks |
|
||||
|
||||
### Microsoft Phi Models
|
||||
|
||||
| Model ID | Description | Tier | Best For |
|
||||
|----------|-------------|------|----------|
|
||||
| `microsoft/phi-4` | Latest Phi generation | Low | Efficient reasoning |
|
||||
| `microsoft/phi-3-medium` | Mid-size variant | Low | General tasks |
|
||||
| `microsoft/phi-3-mini` | Smallest Phi | Low | Quick, simple tasks |
|
||||
|
||||
### DeepSeek Models
|
||||
|
||||
| Model ID | Description | Tier | Special |
|
||||
|----------|-------------|------|---------|
|
||||
| `deepseek/deepseek-r1` | Reasoning model | Very Limited | 8 requests/day |
|
||||
| `deepseek/deepseek-r1-0528` | Updated version | Very Limited | 8 requests/day |
|
||||
|
||||
### xAI Models
|
||||
|
||||
| Model ID | Description | Tier | Special |
|
||||
|----------|-------------|------|---------|
|
||||
| `xai/grok-3` | Latest Grok | Very Limited | 15 requests/day |
|
||||
| `xai/grok-3-mini` | Smaller Grok | Very Limited | 15 requests/day |
|
||||
|
||||
### Getting the Full List
|
||||
|
||||
To see all currently available models:
|
||||
|
||||
```bash
|
||||
fabric --listmodels | grep GitHub
|
||||
```
|
||||
|
||||
Or for a formatted list with details, you can query the GitHub Models API directly:
|
||||
|
||||
```bash
|
||||
curl -H "Authorization: Bearer $GITHUB_TOKEN" \
|
||||
-H "X-GitHub-Api-Version: 2022-11-28" \
|
||||
https://models.github.ai/catalog/models | jq '.[] | {id, publisher, tier: .rate_limit_tier}'
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Rate Limits & Free Tier
|
||||
|
||||
GitHub Models has tiered rate limits based on model complexity. Understanding these helps you use the free tier effectively.
|
||||
|
||||
### Low Tier Models (Recommended for High Volume)
|
||||
|
||||
**Models**: `gpt-4o-mini`, `llama-3.1-*`, `phi-*`
|
||||
|
||||
- **Requests per minute**: 15
|
||||
- **Requests per day**: 150
|
||||
- **Tokens per request**: 8,000 input / 4,000 output
|
||||
- **Concurrent requests**: 5
|
||||
|
||||
**Best practices**: Use these for most Fabric patterns and daily tasks.
|
||||
|
||||
### High Tier Models (Use Sparingly)
|
||||
|
||||
**Models**: `gpt-4.1`, `gpt-4o`, `o1`, `o3`, `llama-3.1-405b`
|
||||
|
||||
- **Requests per minute**: 10
|
||||
- **Requests per day**: 50
|
||||
- **Tokens per request**: 8,000 input / 4,000 output
|
||||
- **Concurrent requests**: 2
|
||||
|
||||
**Best practices**: Save for complex tasks, important queries, or when you need maximum quality.
|
||||
|
||||
### Very Limited Models
|
||||
|
||||
**Models**: `deepseek-r1`, `grok-3`
|
||||
|
||||
- **Requests per minute**: 1
|
||||
- **Requests per day**: 8-15 (varies by model)
|
||||
- **Tokens per request**: 4,000 input / 4,000 output
|
||||
- **Concurrent requests**: 1
|
||||
|
||||
**Best practices**: Use only for special experiments or when you specifically need these models.
|
||||
|
||||
### Rate Limit Reset Times
|
||||
|
||||
- **Per-minute limits**: Reset every 60 seconds
|
||||
- **Daily limits**: Reset at midnight UTC
|
||||
- **Per-user**: Limits are tied to your GitHub account, not the token
|
||||
|
||||
### Enhanced Limits with GitHub Copilot
|
||||
|
||||
If you have a GitHub Copilot subscription, you get higher limits:
|
||||
|
||||
- **Copilot Business**: 2× daily request limits
|
||||
- **Copilot Enterprise**: 3× daily limits + higher token limits
|
||||
|
||||
### What Happens When You Hit Limits?
|
||||
|
||||
You'll receive an HTTP 429 error with a message like:
|
||||
|
||||
```text
|
||||
Rate limit exceeded. Try again in X seconds.
|
||||
```
|
||||
|
||||
Fabric will display this error. Wait for the reset time and try again.
|
||||
|
||||
### Tips for Staying Within Limits
|
||||
|
||||
1. **Use low-tier models** for most tasks (`gpt-4o-mini`, `llama-3.1-8b`)
|
||||
2. **Batch your requests** - process multiple items together when possible
|
||||
3. **Cache results** - save responses for repeated queries
|
||||
4. **Monitor usage** - keep track of daily request counts
|
||||
5. **Set per-pattern models** - configure specific models for specific patterns (see Advanced Usage)
|
||||
|
||||
---
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Error: "Authentication failed" or "Unauthorized"
|
||||
|
||||
**Cause**: Invalid or missing GitHub token
|
||||
|
||||
**Solutions**:
|
||||
|
||||
1. Verify token is in `.env` file:
|
||||
|
||||
```bash
|
||||
grep GITHUB_API_KEY ~/.config/fabric/.env
|
||||
```
|
||||
|
||||
2. Check token has `models:read` permission:
|
||||
- Go to GitHub Settings → Developer settings → Personal access tokens
|
||||
- Click on your token
|
||||
- Verify "AI Models: Read-only" is checked
|
||||
|
||||
3. Re-run setup to reconfigure:
|
||||
|
||||
```bash
|
||||
fabric --setup
|
||||
# Select GitHub (number 8 or similar)
|
||||
# Enter your token again
|
||||
```
|
||||
|
||||
4. Generate a new token if needed (tokens expire)
|
||||
|
||||
### Error: "Rate limit exceeded"
|
||||
|
||||
**Cause**: Too many requests in a short time period
|
||||
|
||||
**Solutions**:
|
||||
|
||||
1. Check which tier your model is in (see [Rate Limits](#rate-limits--free-tier))
|
||||
2. Wait for the reset (check error message for wait time)
|
||||
3. Switch to a lower-tier model:
|
||||
|
||||
```bash
|
||||
# Instead of gpt-4.1 (high tier)
|
||||
fabric --vendor GitHub --model openai/gpt-4.1 ...
|
||||
|
||||
# Use gpt-4o-mini (low tier)
|
||||
fabric --vendor GitHub --model openai/gpt-4o-mini ...
|
||||
```
|
||||
|
||||
### Error: "Model not found" or "Invalid model"
|
||||
|
||||
**Cause**: Model name format incorrect or model not available
|
||||
|
||||
**Solutions**:
|
||||
|
||||
1. Use correct format: `{publisher}/{model-name}`, e.g., `openai/gpt-4o-mini`
|
||||
|
||||
```bash
|
||||
# ❌ Wrong
|
||||
fabric --vendor GitHub --model gpt-4o-mini
|
||||
|
||||
# ✅ Correct
|
||||
fabric --vendor GitHub --model openai/gpt-4o-mini
|
||||
```
|
||||
|
||||
2. List available models to verify name:
|
||||
|
||||
```bash
|
||||
fabric --listmodels --vendor GitHub | grep -i "gpt-4"
|
||||
```
|
||||
|
||||
### Error: "Cannot list models" or Empty model list
|
||||
|
||||
**Cause**: API endpoint issue or authentication problem
|
||||
|
||||
**Solutions**:
|
||||
|
||||
1. Test direct API access:
|
||||
|
||||
```bash
|
||||
curl -H "Authorization: Bearer $GITHUB_TOKEN" \
|
||||
-H "X-GitHub-Api-Version: 2022-11-28" \
|
||||
https://models.github.ai/catalog/models
|
||||
```
|
||||
|
||||
2. If curl works but Fabric doesn't, rebuild Fabric:
|
||||
|
||||
```bash
|
||||
cd /path/to/fabric
|
||||
go build ./cmd/fabric
|
||||
```
|
||||
|
||||
3. Check for network/firewall issues blocking `models.github.ai`
|
||||
|
||||
### Error: "Response format not supported"
|
||||
|
||||
**Cause**: This should be fixed in the latest version with direct fetch fallback
|
||||
|
||||
**Solutions**:
|
||||
|
||||
1. Update to the latest Fabric version with PR #1839 merged
|
||||
2. Verify you're on a version that includes the `FetchModelsDirectly` fallback
|
||||
|
||||
### Models are slow to respond
|
||||
|
||||
**Cause**: High tier models have limited concurrency, or GitHub Models API congestion
|
||||
|
||||
**Solutions**:
|
||||
|
||||
1. Switch to faster models:
|
||||
- `openai/gpt-4o-mini` instead of `gpt-4.1`
|
||||
- `meta/llama-3.1-8b` instead of `llama-3.1-405b`
|
||||
|
||||
2. Check your internet connection
|
||||
|
||||
3. Try again later (API may be experiencing high traffic)
|
||||
|
||||
### Token expires or becomes invalid
|
||||
|
||||
**Cause**: Tokens have expiration dates or can be revoked
|
||||
|
||||
**Solutions**:
|
||||
|
||||
1. Generate a new token (see [Getting Your GitHub Models API Key](#getting-your-github-models-api-key))
|
||||
2. Update `.env` file with new token
|
||||
3. Set longer expiration when creating tokens (e.g., 90 days)
|
||||
|
||||
---
|
||||
|
||||
## Advanced Usage
|
||||
|
||||
### Using Specific Models with Patterns
|
||||
|
||||
You can specify which model to use with any pattern:
|
||||
|
||||
```bash
|
||||
# Use GPT-4.1 with the analyze_claims pattern
|
||||
cat article.txt | fabric --pattern analyze_claims \
|
||||
--vendor GitHub --model openai/gpt-4.1
|
||||
|
||||
# Use Llama for summarization
|
||||
cat document.txt | fabric --pattern summarize \
|
||||
--vendor GitHub --model meta/llama-3.1-70b
|
||||
```
|
||||
|
||||
### Per-Pattern Model Mapping
|
||||
|
||||
Set default models for specific patterns using environment variables:
|
||||
|
||||
Edit `~/.config/fabric/.env`:
|
||||
|
||||
```bash
|
||||
# Use GPT-4.1 for complex analysis
|
||||
FABRIC_MODEL_analyze_claims=GitHub|openai/gpt-4.1
|
||||
FABRIC_MODEL_extract_wisdom=GitHub|openai/gpt-4.1
|
||||
|
||||
# Use GPT-4o-mini for simple tasks
|
||||
FABRIC_MODEL_summarize=GitHub|openai/gpt-4o-mini
|
||||
FABRIC_MODEL_extract_article_wisdom=GitHub|openai/gpt-4o-mini
|
||||
|
||||
# Use Llama for code tasks
|
||||
FABRIC_MODEL_explain_code=GitHub|meta/llama-3.1-70b
|
||||
```
|
||||
|
||||
Now when you run:
|
||||
|
||||
```bash
|
||||
cat article.txt | fabric --pattern analyze_claims
|
||||
```
|
||||
|
||||
It will automatically use `GitHub|openai/gpt-4.1` without needing to specify the vendor and model.
|
||||
|
||||
### Comparing Responses Across Providers
|
||||
|
||||
Compare how different models respond to the same input:
|
||||
|
||||
```bash
|
||||
# OpenAI GPT-4o-mini
|
||||
echo "Explain quantum computing" | \
|
||||
fabric --vendor GitHub --model openai/gpt-4o-mini > response_openai.txt
|
||||
|
||||
# Meta Llama
|
||||
echo "Explain quantum computing" | \
|
||||
fabric --vendor GitHub --model meta/llama-3.1-70b > response_llama.txt
|
||||
|
||||
# Microsoft Phi
|
||||
echo "Explain quantum computing" | \
|
||||
fabric --vendor GitHub --model microsoft/phi-4 > response_phi.txt
|
||||
|
||||
# Compare
|
||||
diff response_openai.txt response_llama.txt
|
||||
```
|
||||
|
||||
### Testing Different Models for a Pattern
|
||||
|
||||
Find the best model for your use case:
|
||||
|
||||
```bash
|
||||
# Create a test script
|
||||
cat > test_models.sh << 'EOF'
|
||||
#!/bin/bash
|
||||
|
||||
INPUT="Explain the concept of recursion in programming"
|
||||
PATTERN="explain_code"
|
||||
|
||||
for MODEL in "openai/gpt-4o-mini" "meta/llama-3.1-8b" "microsoft/phi-4"; do
|
||||
echo "=== Testing $MODEL ==="
|
||||
echo "$INPUT" | fabric --pattern "$PATTERN" --vendor GitHub --model "$MODEL"
|
||||
echo ""
|
||||
done
|
||||
EOF
|
||||
|
||||
chmod +x test_models.sh
|
||||
./test_models.sh
|
||||
```
|
||||
|
||||
### Quick Test Without Setup
|
||||
|
||||
If you want to quickly test without running full setup, you can set the environment variable directly:
|
||||
|
||||
```bash
|
||||
# Temporary test (this session only)
|
||||
export GITHUB_API_KEY=github_pat_YOUR_TOKEN_HERE
|
||||
|
||||
# Test immediately
|
||||
fabric --listmodels --vendor GitHub
|
||||
```
|
||||
|
||||
This is useful for quick tests, but we recommend using `fabric --setup` for permanent configuration.
|
||||
|
||||
### Streaming for Long Responses
|
||||
|
||||
For long-form content, use streaming to see results as they generate:
|
||||
|
||||
```bash
|
||||
cat long_article.txt | \
|
||||
fabric --pattern summarize \
|
||||
--vendor GitHub --model openai/gpt-4o-mini \
|
||||
--stream
|
||||
```
|
||||
|
||||
### Saving Token Usage
|
||||
|
||||
Monitor your usage to stay within rate limits:
|
||||
|
||||
```bash
|
||||
# Create a simple usage tracker
|
||||
echo "$(date): Used gpt-4.1 for analyze_claims" >> ~/.config/fabric/usage.log
|
||||
|
||||
# Check daily usage
|
||||
grep "$(date +%Y-%m-%d)" ~/.config/fabric/usage.log | wc -l
|
||||
```
|
||||
|
||||
### Environment-Based Configuration
|
||||
|
||||
Create different profiles for different use cases:
|
||||
|
||||
```bash
|
||||
# Development profile (uses free GitHub Models)
|
||||
cat > ~/.config/fabric/.env.dev << EOF
|
||||
GITHUB_TOKEN=github_pat_dev_token_here
|
||||
DEFAULT_VENDOR=GitHub
|
||||
DEFAULT_MODEL=openai/gpt-4o-mini
|
||||
EOF
|
||||
|
||||
# Production profile (uses paid OpenAI)
|
||||
cat > ~/.config/fabric/.env.prod << EOF
|
||||
OPENAI_API_KEY=sk-prod-key-here
|
||||
DEFAULT_VENDOR=OpenAI
|
||||
DEFAULT_MODEL=gpt-4
|
||||
EOF
|
||||
|
||||
# Switch profiles
|
||||
ln -sf ~/.config/fabric/.env.dev ~/.config/fabric/.env
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Additional Resources
|
||||
|
||||
### Official Documentation
|
||||
|
||||
- [GitHub Models Quickstart](https://docs.github.com/en/github-models/quickstart)
|
||||
- [GitHub Models API Reference](https://docs.github.com/en/rest/models)
|
||||
- [GitHub Models Marketplace](https://github.com/marketplace/models)
|
||||
|
||||
### Fabric Documentation
|
||||
|
||||
- [Fabric README](../README.md)
|
||||
- [Contexts and Sessions Tutorial](./contexts-and-sessions-tutorial.md)
|
||||
- [Using Speech-to-Text](./Using-Speech-To-Text.md)
|
||||
|
||||
### Community
|
||||
|
||||
- [Fabric GitHub Repository](https://github.com/danielmiessler/fabric)
|
||||
- [Fabric Issues](https://github.com/danielmiessler/fabric/issues)
|
||||
- [Fabric Discussions](https://github.com/danielmiessler/fabric/discussions)
|
||||
|
||||
---
|
||||
|
||||
## Summary
|
||||
|
||||
GitHub Models provides an excellent way to experiment with AI models through Fabric without managing multiple API keys or incurring costs. Key points:
|
||||
|
||||
✅ **Free to start**: No credit card required, 50-150 requests/day
|
||||
✅ **Multiple providers**: OpenAI, Meta, Microsoft, DeepSeek, xAI
|
||||
✅ **Simple setup**: Just one GitHub token via `fabric --setup`
|
||||
✅ **Great for learning**: Try different models and patterns
|
||||
✅ **Production path**: Can upgrade to paid tier when ready
|
||||
|
||||
### Quick Start Commands
|
||||
|
||||
```bash
|
||||
# 1. Get GitHub token with models:read scope from:
|
||||
# https://github.com/settings/tokens
|
||||
|
||||
# 2. Configure Fabric
|
||||
fabric --setup
|
||||
# Select [8] GitHub
|
||||
# Paste your token when prompted
|
||||
|
||||
# 3. List available models
|
||||
fabric --listmodels --vendor GitHub | grep gpt-4o
|
||||
|
||||
# 4. Try it out with gpt-4o-mini
|
||||
echo "What is AI?" | fabric --vendor GitHub --model "gpt-4o-mini"
|
||||
```
|
||||
|
||||
**Recommended starting point**: Use `gpt-4o-mini` for most patterns - it's fast, capable, and has generous rate limits (150 requests/day).
|
||||
|
||||
**Available Models**: `gpt-4o`, `gpt-4o-mini`, `Meta-Llama-3.1-8B-Instruct`, `Meta-Llama-3.1-70B-Instruct`, `Mistral-large-2407`, and more. Use `--listmodels` to see the complete list.
|
||||
|
||||
Happy prompting! 🚀
|
||||
24
flake.lock
generated
24
flake.lock
generated
@@ -5,11 +5,11 @@
|
||||
"systems": "systems"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1694529238,
|
||||
"narHash": "sha256-zsNZZGTGnMOf9YpHKJqMSsa0dXbfmxeoJ7xHlrt+xmY=",
|
||||
"lastModified": 1731533236,
|
||||
"narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"rev": "ff7b65b44d01cf9ba6a71320833626af21126384",
|
||||
"rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -26,11 +26,11 @@
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1742209644,
|
||||
"narHash": "sha256-jMy1XqXqD0/tJprEbUmKilTkvbDY/C0ZGSsJJH4TNCE=",
|
||||
"lastModified": 1763982521,
|
||||
"narHash": "sha256-ur4QIAHwgFc0vXiaxn5No/FuZicxBr2p0gmT54xZkUQ=",
|
||||
"owner": "nix-community",
|
||||
"repo": "gomod2nix",
|
||||
"rev": "8f3534eb8f6c5c3fce799376dc3b91bae6b11884",
|
||||
"rev": "02e63a239d6eabd595db56852535992c898eba72",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -41,11 +41,11 @@
|
||||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1745234285,
|
||||
"narHash": "sha256-GfpyMzxwkfgRVN0cTGQSkTC0OHhEkv3Jf6Tcjm//qZ0=",
|
||||
"lastModified": 1765472234,
|
||||
"narHash": "sha256-9VvC20PJPsleGMewwcWYKGzDIyjckEz8uWmT0vCDYK0=",
|
||||
"owner": "nixos",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "c11863f1e964833214b767f4a369c6e6a7aba141",
|
||||
"rev": "2fbfb1d73d239d2402a8fe03963e37aab15abe8b",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -100,11 +100,11 @@
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1744961264,
|
||||
"narHash": "sha256-aRmUh0AMwcbdjJHnytg1e5h5ECcaWtIFQa6d9gI85AI=",
|
||||
"lastModified": 1762938485,
|
||||
"narHash": "sha256-AlEObg0syDl+Spi4LsZIBrjw+snSVU4T8MOeuZJUJjM=",
|
||||
"owner": "numtide",
|
||||
"repo": "treefmt-nix",
|
||||
"rev": "8d404a69efe76146368885110f29a2ca3700bee6",
|
||||
"rev": "5b4ee75aeefd1e2d5a1cc43cf6ba65eba75e83e4",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
||||
14
go.mod
14
go.mod
@@ -3,7 +3,7 @@ module github.com/danielmiessler/fabric
|
||||
go 1.25.1
|
||||
|
||||
require (
|
||||
github.com/anthropics/anthropic-sdk-go v1.16.0
|
||||
github.com/anthropics/anthropic-sdk-go v1.19.0
|
||||
github.com/atotto/clipboard v0.1.4
|
||||
github.com/aws/aws-sdk-go-v2 v1.39.0
|
||||
github.com/aws/aws-sdk-go-v2/config v1.31.8
|
||||
@@ -21,7 +21,7 @@ require (
|
||||
github.com/mattn/go-sqlite3 v1.14.28
|
||||
github.com/nicksnyder/go-i18n/v2 v2.6.0
|
||||
github.com/ollama/ollama v0.11.7
|
||||
github.com/openai/openai-go v1.8.2
|
||||
github.com/openai/openai-go v1.12.0
|
||||
github.com/otiai10/copy v1.14.1
|
||||
github.com/pkg/errors v0.9.1
|
||||
github.com/samber/lo v1.50.0
|
||||
@@ -29,7 +29,7 @@ require (
|
||||
github.com/spf13/cobra v1.9.1
|
||||
github.com/stretchr/testify v1.11.1
|
||||
golang.org/x/oauth2 v0.30.0
|
||||
golang.org/x/text v0.28.0
|
||||
golang.org/x/text v0.31.0
|
||||
google.golang.org/api v0.247.0
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
)
|
||||
@@ -118,11 +118,11 @@ require (
|
||||
go.opentelemetry.io/otel/metric v1.36.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.36.0 // indirect
|
||||
golang.org/x/arch v0.18.0 // indirect
|
||||
golang.org/x/crypto v0.41.0 // indirect
|
||||
golang.org/x/crypto v0.45.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20250531010427-b6e5de432a8b // indirect
|
||||
golang.org/x/net v0.43.0 // indirect
|
||||
golang.org/x/sync v0.16.0 // indirect
|
||||
golang.org/x/sys v0.35.0 // indirect
|
||||
golang.org/x/net v0.47.0 // indirect
|
||||
golang.org/x/sync v0.18.0 // indirect
|
||||
golang.org/x/sys v0.38.0 // indirect
|
||||
google.golang.org/genai v1.17.0
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20250818200422-3122310a409c // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250818200422-3122310a409c // indirect
|
||||
|
||||
17
go.sum
17
go.sum
@@ -29,6 +29,8 @@ github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFI
|
||||
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4=
|
||||
github.com/anthropics/anthropic-sdk-go v1.16.0 h1:nRkOFDqYXsHteoIhjdJr/5dsiKbFF3rflSv8ax50y8o=
|
||||
github.com/anthropics/anthropic-sdk-go v1.16.0/go.mod h1:WTz31rIUHUHqai2UslPpw5CwXrQP3geYBioRV4WOLvE=
|
||||
github.com/anthropics/anthropic-sdk-go v1.19.0 h1:mO6E+ffSzLRvR/YUH9KJC0uGw0uV8GjISIuzem//3KE=
|
||||
github.com/anthropics/anthropic-sdk-go v1.19.0/go.mod h1:WTz31rIUHUHqai2UslPpw5CwXrQP3geYBioRV4WOLvE=
|
||||
github.com/araddon/dateparse v0.0.0-20210429162001-6b43995a97de h1:FxWPpzIjnTlhPwqqXc4/vE0f7GvRjuAsbW+HOIe8KnA=
|
||||
github.com/araddon/dateparse v0.0.0-20210429162001-6b43995a97de/go.mod h1:DCaWoUhZrYW9p1lxo/cm8EmUOOzAPSEZNGF2DK1dJgw=
|
||||
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
|
||||
@@ -201,8 +203,8 @@ github.com/ollama/ollama v0.11.7 h1:CuYjaJ/YEnvLDpJocJbbVdpdVFyGA/OP6lKFyzZD4dI=
|
||||
github.com/ollama/ollama v0.11.7/go.mod h1:9+1//yWPsDE2u+l1a5mpaKrYw4VdnSsRU3ioq5BvMms=
|
||||
github.com/onsi/gomega v1.34.1 h1:EUMJIKUjM8sKjYbtxQI9A4z2o+rruxnzNvpknOXie6k=
|
||||
github.com/onsi/gomega v1.34.1/go.mod h1:kU1QgUvBDLXBJq618Xvm2LUX6rSAfRaFRTcdOeDLwwY=
|
||||
github.com/openai/openai-go v1.8.2 h1:UqSkJ1vCOPUpz9Ka5tS0324EJFEuOvMc+lA/EarJWP8=
|
||||
github.com/openai/openai-go v1.8.2/go.mod h1:g461MYGXEXBVdV5SaR/5tNzNbSfwTBBefwc+LlDCK0Y=
|
||||
github.com/openai/openai-go v1.12.0 h1:NBQCnXzqOTv5wsgNC36PrFEiskGfO5wccfCWDo9S1U0=
|
||||
github.com/openai/openai-go v1.12.0/go.mod h1:g461MYGXEXBVdV5SaR/5tNzNbSfwTBBefwc+LlDCK0Y=
|
||||
github.com/otiai10/copy v1.14.1 h1:5/7E6qsUMBaH5AnQ0sSLzzTg1oTECmcCmT6lvF45Na8=
|
||||
github.com/otiai10/copy v1.14.1/go.mod h1:oQwrEDDOci3IM8dJF0d8+jnbfPDllW6vUjNc3DoZm9I=
|
||||
github.com/otiai10/mint v1.6.3 h1:87qsV/aw1F5as1eH1zS/yqHY85ANKVMgkDrf9rcxbQs=
|
||||
@@ -290,6 +292,8 @@ golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v
|
||||
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
|
||||
golang.org/x/crypto v0.41.0 h1:WKYxWedPGCTVVl5+WHSSrOBT0O8lx32+zxmHxijgXp4=
|
||||
golang.org/x/crypto v0.41.0/go.mod h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sUc=
|
||||
golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q=
|
||||
golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4=
|
||||
golang.org/x/exp v0.0.0-20250531010427-b6e5de432a8b h1:QoALfVG9rhQ/M7vYDScfPdWjGL9dlsVVM5VGh7aKoAA=
|
||||
golang.org/x/exp v0.0.0-20250531010427-b6e5de432a8b/go.mod h1:U6Lno4MTRCDY+Ba7aCcauB9T60gsv5s4ralQzP72ZoQ=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
@@ -309,6 +313,8 @@ 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.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE=
|
||||
golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg=
|
||||
golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY=
|
||||
golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU=
|
||||
golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI=
|
||||
golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
@@ -320,6 +326,8 @@ 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.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw=
|
||||
golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
||||
golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I=
|
||||
golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
||||
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=
|
||||
@@ -338,6 +346,8 @@ 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.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI=
|
||||
golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
|
||||
golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
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=
|
||||
@@ -349,6 +359,7 @@ 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.34.0 h1:O/2T7POpk0ZZ7MAzMeWFSg6S5IpWd/RXDlM9hgM3DR4=
|
||||
golang.org/x/term v0.34.0/go.mod h1:5jC53AEywhIVebHgPVeg0mj8OD3VO9OzclacVrqpaAw=
|
||||
golang.org/x/term v0.37.0 h1:8EGAD0qCmHYZg6J17DvsMy9/wJ7/D/4pV/wfnld5lTU=
|
||||
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=
|
||||
@@ -361,6 +372,8 @@ 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.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng=
|
||||
golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU=
|
||||
golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM=
|
||||
golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
|
||||
@@ -39,6 +39,11 @@ func handleListingCommands(currentFlags *Flags, fabricDb *fsdb.Db, registry *cor
|
||||
if models, err = registry.VendorManager.GetModels(); err != nil {
|
||||
return true, err
|
||||
}
|
||||
|
||||
if currentFlags.Vendor != "" {
|
||||
models = models.FilterByVendor(currentFlags.Vendor)
|
||||
}
|
||||
|
||||
if currentFlags.ShellCompleteOutput {
|
||||
models.Print(true)
|
||||
} else {
|
||||
|
||||
@@ -17,8 +17,9 @@ func handleTranscription(flags *Flags, registry *core.PluginRegistry) (message s
|
||||
if vendorName == "" {
|
||||
vendorName = "OpenAI"
|
||||
}
|
||||
vendor, ok := registry.VendorManager.VendorsByName[vendorName]
|
||||
if !ok {
|
||||
|
||||
vendor := registry.VendorManager.FindByName(vendorName)
|
||||
if vendor == nil {
|
||||
return "", fmt.Errorf("%s", fmt.Sprintf(i18n.T("vendor_not_configured"), vendorName))
|
||||
}
|
||||
tr, ok := vendor.(transcriber)
|
||||
|
||||
@@ -32,11 +32,9 @@ type Chatter struct {
|
||||
|
||||
// Send processes a chat request and applies file changes for create_coding_feature pattern
|
||||
func (o *Chatter) Send(request *domain.ChatRequest, opts *domain.ChatOptions) (session *fsdb.Session, err error) {
|
||||
modelToUse := opts.Model
|
||||
if modelToUse == "" {
|
||||
modelToUse = o.model
|
||||
}
|
||||
if o.vendor.NeedsRawMode(modelToUse) {
|
||||
// Use o.model (normalized) for NeedsRawMode check instead of opts.Model
|
||||
// This ensures case-insensitive model names work correctly (e.g., "GPT-5" → "gpt-5")
|
||||
if o.vendor.NeedsRawMode(o.model) {
|
||||
opts.Raw = true
|
||||
}
|
||||
if session, err = o.BuildSession(request, opts.Raw); err != nil {
|
||||
@@ -57,6 +55,10 @@ func (o *Chatter) Send(request *domain.ChatRequest, opts *domain.ChatOptions) (s
|
||||
|
||||
if opts.Model == "" {
|
||||
opts.Model = o.model
|
||||
} else {
|
||||
// Ensure opts.Model uses the normalized name from o.model if they refer to the same model
|
||||
// This handles cases where user provides "GPT-5" but we've normalized it to "gpt-5"
|
||||
opts.Model = o.model
|
||||
}
|
||||
|
||||
if opts.ModelContextLength == 0 {
|
||||
|
||||
@@ -222,9 +222,8 @@ func (o *PluginRegistry) Setup() (err error) {
|
||||
}
|
||||
}
|
||||
|
||||
if _, ok := o.VendorManager.VendorsByName[plugin.GetName()]; !ok {
|
||||
var vendor ai.Vendor
|
||||
if vendor, ok = plugin.(ai.Vendor); ok {
|
||||
if o.VendorManager.FindByName(plugin.GetName()) == nil {
|
||||
if vendor, ok := plugin.(ai.Vendor); ok {
|
||||
o.VendorManager.AddVendors(vendor)
|
||||
}
|
||||
}
|
||||
@@ -330,11 +329,22 @@ func (o *PluginRegistry) GetChatter(model string, modelContextLength int, vendor
|
||||
if models, err = vendorManager.GetModels(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Normalize model name to match actual available model (case-insensitive)
|
||||
// This must be done BEFORE checking vendor availability
|
||||
actualModelName := models.FindModelNameCaseInsensitive(model)
|
||||
if actualModelName != "" {
|
||||
model = actualModelName // Use normalized name for all subsequent checks
|
||||
}
|
||||
|
||||
if vendorName != "" {
|
||||
// ensure vendor exists and provides model
|
||||
ret.vendor = vendorManager.FindByName(vendorName)
|
||||
availableVendors := models.FindGroupsByItem(model)
|
||||
if ret.vendor == nil || !lo.Contains(availableVendors, vendorName) {
|
||||
vendorAvailable := lo.ContainsBy(availableVendors, func(name string) bool {
|
||||
return strings.EqualFold(name, vendorName)
|
||||
})
|
||||
if ret.vendor == nil || !vendorAvailable {
|
||||
err = fmt.Errorf("model %s not available for vendor %s", model, vendorName)
|
||||
return
|
||||
}
|
||||
@@ -345,6 +355,7 @@ func (o *PluginRegistry) GetChatter(model string, modelContextLength int, vendor
|
||||
}
|
||||
ret.vendor = vendorManager.FindByName(models.FindGroupsByItemFirst(model))
|
||||
}
|
||||
|
||||
ret.model = model
|
||||
}
|
||||
|
||||
|
||||
@@ -28,6 +28,12 @@
|
||||
"youtube_empty_seconds_string": "leere Sekunden-Zeichenfolge",
|
||||
"youtube_invalid_seconds_format": "ungültiges Sekundenformat %q: %w",
|
||||
"error_fetching_playlist_videos": "Fehler beim Abrufen der Playlist-Videos: %w",
|
||||
"openai_api_base_url_not_configured": "API-Basis-URL für Anbieter %s nicht konfiguriert",
|
||||
"openai_failed_to_create_models_url": "Modell-URL konnte nicht erstellt werden: %w",
|
||||
"openai_unexpected_status_code_with_body": "unerwarteter Statuscode: %d von Anbieter %s, Antwort: %s",
|
||||
"openai_unexpected_status_code_read_error_partial": "unerwarteter Statuscode: %d von Anbieter %s (Fehler beim Lesen: %v), teilweise Antwort: %s",
|
||||
"openai_unexpected_status_code_read_error": "unerwarteter Statuscode: %d von Anbieter %s (Fehler beim Lesen der Antwort: %v)",
|
||||
"openai_unable_to_parse_models_response": "Modell-Antwort konnte nicht geparst werden; rohe Antwort: %s",
|
||||
"scraping_not_configured": "Scraping-Funktionalität ist nicht konfiguriert. Bitte richte Jina ein, um Scraping zu aktivieren",
|
||||
"could_not_determine_home_dir": "konnte Benutzer-Home-Verzeichnis nicht bestimmen: %w",
|
||||
"could_not_stat_env_file": "konnte .env-Datei nicht überprüfen: %w",
|
||||
|
||||
@@ -28,6 +28,12 @@
|
||||
"youtube_empty_seconds_string": "empty seconds string",
|
||||
"youtube_invalid_seconds_format": "invalid seconds format %q: %w",
|
||||
"error_fetching_playlist_videos": "error fetching playlist videos: %w",
|
||||
"openai_api_base_url_not_configured": "API base URL not configured for provider %s",
|
||||
"openai_failed_to_create_models_url": "failed to create models URL: %w",
|
||||
"openai_unexpected_status_code_with_body": "unexpected status code: %d from provider %s, response body: %s",
|
||||
"openai_unexpected_status_code_read_error_partial": "unexpected status code: %d from provider %s (error reading body: %v), partial response: %s",
|
||||
"openai_unexpected_status_code_read_error": "unexpected status code: %d from provider %s (failed to read response body: %v)",
|
||||
"openai_unable_to_parse_models_response": "unable to parse models response; raw response: %s",
|
||||
"scraping_not_configured": "scraping functionality is not configured. Please set up Jina to enable scraping",
|
||||
"could_not_determine_home_dir": "could not determine user home directory: %w",
|
||||
"could_not_stat_env_file": "could not stat .env file: %w",
|
||||
|
||||
@@ -28,6 +28,12 @@
|
||||
"youtube_empty_seconds_string": "cadena de segundos vacía",
|
||||
"youtube_invalid_seconds_format": "formato de segundos inválido %q: %w",
|
||||
"error_fetching_playlist_videos": "error al obtener videos de la lista de reproducción: %w",
|
||||
"openai_api_base_url_not_configured": "URL base de API no configurada para el proveedor %s",
|
||||
"openai_failed_to_create_models_url": "error al crear URL de modelos: %w",
|
||||
"openai_unexpected_status_code_with_body": "código de estado inesperado: %d del proveedor %s, cuerpo de respuesta: %s",
|
||||
"openai_unexpected_status_code_read_error_partial": "código de estado inesperado: %d del proveedor %s (error al leer cuerpo: %v), respuesta parcial: %s",
|
||||
"openai_unexpected_status_code_read_error": "código de estado inesperado: %d del proveedor %s (error al leer cuerpo de respuesta: %v)",
|
||||
"openai_unable_to_parse_models_response": "no se pudo analizar la respuesta de modelos; respuesta cruda: %s",
|
||||
"scraping_not_configured": "la funcionalidad de extracción no está configurada. Por favor configura Jina para habilitar la extracción",
|
||||
"could_not_determine_home_dir": "no se pudo determinar el directorio home del usuario: %w",
|
||||
"could_not_stat_env_file": "no se pudo verificar el archivo .env: %w",
|
||||
|
||||
@@ -28,6 +28,12 @@
|
||||
"youtube_empty_seconds_string": "رشته ثانیه خالی",
|
||||
"youtube_invalid_seconds_format": "فرمت ثانیه نامعتبر %q: %w",
|
||||
"error_fetching_playlist_videos": "خطا در دریافت ویدیوهای فهرست پخش: %w",
|
||||
"openai_api_base_url_not_configured": "URL پایه API برای ارائهدهنده %s پیکربندی نشده است",
|
||||
"openai_failed_to_create_models_url": "ایجاد URL مدلها ناموفق بود: %w",
|
||||
"openai_unexpected_status_code_with_body": "کد وضعیت غیرمنتظره: %d از ارائهدهنده %s، پاسخ: %s",
|
||||
"openai_unexpected_status_code_read_error_partial": "کد وضعیت غیرمنتظره: %d از ارائهدهنده %s (خطا در خواندن: %v)، پاسخ جزئی: %s",
|
||||
"openai_unexpected_status_code_read_error": "کد وضعیت غیرمنتظره: %d از ارائهدهنده %s (خطا در خواندن پاسخ: %v)",
|
||||
"openai_unable_to_parse_models_response": "تجزیه پاسخ مدلها ناموفق بود; پاسخ خام: %s",
|
||||
"scraping_not_configured": "قابلیت استخراج داده پیکربندی نشده است. لطفاً Jina را برای فعالسازی استخراج تنظیم کنید",
|
||||
"could_not_determine_home_dir": "نتوانست دایرکتوری خانه کاربر را تعیین کند: %w",
|
||||
"could_not_stat_env_file": "نتوانست وضعیت فایل .env را بررسی کند: %w",
|
||||
|
||||
@@ -28,6 +28,12 @@
|
||||
"youtube_empty_seconds_string": "chaîne de secondes vide",
|
||||
"youtube_invalid_seconds_format": "format de secondes invalide %q : %w",
|
||||
"error_fetching_playlist_videos": "erreur lors de la récupération des vidéos de la liste de lecture : %w",
|
||||
"openai_api_base_url_not_configured": "URL de base de l'API non configurée pour le fournisseur %s",
|
||||
"openai_failed_to_create_models_url": "échec de création de l'URL des modèles : %w",
|
||||
"openai_unexpected_status_code_with_body": "code d'état inattendu : %d du fournisseur %s, corps de réponse : %s",
|
||||
"openai_unexpected_status_code_read_error_partial": "code d'état inattendu : %d du fournisseur %s (erreur de lecture : %v), réponse partielle : %s",
|
||||
"openai_unexpected_status_code_read_error": "code d'état inattendu : %d du fournisseur %s (échec de lecture du corps de réponse : %v)",
|
||||
"openai_unable_to_parse_models_response": "impossible d'analyser la réponse des modèles ; réponse brute : %s",
|
||||
"scraping_not_configured": "la fonctionnalité de scraping n'est pas configurée. Veuillez configurer Jina pour activer le scraping",
|
||||
"could_not_determine_home_dir": "impossible de déterminer le répertoire home de l'utilisateur : %w",
|
||||
"could_not_stat_env_file": "impossible de vérifier le fichier .env : %w",
|
||||
|
||||
@@ -28,6 +28,12 @@
|
||||
"youtube_empty_seconds_string": "stringa di secondi vuota",
|
||||
"youtube_invalid_seconds_format": "formato secondi non valido %q: %w",
|
||||
"error_fetching_playlist_videos": "errore nel recupero dei video della playlist: %w",
|
||||
"openai_api_base_url_not_configured": "URL base API non configurato per il provider %s",
|
||||
"openai_failed_to_create_models_url": "impossibile creare URL modelli: %w",
|
||||
"openai_unexpected_status_code_with_body": "codice di stato imprevisto: %d dal provider %s, corpo risposta: %s",
|
||||
"openai_unexpected_status_code_read_error_partial": "codice di stato imprevisto: %d dal provider %s (errore lettura corpo: %v), risposta parziale: %s",
|
||||
"openai_unexpected_status_code_read_error": "codice di stato imprevisto: %d dal provider %s (errore lettura corpo risposta: %v)",
|
||||
"openai_unable_to_parse_models_response": "impossibile analizzare risposta modelli; risposta grezza: %s",
|
||||
"scraping_not_configured": "la funzionalità di scraping non è configurata. Per favore configura Jina per abilitare lo scraping",
|
||||
"could_not_determine_home_dir": "impossibile determinare la directory home dell'utente: %w",
|
||||
"could_not_stat_env_file": "impossibile verificare il file .env: %w",
|
||||
|
||||
@@ -28,6 +28,12 @@
|
||||
"youtube_empty_seconds_string": "空の秒文字列",
|
||||
"youtube_invalid_seconds_format": "無効な秒形式 %q: %w",
|
||||
"error_fetching_playlist_videos": "プレイリスト動画の取得エラー: %w",
|
||||
"openai_api_base_url_not_configured": "プロバイダー %s のAPIベースURLが設定されていません",
|
||||
"openai_failed_to_create_models_url": "モデルURLの作成に失敗しました: %w",
|
||||
"openai_unexpected_status_code_with_body": "予期しないステータスコード: プロバイダー %s から %d、レスポンス本文: %s",
|
||||
"openai_unexpected_status_code_read_error_partial": "予期しないステータスコード: プロバイダー %s から %d (本文読み取りエラー: %v)、部分的なレスポンス: %s",
|
||||
"openai_unexpected_status_code_read_error": "予期しないステータスコード: プロバイダー %s から %d (レスポンス本文の読み取りに失敗: %v)",
|
||||
"openai_unable_to_parse_models_response": "モデルレスポンスの解析に失敗しました; 生のレスポンス: %s",
|
||||
"scraping_not_configured": "スクレイピング機能が設定されていません。スクレイピングを有効にするためにJinaを設定してください",
|
||||
"could_not_determine_home_dir": "ユーザーのホームディレクトリを特定できませんでした: %w",
|
||||
"could_not_stat_env_file": ".envファイルの状態を確認できませんでした: %w",
|
||||
|
||||
@@ -28,6 +28,12 @@
|
||||
"youtube_empty_seconds_string": "string de segundos vazia",
|
||||
"youtube_invalid_seconds_format": "formato de segundos inválido %q: %w",
|
||||
"error_fetching_playlist_videos": "erro ao buscar vídeos da playlist: %w",
|
||||
"openai_api_base_url_not_configured": "URL base da API não configurada para o provedor %s",
|
||||
"openai_failed_to_create_models_url": "falha ao criar URL de modelos: %w",
|
||||
"openai_unexpected_status_code_with_body": "código de status inesperado: %d do provedor %s, corpo da resposta: %s",
|
||||
"openai_unexpected_status_code_read_error_partial": "código de status inesperado: %d do provedor %s (erro ao ler corpo: %v), resposta parcial: %s",
|
||||
"openai_unexpected_status_code_read_error": "código de status inesperado: %d do provedor %s (falha ao ler corpo da resposta: %v)",
|
||||
"openai_unable_to_parse_models_response": "não foi possível analisar a resposta de modelos; resposta bruta: %s",
|
||||
"scraping_not_configured": "funcionalidade de scraping não está configurada. Por favor configure o Jina para ativar o scraping",
|
||||
"could_not_determine_home_dir": "não foi possível determinar o diretório home do usuário: %w",
|
||||
"could_not_stat_env_file": "não foi possível verificar o arquivo .env: %w",
|
||||
|
||||
@@ -28,6 +28,12 @@
|
||||
"youtube_empty_seconds_string": "cadeia de segundos vazia",
|
||||
"youtube_invalid_seconds_format": "formato de segundos inválido %q: %w",
|
||||
"error_fetching_playlist_videos": "erro ao obter vídeos da playlist: %w",
|
||||
"openai_api_base_url_not_configured": "URL base da API não configurado para o fornecedor %s",
|
||||
"openai_failed_to_create_models_url": "falha ao criar URL de modelos: %w",
|
||||
"openai_unexpected_status_code_with_body": "código de estado inesperado: %d do fornecedor %s, corpo da resposta: %s",
|
||||
"openai_unexpected_status_code_read_error_partial": "código de estado inesperado: %d do fornecedor %s (erro ao ler corpo: %v), resposta parcial: %s",
|
||||
"openai_unexpected_status_code_read_error": "código de estado inesperado: %d do fornecedor %s (falha ao ler corpo da resposta: %v)",
|
||||
"openai_unable_to_parse_models_response": "não foi possível analisar a resposta de modelos; resposta bruta: %s",
|
||||
"scraping_not_configured": "funcionalidade de scraping não está configurada. Por favor configure o Jina para ativar o scraping",
|
||||
"could_not_determine_home_dir": "não foi possível determinar o diretório home do utilizador: %w",
|
||||
"could_not_stat_env_file": "não foi possível verificar o ficheiro .env: %w",
|
||||
|
||||
@@ -28,6 +28,12 @@
|
||||
"youtube_empty_seconds_string": "秒数字符串为空",
|
||||
"youtube_invalid_seconds_format": "无效的秒数格式 %q:%w",
|
||||
"error_fetching_playlist_videos": "获取播放列表视频时出错: %w",
|
||||
"openai_api_base_url_not_configured": "未为提供商 %s 配置 API 基础 URL",
|
||||
"openai_failed_to_create_models_url": "创建模型 URL 失败:%w",
|
||||
"openai_unexpected_status_code_with_body": "意外的状态码:来自提供商 %s 的 %d,响应主体:%s",
|
||||
"openai_unexpected_status_code_read_error_partial": "意外的状态码:来自提供商 %s 的 %d(读取主体错误:%v),部分响应:%s",
|
||||
"openai_unexpected_status_code_read_error": "意外的状态码:来自提供商 %s 的 %d(读取响应主体失败:%v)",
|
||||
"openai_unable_to_parse_models_response": "无法解析模型响应;原始响应:%s",
|
||||
"scraping_not_configured": "抓取功能未配置。请设置 Jina 以启用抓取功能",
|
||||
"could_not_determine_home_dir": "无法确定用户主目录: %w",
|
||||
"could_not_stat_env_file": "无法获取 .env 文件状态: %w",
|
||||
|
||||
@@ -51,7 +51,7 @@ func LevelFromInt(i int) Level {
|
||||
}
|
||||
|
||||
// Debug writes a debug message if the global level permits.
|
||||
func Debug(l Level, format string, a ...interface{}) {
|
||||
func Debug(l Level, format string, a ...any) {
|
||||
mu.RLock()
|
||||
current := level
|
||||
w := output
|
||||
@@ -63,7 +63,7 @@ func Debug(l Level, format string, a ...interface{}) {
|
||||
|
||||
// Log writes a message unconditionally to stderr.
|
||||
// This is for important messages that should always be shown regardless of debug level.
|
||||
func Log(format string, a ...interface{}) {
|
||||
func Log(format string, a ...any) {
|
||||
mu.RLock()
|
||||
w := output
|
||||
mu.RUnlock()
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
@@ -50,6 +51,10 @@ func NewClient() (ret *Client) {
|
||||
string(anthropic.ModelClaudeOpus4_1_20250805),
|
||||
string(anthropic.ModelClaudeSonnet4_5),
|
||||
string(anthropic.ModelClaudeSonnet4_5_20250929),
|
||||
string(anthropic.ModelClaudeOpus4_5_20251101),
|
||||
string(anthropic.ModelClaudeOpus4_5),
|
||||
string(anthropic.ModelClaudeHaiku4_5),
|
||||
string(anthropic.ModelClaudeHaiku4_5_20251001),
|
||||
}
|
||||
|
||||
ret.modelBetas = map[string][]string{
|
||||
@@ -212,7 +217,7 @@ func (an *Client) SendStream(
|
||||
}
|
||||
|
||||
if stream.Err() != nil {
|
||||
fmt.Printf("Messages stream error: %v\n", stream.Err())
|
||||
fmt.Fprintf(os.Stderr, "Messages stream error: %v\n", stream.Err())
|
||||
}
|
||||
close(channel)
|
||||
return
|
||||
|
||||
@@ -52,7 +52,7 @@ func createExpiredToken(accessToken, refreshToken string) *util.OAuthToken {
|
||||
}
|
||||
|
||||
// mockTokenServer creates a mock OAuth token server for testing
|
||||
func mockTokenServer(_ *testing.T, responses map[string]interface{}) *httptest.Server {
|
||||
func mockTokenServer(_ *testing.T, responses map[string]any) *httptest.Server {
|
||||
return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.URL.Path != "/v1/oauth/token" {
|
||||
http.NotFound(w, r)
|
||||
@@ -80,7 +80,7 @@ func mockTokenServer(_ *testing.T, responses map[string]interface{}) *httptest.S
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
|
||||
if errorResp, ok := response.(map[string]interface{}); ok && errorResp["error"] != nil {
|
||||
if errorResp, ok := response.(map[string]any); ok && errorResp["error"] != nil {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
}
|
||||
|
||||
@@ -114,8 +114,8 @@ func TestGeneratePKCE(t *testing.T) {
|
||||
|
||||
func TestExchangeToken_Success(t *testing.T) {
|
||||
// Create mock server
|
||||
server := mockTokenServer(t, map[string]interface{}{
|
||||
"authorization_code": map[string]interface{}{
|
||||
server := mockTokenServer(t, map[string]any{
|
||||
"authorization_code": map[string]any{
|
||||
"access_token": "test_access_token",
|
||||
"refresh_token": "test_refresh_token",
|
||||
"expires_in": 3600,
|
||||
@@ -161,8 +161,8 @@ func TestRefreshToken_Success(t *testing.T) {
|
||||
os.WriteFile(tokenPath, data, 0600)
|
||||
|
||||
// Create mock server for refresh
|
||||
server := mockTokenServer(t, map[string]interface{}{
|
||||
"refresh_token": map[string]interface{}{
|
||||
server := mockTokenServer(t, map[string]any{
|
||||
"refresh_token": map[string]any{
|
||||
"access_token": "new_access_token",
|
||||
"refresh_token": "new_refresh_token",
|
||||
"expires_in": 3600,
|
||||
|
||||
@@ -90,7 +90,7 @@ func (c *Client) ListModels() ([]string, error) {
|
||||
func (c *Client) SendStream(msgs []*chat.ChatCompletionMessage, opts *domain.ChatOptions, channel chan string) (err error) {
|
||||
url := fmt.Sprintf("%s/chat/completions", c.ApiUrl.Value)
|
||||
|
||||
payload := map[string]interface{}{
|
||||
payload := map[string]any{
|
||||
"messages": msgs,
|
||||
"model": opts.Model,
|
||||
"stream": true, // Enable streaming
|
||||
@@ -148,19 +148,19 @@ func (c *Client) SendStream(msgs []*chat.ChatCompletionMessage, opts *domain.Cha
|
||||
break
|
||||
}
|
||||
|
||||
var result map[string]interface{}
|
||||
var result map[string]any
|
||||
if err = json.Unmarshal(line, &result); err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
var choices []interface{}
|
||||
var choices []any
|
||||
var ok bool
|
||||
if choices, ok = result["choices"].([]interface{}); !ok || len(choices) == 0 {
|
||||
if choices, ok = result["choices"].([]any); !ok || len(choices) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
var delta map[string]interface{}
|
||||
if delta, ok = choices[0].(map[string]interface{})["delta"].(map[string]interface{}); !ok {
|
||||
var delta map[string]any
|
||||
if delta, ok = choices[0].(map[string]any)["delta"].(map[string]any); !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -176,7 +176,7 @@ func (c *Client) SendStream(msgs []*chat.ChatCompletionMessage, opts *domain.Cha
|
||||
func (c *Client) Send(ctx context.Context, msgs []*chat.ChatCompletionMessage, opts *domain.ChatOptions) (content string, err error) {
|
||||
url := fmt.Sprintf("%s/chat/completions", c.ApiUrl.Value)
|
||||
|
||||
payload := map[string]interface{}{
|
||||
payload := map[string]any{
|
||||
"messages": msgs,
|
||||
"model": opts.Model,
|
||||
// Add other options from opts if supported by LM Studio
|
||||
@@ -208,21 +208,21 @@ func (c *Client) Send(ctx context.Context, msgs []*chat.ChatCompletionMessage, o
|
||||
return
|
||||
}
|
||||
|
||||
var result map[string]interface{}
|
||||
var result map[string]any
|
||||
if err = json.NewDecoder(resp.Body).Decode(&result); err != nil {
|
||||
err = fmt.Errorf("failed to decode response: %w", err)
|
||||
return
|
||||
}
|
||||
|
||||
var choices []interface{}
|
||||
var choices []any
|
||||
var ok bool
|
||||
if choices, ok = result["choices"].([]interface{}); !ok || len(choices) == 0 {
|
||||
if choices, ok = result["choices"].([]any); !ok || len(choices) == 0 {
|
||||
err = fmt.Errorf("invalid response format: missing or empty choices")
|
||||
return
|
||||
}
|
||||
|
||||
var message map[string]interface{}
|
||||
if message, ok = choices[0].(map[string]interface{})["message"].(map[string]interface{}); !ok {
|
||||
var message map[string]any
|
||||
if message, ok = choices[0].(map[string]any)["message"].(map[string]any); !ok {
|
||||
err = fmt.Errorf("invalid response format: missing message in first choice")
|
||||
return
|
||||
}
|
||||
@@ -238,7 +238,7 @@ func (c *Client) Send(ctx context.Context, msgs []*chat.ChatCompletionMessage, o
|
||||
func (c *Client) Complete(ctx context.Context, prompt string, opts *domain.ChatOptions) (text string, err error) {
|
||||
url := fmt.Sprintf("%s/completions", c.ApiUrl.Value)
|
||||
|
||||
payload := map[string]interface{}{
|
||||
payload := map[string]any{
|
||||
"prompt": prompt,
|
||||
"model": opts.Model,
|
||||
// Add other options from opts if supported by LM Studio
|
||||
@@ -270,20 +270,20 @@ func (c *Client) Complete(ctx context.Context, prompt string, opts *domain.ChatO
|
||||
return
|
||||
}
|
||||
|
||||
var result map[string]interface{}
|
||||
var result map[string]any
|
||||
if err = json.NewDecoder(resp.Body).Decode(&result); err != nil {
|
||||
err = fmt.Errorf("failed to decode response: %w", err)
|
||||
return
|
||||
}
|
||||
|
||||
var choices []interface{}
|
||||
var choices []any
|
||||
var ok bool
|
||||
if choices, ok = result["choices"].([]interface{}); !ok || len(choices) == 0 {
|
||||
if choices, ok = result["choices"].([]any); !ok || len(choices) == 0 {
|
||||
err = fmt.Errorf("invalid response format: missing or empty choices")
|
||||
return
|
||||
}
|
||||
|
||||
if text, ok = choices[0].(map[string]interface{})["text"].(string); !ok {
|
||||
if text, ok = choices[0].(map[string]any)["text"].(string); !ok {
|
||||
err = fmt.Errorf("invalid response format: missing or non-string text in first choice")
|
||||
return
|
||||
}
|
||||
@@ -294,7 +294,7 @@ func (c *Client) Complete(ctx context.Context, prompt string, opts *domain.ChatO
|
||||
func (c *Client) GetEmbeddings(ctx context.Context, input string, opts *domain.ChatOptions) (embeddings []float64, err error) {
|
||||
url := fmt.Sprintf("%s/embeddings", c.ApiUrl.Value)
|
||||
|
||||
payload := map[string]interface{}{
|
||||
payload := map[string]any{
|
||||
"input": input,
|
||||
"model": opts.Model,
|
||||
// Add other options from opts if supported by LM Studio
|
||||
|
||||
@@ -17,6 +17,35 @@ type VendorsModels struct {
|
||||
*util.GroupsItemsSelectorString
|
||||
}
|
||||
|
||||
// FilterByVendor returns a new VendorsModels containing only the specified vendor's models.
|
||||
// Vendor matching is case-insensitive (e.g., "OpenAI", "openai", and "OPENAI" all match).
|
||||
// If the vendor is not found, an empty VendorsModels is returned.
|
||||
func (o *VendorsModels) FilterByVendor(vendor string) *VendorsModels {
|
||||
filtered := NewVendorsModels()
|
||||
for _, groupItems := range o.GroupsItems {
|
||||
if strings.EqualFold(groupItems.Group, vendor) {
|
||||
filtered.AddGroupItems(groupItems.Group, groupItems.Items...)
|
||||
break
|
||||
}
|
||||
}
|
||||
return filtered
|
||||
}
|
||||
|
||||
// FindModelNameCaseInsensitive returns the actual model name from available models,
|
||||
// matching case-insensitively. Returns empty string if not found.
|
||||
// For example, if the available models contain "gpt-4o" and user queries "GPT-4O",
|
||||
// this returns "gpt-4o" (the actual model name that should be sent to the API).
|
||||
func (o *VendorsModels) FindModelNameCaseInsensitive(modelQuery string) string {
|
||||
for _, groupItems := range o.GroupsItems {
|
||||
for _, item := range groupItems.Items {
|
||||
if strings.EqualFold(item, modelQuery) {
|
||||
return item
|
||||
}
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// PrintWithVendor prints models including their vendor on each line.
|
||||
// When shellCompleteList is true, output is suitable for shell completion.
|
||||
// Default vendor and model are highlighted with an asterisk.
|
||||
|
||||
@@ -19,19 +19,19 @@ func TestNewVendorsModels(t *testing.T) {
|
||||
|
||||
func TestFindVendorsByModelFirst(t *testing.T) {
|
||||
vendors := NewVendorsModels()
|
||||
vendors.AddGroupItems("vendor1", []string{"model1", "model2"}...)
|
||||
vendors.AddGroupItems("Vendor1", []string{"Model1", "model2"}...)
|
||||
vendor := vendors.FindGroupsByItemFirst("model1")
|
||||
if vendor != "vendor1" {
|
||||
t.Fatalf("FindVendorsByModelFirst() = %v, want %v", vendor, "vendor1")
|
||||
if vendor != "Vendor1" {
|
||||
t.Fatalf("FindVendorsByModelFirst() = %v, want %v", vendor, "Vendor1")
|
||||
}
|
||||
}
|
||||
|
||||
func TestFindVendorsByModel(t *testing.T) {
|
||||
vendors := NewVendorsModels()
|
||||
vendors.AddGroupItems("vendor1", []string{"model1", "model2"}...)
|
||||
foundVendors := vendors.FindGroupsByItem("model1")
|
||||
if len(foundVendors) != 1 || foundVendors[0] != "vendor1" {
|
||||
t.Fatalf("FindVendorsByModel() = %v, want %v", foundVendors, []string{"vendor1"})
|
||||
vendors.AddGroupItems("Vendor1", []string{"Model1", "model2"}...)
|
||||
foundVendors := vendors.FindGroupsByItem("MODEL1")
|
||||
if len(foundVendors) != 1 || foundVendors[0] != "Vendor1" {
|
||||
t.Fatalf("FindVendorsByModel() = %v, want %v", foundVendors, []string{"Vendor1"})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,3 +54,51 @@ func TestPrintWithVendorMarksDefault(t *testing.T) {
|
||||
t.Fatalf("default model not marked: %s", out)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFilterByVendorCaseInsensitive(t *testing.T) {
|
||||
vendors := NewVendorsModels()
|
||||
vendors.AddGroupItems("vendor1", []string{"model1"}...)
|
||||
vendors.AddGroupItems("vendor2", []string{"model2"}...)
|
||||
|
||||
filtered := vendors.FilterByVendor("VENDOR2")
|
||||
|
||||
if len(filtered.GroupsItems) != 1 {
|
||||
t.Fatalf("expected 1 vendor group, got %d", len(filtered.GroupsItems))
|
||||
}
|
||||
|
||||
if filtered.GroupsItems[0].Group != "vendor2" {
|
||||
t.Fatalf("expected vendor2, got %s", filtered.GroupsItems[0].Group)
|
||||
}
|
||||
|
||||
if len(filtered.GroupsItems[0].Items) != 1 || filtered.GroupsItems[0].Items[0] != "model2" {
|
||||
t.Fatalf("unexpected models for vendor2: %v", filtered.GroupsItems[0].Items)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFindModelNameCaseInsensitive(t *testing.T) {
|
||||
vendors := NewVendorsModels()
|
||||
vendors.AddGroupItems("OpenAI", []string{"gpt-4o", "gpt-5"}...)
|
||||
vendors.AddGroupItems("Anthropic", []string{"claude-3-opus"}...)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
query string
|
||||
expectedModel string
|
||||
}{
|
||||
{"exact match lowercase", "gpt-4o", "gpt-4o"},
|
||||
{"uppercase query", "GPT-4O", "gpt-4o"},
|
||||
{"mixed case query", "GpT-5", "gpt-5"},
|
||||
{"exact match with hyphens", "claude-3-opus", "claude-3-opus"},
|
||||
{"uppercase with hyphens", "CLAUDE-3-OPUS", "claude-3-opus"},
|
||||
{"non-existent model", "gpt-999", ""},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
result := vendors.FindModelNameCaseInsensitive(tt.query)
|
||||
if result != tt.expectedModel {
|
||||
t.Errorf("FindModelNameCaseInsensitive(%q) = %q, want %q", tt.query, result, tt.expectedModel)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,9 @@ package ollama
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
@@ -10,11 +12,10 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/danielmiessler/fabric/internal/chat"
|
||||
ollamaapi "github.com/ollama/ollama/api"
|
||||
"github.com/samber/lo"
|
||||
|
||||
"github.com/danielmiessler/fabric/internal/domain"
|
||||
debuglog "github.com/danielmiessler/fabric/internal/log"
|
||||
"github.com/danielmiessler/fabric/internal/plugins"
|
||||
ollamaapi "github.com/ollama/ollama/api"
|
||||
)
|
||||
|
||||
const defaultBaseUrl = "http://localhost:11434"
|
||||
@@ -48,6 +49,7 @@ type Client struct {
|
||||
apiUrl *url.URL
|
||||
client *ollamaapi.Client
|
||||
ApiHttpTimeout *plugins.SetupQuestion
|
||||
httpClient *http.Client
|
||||
}
|
||||
|
||||
type transport_sec struct {
|
||||
@@ -84,7 +86,8 @@ func (o *Client) configure() (err error) {
|
||||
}
|
||||
}
|
||||
|
||||
o.client = ollamaapi.NewClient(o.apiUrl, &http.Client{Timeout: timeout, Transport: &transport_sec{underlyingTransport: http.DefaultTransport, ApiKey: o.ApiKey}})
|
||||
o.httpClient = &http.Client{Timeout: timeout, Transport: &transport_sec{underlyingTransport: http.DefaultTransport, ApiKey: o.ApiKey}}
|
||||
o.client = ollamaapi.NewClient(o.apiUrl, o.httpClient)
|
||||
|
||||
return
|
||||
}
|
||||
@@ -104,15 +107,18 @@ func (o *Client) ListModels() (ret []string, err error) {
|
||||
}
|
||||
|
||||
func (o *Client) SendStream(msgs []*chat.ChatCompletionMessage, opts *domain.ChatOptions, channel chan string) (err error) {
|
||||
req := o.createChatRequest(msgs, opts)
|
||||
ctx := context.Background()
|
||||
|
||||
var req ollamaapi.ChatRequest
|
||||
if req, err = o.createChatRequest(ctx, msgs, opts); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
respFunc := func(resp ollamaapi.ChatResponse) (streamErr error) {
|
||||
channel <- resp.Message.Content
|
||||
return
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
if err = o.client.Chat(ctx, &req, respFunc); err != nil {
|
||||
return
|
||||
}
|
||||
@@ -124,7 +130,10 @@ func (o *Client) SendStream(msgs []*chat.ChatCompletionMessage, opts *domain.Cha
|
||||
func (o *Client) Send(ctx context.Context, msgs []*chat.ChatCompletionMessage, opts *domain.ChatOptions) (ret string, err error) {
|
||||
bf := false
|
||||
|
||||
req := o.createChatRequest(msgs, opts)
|
||||
var req ollamaapi.ChatRequest
|
||||
if req, err = o.createChatRequest(ctx, msgs, opts); err != nil {
|
||||
return
|
||||
}
|
||||
req.Stream = &bf
|
||||
|
||||
respFunc := func(resp ollamaapi.ChatResponse) (streamErr error) {
|
||||
@@ -133,17 +142,20 @@ func (o *Client) Send(ctx context.Context, msgs []*chat.ChatCompletionMessage, o
|
||||
}
|
||||
|
||||
if err = o.client.Chat(ctx, &req, respFunc); err != nil {
|
||||
fmt.Printf("FRED --> %s\n", err)
|
||||
debuglog.Debug(debuglog.Basic, "Ollama chat request failed: %v\n", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (o *Client) createChatRequest(msgs []*chat.ChatCompletionMessage, opts *domain.ChatOptions) (ret ollamaapi.ChatRequest) {
|
||||
messages := lo.Map(msgs, func(message *chat.ChatCompletionMessage, _ int) (ret ollamaapi.Message) {
|
||||
return ollamaapi.Message{Role: message.Role, Content: message.Content}
|
||||
})
|
||||
func (o *Client) createChatRequest(ctx context.Context, msgs []*chat.ChatCompletionMessage, opts *domain.ChatOptions) (ret ollamaapi.ChatRequest, err error) {
|
||||
messages := make([]ollamaapi.Message, len(msgs))
|
||||
for i, message := range msgs {
|
||||
if messages[i], err = o.convertMessage(ctx, message); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
options := map[string]interface{}{
|
||||
options := map[string]any{
|
||||
"temperature": opts.Temperature,
|
||||
"presence_penalty": opts.PresencePenalty,
|
||||
"frequency_penalty": opts.FrequencyPenalty,
|
||||
@@ -162,14 +174,85 @@ func (o *Client) createChatRequest(msgs []*chat.ChatCompletionMessage, opts *dom
|
||||
return
|
||||
}
|
||||
|
||||
func (o *Client) convertMessage(ctx context.Context, message *chat.ChatCompletionMessage) (ret ollamaapi.Message, err error) {
|
||||
ret = ollamaapi.Message{Role: message.Role, Content: message.Content}
|
||||
|
||||
if len(message.MultiContent) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
// Pre-allocate with capacity hint
|
||||
textParts := make([]string, 0, len(message.MultiContent))
|
||||
if strings.TrimSpace(ret.Content) != "" {
|
||||
textParts = append(textParts, strings.TrimSpace(ret.Content))
|
||||
}
|
||||
|
||||
for _, part := range message.MultiContent {
|
||||
switch part.Type {
|
||||
case chat.ChatMessagePartTypeText:
|
||||
if trimmed := strings.TrimSpace(part.Text); trimmed != "" {
|
||||
textParts = append(textParts, trimmed)
|
||||
}
|
||||
case chat.ChatMessagePartTypeImageURL:
|
||||
// Nil guard
|
||||
if part.ImageURL == nil || part.ImageURL.URL == "" {
|
||||
continue
|
||||
}
|
||||
var img []byte
|
||||
if img, err = o.loadImageBytes(ctx, part.ImageURL.URL); err != nil {
|
||||
return
|
||||
}
|
||||
ret.Images = append(ret.Images, ollamaapi.ImageData(img))
|
||||
}
|
||||
}
|
||||
|
||||
ret.Content = strings.Join(textParts, "\n")
|
||||
return
|
||||
}
|
||||
|
||||
func (o *Client) loadImageBytes(ctx context.Context, imageURL string) (ret []byte, err error) {
|
||||
// Handle data URLs (base64 encoded)
|
||||
if strings.HasPrefix(imageURL, "data:") {
|
||||
parts := strings.SplitN(imageURL, ",", 2)
|
||||
if len(parts) != 2 {
|
||||
err = fmt.Errorf("invalid data URL format")
|
||||
return
|
||||
}
|
||||
if ret, err = base64.StdEncoding.DecodeString(parts[1]); err != nil {
|
||||
err = fmt.Errorf("failed to decode data URL: %w", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Handle HTTP URLs with context
|
||||
var req *http.Request
|
||||
if req, err = http.NewRequestWithContext(ctx, http.MethodGet, imageURL, nil); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
var resp *http.Response
|
||||
if resp, err = o.httpClient.Do(req); err != nil {
|
||||
return
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode >= http.StatusBadRequest {
|
||||
err = fmt.Errorf("failed to fetch image %s: %s", imageURL, resp.Status)
|
||||
return
|
||||
}
|
||||
|
||||
ret, err = io.ReadAll(resp.Body)
|
||||
return
|
||||
}
|
||||
|
||||
func (o *Client) NeedsRawMode(modelName string) bool {
|
||||
ollamaPrefixes := []string{
|
||||
ollamaSearchStrings := []string{
|
||||
"llama3",
|
||||
"llama2",
|
||||
"mistral",
|
||||
}
|
||||
for _, prefix := range ollamaPrefixes {
|
||||
if strings.HasPrefix(modelName, prefix) {
|
||||
for _, searchString := range ollamaSearchStrings {
|
||||
if strings.Contains(modelName, searchString) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
120
internal/plugins/ai/openai/direct_models.go
Normal file
120
internal/plugins/ai/openai/direct_models.go
Normal file
@@ -0,0 +1,120 @@
|
||||
package openai
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"time"
|
||||
|
||||
"github.com/danielmiessler/fabric/internal/i18n"
|
||||
debuglog "github.com/danielmiessler/fabric/internal/log"
|
||||
)
|
||||
|
||||
// modelResponse represents a minimal model returned by the API.
|
||||
// This mirrors the shape used by OpenAI-compatible providers that return
|
||||
// either an array of models or an object with a `data` field.
|
||||
type modelResponse struct {
|
||||
ID string `json:"id"`
|
||||
}
|
||||
|
||||
// errorResponseLimit defines the maximum length of error response bodies for truncation.
|
||||
const errorResponseLimit = 1024
|
||||
|
||||
// maxResponseSize defines the maximum size of response bodies to prevent memory exhaustion.
|
||||
const maxResponseSize = 10 * 1024 * 1024 // 10MB
|
||||
|
||||
// FetchModelsDirectly is used to fetch models directly from the API when the
|
||||
// standard OpenAI SDK method fails due to a nonstandard format. This is useful
|
||||
// for providers that return a direct array of models (e.g., GitHub Models) or
|
||||
// other OpenAI-compatible implementations.
|
||||
func FetchModelsDirectly(ctx context.Context, baseURL, apiKey, providerName string) ([]string, error) {
|
||||
if ctx == nil {
|
||||
ctx = context.Background()
|
||||
}
|
||||
if baseURL == "" {
|
||||
return nil, fmt.Errorf(i18n.T("openai_api_base_url_not_configured"), providerName)
|
||||
}
|
||||
|
||||
// Build the /models endpoint URL
|
||||
fullURL, err := url.JoinPath(baseURL, "models")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf(i18n.T("openai_failed_to_create_models_url"), err)
|
||||
}
|
||||
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, fullURL, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", apiKey))
|
||||
req.Header.Set("Accept", "application/json")
|
||||
|
||||
// TODO: Consider reusing a single http.Client instance (e.g., as a field on Client) instead of allocating a new one for
|
||||
// each request.
|
||||
client := &http.Client{
|
||||
Timeout: 10 * time.Second,
|
||||
}
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
// Read the response body for debugging, but limit the number of bytes read
|
||||
bodyBytes, readErr := io.ReadAll(io.LimitReader(resp.Body, errorResponseLimit))
|
||||
if readErr != nil {
|
||||
return nil, fmt.Errorf(i18n.T("openai_unexpected_status_code_read_error"),
|
||||
resp.StatusCode, providerName, readErr)
|
||||
}
|
||||
bodyString := string(bodyBytes)
|
||||
return nil, fmt.Errorf(i18n.T("openai_unexpected_status_code_with_body"),
|
||||
resp.StatusCode, providerName, bodyString)
|
||||
}
|
||||
|
||||
// Read the response body once, with a size limit to prevent memory exhaustion
|
||||
// Read up to maxResponseSize + 1 bytes to detect truncation
|
||||
bodyBytes, err := io.ReadAll(io.LimitReader(resp.Body, maxResponseSize+1))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(bodyBytes) > maxResponseSize {
|
||||
return nil, fmt.Errorf(i18n.T("openai_models_response_too_large"), providerName, maxResponseSize)
|
||||
}
|
||||
|
||||
// Try to parse as an object with data field (OpenAI format)
|
||||
var openAIFormat struct {
|
||||
Data []modelResponse `json:"data"`
|
||||
}
|
||||
// Try to parse as a direct array
|
||||
var directArray []modelResponse
|
||||
|
||||
if err := json.Unmarshal(bodyBytes, &openAIFormat); err == nil {
|
||||
debuglog.Debug(debuglog.Detailed, "Successfully parsed models response from %s using OpenAI format (found %d models)\n", providerName, len(openAIFormat.Data))
|
||||
return extractModelIDs(openAIFormat.Data), nil
|
||||
}
|
||||
|
||||
if err := json.Unmarshal(bodyBytes, &directArray); err == nil {
|
||||
debuglog.Debug(debuglog.Detailed, "Successfully parsed models response from %s using direct array format (found %d models)\n", providerName, len(directArray))
|
||||
return extractModelIDs(directArray), nil
|
||||
}
|
||||
|
||||
var truncatedBody string
|
||||
if len(bodyBytes) > errorResponseLimit {
|
||||
truncatedBody = string(bodyBytes[:errorResponseLimit]) + "..."
|
||||
} else {
|
||||
truncatedBody = string(bodyBytes)
|
||||
}
|
||||
return nil, fmt.Errorf(i18n.T("openai_unable_to_parse_models_response"), truncatedBody)
|
||||
}
|
||||
|
||||
func extractModelIDs(models []modelResponse) []string {
|
||||
modelIDs := make([]string, 0, len(models))
|
||||
for _, model := range models {
|
||||
modelIDs = append(modelIDs, model.ID)
|
||||
}
|
||||
return modelIDs
|
||||
}
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
|
||||
"github.com/danielmiessler/fabric/internal/chat"
|
||||
"github.com/danielmiessler/fabric/internal/domain"
|
||||
debuglog "github.com/danielmiessler/fabric/internal/log"
|
||||
"github.com/danielmiessler/fabric/internal/plugins"
|
||||
openai "github.com/openai/openai-go"
|
||||
"github.com/openai/openai-go/option"
|
||||
@@ -83,13 +84,19 @@ func (o *Client) configure() (ret error) {
|
||||
|
||||
func (o *Client) ListModels() (ret []string, err error) {
|
||||
var page *pagination.Page[openai.Model]
|
||||
if page, err = o.ApiClient.Models.List(context.Background()); err != nil {
|
||||
return
|
||||
if page, err = o.ApiClient.Models.List(context.Background()); err == nil {
|
||||
for _, mod := range page.Data {
|
||||
ret = append(ret, mod.ID)
|
||||
}
|
||||
// SDK succeeded - return the result even if empty
|
||||
return ret, nil
|
||||
}
|
||||
for _, mod := range page.Data {
|
||||
ret = append(ret, mod.ID)
|
||||
}
|
||||
return
|
||||
|
||||
// SDK returned an error - fall back to direct API fetch.
|
||||
// Some providers (e.g., GitHub Models) return non-standard response formats
|
||||
// that the SDK fails to parse.
|
||||
debuglog.Debug(debuglog.Basic, "SDK Models.List failed for %s: %v, falling back to direct API fetch\n", o.GetName(), err)
|
||||
return FetchModelsDirectly(context.Background(), o.ApiBaseURL.Value, o.ApiKey.Value, o.GetName())
|
||||
}
|
||||
|
||||
func (o *Client) SendStream(
|
||||
@@ -165,10 +172,11 @@ func (o *Client) supportsResponsesAPI() bool {
|
||||
|
||||
func (o *Client) NeedsRawMode(modelName string) bool {
|
||||
openaiModelsPrefixes := []string{
|
||||
"glm",
|
||||
"gpt-5",
|
||||
"o1",
|
||||
"o3",
|
||||
"o4",
|
||||
"gpt-5",
|
||||
}
|
||||
openAIModelsNeedingRaw := []string{
|
||||
"gpt-4o-mini-search-preview",
|
||||
|
||||
@@ -345,7 +345,7 @@ func TestAddImageGenerationToolWithUserParameters(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
opts *domain.ChatOptions
|
||||
expected map[string]interface{}
|
||||
expected map[string]any
|
||||
}{
|
||||
{
|
||||
name: "All parameters specified",
|
||||
@@ -356,7 +356,7 @@ func TestAddImageGenerationToolWithUserParameters(t *testing.T) {
|
||||
ImageBackground: "transparent",
|
||||
ImageCompression: 0, // Not applicable for PNG
|
||||
},
|
||||
expected: map[string]interface{}{
|
||||
expected: map[string]any{
|
||||
"size": "1536x1024",
|
||||
"quality": "high",
|
||||
"background": "transparent",
|
||||
@@ -372,7 +372,7 @@ func TestAddImageGenerationToolWithUserParameters(t *testing.T) {
|
||||
ImageBackground: "opaque",
|
||||
ImageCompression: 75,
|
||||
},
|
||||
expected: map[string]interface{}{
|
||||
expected: map[string]any{
|
||||
"size": "1024x1024",
|
||||
"quality": "medium",
|
||||
"background": "opaque",
|
||||
@@ -386,7 +386,7 @@ func TestAddImageGenerationToolWithUserParameters(t *testing.T) {
|
||||
ImageFile: "/tmp/test.webp",
|
||||
ImageQuality: "low",
|
||||
},
|
||||
expected: map[string]interface{}{
|
||||
expected: map[string]any{
|
||||
"quality": "low",
|
||||
"output_format": "webp",
|
||||
},
|
||||
@@ -396,7 +396,7 @@ func TestAddImageGenerationToolWithUserParameters(t *testing.T) {
|
||||
opts: &domain.ChatOptions{
|
||||
ImageFile: "/tmp/test.png",
|
||||
},
|
||||
expected: map[string]interface{}{
|
||||
expected: map[string]any{
|
||||
"output_format": "png",
|
||||
},
|
||||
},
|
||||
|
||||
58
internal/plugins/ai/openai/openai_models_test.go
Normal file
58
internal/plugins/ai/openai/openai_models_test.go
Normal file
@@ -0,0 +1,58 @@
|
||||
package openai
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
// Ensures we can fetch models directly when a provider returns a direct array of models
|
||||
// instead of the standard OpenAI list response structure.
|
||||
func TestFetchModelsDirectly_DirectArray(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
assert.Equal(t, "/models", r.URL.Path)
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
_, err := w.Write([]byte(`[{"id":"github-model"}]`))
|
||||
assert.NoError(t, err)
|
||||
}))
|
||||
defer srv.Close()
|
||||
|
||||
models, err := FetchModelsDirectly(context.Background(), srv.URL, "test-key", "TestProvider")
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, 1, len(models))
|
||||
assert.Equal(t, "github-model", models[0])
|
||||
}
|
||||
|
||||
// Ensures we can fetch models when a provider returns the standard OpenAI format
|
||||
func TestFetchModelsDirectly_OpenAIFormat(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
assert.Equal(t, "/models", r.URL.Path)
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
_, err := w.Write([]byte(`{"data":[{"id":"openai-model"}]}`))
|
||||
assert.NoError(t, err)
|
||||
}))
|
||||
defer srv.Close()
|
||||
|
||||
models, err := FetchModelsDirectly(context.Background(), srv.URL, "test-key", "TestProvider")
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, 1, len(models))
|
||||
assert.Equal(t, "openai-model", models[0])
|
||||
}
|
||||
|
||||
// Ensures we handle empty model lists correctly
|
||||
func TestFetchModelsDirectly_EmptyArray(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
assert.Equal(t, "/models", r.URL.Path)
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
_, err := w.Write([]byte(`[]`))
|
||||
assert.NoError(t, err)
|
||||
}))
|
||||
defer srv.Close()
|
||||
|
||||
models, err := FetchModelsDirectly(context.Background(), srv.URL, "test-key", "TestProvider")
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, 0, len(models))
|
||||
}
|
||||
@@ -2,104 +2,12 @@ package openai_compatible
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"time"
|
||||
|
||||
"github.com/danielmiessler/fabric/internal/plugins/ai/openai"
|
||||
)
|
||||
|
||||
// Model represents a model returned by the API
|
||||
type Model struct {
|
||||
ID string `json:"id"`
|
||||
}
|
||||
|
||||
// ErrorResponseLimit defines the maximum length of error response bodies for truncation.
|
||||
const errorResponseLimit = 1024 // Limit for error response body size
|
||||
|
||||
// DirectlyGetModels is used to fetch models directly from the API
|
||||
// when the standard OpenAI SDK method fails due to a nonstandard format.
|
||||
// This is useful for providers like Together that return a direct array of models.
|
||||
// DirectlyGetModels is used to fetch models directly from the API when the
|
||||
// standard OpenAI SDK method fails due to a nonstandard format.
|
||||
func (c *Client) DirectlyGetModels(ctx context.Context) ([]string, error) {
|
||||
if ctx == nil {
|
||||
ctx = context.Background()
|
||||
}
|
||||
baseURL := c.ApiBaseURL.Value
|
||||
if baseURL == "" {
|
||||
return nil, fmt.Errorf("API base URL not configured for provider %s", c.GetName())
|
||||
}
|
||||
|
||||
// Build the /models endpoint URL
|
||||
fullURL, err := url.JoinPath(baseURL, "models")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create models URL: %w", err)
|
||||
}
|
||||
|
||||
req, err := http.NewRequestWithContext(ctx, "GET", fullURL, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", c.ApiKey.Value))
|
||||
req.Header.Set("Accept", "application/json")
|
||||
|
||||
// TODO: Consider reusing a single http.Client instance (e.g., as a field on Client) instead of allocating a new one for each request.
|
||||
|
||||
client := &http.Client{
|
||||
Timeout: 10 * time.Second,
|
||||
}
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
// Read the response body for debugging
|
||||
bodyBytes, _ := io.ReadAll(resp.Body)
|
||||
bodyString := string(bodyBytes)
|
||||
if len(bodyString) > errorResponseLimit { // Truncate if too large
|
||||
bodyString = bodyString[:errorResponseLimit] + "..."
|
||||
}
|
||||
return nil, fmt.Errorf("unexpected status code: %d from provider %s, response body: %s",
|
||||
resp.StatusCode, c.GetName(), bodyString)
|
||||
}
|
||||
|
||||
// Read the response body once
|
||||
bodyBytes, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Try to parse as an object with data field (OpenAI format)
|
||||
var openAIFormat struct {
|
||||
Data []Model `json:"data"`
|
||||
}
|
||||
// Try to parse as a direct array (Together format)
|
||||
var directArray []Model
|
||||
|
||||
if err := json.Unmarshal(bodyBytes, &openAIFormat); err == nil && len(openAIFormat.Data) > 0 {
|
||||
return extractModelIDs(openAIFormat.Data), nil
|
||||
}
|
||||
|
||||
if err := json.Unmarshal(bodyBytes, &directArray); err == nil && len(directArray) > 0 {
|
||||
return extractModelIDs(directArray), nil
|
||||
}
|
||||
|
||||
var truncatedBody string
|
||||
if len(bodyBytes) > errorResponseLimit {
|
||||
truncatedBody = string(bodyBytes[:errorResponseLimit]) + "..."
|
||||
} else {
|
||||
truncatedBody = string(bodyBytes)
|
||||
}
|
||||
return nil, fmt.Errorf("unable to parse models response; raw response: %s", truncatedBody)
|
||||
}
|
||||
|
||||
func extractModelIDs(models []Model) []string {
|
||||
modelIDs := make([]string, 0, len(models))
|
||||
for _, model := range models {
|
||||
modelIDs = append(modelIDs, model.ID)
|
||||
}
|
||||
return modelIDs
|
||||
return openai.FetchModelsDirectly(ctx, c.ApiBaseURL.Value, c.ApiKey.Value, c.GetName())
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package openai_compatible
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
@@ -12,17 +13,21 @@ import (
|
||||
type ProviderConfig struct {
|
||||
Name string
|
||||
BaseURL string
|
||||
ImplementsResponses bool // Whether the provider supports OpenAI's new Responses API
|
||||
ModelsURL string // Optional: Custom endpoint for listing models (if different from BaseURL/models)
|
||||
ImplementsResponses bool // Whether the provider supports OpenAI's new Responses API
|
||||
}
|
||||
|
||||
// Client is the common structure for all OpenAI-compatible providers
|
||||
type Client struct {
|
||||
*openai.Client
|
||||
modelsURL string // Custom URL for listing models (if different from BaseURL/models)
|
||||
}
|
||||
|
||||
// NewClient creates a new OpenAI-compatible client for the specified provider
|
||||
func NewClient(providerConfig ProviderConfig) *Client {
|
||||
client := &Client{}
|
||||
client := &Client{
|
||||
modelsURL: providerConfig.ModelsURL,
|
||||
}
|
||||
client.Client = openai.NewClientCompatibleWithResponses(
|
||||
providerConfig.Name,
|
||||
providerConfig.BaseURL,
|
||||
@@ -34,17 +39,89 @@ func NewClient(providerConfig ProviderConfig) *Client {
|
||||
|
||||
// ListModels overrides the default ListModels to handle different response formats
|
||||
func (c *Client) ListModels() ([]string, error) {
|
||||
// If a custom models URL is provided, handle it
|
||||
if c.modelsURL != "" {
|
||||
// Check for static model list
|
||||
if strings.HasPrefix(c.modelsURL, "static:") {
|
||||
return c.getStaticModels(c.modelsURL)
|
||||
}
|
||||
// TODO: Handle context properly in Fabric by accepting and propagating a context.Context
|
||||
// instead of creating a new one here.
|
||||
return openai.FetchModelsDirectly(context.Background(), c.modelsURL, c.Client.ApiKey.Value, c.GetName())
|
||||
}
|
||||
|
||||
// First try the standard OpenAI SDK approach
|
||||
models, err := c.Client.ListModels()
|
||||
if err == nil && len(models) > 0 { // only return if OpenAI SDK returns models
|
||||
return models, nil
|
||||
}
|
||||
|
||||
// TODO: Handle context properly in Fabric by accepting and propagating a context.Context
|
||||
// instead of creating a new one here.
|
||||
// Fall back to direct API fetch
|
||||
return c.DirectlyGetModels(context.Background())
|
||||
}
|
||||
|
||||
// getStaticModels returns a predefined list of models for providers that don't support model discovery
|
||||
func (c *Client) getStaticModels(modelsKey string) ([]string, error) {
|
||||
switch modelsKey {
|
||||
case "static:abacus":
|
||||
return []string{
|
||||
"route-llm",
|
||||
"gpt-4o-2024-11-20",
|
||||
"gpt-4o-mini",
|
||||
"o4-mini",
|
||||
"o3-pro",
|
||||
"o3",
|
||||
"o3-mini",
|
||||
"gpt-4.1",
|
||||
"gpt-4.1-mini",
|
||||
"gpt-4.1-nano",
|
||||
"gpt-5",
|
||||
"gpt-5-mini",
|
||||
"gpt-5-nano",
|
||||
"gpt-5.1",
|
||||
"gpt-5.1-chat-latest",
|
||||
"openai/gpt-oss-120b",
|
||||
"claude-3-7-sonnet-20250219",
|
||||
"claude-sonnet-4-20250514",
|
||||
"claude-opus-4-20250514",
|
||||
"claude-opus-4-1-20250805",
|
||||
"claude-sonnet-4-5-20250929",
|
||||
"claude-haiku-4-5-20251001",
|
||||
"claude-opus-4-5-20251101",
|
||||
"meta-llama/Llama-4-Maverick-17B-128E-Instruct-FP8",
|
||||
"meta-llama/Meta-Llama-3.1-405B-Instruct-Turbo",
|
||||
"meta-llama/Meta-Llama-3.1-70B-Instruct",
|
||||
"meta-llama/Meta-Llama-3.1-8B-Instruct",
|
||||
"llama-3.3-70b-versatile",
|
||||
"gemini-2.0-flash-001",
|
||||
"gemini-2.0-pro-exp-02-05",
|
||||
"gemini-2.5-pro",
|
||||
"gemini-2.5-flash",
|
||||
"gemini-3-pro-preview",
|
||||
"qwen-2.5-coder-32b",
|
||||
"Qwen/Qwen2.5-72B-Instruct",
|
||||
"Qwen/QwQ-32B",
|
||||
"Qwen/Qwen3-235B-A22B-Instruct-2507",
|
||||
"Qwen/Qwen3-32B",
|
||||
"qwen/qwen3-coder-480b-a35b-instruct",
|
||||
"qwen/qwen3-Max",
|
||||
"grok-4-0709",
|
||||
"grok-4-fast-non-reasoning",
|
||||
"grok-4-1-fast-non-reasoning",
|
||||
"grok-code-fast-1",
|
||||
"kimi-k2-turbo-preview",
|
||||
"deepseek/deepseek-v3.1",
|
||||
"deepseek-ai/DeepSeek-V3.1-Terminus",
|
||||
"deepseek-ai/DeepSeek-R1",
|
||||
"deepseek-ai/DeepSeek-V3.2",
|
||||
"zai-org/glm-4.5",
|
||||
"zai-org/glm-4.6",
|
||||
}, nil
|
||||
default:
|
||||
return nil, fmt.Errorf("unknown static model list: %s", modelsKey)
|
||||
}
|
||||
}
|
||||
|
||||
// ProviderMap is a map of provider name to ProviderConfig for O(1) lookup
|
||||
var ProviderMap = map[string]ProviderConfig{
|
||||
"AIML": {
|
||||
@@ -62,6 +139,12 @@ var ProviderMap = map[string]ProviderConfig{
|
||||
BaseURL: "https://api.deepseek.com",
|
||||
ImplementsResponses: false,
|
||||
},
|
||||
"GitHub": {
|
||||
Name: "GitHub",
|
||||
BaseURL: "https://models.github.ai/inference",
|
||||
ModelsURL: "https://models.github.ai/catalog", // FetchModelsDirectly will append /models
|
||||
ImplementsResponses: false,
|
||||
},
|
||||
"GrokAI": {
|
||||
Name: "GrokAI",
|
||||
BaseURL: "https://api.x.ai/v1",
|
||||
@@ -107,6 +190,17 @@ var ProviderMap = map[string]ProviderConfig{
|
||||
BaseURL: "https://api.venice.ai/api/v1",
|
||||
ImplementsResponses: false,
|
||||
},
|
||||
"Z AI": {
|
||||
Name: "Z AI",
|
||||
BaseURL: "https://api.z.ai/api/paas/v4",
|
||||
ImplementsResponses: false,
|
||||
},
|
||||
"Abacus": {
|
||||
Name: "Abacus",
|
||||
BaseURL: "https://routellm.abacus.ai/v1/",
|
||||
ModelsURL: "static:abacus", // Special marker for static model list
|
||||
ImplementsResponses: false,
|
||||
},
|
||||
}
|
||||
|
||||
// GetProviderByName returns the provider configuration for a given name with O(1) lookup
|
||||
|
||||
@@ -20,6 +20,16 @@ func TestCreateClient(t *testing.T) {
|
||||
provider: "Groq",
|
||||
exists: true,
|
||||
},
|
||||
{
|
||||
name: "Existing provider - Z AI",
|
||||
provider: "Z AI",
|
||||
exists: true,
|
||||
},
|
||||
{
|
||||
name: "Existing provider - Abacus",
|
||||
provider: "Abacus",
|
||||
exists: true,
|
||||
},
|
||||
{
|
||||
name: "Non-existent provider",
|
||||
provider: "NonExistent",
|
||||
|
||||
@@ -25,9 +25,12 @@ type VendorsManager struct {
|
||||
Models *VendorsModels
|
||||
}
|
||||
|
||||
// AddVendors registers one or more vendors with the manager.
|
||||
// Vendors are stored with lowercase keys to enable case-insensitive lookup.
|
||||
func (o *VendorsManager) AddVendors(vendors ...Vendor) {
|
||||
for _, vendor := range vendors {
|
||||
o.VendorsByName[vendor.GetName()] = vendor
|
||||
name := strings.ToLower(vendor.GetName())
|
||||
o.VendorsByName[name] = vendor
|
||||
o.Vendors = append(o.Vendors, vendor)
|
||||
}
|
||||
}
|
||||
@@ -63,8 +66,10 @@ func (o *VendorsManager) HasVendors() bool {
|
||||
return len(o.Vendors) > 0
|
||||
}
|
||||
|
||||
// FindByName returns a vendor by name. Lookup is case-insensitive.
|
||||
// For example, "OpenAI", "openai", and "OPENAI" all match the same vendor.
|
||||
func (o *VendorsManager) FindByName(name string) Vendor {
|
||||
return o.VendorsByName[name]
|
||||
return o.VendorsByName[strings.ToLower(name)]
|
||||
}
|
||||
|
||||
func (o *VendorsManager) readModels() (err error) {
|
||||
@@ -143,9 +148,9 @@ func (o *VendorsManager) SetupVendor(vendorName string, configuredVendors map[st
|
||||
func (o *VendorsManager) setupVendorTo(vendor Vendor, configuredVendors map[string]Vendor) {
|
||||
if vendorErr := vendor.Setup(); vendorErr == nil {
|
||||
fmt.Printf("[%v] configured\n", vendor.GetName())
|
||||
configuredVendors[vendor.GetName()] = vendor
|
||||
configuredVendors[strings.ToLower(vendor.GetName())] = vendor
|
||||
} else {
|
||||
delete(configuredVendors, vendor.GetName())
|
||||
delete(configuredVendors, strings.ToLower(vendor.GetName()))
|
||||
fmt.Printf("[%v] skipped\n", vendor.GetName())
|
||||
}
|
||||
}
|
||||
|
||||
66
internal/plugins/ai/vendors_test.go
Normal file
66
internal/plugins/ai/vendors_test.go
Normal file
@@ -0,0 +1,66 @@
|
||||
package ai
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/danielmiessler/fabric/internal/chat"
|
||||
"github.com/danielmiessler/fabric/internal/domain"
|
||||
)
|
||||
|
||||
type stubVendor struct {
|
||||
name string
|
||||
}
|
||||
|
||||
func (v *stubVendor) GetName() string { return v.name }
|
||||
func (v *stubVendor) GetSetupDescription() string { return "" }
|
||||
func (v *stubVendor) IsConfigured() bool { return true }
|
||||
func (v *stubVendor) Configure() error { return nil }
|
||||
func (v *stubVendor) Setup() error { return nil }
|
||||
func (v *stubVendor) SetupFillEnvFileContent(*bytes.Buffer) {}
|
||||
func (v *stubVendor) ListModels() ([]string, error) { return nil, nil }
|
||||
func (v *stubVendor) SendStream([]*chat.ChatCompletionMessage, *domain.ChatOptions, chan string) error {
|
||||
return nil
|
||||
}
|
||||
func (v *stubVendor) Send(context.Context, []*chat.ChatCompletionMessage, *domain.ChatOptions) (string, error) {
|
||||
return "", nil
|
||||
}
|
||||
func (v *stubVendor) NeedsRawMode(string) bool { return false }
|
||||
|
||||
func TestVendorsManagerFindByNameCaseInsensitive(t *testing.T) {
|
||||
manager := NewVendorsManager()
|
||||
vendor := &stubVendor{name: "OpenAI"}
|
||||
|
||||
manager.AddVendors(vendor)
|
||||
|
||||
if got := manager.FindByName("openai"); got != vendor {
|
||||
t.Fatalf("FindByName lowercase = %v, want %v", got, vendor)
|
||||
}
|
||||
|
||||
if got := manager.FindByName("OPENAI"); got != vendor {
|
||||
t.Fatalf("FindByName uppercase = %v, want %v", got, vendor)
|
||||
}
|
||||
|
||||
if got := manager.FindByName("OpenAI"); got != vendor {
|
||||
t.Fatalf("FindByName mixed case = %v, want %v", got, vendor)
|
||||
}
|
||||
}
|
||||
|
||||
func TestVendorsManagerSetupVendorToCaseInsensitive(t *testing.T) {
|
||||
manager := NewVendorsManager()
|
||||
vendor := &stubVendor{name: "OpenAI"}
|
||||
|
||||
configured := map[string]Vendor{}
|
||||
manager.setupVendorTo(vendor, configured)
|
||||
|
||||
// Verify vendor is stored with lowercase key
|
||||
if _, ok := configured["openai"]; !ok {
|
||||
t.Fatalf("setupVendorTo should store vendor using lowercase key")
|
||||
}
|
||||
|
||||
// Verify original case key is not used
|
||||
if _, ok := configured["OpenAI"]; ok {
|
||||
t.Fatalf("setupVendorTo should not store vendor using original case key")
|
||||
}
|
||||
}
|
||||
@@ -134,7 +134,7 @@ func (o *StorageEntity) buildFileName(name string) string {
|
||||
return fmt.Sprintf("%s%v", name, o.FileExtension)
|
||||
}
|
||||
|
||||
func (o *StorageEntity) SaveAsJson(name string, item interface{}) (err error) {
|
||||
func (o *StorageEntity) SaveAsJson(name string, item any) (err error) {
|
||||
var jsonString []byte
|
||||
if jsonString, err = json.Marshal(item); err == nil {
|
||||
err = o.Save(name, jsonString)
|
||||
@@ -145,7 +145,7 @@ func (o *StorageEntity) SaveAsJson(name string, item interface{}) (err error) {
|
||||
return err
|
||||
}
|
||||
|
||||
func (o *StorageEntity) LoadAsJson(name string, item interface{}) (err error) {
|
||||
func (o *StorageEntity) LoadAsJson(name string, item any) (err error) {
|
||||
var content []byte
|
||||
if content, err = o.Load(name); err != nil {
|
||||
return
|
||||
|
||||
@@ -92,7 +92,11 @@ func (o *PluginBase) Setup() (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
err = o.Configure()
|
||||
// After Setup, run ConfigureCustom if present, but skip re-validation
|
||||
// since Ask() already validated user input (or allowed explicit reset)
|
||||
if o.ConfigureCustom != nil {
|
||||
err = o.ConfigureCustom()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
@@ -198,16 +202,21 @@ func (o *SetupQuestion) Ask(label string) (err error) {
|
||||
var answer string
|
||||
fmt.Scanln(&answer)
|
||||
answer = strings.TrimRight(answer, "\n")
|
||||
isReset := strings.ToLower(answer) == AnswerReset
|
||||
if answer == "" {
|
||||
answer = o.Value
|
||||
} else if strings.ToLower(answer) == AnswerReset {
|
||||
} else if isReset {
|
||||
answer = ""
|
||||
}
|
||||
err = o.OnAnswer(answer)
|
||||
err = o.OnAnswerWithReset(answer, isReset)
|
||||
return
|
||||
}
|
||||
|
||||
func (o *SetupQuestion) OnAnswer(answer string) (err error) {
|
||||
return o.OnAnswerWithReset(answer, false)
|
||||
}
|
||||
|
||||
func (o *SetupQuestion) OnAnswerWithReset(answer string, isReset bool) (err error) {
|
||||
if o.Type == SettingTypeBool {
|
||||
if answer == "" {
|
||||
o.Value = ""
|
||||
@@ -226,6 +235,11 @@ func (o *SetupQuestion) OnAnswer(answer string) (err error) {
|
||||
return
|
||||
}
|
||||
}
|
||||
// Skip validation when explicitly resetting a value - the user intentionally
|
||||
// wants to clear the value even if it's required
|
||||
if isReset {
|
||||
return nil
|
||||
}
|
||||
err = o.IsValidErr()
|
||||
return
|
||||
}
|
||||
|
||||
@@ -116,6 +116,91 @@ func TestSetupQuestion_Ask(t *testing.T) {
|
||||
assert.Equal(t, "user_value", setting.Value)
|
||||
}
|
||||
|
||||
func TestSetupQuestion_Ask_Reset(t *testing.T) {
|
||||
// Test that resetting a required field doesn't produce an error
|
||||
setting := &Setting{
|
||||
EnvVariable: "TEST_RESET_SETTING",
|
||||
Value: "existing_value",
|
||||
Required: true,
|
||||
}
|
||||
question := &SetupQuestion{
|
||||
Setting: setting,
|
||||
Question: "Enter test setting:",
|
||||
}
|
||||
input := "reset\n"
|
||||
fmtInput := captureInput(input)
|
||||
defer fmtInput()
|
||||
err := question.Ask("TestConfigurable")
|
||||
// Should NOT return an error even though the field is required
|
||||
assert.NoError(t, err)
|
||||
// Value should be cleared
|
||||
assert.Equal(t, "", setting.Value)
|
||||
}
|
||||
|
||||
func TestSetupQuestion_OnAnswerWithReset(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
setting *Setting
|
||||
answer string
|
||||
isReset bool
|
||||
expectError bool
|
||||
expectValue string
|
||||
}{
|
||||
{
|
||||
name: "reset required field should not error",
|
||||
setting: &Setting{
|
||||
EnvVariable: "TEST_SETTING",
|
||||
Value: "old_value",
|
||||
Required: true,
|
||||
},
|
||||
answer: "",
|
||||
isReset: true,
|
||||
expectError: false,
|
||||
expectValue: "",
|
||||
},
|
||||
{
|
||||
name: "empty answer on required field should error",
|
||||
setting: &Setting{
|
||||
EnvVariable: "TEST_SETTING",
|
||||
Value: "",
|
||||
Required: true,
|
||||
},
|
||||
answer: "",
|
||||
isReset: false,
|
||||
expectError: true,
|
||||
expectValue: "",
|
||||
},
|
||||
{
|
||||
name: "valid answer on required field should not error",
|
||||
setting: &Setting{
|
||||
EnvVariable: "TEST_SETTING",
|
||||
Value: "",
|
||||
Required: true,
|
||||
},
|
||||
answer: "new_value",
|
||||
isReset: false,
|
||||
expectError: false,
|
||||
expectValue: "new_value",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
question := &SetupQuestion{
|
||||
Setting: tt.setting,
|
||||
Question: "Test question",
|
||||
}
|
||||
err := question.OnAnswerWithReset(tt.answer, tt.isReset)
|
||||
if tt.expectError {
|
||||
assert.Error(t, err)
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
assert.Equal(t, tt.expectValue, tt.setting.Value)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSettings_IsConfigured(t *testing.T) {
|
||||
settings := Settings{
|
||||
{EnvVariable: "TEST_SETTING1", Value: "value1", Required: true},
|
||||
|
||||
@@ -187,7 +187,7 @@ esac`
|
||||
executor := NewExtensionExecutor(registry)
|
||||
|
||||
// Helper function to create and register extension
|
||||
createExtension := func(name, opName, cmdTemplate string, config map[string]interface{}) error {
|
||||
createExtension := func(name, opName, cmdTemplate string, config map[string]any) error {
|
||||
configPath := filepath.Join(tmpDir, name+".yaml")
|
||||
configContent := `name: ` + name + `
|
||||
executable: ` + testScript + `
|
||||
@@ -216,7 +216,7 @@ config:
|
||||
// Test basic fixed file output
|
||||
t.Run("BasicFixedFile", func(t *testing.T) {
|
||||
outputFile := filepath.Join(tmpDir, "output.txt")
|
||||
config := map[string]interface{}{
|
||||
config := map[string]any{
|
||||
"output_file": `"output.txt"`,
|
||||
"work_dir": `"` + tmpDir + `"`,
|
||||
"cleanup": "true",
|
||||
@@ -241,7 +241,7 @@ config:
|
||||
|
||||
// Test no work_dir specified
|
||||
t.Run("NoWorkDir", func(t *testing.T) {
|
||||
config := map[string]interface{}{
|
||||
config := map[string]any{
|
||||
"output_file": `"direct-output.txt"`,
|
||||
"cleanup": "true",
|
||||
}
|
||||
@@ -263,7 +263,7 @@ config:
|
||||
outputFile := filepath.Join(tmpDir, "cleanup-test.txt")
|
||||
|
||||
// Test with cleanup enabled
|
||||
config := map[string]interface{}{
|
||||
config := map[string]any{
|
||||
"output_file": `"cleanup-test.txt"`,
|
||||
"work_dir": `"` + tmpDir + `"`,
|
||||
"cleanup": "true",
|
||||
@@ -307,7 +307,7 @@ config:
|
||||
// Test error cases
|
||||
t.Run("ErrorCases", func(t *testing.T) {
|
||||
outputFile := filepath.Join(tmpDir, "error-test.txt")
|
||||
config := map[string]interface{}{
|
||||
config := map[string]any{
|
||||
"output_file": `"error-test.txt"`,
|
||||
"work_dir": `"` + tmpDir + `"`,
|
||||
"cleanup": "true",
|
||||
@@ -341,7 +341,7 @@ config:
|
||||
|
||||
// Test with missing output_file
|
||||
t.Run("MissingOutputFile", func(t *testing.T) {
|
||||
config := map[string]interface{}{
|
||||
config := map[string]any{
|
||||
"work_dir": `"` + tmpDir + `"`,
|
||||
"cleanup": "true",
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@ type ExtensionDefinition struct {
|
||||
Operations map[string]OperationConfig `yaml:"operations"`
|
||||
|
||||
// Additional config
|
||||
Config map[string]interface{} `yaml:"config"`
|
||||
Config map[string]any `yaml:"config"`
|
||||
}
|
||||
|
||||
type OperationConfig struct {
|
||||
@@ -53,7 +53,7 @@ type ExtensionRegistry struct {
|
||||
|
||||
// Helper methods for Config access
|
||||
func (e *ExtensionDefinition) GetOutputMethod() string {
|
||||
if output, ok := e.Config["output"].(map[string]interface{}); ok {
|
||||
if output, ok := e.Config["output"].(map[string]any); ok {
|
||||
if method, ok := output["method"].(string); ok {
|
||||
return method
|
||||
}
|
||||
@@ -61,9 +61,9 @@ func (e *ExtensionDefinition) GetOutputMethod() string {
|
||||
return "stdout" // default to stdout if not specified
|
||||
}
|
||||
|
||||
func (e *ExtensionDefinition) GetFileConfig() map[string]interface{} {
|
||||
if output, ok := e.Config["output"].(map[string]interface{}); ok {
|
||||
if fileConfig, ok := output["file_config"].(map[string]interface{}); ok {
|
||||
func (e *ExtensionDefinition) GetFileConfig() map[string]any {
|
||||
if output, ok := e.Config["output"].(map[string]any); ok {
|
||||
if fileConfig, ok := output["file_config"].(map[string]any); ok {
|
||||
return fileConfig
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,7 +33,7 @@ func init() {
|
||||
var pluginPattern = regexp.MustCompile(`\{\{plugin:([^:]+):([^:]+)(?::([^}]+))?\}\}`)
|
||||
var extensionPattern = regexp.MustCompile(`\{\{ext:([^:]+):([^:]+)(?::([^}]+))?\}\}`)
|
||||
|
||||
func debugf(format string, a ...interface{}) {
|
||||
func debugf(format string, a ...any) {
|
||||
debuglog.Debug(debuglog.Trace, format, a...)
|
||||
}
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ func (h *ModelsHandler) GetModelNames(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
response := make(map[string]interface{})
|
||||
response := make(map[string]any)
|
||||
vendors := make(map[string][]string)
|
||||
|
||||
for _, groupItems := range vendorsModels.GroupsItems {
|
||||
|
||||
@@ -133,7 +133,7 @@ func (o *GroupsItemsSelector[I]) Print(shellCompleteList bool) {
|
||||
|
||||
func (o *GroupsItemsSelector[I]) HasGroup(group string) (ret bool) {
|
||||
for _, groupItems := range o.GroupsItems {
|
||||
if ret = groupItems.Group == group; ret {
|
||||
if ret = strings.EqualFold(groupItems.Group, group); ret {
|
||||
break
|
||||
}
|
||||
}
|
||||
@@ -146,7 +146,7 @@ func (o *GroupsItemsSelector[I]) FindGroupsByItemFirst(item I) (ret string) {
|
||||
for _, groupItems := range o.GroupsItems {
|
||||
if groupItems.ContainsItemBy(func(groupItem I) bool {
|
||||
groupItemKey := o.GetItemKey(groupItem)
|
||||
return groupItemKey == itemKey
|
||||
return strings.EqualFold(groupItemKey, itemKey)
|
||||
}) {
|
||||
ret = groupItems.Group
|
||||
break
|
||||
@@ -161,7 +161,7 @@ func (o *GroupsItemsSelector[I]) FindGroupsByItem(item I) (groups []string) {
|
||||
for _, groupItems := range o.GroupsItems {
|
||||
if groupItems.ContainsItemBy(func(groupItem I) bool {
|
||||
groupItemKey := o.GetItemKey(groupItem)
|
||||
return groupItemKey == itemKey
|
||||
return strings.EqualFold(groupItemKey, itemKey)
|
||||
}) {
|
||||
groups = append(groups, groupItems.Group)
|
||||
}
|
||||
|
||||
@@ -32,8 +32,8 @@ schema = 3
|
||||
version = "v1.3.3"
|
||||
hash = "sha256-jv7ZshpSd7FZzKKN6hqlUgiR8C3y85zNIS/hq7g76Ho="
|
||||
[mod."github.com/anthropics/anthropic-sdk-go"]
|
||||
version = "v1.16.0"
|
||||
hash = "sha256-hD6Ix+V5IBFfoaCuAZemrDQx/+G111fCYHn2FAxFuEE="
|
||||
version = "v1.19.0"
|
||||
hash = "sha256-ubYeau5XL0tx4c/79L58rzJGOdOWs9z6WQOtN6mpgxw="
|
||||
[mod."github.com/araddon/dateparse"]
|
||||
version = "v0.0.0-20210429162001-6b43995a97de"
|
||||
hash = "sha256-UuX84naeRGMsFOgIgRoBHG5sNy1CzBkWPKmd6VbLwFw="
|
||||
@@ -236,8 +236,8 @@ schema = 3
|
||||
version = "v0.11.7"
|
||||
hash = "sha256-3Wn1JWmil0aQQ2I/r398HbnUsi8ADoroqNyPziuxn/c="
|
||||
[mod."github.com/openai/openai-go"]
|
||||
version = "v1.8.2"
|
||||
hash = "sha256-O8aV3zEj6o8kIlzlkYaTW4RzvwR3qNUBYiN8SuTM1R0="
|
||||
version = "v1.12.0"
|
||||
hash = "sha256-JHLlKvDwERPf728GUXBsKU58ODgCxcxEe9TKJTGAG1w="
|
||||
[mod."github.com/otiai10/copy"]
|
||||
version = "v1.14.1"
|
||||
hash = "sha256-8RR7u17SbYg9AeBXVHIv5ZMU+kHmOcx0rLUKyz6YtU0="
|
||||
@@ -317,26 +317,26 @@ schema = 3
|
||||
version = "v0.18.0"
|
||||
hash = "sha256-tUpUPERjmRi7zldj0oPlnbnBhEkcI9iQGvP1HqlsK10="
|
||||
[mod."golang.org/x/crypto"]
|
||||
version = "v0.41.0"
|
||||
hash = "sha256-o5Di0lsFmYnXl7a5MBTqmN9vXMCRpE9ay71C1Ar8jEY="
|
||||
version = "v0.45.0"
|
||||
hash = "sha256-IpNesJYxFcs2jGvagwJrUD/gsJfA3UiETjQwYByXxSY="
|
||||
[mod."golang.org/x/exp"]
|
||||
version = "v0.0.0-20250531010427-b6e5de432a8b"
|
||||
hash = "sha256-QaFfjyB+pogCkUkJskR9xnXwkCOU828XJRrzwwLm6Ms="
|
||||
[mod."golang.org/x/net"]
|
||||
version = "v0.43.0"
|
||||
hash = "sha256-bf3iQFrsC8BoarVaS0uSspEFAcr1zHp1uziTtBpwV34="
|
||||
version = "v0.47.0"
|
||||
hash = "sha256-2qFgCd0YfNCGkLrf+xvnhQtKjSe8CymMdLlN3svUYTg="
|
||||
[mod."golang.org/x/oauth2"]
|
||||
version = "v0.30.0"
|
||||
hash = "sha256-btD7BUtQpOswusZY5qIU90uDo38buVrQ0tmmQ8qNHDg="
|
||||
[mod."golang.org/x/sync"]
|
||||
version = "v0.16.0"
|
||||
hash = "sha256-sqKDRESeMzLe0jWGWltLZL/JIgrn0XaIeBWCzVN3Bks="
|
||||
version = "v0.18.0"
|
||||
hash = "sha256-S8o6y7GOaYWeq+TzT8BB6T+1mg82Mu08V0TL3ukJprg="
|
||||
[mod."golang.org/x/sys"]
|
||||
version = "v0.35.0"
|
||||
hash = "sha256-ZKM8pesQE6NAFZeKQ84oPn5JMhGr8g4TSwLYAsHMGSI="
|
||||
version = "v0.38.0"
|
||||
hash = "sha256-1+i5EaG3JwH3KMtefzJLG5R6jbOeJM4GK3/LHBVnSy0="
|
||||
[mod."golang.org/x/text"]
|
||||
version = "v0.28.0"
|
||||
hash = "sha256-8UlJniGK+km4Hmrw6XMxELnExgrih7+z8tU26Cntmto="
|
||||
version = "v0.31.0"
|
||||
hash = "sha256-AT46RrSmV6+/d5FDhs9fPwYzmQ7WSo+YL9tPfhREwLw="
|
||||
[mod."google.golang.org/api"]
|
||||
version = "v0.247.0"
|
||||
hash = "sha256-UzTtydHmNqh1OXbxcN5qNKQxb5dV6h2Mo6DH4P219Ec="
|
||||
|
||||
@@ -1 +1 @@
|
||||
"1.4.329"
|
||||
"1.4.346"
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
"""Extracts pattern information from the ~/.config/fabric/patterns directory,
|
||||
creates JSON files for pattern extracts and descriptions, and updates web static files.
|
||||
"""
|
||||
|
||||
import os
|
||||
import json
|
||||
import shutil
|
||||
@@ -33,7 +34,13 @@ def get_pattern_extract(pattern_path):
|
||||
def extract_pattern_info():
|
||||
"""Extract pattern information from the patterns directory"""
|
||||
script_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
patterns_dir = os.path.expanduser("~/.config/fabric/patterns")
|
||||
local_patterns_dir = os.path.join(script_dir, "..", "..", "data", "patterns")
|
||||
|
||||
if os.path.exists(local_patterns_dir):
|
||||
patterns_dir = local_patterns_dir
|
||||
else:
|
||||
patterns_dir = os.path.expanduser("~/.config/fabric/patterns")
|
||||
|
||||
print(f"\nScanning patterns directory: {patterns_dir}")
|
||||
|
||||
extracts_path = os.path.join(script_dir, "pattern_extracts.json")
|
||||
|
||||
@@ -1924,6 +1924,14 @@
|
||||
"tags": [
|
||||
"VISUALIZE"
|
||||
]
|
||||
},
|
||||
{
|
||||
"patternName": "concall_summary",
|
||||
"description": "Extract strategic insights from earnings transcripts for investors.",
|
||||
"tags": [
|
||||
"SUMMARIZE",
|
||||
"BUSINESS"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -931,6 +931,10 @@
|
||||
{
|
||||
"patternName": "create_conceptmap",
|
||||
"pattern_extract": "--- ### IDENTITY AND PURPOSE You are an intelligent assistant specialized in **knowledge visualization and educational data structuring**. You are capable of reading unstructured textual content (.txt or .md files), extracting **main concepts, subthemes, and logical relationships**, and transforming them into a **fully interactive conceptual map** built in **HTML using Vis.js (vis-network)**. You understand hierarchical, causal, and correlative relations between ideas and express them through **nodes and directed edges**. You ensure that the resulting HTML file is **autonomous, interactive, and visually consistent** with the Vis.js framework. You are precise, systematic, and maintain semantic coherence between concepts and their relationships. You automatically name the output file according to the **detected topic**, ensuring compatibility and clarity (e.g., `map_hist_china.html`). --- ### TASK You are given a `.txt` or `.md` file containing explanatory, conceptual, or thematic content. Your task is to: 1. **Extract** the main concepts and secondary ideas. 2. **Identify logical or hierarchical relationships** among these concepts using concise action verbs. 3. **Structure the output** as a self-contained, interactive HTML document that visually represents these relationships using the **Vis.js (vis-network)** library. The goal is to generate a **fully functional conceptual map** that can be opened directly in a browser without external dependencies. --- ### ACTIONS 1. **Analyze and Extract Concepts** - Read and process the uploaded `.txt` or `.md` file. - Identify main themes, subthemes, and key terms. - Convert each key concept into a node. 2. **Map Relationships** - Detect logical and hierarchical relations between concepts. - Use short, descriptive verbs such as: \"causes\", \"contributes to\", \"depends on\", \"evolves into\", \"results in\", \"influences\", \"generates\" / \"creates\", \"culminates in. 3. **Generate Node Structure** ```json {\"id\": \"conceito_id\", \"label\": \"Conceito\", \"title\": \"<b>Concept:</b> Conceito<br><i>Drag to position, double-click to release.</i>\"} ``` 4. **Generate Edge Structure** ```json {\"from\": \"conceito_origem\", \"to\": \"conceito_destino\", \"label\": \"verbo\", \"title\": \"<b>Relationship:</b> verbo\"} ``` 5. **Apply Visual and Physical Configuration** ```js shape: \"dot\", color: { border: \"#4285F4\", background: \"#ffffff\", highlight: { border: \"#34A853\", background: \"#e6f4ea\" } }, font: { size: 14, color: \"#3c4043\" }, borderWidth: 2, size: 20 // Edges color: { color: \"#dee2e6\", highlight: \"#34A853\" }, arrows: { to: { enabled: true, scaleFactor: 0.7 } }, font: { align: \"middle\", size: 12, color: \"#5f6368\" }, width: 2 // Physics physics: { solver: \"forceAtlas2Based\", forceAtlas2Based: { gravitationalConstant: -50, centralGravity: 0.005, springLength: 100, springConstant: 0.18 }, maxVelocity: 146, minVelocity: 0.1, stabilization: { iterations: 150 } } ``` 6. **Implement Interactivity** ```js // Fix node on drag end network.on(\"dragEnd\", (params) => { if (params.nodes.length > 0) { nodes.update({ id: params.nodes[0], fixed: true }); } }); // Release node on double click network.on(\"doubleClick\", (params) => { if (params.nodes.length > 0) { nodes.update({ id: params.nodes[0], fixed: false }); } }); ``` 7. **Assemble the Complete HTML Structure** ```html <head> <title>Mapa Conceitual — [TEMA DETECTADO DO ARQUIVO]</title> <script src=\"https://unpkg.com/vis-network/standalone/umd/vis-network.min.js\"></script> <link href=\"https://unpkg.com/vis-network/styles/vis-network.min.css\" rel=\"stylesheet\" /> </head> <body> <div id=\"map\"></div> <script type=\"text/javascript\"> // nodes, edges, options, and interactive network initialization </script> </body> ``` 8. **Auto-name Output File** Automatically save the generated HTML file based on the detected topic: ``` mapa_[tema_detectado].html ``` --- ###"
|
||||
},
|
||||
{
|
||||
"patternName": "concall_summary",
|
||||
"pattern_extract": "# IDENTITY and PURPOSE You are an equity research analyst specializing in earnings and conference call analysis. Your role involves carefully examining transcripts to extract actionable insights that can inform investment decisions. You need to focus on several key areas, including management commentary, analyst questions, financial and operational insights, risks and red flags, hidden signals, and an executive summary. Your task is to distill complex information into clear, concise bullet points, capturing strategic themes, growth drivers, and potential concerns. It is crucial to interpret the tone, identify contradictions, and highlight any subtle cues that may indicate future strategic shifts or risks. Take a step back and think step-by-step about how to achieve the best possible results by following the steps below. # STEPS * Analyze the transcript to extract management commentary, focusing on strategic themes, growth drivers, margin commentary, guidance, tone analysis, and any contradictions or vague areas. * Extract a summary of the content in exactly **25 words**, including who is presenting and the content being discussed; place this under a **SUMMARY** section. * For each analyst's question, determine the underlying concern, summarize management’s exact answer, evaluate if the answers address the question fully, and identify anything the management avoided or deflected. * Gather financial and operational insights, including commentary on demand, pricing, capacity, market share, cost inflation, raw material trends, and supply-chain issues. * Identify risks and red flags by noting any negative commentary, early warning signs, unusual wording, delayed responses, repeated disclaimers, and areas where management seemed less confident. * Detect hidden signals such as forward-looking hints, unasked but important questions, and subtle cues about strategy shifts or stress. * Create an executive summary in bullet points, listing the 10 most important takeaways, 3 surprises, and 3 things to track in the next quarter. # OUTPUT STRUCTURE * MANAGEMENT COMMENTARY * Key strategic themes * Growth drivers discussed * Margin commentary * Guidance (explicit + implicit) * Tone analysis (positive/neutral/negative) * Any contradictions or vague areas * ANALYST QUESTIONS (Q&A) * For each analyst (use bullets, one analyst per bullet-group): * Underlying concern (what the question REALLY asked) * Management’s exact answer (concise) * Answer completeness (Yes/No — short explanation) * Items management avoided or deflected * FINANCIAL & OPERATIONAL INSIGHTS * Demand, pricing, capacity, market share commentary * Cost inflation, raw material trends, supply-chain issues * Segment-wise performance and commentary (if applicable) * RISKS & RED FLAGS * Negative commentary or early-warning signs * Unusual wording, delayed responses, repeated disclaimers * Areas where management was less confident * HIDDEN SIGNALS * Forward-looking hints and tone shifts * Important topics not asked by analysts but relevant * Subtle cues of strategy change, stress, or opportunity * EXECUTIVE SUMMARY * 10 most important takeaways (bullet points) * 3 surprises (bullet points) * 3 things to track next quarter (bullet points) * SUMMARY (exactly 25 words) * A single 25-word sentence summarizing who presented and what was discussed # OUTPUT INSTRUCTIONS * Only output Markdown. * Provide everything in"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -26,9 +26,9 @@
|
||||
"eslint-plugin-svelte": "^2.46.1",
|
||||
"lucide-svelte": "^0.309.0",
|
||||
"mdsvex": "^0.11.2",
|
||||
"patch-package": "^8.0.0",
|
||||
"patch-package": "^8.0.1",
|
||||
"pdf-to-markdown-core": "github:jzillmann/pdf-to-markdown#modularize",
|
||||
"pdfjs-dist": "^4.2.67",
|
||||
"pdfjs-dist": "^5.4.449",
|
||||
"postcss": "^8.5.3",
|
||||
"postcss-load-config": "^6.0.1",
|
||||
"rehype-autolink-headings": "^7.1.0",
|
||||
|
||||
565
web/pnpm-lock.yaml
generated
565
web/pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -1,239 +1,284 @@
|
||||
import { get } from "svelte/store";
|
||||
import type {
|
||||
ChatRequest,
|
||||
StreamResponse,
|
||||
ChatError as IChatError,
|
||||
ChatPrompt
|
||||
} from '$lib/interfaces/chat-interface';
|
||||
import { get } from 'svelte/store';
|
||||
import { modelConfig } from '$lib/store/model-store';
|
||||
import { systemPrompt, selectedPatternName, patternVariables } 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';
|
||||
ChatPrompt,
|
||||
ChatRequest,
|
||||
ChatError as IChatError,
|
||||
StreamResponse,
|
||||
} from "$lib/interfaces/chat-interface";
|
||||
import { chatConfig } from "$lib/store/chat-config";
|
||||
import { languageStore } from "$lib/store/language-store";
|
||||
import { modelConfig } from "$lib/store/model-store";
|
||||
import {
|
||||
patternVariables,
|
||||
selectedPatternName,
|
||||
systemPrompt,
|
||||
} from "$lib/store/pattern-store";
|
||||
import { selectedStrategy } from "$lib/store/strategy-store";
|
||||
|
||||
class LanguageValidator {
|
||||
constructor(private targetLanguage: string) {}
|
||||
constructor(private targetLanguage: string) {}
|
||||
|
||||
enforceLanguage(content: string): string {
|
||||
if (this.targetLanguage === 'en') return content;
|
||||
return `[Language: ${this.targetLanguage}]\n${content}`;
|
||||
}
|
||||
enforceLanguage(content: string): string {
|
||||
if (this.targetLanguage === "en") return content;
|
||||
return `[Language: ${this.targetLanguage}]\n${content}`;
|
||||
}
|
||||
}
|
||||
|
||||
export class ChatError extends Error implements IChatError {
|
||||
constructor(
|
||||
message: string,
|
||||
public readonly code: string = 'CHAT_ERROR',
|
||||
public readonly details?: unknown
|
||||
) {
|
||||
super(message);
|
||||
this.name = 'ChatError';
|
||||
}
|
||||
constructor(
|
||||
message: string,
|
||||
public readonly code: string = "CHAT_ERROR",
|
||||
public readonly details?: unknown,
|
||||
) {
|
||||
super(message);
|
||||
this.name = "ChatError";
|
||||
}
|
||||
}
|
||||
|
||||
export class ChatService {
|
||||
private validator: LanguageValidator;
|
||||
private validator: LanguageValidator;
|
||||
|
||||
constructor() {
|
||||
this.validator = new LanguageValidator(get(languageStore));
|
||||
}
|
||||
constructor() {
|
||||
this.validator = new LanguageValidator(get(languageStore));
|
||||
}
|
||||
|
||||
private async fetchStream(request: ChatRequest): Promise<ReadableStream<StreamResponse>> {
|
||||
try {
|
||||
console.log('\n=== ChatService Request Start ===');
|
||||
console.log('1. Request details:', {
|
||||
language: get(languageStore),
|
||||
pattern: get(selectedPatternName),
|
||||
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));
|
||||
private async fetchStream(
|
||||
request: ChatRequest,
|
||||
): Promise<ReadableStream<StreamResponse>> {
|
||||
try {
|
||||
console.log("\n=== ChatService Request Start ===");
|
||||
console.log("1. Request details:", {
|
||||
language: get(languageStore),
|
||||
pattern: get(selectedPatternName),
|
||||
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',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(request),
|
||||
});
|
||||
const response = await fetch("/api/chat", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify(request),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new ChatError(`HTTP error! status: ${response.status}`, 'HTTP_ERROR', { status: response.status });
|
||||
}
|
||||
if (!response.ok) {
|
||||
throw new ChatError(
|
||||
`HTTP error! status: ${response.status}`,
|
||||
"HTTP_ERROR",
|
||||
{ status: response.status },
|
||||
);
|
||||
}
|
||||
|
||||
const reader = response.body?.getReader();
|
||||
if (!reader) {
|
||||
throw new ChatError('Response body is null', 'NULL_RESPONSE');
|
||||
}
|
||||
const reader = response.body?.getReader();
|
||||
if (!reader) {
|
||||
throw new ChatError("Response body is null", "NULL_RESPONSE");
|
||||
}
|
||||
|
||||
return this.createMessageStream(reader);
|
||||
} catch (error) {
|
||||
if (error instanceof ChatError) throw error;
|
||||
throw new ChatError('Failed to fetch chat stream', 'FETCH_ERROR', error);
|
||||
}
|
||||
}
|
||||
return this.createMessageStream(reader);
|
||||
} catch (error) {
|
||||
if (error instanceof ChatError) throw error;
|
||||
throw new ChatError("Failed to fetch chat stream", "FETCH_ERROR", error);
|
||||
}
|
||||
}
|
||||
|
||||
private cleanPatternOutput(content: string): string {
|
||||
// Remove markdown fence if present
|
||||
let cleaned = content.replace(/^```markdown\n/, '');
|
||||
cleaned = cleaned.replace(/\n```$/, '');
|
||||
private cleanPatternOutput(content: string): string {
|
||||
// Remove markdown fence if present
|
||||
let cleaned = content.replace(/^```markdown\n/, "");
|
||||
cleaned = cleaned.replace(/\n```$/, "");
|
||||
|
||||
// Existing cleaning
|
||||
cleaned = cleaned.replace(/^# OUTPUT\s*\n/, '');
|
||||
cleaned = cleaned.replace(/^\s*\n/, '');
|
||||
cleaned = cleaned.replace(/\n\s*$/, '');
|
||||
cleaned = cleaned.replace(/^#\s+([A-Z]+):/gm, '$1:');
|
||||
cleaned = cleaned.replace(/^#\s+([A-Z]+)\s*$/gm, '$1');
|
||||
cleaned = cleaned.trim();
|
||||
cleaned = cleaned.replace(/\n{3,}/g, '\n\n');
|
||||
return cleaned;
|
||||
}
|
||||
// Existing cleaning
|
||||
cleaned = cleaned.replace(/^# OUTPUT\s*\n/, "");
|
||||
cleaned = cleaned.replace(/^\s*\n/, "");
|
||||
cleaned = cleaned.replace(/\n\s*$/, "");
|
||||
cleaned = cleaned.replace(/^#\s+([A-Z]+):/gm, "$1:");
|
||||
cleaned = cleaned.replace(/^#\s+([A-Z]+)\s*$/gm, "$1");
|
||||
cleaned = cleaned.trim();
|
||||
cleaned = cleaned.replace(/\n{3,}/g, "\n\n");
|
||||
return cleaned;
|
||||
}
|
||||
|
||||
private createMessageStream(reader: ReadableStreamDefaultReader<Uint8Array>): ReadableStream<StreamResponse> {
|
||||
let buffer = '';
|
||||
const cleanPatternOutput = this.cleanPatternOutput.bind(this);
|
||||
const language = get(languageStore);
|
||||
const validator = new LanguageValidator(language);
|
||||
private createMessageStream(
|
||||
reader: ReadableStreamDefaultReader<Uint8Array>,
|
||||
): ReadableStream<StreamResponse> {
|
||||
let buffer = "";
|
||||
const cleanPatternOutput = this.cleanPatternOutput.bind(this);
|
||||
const language = get(languageStore);
|
||||
const validator = new LanguageValidator(language);
|
||||
|
||||
const processResponse = (response: StreamResponse) => {
|
||||
const pattern = get(selectedPatternName);
|
||||
const processResponse = (response: StreamResponse) => {
|
||||
const pattern = get(selectedPatternName);
|
||||
|
||||
if (pattern) {
|
||||
response.content = cleanPatternOutput(response.content);
|
||||
// Simplified format determination - always markdown unless mermaid
|
||||
const isMermaid = [
|
||||
'graph TD', 'gantt', 'flowchart',
|
||||
'sequenceDiagram', 'classDiagram', 'stateDiagram'
|
||||
].some(starter => response.content.trim().startsWith(starter));
|
||||
if (pattern) {
|
||||
response.content = cleanPatternOutput(response.content);
|
||||
// Simplified format determination - always markdown unless mermaid
|
||||
const isMermaid = [
|
||||
"graph TD",
|
||||
"gantt",
|
||||
"flowchart",
|
||||
"sequenceDiagram",
|
||||
"classDiagram",
|
||||
"stateDiagram",
|
||||
].some((starter) => response.content.trim().startsWith(starter));
|
||||
|
||||
response.format = isMermaid ? 'mermaid' : 'markdown';
|
||||
}
|
||||
response.format = isMermaid ? "mermaid" : "markdown";
|
||||
}
|
||||
|
||||
if (response.type === 'content') {
|
||||
response.content = validator.enforceLanguage(response.content);
|
||||
}
|
||||
if (response.type === "content") {
|
||||
response.content = validator.enforceLanguage(response.content);
|
||||
}
|
||||
|
||||
return response;
|
||||
};
|
||||
return new ReadableStream({
|
||||
async start(controller) {
|
||||
try {
|
||||
while (true) {
|
||||
const { done, value } = await reader.read();
|
||||
if (done) break;
|
||||
return response;
|
||||
};
|
||||
return new ReadableStream({
|
||||
async start(controller) {
|
||||
try {
|
||||
while (true) {
|
||||
const { done, value } = await reader.read();
|
||||
if (done) break;
|
||||
|
||||
buffer += new TextDecoder().decode(value);
|
||||
const messages = buffer.split('\n\n').filter(msg => msg.startsWith('data: '));
|
||||
buffer += new TextDecoder().decode(value);
|
||||
const messages = buffer
|
||||
.split("\n\n")
|
||||
.filter((msg) => msg.startsWith("data: "));
|
||||
|
||||
if (messages.length > 1) {
|
||||
buffer = messages.pop() || '';
|
||||
for (const msg of messages) {
|
||||
try {
|
||||
let response = JSON.parse(msg.slice(6)) as StreamResponse;
|
||||
response = processResponse(response);
|
||||
controller.enqueue(response);
|
||||
} catch (parseError) {
|
||||
console.error('Error parsing stream message:', parseError);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (messages.length > 1) {
|
||||
buffer = messages.pop() || "";
|
||||
for (const msg of messages) {
|
||||
try {
|
||||
let response = JSON.parse(msg.slice(6)) as StreamResponse;
|
||||
response = processResponse(response);
|
||||
controller.enqueue(response);
|
||||
} catch (parseError) {
|
||||
console.error("Error parsing stream message:", parseError);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (buffer.startsWith('data: ')) {
|
||||
try {
|
||||
let response = JSON.parse(buffer.slice(6)) as StreamResponse;
|
||||
response = processResponse(response);
|
||||
controller.enqueue(response);
|
||||
} catch (parseError) {
|
||||
console.error('Error parsing final message:', parseError);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
controller.error(new ChatError('Stream processing error', 'STREAM_ERROR', error));
|
||||
} finally {
|
||||
reader.releaseLock();
|
||||
controller.close();
|
||||
}
|
||||
},
|
||||
cancel() {
|
||||
reader.cancel();
|
||||
}
|
||||
});
|
||||
}
|
||||
if (buffer.startsWith("data: ")) {
|
||||
try {
|
||||
let response = JSON.parse(buffer.slice(6)) as StreamResponse;
|
||||
response = processResponse(response);
|
||||
controller.enqueue(response);
|
||||
} catch (parseError) {
|
||||
console.error("Error parsing final message:", parseError);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
controller.error(
|
||||
new ChatError("Stream processing error", "STREAM_ERROR", error),
|
||||
);
|
||||
} finally {
|
||||
reader.releaseLock();
|
||||
controller.close();
|
||||
}
|
||||
},
|
||||
cancel() {
|
||||
reader.cancel();
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
private createChatPrompt(userInput: string, systemPromptText?: string): ChatPrompt {
|
||||
const config = get(modelConfig);
|
||||
const language = get(languageStore);
|
||||
private createChatPrompt(
|
||||
userInput: string,
|
||||
systemPromptText?: string,
|
||||
): ChatPrompt {
|
||||
const config = get(modelConfig);
|
||||
const language = get(languageStore);
|
||||
|
||||
const languageInstruction = language !== 'en'
|
||||
? `You MUST respond in ${language} language. All output must be in ${language}. `
|
||||
// ? `You MUST respond in ${language} language. ALL output, including section headers, titles, and formatting, MUST be translated into ${language}. It is CRITICAL that you translate ALL headers, such as SUMMARY, IDEAS, QUOTES, TAKEAWAYS, MAIN POINTS, etc., into ${language}. Maintain markdown formatting in the response. Do not output any English headers.`
|
||||
: '';
|
||||
const languageInstruction =
|
||||
language !== "en"
|
||||
? `You MUST respond in ${language} language. All output must be in ${language}. `
|
||||
: // ? `You MUST respond in ${language} language. ALL output, including section headers, titles, and formatting, MUST be translated into ${language}. It is CRITICAL that you translate ALL headers, such as SUMMARY, IDEAS, QUOTES, TAKEAWAYS, MAIN POINTS, etc., into ${language}. Maintain markdown formatting in the response. Do not output any English headers.`
|
||||
"";
|
||||
|
||||
const finalSystemPrompt = languageInstruction + (systemPromptText ?? get(systemPrompt));
|
||||
const finalSystemPrompt =
|
||||
languageInstruction + (systemPromptText ?? get(systemPrompt));
|
||||
|
||||
const finalUserInput = language !== 'en'
|
||||
? `${userInput}\n\nIMPORTANT: Respond in ${language} language only.`
|
||||
: userInput;
|
||||
const finalUserInput =
|
||||
language !== "en"
|
||||
? `${userInput}\n\nIMPORTANT: Respond in ${language} language only.`
|
||||
: userInput;
|
||||
|
||||
return {
|
||||
userInput: finalUserInput,
|
||||
systemPrompt: finalSystemPrompt,
|
||||
model: config.model,
|
||||
patternName: get(selectedPatternName),
|
||||
strategyName: get(selectedStrategy), // Add selected strategy to prompt
|
||||
variables: get(patternVariables) // Add pattern variables
|
||||
};
|
||||
}
|
||||
|
||||
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
|
||||
};
|
||||
}
|
||||
|
||||
public async streamPattern(userInput: string, systemPromptText?: string): Promise<ReadableStream<StreamResponse>> {
|
||||
const request = await this.createChatRequest(userInput, systemPromptText, true);
|
||||
return this.fetchStream(request);
|
||||
}
|
||||
|
||||
public async streamChat(userInput: string, systemPromptText?: string): Promise<ReadableStream<StreamResponse>> {
|
||||
const request = await this.createChatRequest(userInput, systemPromptText);
|
||||
return this.fetchStream(request);
|
||||
}
|
||||
|
||||
public async processStream(
|
||||
stream: ReadableStream<StreamResponse>,
|
||||
onContent: (content: string, response?: StreamResponse) => void,
|
||||
onError: (error: Error) => void
|
||||
): Promise<void> {
|
||||
const reader = stream.getReader();
|
||||
|
||||
try {
|
||||
while (true) {
|
||||
const { done, value } = await reader.read();
|
||||
if (done) break;
|
||||
|
||||
if (value.type === 'error') {
|
||||
throw new ChatError(value.content, 'STREAM_CONTENT_ERROR');
|
||||
}
|
||||
|
||||
if (value.type === 'content') {
|
||||
onContent(value.content, value);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
onError(error instanceof ChatError ? error : new ChatError('Stream processing error', 'STREAM_ERROR', error));
|
||||
} finally {
|
||||
reader.releaseLock();
|
||||
|
||||
}
|
||||
}
|
||||
return {
|
||||
userInput: finalUserInput,
|
||||
systemPrompt: finalSystemPrompt,
|
||||
model: config.model,
|
||||
patternName: get(selectedPatternName),
|
||||
strategyName: get(selectedStrategy), // Add selected strategy to prompt
|
||||
variables: get(patternVariables), // Add pattern variables
|
||||
};
|
||||
}
|
||||
|
||||
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,
|
||||
};
|
||||
}
|
||||
|
||||
public async streamPattern(
|
||||
userInput: string,
|
||||
systemPromptText?: string,
|
||||
): Promise<ReadableStream<StreamResponse>> {
|
||||
const request = await this.createChatRequest(
|
||||
userInput,
|
||||
systemPromptText,
|
||||
true,
|
||||
);
|
||||
return this.fetchStream(request);
|
||||
}
|
||||
|
||||
public async streamChat(
|
||||
userInput: string,
|
||||
systemPromptText?: string,
|
||||
): Promise<ReadableStream<StreamResponse>> {
|
||||
const request = await this.createChatRequest(userInput, systemPromptText);
|
||||
return this.fetchStream(request);
|
||||
}
|
||||
|
||||
public async processStream(
|
||||
stream: ReadableStream<StreamResponse>,
|
||||
onContent: (content: string, response?: StreamResponse) => void,
|
||||
onError: (error: Error) => void,
|
||||
): Promise<void> {
|
||||
const reader = stream.getReader();
|
||||
|
||||
try {
|
||||
while (true) {
|
||||
const { done, value } = await reader.read();
|
||||
if (done) break;
|
||||
|
||||
if (value.type === "error") {
|
||||
throw new ChatError(value.content, "STREAM_CONTENT_ERROR");
|
||||
}
|
||||
|
||||
if (value.type === "content") {
|
||||
onContent(value.content, value);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
onError(
|
||||
error instanceof ChatError
|
||||
? error
|
||||
: new ChatError("Stream processing error", "STREAM_ERROR", error),
|
||||
);
|
||||
} finally {
|
||||
reader.releaseLock();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,78 +1,74 @@
|
||||
import { createPipeline, transformers } from 'pdf-to-markdown-core/lib/src';
|
||||
import { PARSE_SCHEMA } from 'pdf-to-markdown-core/lib/src/PdfParser';
|
||||
import * as pdfjs from 'pdfjs-dist';
|
||||
import pdfConfig from './pdf-config';
|
||||
import { createPipeline, transformers } from "pdf-to-markdown-core/lib/src";
|
||||
import { PARSE_SCHEMA } from "pdf-to-markdown-core/lib/src/PdfParser";
|
||||
|
||||
// pdfjs-dist v5+ requires browser APIs at import time, so we use dynamic imports
|
||||
let pdfjs: typeof import("pdfjs-dist") | null = null;
|
||||
|
||||
export class PdfConversionService {
|
||||
constructor() {
|
||||
if (typeof window !== 'undefined') {
|
||||
console.log('PDF.js version:', pdfjs.version);
|
||||
// Initialize PDF.js configuration from the shared config
|
||||
pdfConfig.initialize();
|
||||
console.log('Worker configuration complete');
|
||||
}
|
||||
}
|
||||
private async ensureInitialized(): Promise<typeof import("pdfjs-dist")> {
|
||||
if (!pdfjs) {
|
||||
// Dynamic import to avoid SSR issues with pdfjs-dist v5+
|
||||
pdfjs = await import("pdfjs-dist");
|
||||
const pdfConfig = (await import("./pdf-config")).default;
|
||||
console.log("PDF.js version:", pdfjs.version);
|
||||
await pdfConfig.initialize();
|
||||
console.log("Worker configuration complete");
|
||||
}
|
||||
return pdfjs;
|
||||
}
|
||||
|
||||
async convertToMarkdown(file: File): Promise<string> {
|
||||
console.log('Starting PDF conversion:', {
|
||||
fileName: file.name,
|
||||
fileSize: file.size
|
||||
});
|
||||
async convertToMarkdown(file: File): Promise<string> {
|
||||
console.log("Starting PDF conversion:", {
|
||||
fileName: file.name,
|
||||
fileSize: file.size,
|
||||
});
|
||||
|
||||
const buffer = await file.arrayBuffer();
|
||||
console.log('Buffer created:', buffer.byteLength);
|
||||
const pdfjsLib = await this.ensureInitialized();
|
||||
|
||||
const pipeline = createPipeline(pdfjs, {
|
||||
transformConfig: {
|
||||
transformers
|
||||
}
|
||||
});
|
||||
console.log('Pipeline created');
|
||||
const buffer = await file.arrayBuffer();
|
||||
console.log("Buffer created:", buffer.byteLength);
|
||||
|
||||
const result = await pipeline.parse(
|
||||
buffer,
|
||||
(progress) => console.log('Processing:', {
|
||||
stage: progress.stages,
|
||||
details: progress.stageDetails,
|
||||
progress: progress.stageProgress
|
||||
})
|
||||
);
|
||||
console.log('Parse complete, validating result');
|
||||
const pipeline = createPipeline(pdfjsLib, {
|
||||
transformConfig: {
|
||||
transformers,
|
||||
},
|
||||
});
|
||||
console.log("Pipeline created");
|
||||
|
||||
const transformed = result.transform();
|
||||
console.log('Transform applied:', transformed);
|
||||
const result = await pipeline.parse(buffer, (progress) =>
|
||||
console.log("Processing:", {
|
||||
stage: progress.stages,
|
||||
details: progress.stageDetails,
|
||||
progress: progress.stageProgress,
|
||||
}),
|
||||
);
|
||||
console.log("Parse complete, validating result");
|
||||
|
||||
const markdown = transformed.convert({
|
||||
convert: (items) => {
|
||||
console.log('PDF Structure:', {
|
||||
itemCount: items.length,
|
||||
firstItem: items[0],
|
||||
schema: PARSE_SCHEMA // ['transform', 'width', 'height', 'str', 'fontName', 'dir']
|
||||
});
|
||||
|
||||
const text = items
|
||||
.map(item => item.value('str')) // Using 'str' instead of 'text' based on PARSE_SCHEMA
|
||||
.filter(Boolean)
|
||||
.join('\n');
|
||||
|
||||
console.log('Converted text:', {
|
||||
length: text.length,
|
||||
preview: text.substring(0, 100)
|
||||
});
|
||||
|
||||
return text;
|
||||
}
|
||||
});
|
||||
|
||||
const transformed = result.transform();
|
||||
console.log("Transform applied:", transformed);
|
||||
|
||||
return markdown;
|
||||
}
|
||||
const markdown = transformed.convert({
|
||||
convert: (items) => {
|
||||
console.log("PDF Structure:", {
|
||||
itemCount: items.length,
|
||||
firstItem: items[0],
|
||||
schema: PARSE_SCHEMA, // ['transform', 'width', 'height', 'str', 'fontName', 'dir']
|
||||
});
|
||||
|
||||
const text = items
|
||||
.map((item) => item.value("str")) // Using 'str' instead of 'text' based on PARSE_SCHEMA
|
||||
.filter(Boolean)
|
||||
.join("\n");
|
||||
|
||||
console.log("Converted text:", {
|
||||
length: text.length,
|
||||
preview: text.substring(0, 100),
|
||||
});
|
||||
|
||||
return text;
|
||||
},
|
||||
});
|
||||
|
||||
return markdown;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,20 +1,19 @@
|
||||
import { browser } from '$app/environment';
|
||||
import { GlobalWorkerOptions } from 'pdfjs-dist';
|
||||
import { browser } from "$app/environment";
|
||||
|
||||
// Set up the worker source location - point to static file in public directory
|
||||
const workerSrc = '/pdf.worker.min.mjs';
|
||||
|
||||
// Configure the worker options only on the client side
|
||||
if (browser) {
|
||||
GlobalWorkerOptions.workerSrc = workerSrc;
|
||||
}
|
||||
|
||||
// Export the configuration
|
||||
// Export the configuration - accepts pdfjs module to avoid top-level import
|
||||
// This is necessary because pdfjs-dist v5+ uses browser APIs at import time
|
||||
export default {
|
||||
initialize: () => {
|
||||
if (browser) {
|
||||
console.log('PDF.js worker initialized at', workerSrc);
|
||||
}
|
||||
}
|
||||
};
|
||||
initialize: async () => {
|
||||
if (browser) {
|
||||
// Dynamic import to avoid SSR issues
|
||||
const pdfjs = await import("pdfjs-dist");
|
||||
const { GlobalWorkerOptions, version } = pdfjs;
|
||||
|
||||
// Use CDN-hosted worker to avoid bundling third-party minified code in the repo
|
||||
const workerSrc = `https://unpkg.com/pdfjs-dist@${version}/build/pdf.worker.min.mjs`;
|
||||
GlobalWorkerOptions.workerSrc = workerSrc;
|
||||
|
||||
console.log(`PDF.js worker v${version} initialized from CDN`);
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
@@ -1,19 +1,24 @@
|
||||
import { writable, derived, get } from 'svelte/store';
|
||||
import type { ChatState, Message, StreamResponse } from '$lib/interfaces/chat-interface';
|
||||
import { ChatService, ChatError } from '$lib/services/ChatService';
|
||||
import { languageStore } from '$lib/store/language-store';
|
||||
import { selectedPatternName } from '$lib/store/pattern-store';
|
||||
import { derived, get, writable } from "svelte/store";
|
||||
import { browser } from "$app/environment";
|
||||
import type {
|
||||
ChatState,
|
||||
Message,
|
||||
StreamResponse,
|
||||
} from "$lib/interfaces/chat-interface";
|
||||
import { ChatError, ChatService } from "$lib/services/ChatService";
|
||||
import { languageStore } from "$lib/store/language-store";
|
||||
import { selectedPatternName } from "$lib/store/pattern-store";
|
||||
|
||||
// Initialize chat service
|
||||
const chatService = new ChatService();
|
||||
|
||||
// Local storage key for persisting messages
|
||||
const MESSAGES_STORAGE_KEY = 'chat_messages';
|
||||
const MESSAGES_STORAGE_KEY = "chat_messages";
|
||||
|
||||
// Load initial messages from local storage
|
||||
const initialMessages = typeof localStorage !== 'undefined'
|
||||
? JSON.parse(localStorage.getItem(MESSAGES_STORAGE_KEY) || '[]')
|
||||
: [];
|
||||
// Load initial messages from local storage (only in browser)
|
||||
const initialMessages = browser
|
||||
? JSON.parse(localStorage.getItem(MESSAGES_STORAGE_KEY) || "[]")
|
||||
: [];
|
||||
|
||||
// Separate stores for different concerns
|
||||
export const messageStore = writable<Message[]>(initialMessages);
|
||||
@@ -21,134 +26,144 @@ export const streamingStore = writable<boolean>(false);
|
||||
export const errorStore = writable<string | null>(null);
|
||||
export const currentSession = writable<string | null>(null);
|
||||
|
||||
// Subscribe to messageStore changes to persist messages
|
||||
if (typeof localStorage !== 'undefined') {
|
||||
messageStore.subscribe($messages => {
|
||||
localStorage.setItem(MESSAGES_STORAGE_KEY, JSON.stringify($messages));
|
||||
});
|
||||
// Subscribe to messageStore changes to persist messages (only in browser)
|
||||
if (browser) {
|
||||
messageStore.subscribe(($messages) => {
|
||||
localStorage.setItem(MESSAGES_STORAGE_KEY, JSON.stringify($messages));
|
||||
});
|
||||
}
|
||||
|
||||
// Derived store for chat state
|
||||
export const chatState = derived(
|
||||
[messageStore, streamingStore],
|
||||
([$messages, $streaming]) => ({
|
||||
messages: $messages,
|
||||
isStreaming: $streaming
|
||||
})
|
||||
[messageStore, streamingStore],
|
||||
([$messages, $streaming]) => ({
|
||||
messages: $messages,
|
||||
isStreaming: $streaming,
|
||||
}),
|
||||
);
|
||||
|
||||
// Error handling utility
|
||||
function handleError(error: Error | string) {
|
||||
const errorMessage = error instanceof ChatError
|
||||
? `${error.code}: ${error.message}`
|
||||
: error instanceof Error
|
||||
? error.message
|
||||
: error;
|
||||
const errorMessage =
|
||||
error instanceof ChatError
|
||||
? `${error.code}: ${error.message}`
|
||||
: error instanceof Error
|
||||
? error.message
|
||||
: error;
|
||||
|
||||
errorStore.set(errorMessage);
|
||||
streamingStore.set(false);
|
||||
return errorMessage;
|
||||
errorStore.set(errorMessage);
|
||||
streamingStore.set(false);
|
||||
return errorMessage;
|
||||
}
|
||||
|
||||
export const setSession = (sessionName: string | null) => {
|
||||
currentSession.set(sessionName);
|
||||
if (!sessionName) {
|
||||
clearMessages();
|
||||
}
|
||||
currentSession.set(sessionName);
|
||||
if (!sessionName) {
|
||||
clearMessages();
|
||||
}
|
||||
};
|
||||
|
||||
export const clearMessages = () => {
|
||||
messageStore.set([]);
|
||||
errorStore.set(null);
|
||||
if (typeof localStorage !== 'undefined') {
|
||||
localStorage.removeItem(MESSAGES_STORAGE_KEY);
|
||||
}
|
||||
messageStore.set([]);
|
||||
errorStore.set(null);
|
||||
if (typeof localStorage !== "undefined") {
|
||||
localStorage.removeItem(MESSAGES_STORAGE_KEY);
|
||||
}
|
||||
};
|
||||
|
||||
export const revertLastMessage = () => {
|
||||
messageStore.update(messages => messages.slice(0, -1));
|
||||
messageStore.update((messages) => messages.slice(0, -1));
|
||||
};
|
||||
|
||||
export async function sendMessage(
|
||||
content: string,
|
||||
systemPromptText?: string,
|
||||
isSystem: boolean = false,
|
||||
) {
|
||||
try {
|
||||
console.log("\n=== Message Processing Start ===");
|
||||
console.log("1. Initial state:", {
|
||||
isSystem,
|
||||
hasSystemPrompt: !!systemPromptText,
|
||||
currentLanguage: get(languageStore),
|
||||
pattern: get(selectedPatternName),
|
||||
});
|
||||
|
||||
export async function sendMessage(content: string, systemPromptText?: string, isSystem: boolean = false) {
|
||||
try {
|
||||
console.log('\n=== Message Processing Start ===');
|
||||
console.log('1. Initial state:', {
|
||||
isSystem,
|
||||
hasSystemPrompt: !!systemPromptText,
|
||||
currentLanguage: get(languageStore),
|
||||
pattern: get(selectedPatternName)
|
||||
});
|
||||
const $streaming = get(streamingStore);
|
||||
if ($streaming) {
|
||||
throw new ChatError(
|
||||
"Message submission blocked - already streaming",
|
||||
"STREAMING_BLOCKED",
|
||||
);
|
||||
}
|
||||
|
||||
const $streaming = get(streamingStore);
|
||||
if ($streaming) {
|
||||
throw new ChatError('Message submission blocked - already streaming', 'STREAMING_BLOCKED');
|
||||
}
|
||||
streamingStore.set(true);
|
||||
errorStore.set(null);
|
||||
|
||||
streamingStore.set(true);
|
||||
errorStore.set(null);
|
||||
// Add message
|
||||
messageStore.update((messages) => [
|
||||
...messages,
|
||||
{
|
||||
role: isSystem ? "system" : "user",
|
||||
content,
|
||||
},
|
||||
]);
|
||||
|
||||
// Add message
|
||||
messageStore.update(messages => [...messages, {
|
||||
role: isSystem ? 'system' : 'user',
|
||||
content
|
||||
}]);
|
||||
console.log("2. Message added:", {
|
||||
role: isSystem ? "system" : "user",
|
||||
language: get(languageStore),
|
||||
});
|
||||
|
||||
console.log('2. Message added:', {
|
||||
role: isSystem ? 'system' : 'user',
|
||||
language: get(languageStore)
|
||||
});
|
||||
if (!isSystem) {
|
||||
console.log("3. Preparing chat stream:", {
|
||||
language: get(languageStore),
|
||||
pattern: get(selectedPatternName),
|
||||
hasSystemPrompt: !!systemPromptText,
|
||||
});
|
||||
|
||||
if (!isSystem) {
|
||||
console.log('3. Preparing chat stream:', {
|
||||
language: get(languageStore),
|
||||
pattern: get(selectedPatternName),
|
||||
hasSystemPrompt: !!systemPromptText
|
||||
});
|
||||
const stream = await chatService.streamChat(content, systemPromptText);
|
||||
console.log("4. Stream created");
|
||||
|
||||
const stream = await chatService.streamChat(content, systemPromptText);
|
||||
console.log('4. Stream created');
|
||||
await chatService.processStream(
|
||||
stream,
|
||||
(content: string, response?: StreamResponse) => {
|
||||
messageStore.update((messages) => {
|
||||
const newMessages = [...messages];
|
||||
const lastMessage = newMessages[newMessages.length - 1];
|
||||
|
||||
await chatService.processStream(
|
||||
stream,
|
||||
(content: string, response?: StreamResponse) => {
|
||||
messageStore.update(messages => {
|
||||
const newMessages = [...messages];
|
||||
const lastMessage = newMessages[newMessages.length - 1];
|
||||
if (lastMessage?.role === "assistant") {
|
||||
lastMessage.content = content;
|
||||
lastMessage.format = response?.format;
|
||||
console.log("Message updated:", {
|
||||
role: "assistant",
|
||||
format: lastMessage.format,
|
||||
});
|
||||
} else {
|
||||
newMessages.push({
|
||||
role: "assistant",
|
||||
content,
|
||||
format: response?.format,
|
||||
});
|
||||
}
|
||||
|
||||
if (lastMessage?.role === 'assistant') {
|
||||
lastMessage.content = content;
|
||||
lastMessage.format = response?.format;
|
||||
console.log('Message updated:', {
|
||||
role: 'assistant',
|
||||
format: lastMessage.format
|
||||
});
|
||||
} else {
|
||||
newMessages.push({
|
||||
role: 'assistant',
|
||||
content,
|
||||
format: response?.format
|
||||
});
|
||||
}
|
||||
return newMessages;
|
||||
});
|
||||
},
|
||||
(error) => {
|
||||
handleError(error);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
return newMessages;
|
||||
});
|
||||
},
|
||||
(error) => {
|
||||
handleError(error);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
streamingStore.set(false);
|
||||
} catch (error) {
|
||||
if (error instanceof Error) {
|
||||
handleError(error);
|
||||
} else {
|
||||
handleError(String(error));
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
streamingStore.set(false);
|
||||
} catch (error) {
|
||||
if (error instanceof Error) {
|
||||
handleError(error);
|
||||
} else {
|
||||
handleError(String(error));
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
// Re-export types for convenience
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
import { writable } from 'svelte/store';
|
||||
import { browser } from '$app/environment';
|
||||
|
||||
// Load favorites from localStorage if available
|
||||
const storedFavorites = typeof localStorage !== 'undefined'
|
||||
const storedFavorites = browser
|
||||
? JSON.parse(localStorage.getItem('favoritePatterns') || '[]')
|
||||
: [];
|
||||
|
||||
const createFavoritesStore = () => {
|
||||
const { subscribe, set, update } = writable<string[]>(storedFavorites);
|
||||
|
||||
|
||||
return {
|
||||
subscribe,
|
||||
toggleFavorite: (patternName: string) => {
|
||||
@@ -17,7 +18,7 @@ const createFavoritesStore = () => {
|
||||
: [...favorites, patternName];
|
||||
|
||||
// Save to localStorage
|
||||
if (typeof localStorage !== 'undefined') {
|
||||
if (browser) {
|
||||
localStorage.setItem('favoritePatterns', JSON.stringify(newFavorites));
|
||||
}
|
||||
|
||||
@@ -26,11 +27,11 @@ const createFavoritesStore = () => {
|
||||
},
|
||||
reset: () => {
|
||||
set([]);
|
||||
if (typeof localStorage !== 'undefined') {
|
||||
if (browser) {
|
||||
localStorage.removeItem('favoritePatterns');
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
export const favorites = createFavoritesStore();
|
||||
export const favorites = createFavoritesStore();
|
||||
|
||||
@@ -1924,6 +1924,14 @@
|
||||
"tags": [
|
||||
"VISUALIZE"
|
||||
]
|
||||
},
|
||||
{
|
||||
"patternName": "concall_summary",
|
||||
"description": "Extract strategic insights from earnings transcripts for investors.",
|
||||
"tags": [
|
||||
"SUMMARIZE",
|
||||
"BUSINESS"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user