basic JSON parsing into genai struc
This commit is contained in:
parent
c971bfddf7
commit
7f8f5e3b9b
12
Makefile
12
Makefile
|
@ -1,7 +1,8 @@
|
|||
VERSION = $(shell git describe --tags)
|
||||
BUILDTIME = $(shell date +%Y.%m.%d_%H%M)
|
||||
|
||||
default: verbose
|
||||
default: install
|
||||
regex --json /tmp/regex.55128216-e93b-4339-8854-622ca11af890.gemini-api-request.99.json connect
|
||||
|
||||
vet:
|
||||
@GO111MODULE=off go vet
|
||||
|
@ -15,16 +16,16 @@ build: goimports
|
|||
GO111MODULE=off go build \
|
||||
-ldflags "-X main.VERSION=${VERSION} -X main.BUILDTIME=${BUILDTIME} -X gui.GUIVERSION=${VERSION}"
|
||||
|
||||
install: goimports vet
|
||||
install: goimports
|
||||
GO111MODULE=off go install \
|
||||
-ldflags "-X main.VERSION=${VERSION} -X main.BUILDTIME=${BUILDTIME} -X gui.GUIVERSION=${VERSION}"
|
||||
|
||||
regex:
|
||||
regex: goimports
|
||||
GO111MODULE=off go install \
|
||||
-ldflags "-X main.VERSION=${VERSION} -X main.BUILDTIME=${BUILDTIME} -X gui.GUIVERSION=${VERSION}"
|
||||
|
||||
dumb-build:
|
||||
go install \
|
||||
go install -v -x \
|
||||
-ldflags "-X main.VERSION=${VERSION} -X main.BUILDTIME=${BUILDTIME} -X gui.GUIVERSION=${VERSION}"
|
||||
|
||||
install-raw: goimports vet
|
||||
|
@ -38,7 +39,6 @@ gocui: install
|
|||
regex --gui gocui --gui-verbose --gui-file ../../toolkits/gocui/gocui.so >/tmp/regex.log 2>&1
|
||||
|
||||
goimports:
|
||||
reset
|
||||
goimports -w *.go
|
||||
@# // to globally reset paths:
|
||||
@# // gofmt -w -r '"go.wit.com/gui/gadgets" -> "go.wit.com/lib/gadgets"' *.go
|
||||
|
@ -55,3 +55,5 @@ playback:
|
|||
regex playback
|
||||
# regex playback --uuid a1b2c3d4-e5f6-4a5b-8c9d-1e2f3a4b5c6d
|
||||
|
||||
tmp:
|
||||
ls -tl /tmp/regex.* |head
|
||||
|
|
11
add.go
11
add.go
|
@ -1,7 +1,6 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
|
@ -14,15 +13,15 @@ import (
|
|||
func addFile(filename string) (*chatpb.Chats, error) {
|
||||
data, err := os.ReadFile(filename)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to read file %s: %w", filename, err)
|
||||
return nil, log.Errorf("failed to read file %s: %w", filename, err)
|
||||
}
|
||||
|
||||
logData, err := chatpb.UnmarshalChatsTEXT(data)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to unmarshal log file %s: %w", filename, err)
|
||||
return nil, log.Errorf("failed to unmarshal log file %s: %w", filename, err)
|
||||
}
|
||||
|
||||
log.Infof("Successfully parsed log file: %s", filename)
|
||||
log.Info("Successfully parsed log file: %s", filename)
|
||||
|
||||
// Get the directory of the log file to resolve relative content paths.
|
||||
logDir := filepath.Dir(filename)
|
||||
|
@ -35,7 +34,7 @@ func addFile(filename string) (*chatpb.Chats, error) {
|
|||
contentPath := filepath.Join(logDir, contentFile)
|
||||
contentBytes, err := os.ReadFile(contentPath)
|
||||
if err != nil {
|
||||
entry.Content = fmt.Sprintf("--- ERROR: Could not read content file %s: %v ---", contentPath, err)
|
||||
entry.Content = log.Sprintf("--- ERROR: Could not read content file %s: %v ---", contentPath, err)
|
||||
} else {
|
||||
entry.Content = string(contentBytes)
|
||||
}
|
||||
|
@ -48,7 +47,7 @@ func addFile(filename string) (*chatpb.Chats, error) {
|
|||
snippetPath := filepath.Join(logDir, snippetFile)
|
||||
contentBytes, err := os.ReadFile(snippetPath)
|
||||
if err != nil {
|
||||
snippet.Content = fmt.Sprintf("--- ERROR: Could not read snippet file %s: %v ---", snippetPath, err)
|
||||
snippet.Content = log.Sprintf("--- ERROR: Could not read snippet file %s: %v ---", snippetPath, err)
|
||||
} else {
|
||||
snippet.Content = string(contentBytes)
|
||||
}
|
||||
|
|
9
argv.go
9
argv.go
|
@ -12,13 +12,17 @@ var argv args
|
|||
type args struct {
|
||||
Add string `arg:"--add" help:"add a new chat"`
|
||||
Format *EmptyCmd `arg:"subcommand:format" help:"add a conversation"`
|
||||
Connect *EmptyCmd `arg:"subcommand:connect" help:"connect to gemini AI"`
|
||||
Playback *PlaybackCmd `arg:"subcommand:playback" help:"dump your prior conversations to the terminal'"`
|
||||
Output string `arg:"--output" help:"should get a string from regex-cli"`
|
||||
Input string `arg:"--input" help:"should get a string from regex-cli"`
|
||||
Editor *EmptyCmd `arg:"subcommand:editor" help:"open env EDITOR"`
|
||||
Editor *EmptyCmd `arg:"subcommand:interact" help:"open env EDITOR"`
|
||||
ImportFile string `arg:"--import" help:"import a file from regex-cli"`
|
||||
JsonFile string `arg:"--json" help:"import a JSON file from gemini-cli"`
|
||||
Uuid string `arg:"--uuid" help:"look at this uuid"`
|
||||
Topic string `arg:"--topic" help:"the topic"`
|
||||
Stats []string `arg:"--stats" help:"add stats to a chat"`
|
||||
NewChat []string `arg:"--new-chat" help:"create a new chat"`
|
||||
NewChat *EmptyCmd `arg:"subcommand:newchat" help:"create a new chat"`
|
||||
GetNextAutoTopic bool `arg:"--get-next-auto-topic" help:"get the next auto topic name"`
|
||||
Force bool `arg:"--force" help:"try to strong arm things"`
|
||||
Verbose bool `arg:"--verbose" help:"show more output"`
|
||||
|
@ -32,7 +36,6 @@ type EmptyCmd struct {
|
|||
type PlaybackCmd struct {
|
||||
List *EmptyCmd `arg:"subcommand:list" help:"list memories"`
|
||||
Long *EmptyCmd `arg:"subcommand:long" help:"show info on each chat"`
|
||||
Uuid string `arg:"--uuid" help:"look at this uuid"`
|
||||
}
|
||||
|
||||
func (args) Version() string {
|
||||
|
|
|
@ -30,7 +30,7 @@ func (args) doBashAuto() {
|
|||
default:
|
||||
if argv.BashAuto[0] == ARGNAME {
|
||||
// list the subcommands here
|
||||
fmt.Println("--add format playback editor")
|
||||
fmt.Println("--add connect format interact playback")
|
||||
}
|
||||
}
|
||||
os.Exit(0)
|
||||
|
|
|
@ -0,0 +1,73 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"go.wit.com/log"
|
||||
"google.golang.org/genai"
|
||||
)
|
||||
|
||||
// doConnect initializes the Gemini client and handles the request flow.
|
||||
func doConnect() error {
|
||||
apiKey := os.Getenv("GEMINI_API_KEY")
|
||||
if apiKey == "" {
|
||||
return log.Errorf("GEMINI_API_KEY environment variable not set")
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
client, err := genai.NewClient(ctx, &genai.ClientConfig{APIKey: apiKey})
|
||||
if err != nil {
|
||||
return log.Errorf("failed to create new genai client: %w", err)
|
||||
}
|
||||
|
||||
if argv.JsonFile != "" {
|
||||
req, err := parseJSON(argv.JsonFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
log.Info("parseJSON() ok. model =", req.Model)
|
||||
|
||||
genaiContent, err := convertToGenai(req)
|
||||
if err != nil {
|
||||
return log.Errorf("failed to convert to genai.Content: %w", err)
|
||||
}
|
||||
log.Info("Successfully converted JSON to genai.Content")
|
||||
// Here you would now use the 'genaiContent' to send to the API
|
||||
_ = genaiContent // Prevent unused variable error for now
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
log.Info("doing sampleHello()")
|
||||
return sampleHello(client)
|
||||
}
|
||||
|
||||
// sampleHello sends a hardcoded prompt to the model and prints the response.
|
||||
func sampleHello(client *genai.Client) error {
|
||||
log.Info("Sending 'hello, how are you' to the Gemini API...")
|
||||
ctx := context.Background()
|
||||
|
||||
// Create the parts slice
|
||||
parts := []*genai.Part{
|
||||
{Text: "hello, how are you"},
|
||||
}
|
||||
|
||||
content := []*genai.Content{{Parts: parts}}
|
||||
|
||||
resp, err := client.Models.GenerateContent(ctx, "gemini-1.5-flash-latest", content, nil)
|
||||
if err != nil {
|
||||
return log.Errorf("error sending message: %v", err)
|
||||
}
|
||||
|
||||
log.Info("Response from API:")
|
||||
for _, cand := range resp.Candidates {
|
||||
if cand.Content != nil {
|
||||
for _, part := range cand.Content.Parts {
|
||||
fmt.Println(part)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
|
@ -1,28 +0,0 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/google/generative-ai-go/genai"
|
||||
"google.golang.org/api/option"
|
||||
)
|
||||
|
||||
// doConnect initializes and returns a Gemini client.
|
||||
// It reads the API key from the GEMINI_API_KEY environment variable.
|
||||
func doConnect() (*genai.GenerativeModel, error) {
|
||||
apiKey := os.Getenv("GEMINI_API_KEY")
|
||||
if apiKey == "" {
|
||||
return nil, fmt.Errorf("GEMINI_API_KEY environment variable not set")
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
client, err := genai.NewClient(ctx, option.WithAPIKey(apiKey))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create new genai client: %w", err)
|
||||
}
|
||||
|
||||
model := client.GenerativeModel("gemini-pro")
|
||||
return model, nil
|
||||
}
|
|
@ -32,7 +32,7 @@ func doEditor() error {
|
|||
log.Error(err)
|
||||
}
|
||||
os.Remove("/tmp/regex.ready")
|
||||
log.Infof("SessionID: %s", string(content))
|
||||
log.Info("SessionID: %s", string(content))
|
||||
|
||||
logContent, err := ioutil.ReadFile("/tmp/regex.log")
|
||||
if err != nil {
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"go.wit.com/log"
|
||||
)
|
||||
|
||||
func doGetNextAutoTopic() {
|
||||
|
@ -20,5 +21,5 @@ func doGetNextAutoTopic() {
|
|||
}
|
||||
}
|
||||
}
|
||||
fmt.Printf("Auto %d", max+1)
|
||||
log.Printf("Auto %d", max+1)
|
||||
}
|
||||
|
|
15
doNewChat.go
15
doNewChat.go
|
@ -1,28 +1,19 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"go.wit.com/lib/protobuf/chatpb"
|
||||
"go.wit.com/log"
|
||||
"google.golang.org/protobuf/types/known/timestamppb"
|
||||
)
|
||||
|
||||
func doNewChat() {
|
||||
if len(argv.NewChat) != 2 {
|
||||
log.Error(fmt.Errorf("expected 2 arguments for --new-chat"))
|
||||
return
|
||||
}
|
||||
uuid := argv.NewChat[0]
|
||||
topic := argv.NewChat[1]
|
||||
|
||||
chat := &chatpb.Chat{
|
||||
Uuid: uuid,
|
||||
ChatName: topic,
|
||||
Uuid: argv.Uuid,
|
||||
ChatName: argv.Topic,
|
||||
Ctime: timestamppb.Now(),
|
||||
}
|
||||
|
||||
me.chats.Chats = append(me.chats.Chats, chat)
|
||||
me.chats.ConfigSave()
|
||||
log.Info("created new chat for", uuid)
|
||||
log.Info("created new chat for", argv.Uuid)
|
||||
}
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"go.wit.com/lib/protobuf/chatpb"
|
||||
|
@ -9,8 +8,8 @@ import (
|
|||
)
|
||||
|
||||
func doPlayback() {
|
||||
if argv.Playback.Uuid != "" {
|
||||
showChat(argv.Playback.Uuid)
|
||||
if argv.Uuid != "" {
|
||||
showChat(argv.Uuid)
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -34,8 +33,8 @@ func listChats(chats *chatpb.Chats) {
|
|||
return
|
||||
}
|
||||
|
||||
log.Infof("Found %d chat topic(s) in the log.", len(chats.GetChats()))
|
||||
fmt.Println("-------------------------------------------------")
|
||||
log.Info("Found %d chat topic(s) in the log.", len(chats.GetChats()))
|
||||
log.Println("-------------------------------------------------")
|
||||
|
||||
for _, chat := range chats.GetChats() {
|
||||
entryCount := len(chat.GetEntries())
|
||||
|
@ -47,19 +46,19 @@ func listChats(chats *chatpb.Chats) {
|
|||
formattedTime = "No Timestamp"
|
||||
}
|
||||
|
||||
fmt.Printf("Topic: %-25s | Entries: %-4d | Started: %s | UUID: %s\n",
|
||||
log.Printf("Topic: %-25s | Entries: %-4d | Started: %s | UUID: %s\n",
|
||||
chat.GetChatName(),
|
||||
entryCount,
|
||||
formattedTime,
|
||||
chat.GetUuid(),
|
||||
)
|
||||
}
|
||||
fmt.Println("-------------------------------------------------")
|
||||
log.Println("-------------------------------------------------")
|
||||
}
|
||||
|
||||
// print out one line for each chat entry
|
||||
func listEntries(chat *chatpb.Chat) {
|
||||
fmt.Printf("--- Entries for Topic: %s ---\n", chat.GetChatName())
|
||||
log.Printf("--- Entries for Topic: %s ---\n", chat.GetChatName())
|
||||
width := getTerminalWidth()
|
||||
|
||||
// Determine the maximum length of the author and time string
|
||||
|
@ -73,7 +72,7 @@ func listEntries(chat *chatpb.Chat) {
|
|||
} else {
|
||||
formattedTime = "No Time"
|
||||
}
|
||||
authorAndTime := fmt.Sprintf("[%s] (%s)", author, formattedTime)
|
||||
authorAndTime := log.Sprintf("[%s] (%s)", author, formattedTime)
|
||||
if len(authorAndTime) > maxAuthorAndTimeLen {
|
||||
maxAuthorAndTimeLen = len(authorAndTime)
|
||||
}
|
||||
|
@ -94,7 +93,7 @@ func listEntries(chat *chatpb.Chat) {
|
|||
// Replace newlines with spaces for a clean one-line view
|
||||
contentPreview = strings.ReplaceAll(contentPreview, "\n", " ")
|
||||
|
||||
authorAndTime := fmt.Sprintf("[%s] (%s)", author, formattedTime)
|
||||
authorAndTime := log.Sprintf("[%s] (%s)", author, formattedTime)
|
||||
|
||||
availableWidth := width - maxAuthorAndTimeLen - 1 // -1 for a space
|
||||
if len(contentPreview) > availableWidth {
|
||||
|
@ -112,14 +111,14 @@ func listEntries(chat *chatpb.Chat) {
|
|||
if authorAndTimePadding < 0 {
|
||||
authorAndTimePadding = 0
|
||||
}
|
||||
fmt.Printf("%s%s%s%s\n", contentPreview, strings.Repeat(" ", padding), strings.Repeat(" ", authorAndTimePadding), authorAndTime)
|
||||
log.Printf("%s%s%s%s\n", contentPreview, strings.Repeat(" ", padding), strings.Repeat(" ", authorAndTimePadding), authorAndTime)
|
||||
} else {
|
||||
padding := maxAuthorAndTimeLen - len(authorAndTime)
|
||||
if padding < 0 {
|
||||
padding = 0
|
||||
}
|
||||
fmt.Printf("%s%s %s\n", authorAndTime, strings.Repeat(" ", padding), contentPreview)
|
||||
log.Printf("%s%s %s\n", authorAndTime, strings.Repeat(" ", padding), contentPreview)
|
||||
}
|
||||
}
|
||||
fmt.Println("-------------------------------------------------")
|
||||
log.Println("-------------------------------------------------")
|
||||
}
|
||||
|
|
|
@ -2,7 +2,6 @@ package main
|
|||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"go.wit.com/lib/protobuf/chatpb"
|
||||
"go.wit.com/log"
|
||||
|
@ -11,7 +10,7 @@ import (
|
|||
|
||||
func doStats() {
|
||||
if len(argv.Stats) != 2 {
|
||||
log.Error(fmt.Errorf("expected 2 arguments for --stats"))
|
||||
log.Warn("expected 2 arguments for --stats")
|
||||
return
|
||||
}
|
||||
sessionUuid := argv.Stats[0]
|
||||
|
@ -36,7 +35,7 @@ func doStats() {
|
|||
var stats chatpb.SessionStats
|
||||
err := json.Unmarshal([]byte(statsString), &stats)
|
||||
if err != nil {
|
||||
log.Error(fmt.Errorf("error unmarshalling stats: %w", err))
|
||||
log.Printf("error unmarshalling stats: %w", err)
|
||||
return
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,134 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"os"
|
||||
|
||||
"go.wit.com/log"
|
||||
"google.golang.org/genai"
|
||||
)
|
||||
|
||||
// GeminiRequest matches the overall structure of the gemini-cli JSON output.
|
||||
type GeminiRequest struct {
|
||||
Model string `json:"model"`
|
||||
Contents []Content `json:"contents"`
|
||||
// Config is left as a raw message because its structure is complex and not needed for now.
|
||||
Config json.RawMessage `json:"config"`
|
||||
}
|
||||
|
||||
// Content matches the 'contents' array elements.
|
||||
type Content struct {
|
||||
Role string `json:"role"`
|
||||
Parts []Part `json:"parts"`
|
||||
}
|
||||
|
||||
// Part matches the 'parts' array elements.
|
||||
// It can contain one of several types of data.
|
||||
type Part struct {
|
||||
Text string `json:"text,omitempty"`
|
||||
ThoughtSignature string `json:"thoughtSignature,omitempty"`
|
||||
FunctionCall *FunctionCall `json:"functionCall,omitempty"`
|
||||
FunctionResponse *FunctionResponse `json:"functionResponse,omitempty"`
|
||||
}
|
||||
|
||||
// FunctionCall matches the 'functionCall' object.
|
||||
type FunctionCall struct {
|
||||
Name string `json:"name"`
|
||||
Args map[string]string `json:"args"`
|
||||
}
|
||||
|
||||
// FunctionResponse matches the 'functionResponse' object.
|
||||
type FunctionResponse struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Response map[string]interface{} `json:"response"`
|
||||
}
|
||||
|
||||
// parseJSON opens the given file, reads it, and unmarshals it into our structs.
|
||||
func parseJSON(filename string) (*GeminiRequest, error) {
|
||||
log.Infof("Attempting to parse file: %s\n", filename)
|
||||
|
||||
// Read the entire file
|
||||
data, err := os.ReadFile(filename)
|
||||
if err != nil {
|
||||
return nil, log.Errorf("failed to read file %s: %w", filename, err)
|
||||
}
|
||||
|
||||
// Unmarshal the JSON data
|
||||
var req *GeminiRequest
|
||||
req = new(GeminiRequest)
|
||||
if err := json.Unmarshal(data, &req); err != nil {
|
||||
return nil, log.Errorf("failed to unmarshal JSON from %s: %w", filename, err)
|
||||
}
|
||||
|
||||
dumpSummaryJSON(req)
|
||||
return req, nil
|
||||
}
|
||||
|
||||
func dumpSummaryJSON(req *GeminiRequest) {
|
||||
var totalFC, totalTexts, totalFR int
|
||||
|
||||
// Log the parsed data to confirm it worked
|
||||
|
||||
// Example of accessing deeper data
|
||||
for _, content := range req.Contents {
|
||||
// log.Infof("Content[%d] Role: %s", i, content.Role)
|
||||
for _, part := range content.Parts {
|
||||
if part.Text != "" {
|
||||
// log.Infof(" Part[%d] Text: %.60s...", j, part.Text) // Print snippet
|
||||
totalTexts += 1
|
||||
}
|
||||
if part.FunctionCall != nil {
|
||||
// log.Infof(" Part[%d] FunctionCall: %s", j, part.FunctionCall.Name)
|
||||
totalFC += 1
|
||||
}
|
||||
if part.FunctionResponse != nil {
|
||||
// log.Infof(" Part[%d] FunctionCall: %s", j, part.FunctionCall.Name)
|
||||
totalFR += 1
|
||||
}
|
||||
}
|
||||
}
|
||||
log.Printf("Parsed JSON (Model: %s) (# of content blocks %d) (Text #=%d) (FC=%d) (FR=%d)\n", req.Model, len(req.Contents), totalTexts, totalFC, totalFR)
|
||||
}
|
||||
|
||||
func dumpFullJSON(req *GeminiRequest) {
|
||||
// Log the parsed data to confirm it worked
|
||||
log.Info("Successfully parsed JSON file.")
|
||||
log.Infof("Model: %s", req.Model)
|
||||
log.Infof("Number of content blocks: %d", len(req.Contents))
|
||||
|
||||
// Example of accessing deeper data
|
||||
for i, content := range req.Contents {
|
||||
log.Infof("Content[%d] Role: %s", i, content.Role)
|
||||
for j, part := range content.Parts {
|
||||
if part.Text != "" {
|
||||
log.Infof(" Part[%d] Text: %.60s...", j, part.Text) // Print snippet
|
||||
}
|
||||
if part.FunctionCall != nil {
|
||||
log.Infof(" Part[%d] FunctionCall: %s", j, part.FunctionCall.Name)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// convertToGenai transforms the parsed JSON request into the genai.Content format.
|
||||
func convertToGenai(req *GeminiRequest) ([]*genai.Content, error) {
|
||||
var contents []*genai.Content
|
||||
for _, c := range req.Contents {
|
||||
genaiParts := []*genai.Part{} // Create a slice of the interface type
|
||||
for _, p := range c.Parts {
|
||||
if p.Text != "" {
|
||||
// genai.Text returns a Part interface, which is what we need
|
||||
var tmp *genai.Part
|
||||
tmp = new(genai.Part)
|
||||
tmp.Text = p.Text
|
||||
genaiParts = append(genaiParts, tmp)
|
||||
}
|
||||
}
|
||||
contents = append(contents, &genai.Content{
|
||||
Role: c.Role,
|
||||
Parts: genaiParts,
|
||||
})
|
||||
}
|
||||
return contents, nil
|
||||
}
|
35
main.go
35
main.go
|
@ -32,6 +32,8 @@ var ARGNAME string = "regex"
|
|||
var configSave bool
|
||||
|
||||
func main() {
|
||||
// f, _ := os.OpenFile("/tmp/regex.secret.log", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
|
||||
// log.CaptureMode(f)
|
||||
me = new(mainType)
|
||||
gui.InitArg()
|
||||
me.pp = arg.MustParse(&argv)
|
||||
|
@ -58,6 +60,14 @@ func main() {
|
|||
okExit("")
|
||||
}
|
||||
|
||||
if argv.Connect != nil {
|
||||
err := doConnect()
|
||||
if err != nil {
|
||||
badExit(err)
|
||||
}
|
||||
okExit("")
|
||||
}
|
||||
|
||||
if argv.Editor != nil {
|
||||
doEditor()
|
||||
okExit("")
|
||||
|
@ -105,24 +115,29 @@ func main() {
|
|||
}
|
||||
|
||||
if argv.Playback != nil {
|
||||
if argv.Playback.Uuid != "" {
|
||||
showChat(argv.Playback.Uuid)
|
||||
if argv.Uuid != "" {
|
||||
showChat(argv.Uuid)
|
||||
} else {
|
||||
doPlayback()
|
||||
}
|
||||
okExit("")
|
||||
}
|
||||
|
||||
// if opening the GUI, always check git for dirty repos
|
||||
log.Info("look for 'auto' here")
|
||||
// Find the "auto" chat.
|
||||
for _, chat := range me.chats.GetChats() {
|
||||
if chat.GetChatName() == "auto" {
|
||||
prettyFormatChat(chat)
|
||||
okExit("")
|
||||
/*
|
||||
// if opening the GUI, always check git for dirty repos
|
||||
log.Info("look for 'auto' here")
|
||||
// Find the "auto" chat.
|
||||
for _, chat := range me.chats.GetChats() {
|
||||
if chat.GetChatName() == "auto" {
|
||||
prettyFormatChat(chat)
|
||||
okExit("")
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
// doGui()
|
||||
|
||||
// by default, start interacting with gemini-cli
|
||||
doEditor()
|
||||
okExit("")
|
||||
}
|
||||
|
||||
|
|
|
@ -7,20 +7,20 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"go.wit.com/lib/protobuf/chatpb"
|
||||
"go.wit.com/log"
|
||||
)
|
||||
|
||||
const termWidth = 100 // The target width for the formatted output boxes.
|
||||
|
||||
// prettyFormatChat is the main entry point to print a detailed view of a Chat topic.
|
||||
func prettyFormatChat(chat *chatpb.Chat) {
|
||||
fmt.Printf("\n========================================================\n")
|
||||
fmt.Printf("== Chat Topic: %s (UUID: %s)\n", chat.GetChatName(), chat.GetUuid())
|
||||
fmt.Printf("========================================================\n\n")
|
||||
log.Printf("\n========================================================\n")
|
||||
log.Printf("== Chat Topic: %s (UUID: %s)\n", chat.GetChatName(), chat.GetUuid())
|
||||
log.Printf("========================================================\n\n")
|
||||
|
||||
for _, entry := range chat.GetEntries() {
|
||||
author := entry.GetFrom().String()
|
||||
|
@ -50,7 +50,7 @@ func prettyFormatChat(chat *chatpb.Chat) {
|
|||
printCodeSnippet(snippet)
|
||||
}
|
||||
}
|
||||
fmt.Println()
|
||||
log.Println()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -65,8 +65,8 @@ func printContent(author, timestamp, content string) {
|
|||
}
|
||||
|
||||
func printLeftAligned(author, timestamp, content string) {
|
||||
prefix := fmt.Sprintf("✦ %s (%s):", author, timestamp)
|
||||
fmt.Println(prefix)
|
||||
prefix := log.Sprintf("✦ %s (%s):", author, timestamp)
|
||||
log.Println(prefix)
|
||||
|
||||
indent := "\t"
|
||||
contentWidth := termWidth - 8 // 8 spaces for a standard tab
|
||||
|
@ -76,7 +76,7 @@ func printLeftAligned(author, timestamp, content string) {
|
|||
for _, paragraph := range paragraphs {
|
||||
words := strings.Fields(paragraph)
|
||||
if len(words) == 0 {
|
||||
fmt.Println() // Preserve paragraph breaks
|
||||
log.Println() // Preserve paragraph breaks
|
||||
continue
|
||||
}
|
||||
|
||||
|
@ -85,19 +85,19 @@ func printLeftAligned(author, timestamp, content string) {
|
|||
if len(currentLine)+1+len(word) <= contentWidth {
|
||||
currentLine += " " + word
|
||||
} else {
|
||||
fmt.Println(currentLine)
|
||||
log.Println(currentLine)
|
||||
currentLine = indent + word
|
||||
}
|
||||
}
|
||||
fmt.Println(currentLine)
|
||||
log.Println(currentLine)
|
||||
}
|
||||
}
|
||||
|
||||
func printRightAligned(author, timestamp, content string) {
|
||||
prefix := fmt.Sprintf("(%s) %s ✦", timestamp, author)
|
||||
prefix := log.Sprintf("(%s) %s ✦", timestamp, author)
|
||||
|
||||
// Print the prefix first, right-aligned.
|
||||
fmt.Printf("%*s\n", termWidth, prefix)
|
||||
log.Printf("%*s\n", termWidth, prefix)
|
||||
|
||||
// The available width for the text.
|
||||
contentWidth := termWidth - 8 // Leave a tab's worth of margin on the left
|
||||
|
@ -107,7 +107,7 @@ func printRightAligned(author, timestamp, content string) {
|
|||
for _, paragraph := range paragraphs {
|
||||
words := strings.Fields(paragraph)
|
||||
if len(words) == 0 {
|
||||
fmt.Println() // Preserve paragraph breaks
|
||||
log.Println() // Preserve paragraph breaks
|
||||
continue
|
||||
}
|
||||
|
||||
|
@ -117,12 +117,12 @@ func printRightAligned(author, timestamp, content string) {
|
|||
currentLine += " " + word
|
||||
} else {
|
||||
// Print the completed line, right-aligned.
|
||||
fmt.Printf("%*s\n", termWidth, currentLine)
|
||||
log.Printf("%*s\n", termWidth, currentLine)
|
||||
currentLine = word
|
||||
}
|
||||
}
|
||||
// Print the last remaining line of the paragraph, right-aligned.
|
||||
fmt.Printf("%*s\n", termWidth, currentLine)
|
||||
log.Printf("%*s\n", termWidth, currentLine)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -130,11 +130,11 @@ func printTable(table *chatpb.Table) {
|
|||
if table == nil || len(table.GetRows()) == 0 {
|
||||
return
|
||||
}
|
||||
fmt.Println("┌─[ Table Data ]──────────────────────────────────────────")
|
||||
log.Println("┌─[ Table Data ]──────────────────────────────────────────")
|
||||
for _, row := range table.GetRows() {
|
||||
fmt.Printf("│ %s\n", strings.Join(row.GetFields(), " │ "))
|
||||
log.Printf("│ %s\n", strings.Join(row.GetFields(), " │ "))
|
||||
}
|
||||
fmt.Printf("└─────────────────────────────────────────────────────────\n\n")
|
||||
log.Printf("└─────────────────────────────────────────────────────────\n\n")
|
||||
}
|
||||
|
||||
func printCodeSnippet(snippet *chatpb.CodeSnippet) {
|
||||
|
@ -142,11 +142,11 @@ func printCodeSnippet(snippet *chatpb.CodeSnippet) {
|
|||
code := snippet.GetContent()
|
||||
language := filepath.Base(snippet.GetFilename()) // Still useful for display
|
||||
|
||||
fmt.Println() // Add extra line feed for spacing
|
||||
log.Println() // Add extra line feed for spacing
|
||||
|
||||
// --- Top Border ---
|
||||
topBorder := fmt.Sprintf("┌─[ Code Snippet: %s ]", language)
|
||||
fmt.Printf("%s%s┐\n", topBorder, strings.Repeat("─", termWidth-len(topBorder)-1))
|
||||
topBorder := log.Sprintf("┌─[ Code Snippet: %s ]", language)
|
||||
log.Printf("%s%s┐\n", topBorder, strings.Repeat("─", termWidth-len(topBorder)-1))
|
||||
|
||||
// --- Content Lines ---
|
||||
for _, line := range strings.Split(strings.TrimSpace(code), "\n") {
|
||||
|
@ -155,17 +155,17 @@ func printCodeSnippet(snippet *chatpb.CodeSnippet) {
|
|||
if padding < 0 {
|
||||
padding = 0 // Should not happen with wrapping, but as a safeguard
|
||||
}
|
||||
fmt.Printf("│ %s%s │\n", line, strings.Repeat(" ", padding))
|
||||
log.Printf("│ %s%s │\n", line, strings.Repeat(" ", padding))
|
||||
}
|
||||
|
||||
// --- Bottom Border ---
|
||||
fmt.Printf("└%s┘\n\n", strings.Repeat("─", termWidth-2))
|
||||
log.Printf("└%s┘\n\n", strings.Repeat("─", termWidth-2))
|
||||
}
|
||||
|
||||
func printToolCallBox(tc *chatpb.ToolCall) {
|
||||
boxWidth := termWidth - 2
|
||||
fmt.Printf(" ╭%s╮\n", strings.Repeat("─", boxWidth))
|
||||
header := fmt.Sprintf(" ✔ %s %s (%s)", tc.GetName(), tc.GetInput(), tc.GetDescription())
|
||||
log.Printf(" ╭%s╮\n", strings.Repeat("─", boxWidth))
|
||||
header := log.Sprintf(" ✔ %s %s (%s)", tc.GetName(), tc.GetInput(), tc.GetDescription())
|
||||
printWrappedLine(header, boxWidth)
|
||||
printEmptyLine(boxWidth)
|
||||
if stdout := tc.GetOutputStdout(); stdout != "" {
|
||||
|
@ -179,7 +179,7 @@ func printToolCallBox(tc *chatpb.ToolCall) {
|
|||
}
|
||||
}
|
||||
printEmptyLine(boxWidth)
|
||||
fmt.Printf(" ╰%s╯\n", strings.Repeat("─", boxWidth))
|
||||
log.Printf(" ╰%s╯\n", strings.Repeat("─", boxWidth))
|
||||
}
|
||||
|
||||
func printWrappedLine(text string, width int) {
|
||||
|
@ -188,12 +188,12 @@ func printWrappedLine(text string, width int) {
|
|||
return
|
||||
}
|
||||
for len(text) > width {
|
||||
fmt.Printf(" │ %-*s │\n", width, text[:width])
|
||||
log.Printf(" │ %-*s │\n", width, text[:width])
|
||||
text = text[width:]
|
||||
}
|
||||
fmt.Printf(" │ %-*s │\n", width, text)
|
||||
log.Printf(" │ %-*s │\n", width, text)
|
||||
}
|
||||
|
||||
func printEmptyLine(width int) {
|
||||
fmt.Printf(" │ %*s │\n", width, "")
|
||||
log.Printf(" │ %*s │\n", width, "")
|
||||
}
|
||||
|
|
|
@ -0,0 +1,69 @@
|
|||
package main
|
||||
|
||||
/*
|
||||
// The following are the Go equivalents of the gemini-cli's TypeScript
|
||||
// interfaces for session statistics. You can use these to collect and
|
||||
// store metrics in your application.
|
||||
|
||||
// ToolCallDecision represents the user's decision on a tool call.
|
||||
type ToolCallDecision string
|
||||
|
||||
const (
|
||||
Accept ToolCallDecision = "accept"
|
||||
Reject ToolCallDecision = "reject"
|
||||
Modify ToolCallDecision = "modify"
|
||||
AutoAccept ToolCallDecision = "auto_accept"
|
||||
)
|
||||
|
||||
// ToolCallStats holds the statistics for a single tool.
|
||||
type ToolCallStats struct {
|
||||
Count int
|
||||
Success int
|
||||
Fail int
|
||||
DurationMs int
|
||||
Decisions map[ToolCallDecision]int
|
||||
}
|
||||
|
||||
// ModelMetrics holds the statistics for a single model.
|
||||
type ModelMetrics struct {
|
||||
API struct {
|
||||
TotalRequests int
|
||||
TotalErrors int
|
||||
TotalLatencyMs int
|
||||
}
|
||||
Tokens struct {
|
||||
Prompt int
|
||||
Candidates int
|
||||
Total int
|
||||
Cached int
|
||||
Thoughts int
|
||||
Tool int
|
||||
}
|
||||
}
|
||||
|
||||
// SessionMetrics holds all the statistics for a session.
|
||||
type SessionMetrics struct {
|
||||
Models map[string]ModelMetrics
|
||||
Tools struct {
|
||||
TotalCalls int
|
||||
TotalSuccess int
|
||||
TotalFail int
|
||||
TotalDurationMs int
|
||||
TotalDecisions map[ToolCallDecision]int
|
||||
ByName map[string]ToolCallStats
|
||||
}
|
||||
Files struct {
|
||||
TotalLinesAdded int
|
||||
TotalLinesRemoved int
|
||||
}
|
||||
}
|
||||
|
||||
// You will need to initialize and update this struct as your application
|
||||
// makes API calls and runs tools.
|
||||
var sessionMetrics SessionMetrics
|
||||
|
||||
// Example of how you might update the metrics after an API call:
|
||||
// modelMetrics := sessionMetrics.Models["gemini-pro"]
|
||||
// modelMetrics.API.TotalRequests++
|
||||
// ... and so on.
|
||||
*/
|
Loading…
Reference in New Issue