package main import ( "encoding/base64" "encoding/json" "fmt" "io" "net/http" "os" "path/filepath" "strings" "fyne.io/fyne/v2" "fyne.io/fyne/v2/app" "fyne.io/fyne/v2/container" "fyne.io/fyne/v2/dialog" "fyne.io/fyne/v2/driver/desktop" "fyne.io/fyne/v2/storage" "fyne.io/fyne/v2/widget" ) func main() { myApp := app.New() myWindow := myApp.NewWindow("Finder2e Foundry Converter") // LLM Provider Selection providerSelect := widget.NewSelect([]string{"LM Studio"}, func(s string) { fmt.Printf("Provider changed to: %s\n", s) }) providerSelect.SetSelected("LM Studio") // LM Studio Integration modelSelect := widget.NewSelect([]string{"Loading models..."}, nil) modelSelect.PlaceHolder = "Select Model" refreshModels := func() { if providerSelect.Selected != "LM Studio" { return } resp, err := http.Get("http://localhost:1234/v1/models") if err != nil { modelSelect.Options = []string{"Error: LM Studio not found"} modelSelect.Refresh() return } defer resp.Body.Close() var result struct { Data []struct { ID string `json:"id"` } `json:"data"` } if err := json.NewDecoder(resp.Body).Decode(&result); err != nil { modelSelect.Options = []string{"Error decoding models"} modelSelect.Refresh() return } var models []string for _, m := range result.Data { models = append(models, m.ID) } if len(models) == 0 { models = []string{"No models loaded in LM Studio"} } modelSelect.Options = models modelSelect.Refresh() } // Initial fetch go refreshModels() textEntry := widget.NewMultiLineEntry() textEntry.SetPlaceHolder("Enter description here...") imagePathsContainer := container.NewVBox() addImageEntry := func(path string) { newEntry := widget.NewEntry() newEntry.SetText(path) imagePathsContainer.Add(newEntry) imagePathsContainer.Refresh() } isImage := func(ext string) bool { switch ext { case ".png", ".jpg", ".jpeg", ".webp": return true } return false } imagePickerButton := widget.NewButton("Select Image", func() { fd := dialog.NewFileOpen(func(reader fyne.URIReadCloser, err error) { if err != nil || reader == nil { return } defer reader.Close() addImageEntry(reader.URI().Path()) }, myWindow) fd.SetFilter(storage.NewExtensionFileFilter([]string{".png", ".jpg", ".jpeg", ".webp"})) fd.Show() }) // Drag and Drop support myWindow.SetOnDropped(func(pos fyne.Position, uris []fyne.URI) { for _, u := range uris { if isImage(u.Extension()) { addImageEntry(u.Path()) } } }) // Paste support (Ctrl+V) pasteShortcut := &desktop.CustomShortcut{KeyName: fyne.KeyV, Modifier: fyne.KeyModifierShortcutDefault} myWindow.Canvas().AddShortcut(pasteShortcut, func(shortcut fyne.Shortcut) { content := myWindow.Clipboard().Content() if content == "" { return } // Handle file URLs (sometimes copied from file managers) path := strings.TrimPrefix(content, "file://") path = strings.TrimSpace(path) ext := strings.ToLower(filepath.Ext(path)) if isImage(ext) { // Check if it's a valid local path if _, err := os.Stat(path); err == nil { addImageEntry(path) } } }) generateButton := widget.NewButton("Generate", func() { if modelSelect.Selected == "" { dialog.ShowInformation("Selection Required", "Please select a model first.", myWindow) return } if providerSelect.Selected == "LM Studio" { fmt.Printf("Sending request to LM Studio (%s)...\n", modelSelect.Selected) // ... (rest of the existing LM Studio generation logic remains below) } }) // (I'll keep the actual generation logic block to keep the edit clean) // Actually, I should include it to ensure the code is complete. // RE-IMPLEMENTING the generate button with provider check: generateButton.OnTapped = func() { if modelSelect.Selected == "" { dialog.ShowInformation("Selection Required", "Please select a model first.", myWindow) return } if providerSelect.Selected == "LM Studio" { fmt.Printf("Sending request to LM Studio (%s)...\n", modelSelect.Selected) // Prepare messages type MessageContent struct { Type string `json:"type"` Text string `json:"text,omitempty"` ImageURL *struct { URL string `json:"url"` } `json:"image_url,omitempty"` } var content []MessageContent content = append(content, MessageContent{Type: "text", Text: textEntry.Text}) for _, obj := range imagePathsContainer.Objects { if entry, ok := obj.(*widget.Entry); ok && entry.Text != "" { imgData, err := os.ReadFile(entry.Text) if err != nil { fmt.Printf("Error reading image %s: %v\n", entry.Text, err) continue } base64Img := base64.StdEncoding.EncodeToString(imgData) mimeType := "image/png" if strings.HasSuffix(strings.ToLower(entry.Text), ".jpg") || strings.HasSuffix(strings.ToLower(entry.Text), ".jpeg") { mimeType = "image/jpeg" } else if strings.HasSuffix(strings.ToLower(entry.Text), ".webp") { mimeType = "image/webp" } content = append(content, MessageContent{ Type: "image_url", ImageURL: &struct { URL string `json:"url"` }{ URL: fmt.Sprintf("data:%s;base64,%s", mimeType, base64Img), }, }) } } requestBody, _ := json.Marshal(map[string]interface{}{ "model": modelSelect.Selected, "messages": []interface{}{map[string]interface{}{"role": "user", "content": content}}, }) resp, err := http.Post("http://localhost:1234/v1/chat/completions", "application/json", strings.NewReader(string(requestBody))) if err != nil { fmt.Printf("Error calling LM Studio: %v\n", err) return } defer resp.Body.Close() body, _ := io.ReadAll(resp.Body) var chatResp struct { Choices []struct { Message struct { Content string `json:"content"` } `json:"message"` } `json:"choices"` Error struct { Message string `json:"message"` } `json:"error"` } if err := json.Unmarshal(body, &chatResp); err != nil { fmt.Printf("Error decoding response: %v\n", err) fmt.Println("Raw response:", string(body)) return } if chatResp.Error.Message != "" { fmt.Printf("LM Studio Error: %s\n", chatResp.Error.Message) return } if len(chatResp.Choices) > 0 { fmt.Println("\nLM Studio Response:") fmt.Println(chatResp.Choices[0].Message.Content) } else { fmt.Println("No response choices returned.") } } } scrollContainer := container.NewVScroll(imagePathsContainer) scrollContainer.SetMinSize(fyne.NewSize(0, 150)) content := container.NewVBox( widget.NewLabel("Provider:"), providerSelect, widget.NewLabel("Model:"), container.NewBorder(nil, nil, nil, widget.NewButton("Refresh", refreshModels), modelSelect), widget.NewLabel("Description:"), textEntry, widget.NewLabel("Images:"), scrollContainer, imagePickerButton, generateButton, ) myWindow.SetContent(content) myWindow.Resize(fyne.NewSize(500, 500)) myWindow.ShowAndRun() }