diff --git a/restapi/chat.go b/restapi/chat.go index 3e6ecb73..34677b23 100755 --- a/restapi/chat.go +++ b/restapi/chat.go @@ -3,8 +3,11 @@ package restapi import ( "encoding/json" "fmt" + "io/ioutil" "log" "net/http" + "os" + "path/filepath" "strings" goopenai "github.com/sashabaranov/go-openai" @@ -21,11 +24,12 @@ type ChatHandler struct { } type PromptRequest struct { - UserInput string `json:"userInput"` - Vendor string `json:"vendor"` - Model string `json:"model"` - ContextName string `json:"contextName"` - PatternName string `json:"patternName"` + UserInput string `json:"userInput"` + Vendor string `json:"vendor"` + Model string `json:"model"` + ContextName string `json:"contextName"` + PatternName string `json:"patternName"` + StrategyName string `json:"strategyName"` // Optional strategy name } type ChatRequest struct { @@ -80,13 +84,25 @@ func (h *ChatHandler) HandleChat(c *gin.Context) { log.Printf("Processing prompt %d: Model=%s Pattern=%s Context=%s", i+1, prompt.Model, prompt.PatternName, prompt.ContextName) - // Create chat channel for streaming streamChan := make(chan string) - // Start chat processing in goroutine go func(p PromptRequest) { defer close(streamChan) + // Load and prepend strategy prompt if strategyName is set + if p.StrategyName != "" { + strategyFile := filepath.Join(os.Getenv("HOME"), ".config", "fabric", "strategies", p.StrategyName+".json") + data, err := ioutil.ReadFile(strategyFile) + if err == nil { + var s struct { + Prompt string `json:"prompt"` + } + if err := json.Unmarshal(data, &s); err == nil && s.Prompt != "" { + p.UserInput = s.Prompt + "\n" + p.UserInput + } + } + } + chatter, err := h.registry.GetChatter(p.Model, 2048, "", false, false) if err != nil { log.Printf("Error creating chatter: %v", err) @@ -124,7 +140,6 @@ func (h *ChatHandler) HandleChat(c *gin.Context) { return } - // Get the last message from the session lastMsg := session.GetLastMessage() if lastMsg != nil { streamChan <- lastMsg.Content @@ -134,37 +149,32 @@ func (h *ChatHandler) HandleChat(c *gin.Context) { } }(prompt) - // Read from streamChan and write to client for content := range streamChan { select { case <-clientGone: return default: + var response StreamResponse if strings.HasPrefix(content, "Error:") { - response := StreamResponse{ + response = StreamResponse{ Type: "error", Format: "plain", Content: content, } - if err := writeSSEResponse(c.Writer, response); err != nil { - log.Printf("Error writing error response: %v", err) - return - } } else { - response := StreamResponse{ + response = StreamResponse{ Type: "content", Format: detectFormat(content), Content: content, } - if err := writeSSEResponse(c.Writer, response); err != nil { - log.Printf("Error writing content response: %v", err) - return - } + } + if err := writeSSEResponse(c.Writer, response); err != nil { + log.Printf("Error writing response: %v", err) + return } } } - // Signal completion of this prompt completeResponse := StreamResponse{ Type: "complete", Format: "plain", @@ -192,26 +202,6 @@ func writeSSEResponse(w gin.ResponseWriter, response StreamResponse) error { return nil } -/* - func detectFormat(content string) string { - if strings.HasPrefix(content, "graph TD") || - strings.HasPrefix(content, "gantt") || - strings.HasPrefix(content, "flowchart") || - strings.HasPrefix(content, "sequenceDiagram") || - strings.HasPrefix(content, "classDiagram") || - strings.HasPrefix(content, "stateDiagram") { - return "mermaid" - } - if strings.Contains(content, "```") || - strings.Contains(content, "#") || - strings.Contains(content, "*") || - strings.Contains(content, "_") || - strings.Contains(content, "-") { - return "markdown" - } - return "plain" - } -*/ func detectFormat(content string) string { if strings.HasPrefix(content, "graph TD") || strings.HasPrefix(content, "gantt") || diff --git a/restapi/serve.go b/restapi/serve.go index f1a81a0d..103a5ea5 100644 --- a/restapi/serve.go +++ b/restapi/serve.go @@ -28,6 +28,7 @@ func Serve(registry *core.PluginRegistry, address string, apiKey string) (err er NewChatHandler(r, registry, fabricDb) NewConfigHandler(r, fabricDb) NewModelsHandler(r, registry.VendorManager) + NewStrategiesHandler(r) // Start server err = r.Run(address) diff --git a/restapi/strategies.go b/restapi/strategies.go new file mode 100644 index 00000000..5525f8dd --- /dev/null +++ b/restapi/strategies.go @@ -0,0 +1,59 @@ +package restapi + +import ( + "encoding/json" + "io/ioutil" + "net/http" + "os" + "path/filepath" + + "github.com/gin-gonic/gin" +) + +// StrategyMeta represents the minimal info about a strategy +type StrategyMeta struct { + Name string `json:"name"` + Description string `json:"description"` +} + +// NewStrategiesHandler registers the /strategies GET endpoint +func NewStrategiesHandler(r *gin.Engine) { + r.GET("/strategies", func(c *gin.Context) { + strategiesDir := filepath.Join(os.Getenv("HOME"), ".config", "fabric", "strategies") + + files, err := ioutil.ReadDir(strategiesDir) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to read strategies directory"}) + return + } + + var strategies []StrategyMeta + + for _, file := range files { + if file.IsDir() || filepath.Ext(file.Name()) != ".json" { + continue + } + + fullPath := filepath.Join(strategiesDir, file.Name()) + data, err := ioutil.ReadFile(fullPath) + if err != nil { + continue + } + + var s struct { + Name string `json:"name"` + Description string `json:"description"` + } + if err := json.Unmarshal(data, &s); err != nil { + continue + } + + strategies = append(strategies, StrategyMeta{ + Name: s.Name, + Description: s.Description, + }) + } + + c.JSON(http.StatusOK, strategies) + }) +} diff --git a/web/Web Interface Update README Files/pr-1284-update.md b/web/Web Interface Update README Files/pr-1284-update.md index df119661..c33b8355 100644 --- a/web/Web Interface Update README Files/pr-1284-update.md +++ b/web/Web Interface Update README Files/pr-1284-update.md @@ -35,6 +35,11 @@ https://youtu.be/fcVitd4Kb98 The tag filtering system has been deeply integrated into the Pattern Selection interface through several UI enhancements: +### 5. Strategy flags +- strategies are fetch from .config/fabric/strategies for server processing +- for gui, they are fetched from static/strategies + + 1. **Dual-Position Tag Panel** - Sliding panel positioned to the right of pattern modal - Dynamic toggle button that adapts position and text based on panel state diff --git a/web/src/lib/components/chat/DropdownGroup.svelte b/web/src/lib/components/chat/DropdownGroup.svelte index 996dec4b..bc248c74 100644 --- a/web/src/lib/components/chat/DropdownGroup.svelte +++ b/web/src/lib/components/chat/DropdownGroup.svelte @@ -4,6 +4,8 @@ import ModelConfig from "./ModelConfig.svelte"; import { Select } from "$lib/components/ui/select"; import { languageStore } from '$lib/store/language-store'; + import { strategies, selectedStrategy, fetchStrategies } from '$lib/store/strategy-store'; + import { onMount } from 'svelte'; const languages = [ { code: '', name: 'Default Language' }, @@ -15,6 +17,10 @@ { code: 'ja', name: 'Japanese' }, { code: 'it', name: 'Italian' } ]; + + onMount(() => { + fetchStrategies(); + });
@@ -36,6 +42,17 @@ {/each}
+
+ +
diff --git a/web/src/lib/interfaces/chat-interface.ts b/web/src/lib/interfaces/chat-interface.ts index 3bedd9e9..218d62e7 100644 --- a/web/src/lib/interfaces/chat-interface.ts +++ b/web/src/lib/interfaces/chat-interface.ts @@ -6,7 +6,8 @@ export interface ChatPrompt { userInput: string; systemPrompt: string; model: string; - patternName: string; + patternName?: string; + strategyName?: string; // Optional strategy name to prepend strategy prompt } export interface ChatConfig { diff --git a/web/src/lib/services/ChatService.ts b/web/src/lib/services/ChatService.ts index 28ce1d21..3606fd02 100644 --- a/web/src/lib/services/ChatService.ts +++ b/web/src/lib/services/ChatService.ts @@ -10,6 +10,7 @@ import { systemPrompt, selectedPatternName } from '$lib/store/pattern-store'; import { chatConfig } from '$lib/store/chat-config'; import { messageStore } from '$lib/store/chat-store'; import { languageStore } from '$lib/store/language-store'; +import { selectedStrategy } from '$lib/store/strategy-store'; class LanguageValidator { constructor(private targetLanguage: string) {} @@ -179,7 +180,8 @@ export class ChatService { userInput: finalUserInput, systemPrompt: finalSystemPrompt, model: config.model, - patternName: get(selectedPatternName) + patternName: get(selectedPatternName), + strategyName: get(selectedStrategy) // Add selected strategy to prompt }; } diff --git a/web/src/lib/store/strategy-store.ts b/web/src/lib/store/strategy-store.ts new file mode 100644 index 00000000..3cda94a4 --- /dev/null +++ b/web/src/lib/store/strategy-store.ts @@ -0,0 +1,32 @@ +import { writable } from 'svelte/store'; + +/** + * List of available strategies fetched from backend. + * Each strategy has a name and description. + */ +export const strategies = writable>([]); + +/** + * Currently selected strategy name. + * Default is empty string meaning "None". + */ +export const selectedStrategy = writable(""); + +/** + * Fetches available strategies from the backend `/strategies` endpoint. + * Populates the `strategies` store. + */ +export async function fetchStrategies() { + try { + const response = await fetch('/strategies/strategies.json'); + if (!response.ok) { + console.error('Failed to fetch strategies:', response.statusText); + return; + } + const data = await response.json(); + // Expecting an array of { name, description } + strategies.set(data); + } catch (error) { + console.error('Error fetching strategies:', error); + } +} diff --git a/web/static/strategies/strategies.json b/web/static/strategies/strategies.json new file mode 100644 index 00000000..def5803e --- /dev/null +++ b/web/static/strategies/strategies.json @@ -0,0 +1,11 @@ +[ + { "name": "cod", "description": "Chain-of-Density (CoD)" }, + { "name": "cot", "description": "Chain-of-Thought (CoT) Prompting" }, + { "name": "ltm", "description": "Least-to-Most Prompting" }, + { "name": "reflexion", "description": "Reflexion Prompting" }, + { "name": "self-consistent", "description": "Self-Consistency Prompting" }, + { "name": "self-refine", "description": "Self-Refinement" }, + { "name": "standard", "description": "Standard Prompting" }, + { "name": "test", "description": "Test strategy" }, + { "name": "tot", "description": "Tree-of-Thoughts (ToT)" } +]