Implement interactive CLI for LLM providers with chat, tools, and image support

- Add Bubble Tea-based CLI interface for LLM interactions.
- Implement `.env.example` for environment variable setup.
- Add provider, model, and tool selection screens.
- Include support for API key configuration.
- Enable chat interactions with optional image and tool support.
- Introduce core utility functions: image handling, tool execution, chat request management, and response rendering.
- Implement style customization with Lip Gloss.
This commit is contained in:
2026-01-24 15:53:36 -05:00
parent bf7c86ab2a
commit 97d54c10ae
12 changed files with 1550 additions and 0 deletions

105
cmd/llm/tools.go Normal file
View File

@@ -0,0 +1,105 @@
package main
import (
"fmt"
"math"
"strconv"
"strings"
"time"
llm "gitea.stevedudenhoeffer.com/steve/go-llm"
)
// TimeParams is the parameter struct for the GetTime function
type TimeParams struct{}
// GetTime returns the current time
func GetTime(_ *llm.Context, _ TimeParams) (any, error) {
return time.Now().Format("Monday, January 2, 2006 3:04:05 PM MST"), nil
}
// CalcParams is the parameter struct for the Calculate function
type CalcParams struct {
A float64 `json:"a" description:"First number"`
B float64 `json:"b" description:"Second number"`
Op string `json:"op" description:"Operation: add, subtract, multiply, divide, power, sqrt, mod"`
}
// Calculate performs basic math operations
func Calculate(_ *llm.Context, params CalcParams) (any, error) {
switch strings.ToLower(params.Op) {
case "add", "+":
return params.A + params.B, nil
case "subtract", "sub", "-":
return params.A - params.B, nil
case "multiply", "mul", "*":
return params.A * params.B, nil
case "divide", "div", "/":
if params.B == 0 {
return nil, fmt.Errorf("division by zero")
}
return params.A / params.B, nil
case "power", "pow", "^":
return math.Pow(params.A, params.B), nil
case "sqrt":
if params.A < 0 {
return nil, fmt.Errorf("cannot take square root of negative number")
}
return math.Sqrt(params.A), nil
case "mod", "%":
return math.Mod(params.A, params.B), nil
default:
return nil, fmt.Errorf("unknown operation: %s", params.Op)
}
}
// WeatherParams is the parameter struct for the GetWeather function
type WeatherParams struct {
Location string `json:"location" description:"City name or location"`
}
// GetWeather returns mock weather data (for demo purposes)
func GetWeather(_ *llm.Context, params WeatherParams) (any, error) {
// This is a demo function - returns mock data
weathers := []string{"sunny", "cloudy", "rainy", "partly cloudy", "windy"}
temps := []int{65, 72, 58, 80, 45}
// Use location string to deterministically pick weather
idx := len(params.Location) % len(weathers)
return map[string]any{
"location": params.Location,
"temperature": strconv.Itoa(temps[idx]) + "F",
"condition": weathers[idx],
"humidity": "45%",
"note": "This is mock data for demonstration purposes",
}, nil
}
// RandomNumberParams is the parameter struct for the RandomNumber function
type RandomNumberParams struct {
Min int `json:"min" description:"Minimum value (inclusive)"`
Max int `json:"max" description:"Maximum value (inclusive)"`
}
// RandomNumber generates a pseudo-random number (using current time nanoseconds)
func RandomNumber(_ *llm.Context, params RandomNumberParams) (any, error) {
if params.Min > params.Max {
return nil, fmt.Errorf("min cannot be greater than max")
}
// Simple pseudo-random using time
n := time.Now().UnixNano()
rangeSize := params.Max - params.Min + 1
result := params.Min + int(n%int64(rangeSize))
return result, nil
}
// createDemoToolbox creates a toolbox with demo tools for testing
func createDemoToolbox() llm.ToolBox {
return llm.NewToolBox(
llm.NewFunction("get_time", "Get the current date and time", GetTime),
llm.NewFunction("calculate", "Perform basic math operations (add, subtract, multiply, divide, power, sqrt, mod)", Calculate),
llm.NewFunction("get_weather", "Get weather information for a location (demo data)", GetWeather),
llm.NewFunction("random_number", "Generate a random number between min and max", RandomNumber),
)
}