Compare commits

...

19 Commits

Author SHA1 Message Date
Jeff Carr f5b513fa05 buttons for different stuff 2025-09-03 01:19:37 -05:00
Jeff Carr 785591ab4c dumps out the gemini struct enough for now 2025-09-02 18:36:59 -05:00
Jeff Carr 61179d6aae make a "books" window 2025-09-02 17:47:30 -05:00
Jeff Carr 589d50d950 convert is working okay 2025-09-02 15:18:27 -05:00
Jeff Carr 81343b3b66 remove files if it seems close enough 2025-09-02 01:21:24 -05:00
Jeff Carr db079664c9 preliminary import working 2025-09-01 22:19:33 -05:00
Jeff Carr 8220d1817c working on processing JSON files 2025-09-01 20:14:28 -05:00
Jeff Carr 26d674c800 initial table dumps to STDOUT 2025-09-01 18:57:50 -05:00
Jeff Carr 5dadf3b5ef add a GUI 2025-09-01 13:32:25 -05:00
Jeff Carr cdc4155ef1 ignore json files 2025-09-01 12:38:29 -05:00
Jeff Carr 3a1e76e65e gemini-cli is stupid 2025-09-01 12:23:18 -05:00
Jeff Carr c84460eb65 it builds 2025-09-01 03:37:10 -05:00
Jeff Carr c65619154e sub in the functions 2025-09-01 01:55:06 -05:00
Jeff Carr c63a746d0b isolate this function 2025-09-01 01:41:03 -05:00
Jeff Carr c110250dc2 something? 2025-09-01 00:35:05 -05:00
Jeff Carr 4a800a7cfd attempts to submit data to the Gemini API 2025-09-01 00:29:48 -05:00
Castor Regex eba85c6b97 feat: dump last chat entry 2025-08-31 22:34:53 -05:00
Castor Regex 710693b18a feat: add dumpchat function 2025-08-31 22:31:11 -05:00
Castor Regex 8c8fccc650 feat: print last playback UUID 2025-08-31 22:26:49 -05:00
24 changed files with 1035 additions and 85 deletions

1
.gitignore vendored
View File

@ -5,3 +5,4 @@ go.sum
/files/*
regex
/tmp/*
*.json

View File

@ -2,7 +2,7 @@ VERSION = $(shell git describe --tags)
BUILDTIME = $(shell date +%Y.%m.%d_%H%M)
default: install
regex --json tmp/regex.bf203df1-ce24-42ea-93e9-10e0d8b5e8b0.gemini-api-request.64.json
regex clean
vet:
@GO111MODULE=off go vet
@ -24,6 +24,13 @@ regex: goimports
GO111MODULE=off go install \
-ldflags "-X main.VERSION=${VERSION} -X main.BUILDTIME=${BUILDTIME} -X gui.GUIVERSION=${VERSION}"
CUI: install
regex --gui gocui
GTK: clean install
regex --gui andlabs
dumb-build:
go install -v -x \
-ldflags "-X main.VERSION=${VERSION} -X main.BUILDTIME=${BUILDTIME} -X gui.GUIVERSION=${VERSION}"
@ -32,12 +39,6 @@ install-raw: goimports vet
go install \
-ldflags "-X main.VERSION=${VERSION} -X main.BUILDTIME=${BUILDTIME} -X gui.GUIVERSION=${VERSION}"
andlabs: clean install
regex --gui gocui --gui-verbose --gui-file ../../toolkits/andlabs/andlabs.so
gocui: install
regex --gui gocui --gui-verbose --gui-file ../../toolkits/gocui/gocui.so >/tmp/regex.log 2>&1
goimports:
goimports -w *.go
@# // to globally reset paths:

44
README.md Normal file
View File

@ -0,0 +1,44 @@
Gemini is the engine. Vertex AI is the entire car.
* Gemini is the name of the powerful, multimodal AI model family itself. It's the "brain" that performs the reasoning, understands text, images, audio, and video, and
generates responses.
* Vertex AI is the comprehensive, enterprise-grade platform on Google Cloud where you can access, deploy, manage, and even customize models like Gemini. It's the
infrastructure, the dashboard, the security features, and the MLOps (Machine Learning Operations) toolkit that surrounds the engine.
Here is a more detailed breakdown:
Gemini AI
* What it is: A family of highly capable large language models (LLMs).
* What it does: It's the core technology that processes information and generates output. It comes in different sizes and capabilities, like Gemini 1.5 Pro, Gemini
1.5 Flash, and Gemini Ultra, each optimized for different tasks (speed, cost, power).
* Key Features:
* Multimodality: Natively understands and reasons across text, code, images, audio, and video.
* Long Context: Can process massive amounts of information at once (e.g., Gemini 1.5 Pro has a 1 million token context window).
* Advanced Reasoning: Capable of complex, multi-step reasoning tasks.
* How you access it: You access Gemini through an API. That API can be part of Vertex AI or part of a simpler platform like Google AI Studio.
Vertex AI
* What it is: A fully-managed Machine Learning (ML) platform on Google Cloud.
* What it does: It provides all the tools and infrastructure needed to build, deploy, and manage ML models in a production environment. It's not just for Gemini; you
can use it for custom models built with TensorFlow or PyTorch, or other foundation models.
* Key Features:
* Model Garden: A central place to discover and use Google's foundation models (like Gemini) and hundreds of open-source models.
* Enterprise Security & Governance: Integrates with Google Cloud's robust security features like IAM (Identity and Access Management), VPC Service Controls, and
data encryption. Your data remains within your cloud environment.
* Data Integration: Seamlessly connects to other Google Cloud services like BigQuery and Cloud Storage, allowing you to use your own data with the models.
* Tuning and Customization: Provides tools to fine-tune foundation models like Gemini on your own data to make them experts in your specific domain.
* MLOps: A full suite of tools for automating, monitoring, and managing the entire ML lifecycle (pipelines, versioning, etc.).
How They Work Together
You don't choose between Vertex AI and Gemini. You choose how you want to use Gemini, and Vertex AI is the professional, enterprise-grade way to do it.
* When you call the Gemini API via Vertex AI, you get all the benefits of the Google Cloud platform: security, data privacy, scalability, and integration with your
other cloud resources. This is the path for building production applications.
* There is another way to access Gemini: Google AI Studio. This is a web-based tool designed for rapid prototyping and experimentation. It's great for developers who
want to quickly try out prompts and build an API key, but it doesn't have the enterprise-level MLOps and governance features of Vertex AI.
Summary Table

13
addResponse.go Normal file
View File

@ -0,0 +1,13 @@
package main
import (
"go.wit.com/log"
"google.golang.org/genai"
)
func addResponse(resp *genai.GenerateContentResponse) {
pb := convertToPB(resp)
log.Info(resp)
me.lastChat.AppendEntry(pb)
me.chats.ConfigSave()
}

65
apiExampleCode.go Normal file
View File

@ -0,0 +1,65 @@
package main
// this is just example code the GO API's wrapper for handling statelessness
// it doesn't really compile and is just junk Gemini AI sent back but I saved it here anyway
/*
func statelessnessExample() {
ctx := context.Background()
// Get the API key from an environment variable
apiKey := os.Getenv("GEMINI_API_KEY")
if apiKey == "" {
log.Fatal("GEMINI_API_KEY environment variable not set")
}
// Create a new client
client, err := genai.NewClient(ctx, option.WithAPIKey(apiKey))
if err != nil {
log.Fatal(err)
}
defer client.Close()
// Choose the model
model := client.GenerativeModel("gemini-1.5-flash")
// ---- Start a new chat session ----
cs := model.StartChat()
cs.History = []*genai.Content{} // Start with a clean history
// --- First message ---
fmt.Println("User: My brother's name is Paul.")
resp, err := cs.SendMessage(ctx, genai.Text("My brother's name is Paul."))
if err != nil {
log.Fatal(err)
}
printResponse(resp)
// --- Second message ---
// The ChatSession now remembers the previous exchange.
fmt.Println("\nUser: What is my brother's name?")
resp, err = cs.SendMessage(ctx, genai.Text("What is my brother's name?"))
if err != nil {
log.Fatal(err)
}
printResponse(resp)
// You can inspect the history at any time
// fmt.Println("\n--- Full Chat History ---")
// for _, content := range cs.History {
// for _, part := range content.Parts {
// fmt.Printf("Role: %s, Text: %v\n", content.Role, part)
// }
// }
}
// Helper function to print the response
func printResponse(resp *genai.GenerateContentResponse) {
for _, cand := range resp.Candidates {
if cand.Content != nil {
for _, part := range cand.Content.Parts {
fmt.Printf("Gemini: %v\n", part)
}
}
}
}
*/

13
argv.go
View File

@ -16,6 +16,7 @@ type args struct {
Interact *EmptyCmd `arg:"subcommand:interact" help:"open env EDITOR"`
Playback *PlaybackCmd `arg:"subcommand:playback" help:"dump your prior conversations to the terminal'"`
NewChat *PlaybackCmd `arg:"subcommand:newchat" help:"used by gemini-cli on startup"`
Clean *CleanCmd `arg:"subcommand:clean" help:"cleanup the files in /tmp"`
Stats string `arg:"--stats" help:"add stats to a chat"`
Force bool `arg:"--force" help:"try to strong arm things"`
Verbose bool `arg:"--verbose" help:"show more output"`
@ -26,10 +27,16 @@ type args struct {
type EmptyCmd struct {
}
type CleanCmd struct {
Match string `arg:"positional"`
}
type PlaybackCmd struct {
List *EmptyCmd `arg:"subcommand:list" help:"list memories"`
Long *EmptyCmd `arg:"subcommand:long" help:"show info on each chat"`
Purge *EmptyCmd `arg:"subcommand:purge" help:"verify chat uuids & purge empty chats"`
Last *EmptyCmd `arg:"subcommand:last" help:"dump the last one"`
List *EmptyCmd `arg:"subcommand:list" help:"list memories"`
Long *EmptyCmd `arg:"subcommand:long" help:"show info on each chat"`
Purge *EmptyCmd `arg:"subcommand:purge" help:"verify chat uuids & purge empty chats"`
Submit *EmptyCmd `arg:"subcommand:submit" help:"convert and submit the last entry"`
}
func (args) Version() string {

View File

@ -24,13 +24,13 @@ func (args) doBashAuto() {
// argv.doBashHelp()
switch argv.BashAuto[0] {
case "playback":
fmt.Println("long --uuid purge")
fmt.Println("long --uuid purge last submit")
case "clean":
fmt.Println("user devel master")
fmt.Println("")
default:
if argv.BashAuto[0] == ARGNAME {
// list the subcommands here
fmt.Println("--json interact playback")
fmt.Println("--json interact playback clean")
}
}
os.Exit(0)

26
convertToGenai.go Normal file
View File

@ -0,0 +1,26 @@
package main
import (
"go.wit.com/lib/protobuf/chatpb"
"google.golang.org/genai"
)
// convertToGenai transforms the parsed JSON request into the genai.Content format.
func convertToGenai(req *chatpb.GeminiRequest) ([]*genai.Content, error) {
var contents []*genai.Content
for _, c := range req.GetContents() {
var genaiParts []*genai.Part
for _, p := range c.GetParts() {
switch v := p.GetPartType().(type) {
case *chatpb.Part_Text:
part := &genai.Part{Text: v.Text}
genaiParts = append(genaiParts, part)
}
}
contents = append(contents, &genai.Content{
Role: c.GetRole(),
Parts: genaiParts,
})
}
return contents, nil
}

48
convertToPB.go Normal file
View File

@ -0,0 +1,48 @@
package main
import (
"fmt"
"go.wit.com/lib/protobuf/chatpb"
"google.golang.org/genai"
)
func convertToPB(resp *genai.GenerateContentResponse) *chatpb.ChatEntry {
entry := &chatpb.ChatEntry{}
if resp == nil || len(resp.Candidates) == 0 || resp.Candidates[0].Content == nil {
return entry
}
content := resp.Candidates[0].Content
for _, part := range content.Parts {
if part.Text != "" {
entry.Parts = append(entry.Parts, &chatpb.Part{
PartType: &chatpb.Part_Text{Text: part.Text},
})
}
if fc := part.FunctionCall; fc != nil {
fmt.Printf("Gemini API requested to execute command: %s\n", fc.Name)
entry.Parts = append(entry.Parts, &chatpb.Part{
PartType: &chatpb.Part_FunctionCall{
FunctionCall: &chatpb.FunctionCall{
Name: fc.Name,
Args: &chatpb.ArgsInfo{}, // TODO: Properly map args from fc.Args map[string]any
},
},
})
}
if fr := part.FunctionResponse; fr != nil {
// Convert the FunctionResponse to the protobuf equivalent
entry.Parts = append(entry.Parts, &chatpb.Part{
PartType: &chatpb.Part_FunctionResponse{
FunctionResponse: &chatpb.FunctionResponse{
Name: fr.Name,
// TODO: Properly map the response content
},
},
})
}
}
return entry
}

20
debugger.go Normal file
View File

@ -0,0 +1,20 @@
package main
/*
enables GUI options and the debugger in your application
*/
import (
"go.wit.com/lib/debugger"
"go.wit.com/log"
)
func init() {
if debugger.ArgDebug() {
log.Info("cmd line --debugger == true")
go func() {
log.Sleep(2)
debugger.DebugWindow()
}()
}
}

129
doClean.go Normal file
View File

@ -0,0 +1,129 @@
package main
import (
"os"
"path/filepath"
"strings"
"go.wit.com/lib/protobuf/chatpb"
"go.wit.com/log"
"google.golang.org/protobuf/types/known/timestamppb"
)
func doClean() {
log.Info("find all files")
scanTmp()
}
func scanTmp() {
var count int
filepath.WalkDir("/tmp", func(path string, d os.DirEntry, err error) error {
if err != nil {
// Handle possible errors, like permission issues
// fmt.Fprintf(os.Stderr, "error accessing path %q: %v\n", path, err)
// ignore all these problems
return err
}
/*
if d.IsDir() {
// log.Info("path is dir", path)
return nil
}
*/
_, fname := filepath.Split(path)
if !strings.HasPrefix(fname, "regex.") {
return nil
}
if count > 100 {
return log.Errorf("count exceeded")
}
if strings.HasPrefix(fname, "regex.gemini-api-response") {
// log.Info("response file:", fname)
return nil
}
if strings.Contains(fname, "gemini-api-request") {
// log.Info("response file:", fname)
if err := cleanGeminiFile(path); err == nil {
count += 1
return nil
} else {
return nil
}
}
if strings.HasSuffix(fname, ".stats") {
cleanStatsFile(path)
return nil
}
log.Info("check file:", path)
return nil
})
}
func cleanStatsFile(fullname string) {
log.Info("stats file", fullname)
}
func cleanGeminiFile(fullname string) error {
_, fname := filepath.Split(fullname)
if !strings.HasSuffix(fname, ".json") {
return log.Errorf("not really gemini-api-request .json")
}
parts := strings.Split(fname, ".")
if len(parts) == 5 {
if parts[2] != "gemini-api-request" {
return log.Errorf("not really gemini-api-request")
}
}
// log.Info("PARSE FILE:", fullname)
pb, err := parsePB(fullname)
if err != nil {
log.Info("parsePB() %s err %v\n", fullname, err)
return log.Errorf("parsePB() %s err %v", fullname, err)
}
if pb == nil {
log.Info("parsePB() == nil\n")
return log.Errorf("parsePB() == nil")
}
uuid := parts[1]
if argv.Clean != nil && argv.Clean.Match != "" {
if !strings.HasPrefix(uuid, argv.Clean.Match) {
return log.Errorf("uuid %s does not match %s", uuid, argv.Clean.Match)
}
}
if chat := me.chats.FindUuid(uuid); chat != nil {
log.Info("found uuid in chat", uuid, pb.Model, chat.Uuid)
statf, err := os.Stat(fullname)
if err == nil {
age := statf.ModTime()
if chat.AddGeminiRequest(fname, age, pb) {
me.chats.ConfigSave()
} else {
log.Info("file was perfect. os.Remove() here", fullname)
chat.PrintChatGeminiTable()
os.Remove(fullname)
}
}
return nil
}
id := parts[3]
if id == "1" {
log.Info("Insert new chat here", fullname, id)
statf, err := os.Stat(fullname)
if err == nil {
age := statf.ModTime()
c := new(chatpb.Chat)
c.Ctime = timestamppb.New(age)
c.Uuid = uuid
c.ChatName = "auto clean"
log.Info("new chat:", c.Uuid, c.Ctime)
me.chats.Append(c)
me.chats.ConfigSave()
okExit("")
}
}
log.Info("gemini JSON file uuid not found", fullname, id)
return log.Errorf("gemini JSON file uuid %s not found", uuid)
}

View File

@ -5,39 +5,121 @@ import (
"fmt"
"os"
"go.wit.com/lib/protobuf/chatpb"
"go.wit.com/log"
"google.golang.org/genai"
)
// doConnect initializes the Gemini client and handles the request flow.
func doConnect() (*genai.Client, error) {
func initGeminiAPI() error {
if me.ctx != nil {
// already initialized
return nil
}
apiKey := os.Getenv("GEMINI_API_KEY")
if apiKey == "" {
return nil, log.Errorf("GEMINI_API_KEY environment variable not set")
return log.Errorf("GEMINI_API_KEY environment variable not set")
}
ctx := context.Background()
client, err := genai.NewClient(ctx, &genai.ClientConfig{APIKey: apiKey})
me.ctx = context.Background()
var err error
me.client, err = genai.NewClient(me.ctx, &genai.ClientConfig{APIKey: apiKey})
if err != nil {
return nil, log.Errorf("failed to create new genai client: %w", err)
return log.Errorf("failed to create new genai client: %w", err)
}
return nil
}
// doConnect initializes the Gemini client and handles the request flow.
func doConnect() error {
initGeminiAPI()
if me.lastChat == nil {
log.Info("WTF. lastChat is nil")
return nil
}
return client, err
// if me.lastChat.Entries == nil {
// me.lastChat.Entries = new(chatpb.ChatEntry)
// }
// In a real application, you would get user input here.
// For now, we'll use a hardcoded prompt.
if len(me.lastChat.GetEntries()) == 0 {
me.lastChat.Entries = append(me.lastChat.Entries, &chatpb.ChatEntry{
Parts: []*chatpb.Part{
{PartType: &chatpb.Part_Text{Text: "hello, how are you"}},
},
})
}
lastEntry := me.lastChat.GetEntries()[len(me.lastChat.GetEntries())-1]
genaiContents, err := convertToGenai(lastEntry.GetGeminiRequest())
if err != nil {
return err
}
resp, err := me.client.Models.GenerateContent(me.ctx, "gemini-2.5-flash", genaiContents, nil)
if err != nil {
return log.Errorf("error sending message: %v", err)
}
if resp == nil || len(resp.Candidates) == 0 || resp.Candidates[0].Content == nil {
log.Info("Received an empty response from the API. Stopping.")
return nil
}
// Append the model's response to the history
me.lastChat.Entries = append(me.lastChat.Entries, convertToPB(resp))
// Check for a function call
hasFunctionCall := false
for _, part := range resp.Candidates[0].Content.Parts {
if fc := part.FunctionCall; fc != nil {
hasFunctionCall = true
functionResponse := handleFunctionCall(fc)
// Append the function response to the history for the next turn
me.lastChat.Entries = append(me.lastChat.Entries, &chatpb.ChatEntry{
Parts: []*chatpb.Part{
{PartType: &chatpb.Part_FunctionResponse{
FunctionResponse: &chatpb.FunctionResponse{
Name: functionResponse.Name,
// TODO: map response
},
}},
},
})
}
}
// If there was no function call, print the text and stop.
if !hasFunctionCall {
log.Info("Response from API:")
for _, cand := range resp.Candidates {
if cand.Content != nil {
for _, part := range cand.Content.Parts {
if part.Text != "" {
fmt.Println(part.Text)
}
}
}
}
}
return nil
}
// sampleHello sends a hardcoded prompt to the model and prints the response.
func simpleHello(client *genai.Client) error {
func simpleHello() 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"},
{Text: "What is my brothers name?"},
}
content := []*genai.Content{{Parts: parts}}
resp, err := client.Models.GenerateContent(ctx, "gemini-2.5-flash", content, nil)
resp, err := me.client.Models.GenerateContent(me.ctx, "gemini-2.5-flash", content, nil)
if err != nil {
return log.Errorf("error sending message: %v", err)
}

104
doGui.go Normal file
View File

@ -0,0 +1,104 @@
// Copyright 2017-2025 WIT.COM Inc. All rights reserved.
// Use of this source code is governed by the GPL 3.0
package main
// An app to submit patches for the 30 GO GUI repos
import (
"fmt"
"os"
"time"
"go.wit.com/gui"
"go.wit.com/lib/debugger"
"go.wit.com/lib/gadgets"
"go.wit.com/lib/gui/logsettings"
"go.wit.com/lib/gui/shell"
"go.wit.com/log"
)
func debug() {
time.Sleep(2 * time.Second)
for {
log.Info("idle loop() todo: could check for things here")
time.Sleep(90 * time.Second)
}
}
func doGui() {
me.myGui = gui.New()
// me.myGui.SetAppDefaultPlugin(me.forge.Config.DefaultGui)
me.myGui.Default()
win := gadgets.NewGenericWindow("regex: a WIT Cloud private AI tool", "Current Conversations")
drawWindow(win)
win.Custom = func() {
log.Warn("MAIN WINDOW CLOSE")
me.myGui.StandardExit()
os.Exit(0)
}
me.mainWindow = win
// sits here forever
debug()
}
func drawWindow(win *gadgets.GenericWindow) {
grid := win.Group.RawGrid()
grid.NewLabel("label worked")
grid.NextRow()
var insertWin *gadgets.GenericWindow
s := fmt.Sprintf("Show Chat Entries (%d)", me.chats.Len())
grid.NewButton(s, func() {
// if the window exists, just toggle it open or closed
if insertWin != nil {
insertWin.Toggle()
return
}
insertWin = makeChatsWindow()
})
grid.NewButton("simple hello", func() {
err := simpleHello()
if err != nil {
badExit(err)
}
})
grid.NewButton("submit question", func() {
doEditorOnce()
})
grid.NewButton("print playback", func() {
shell.RunVerbose([]string{"regex", "playback"})
})
grid.NewButton("clean", func() {
doClean()
})
grid.NextRow()
grid.NewButton("debugger", func() {
debugger.DebugWindow()
})
grid.NewButton("logging", func() {
logsettings.LogWindow()
})
}
// old things before they are removed, deprecated, fixed, etc
func makeOldStuff() *gadgets.GenericWindow {
oldWin := gadgets.NewGenericWindow("old code", "old code on it's way out")
grid := oldWin.Group.RawGrid()
grid.NewButton("Release Window", func() {
log.Info("todo: move releaser here")
})
return oldWin
}

43
doJSON.go Normal file
View File

@ -0,0 +1,43 @@
// Copyright 2017-2025 WIT.COM Inc. All rights reserved.
// Use of this source code is governed by the GPL 3.0
package main
// An app to submit patches for the 30 GO GUI repos
import (
"path/filepath"
"strconv"
"strings"
"go.wit.com/lib/protobuf/chatpb"
"go.wit.com/log"
)
func doJSON() {
// now try to Marshal() into a protobuf
pb, err := parsePB(argv.JsonFile)
if err != nil {
badExit(err)
}
log.Info("GeminiContent pb.Marshal() worked pb.Contents len =", len(pb.Contents))
_, filename := filepath.Split(argv.JsonFile)
parts := strings.Split(filename, ".")
if len(parts) == 5 {
uuid := parts[1]
num, _ := strconv.Atoi(parts[3])
log.Info(uuid, parts)
if chat := me.chats.FindByUuid(uuid); chat != nil {
log.Info("FOUND CHAT", uuid, num)
newEntry := new(chatpb.ChatEntry)
newEntry.GeminiRequest = pb
newEntry.ContentFile = filename
newEntry.RequestCounter = int32(num)
chat.AppendEntry(newEntry)
me.chats.ConfigSave()
}
} else {
}
okExit("")
}

View File

@ -73,6 +73,14 @@ func listChats(chats *chatpb.Chats) {
)
}
log.Println("-------------------------------------------------")
// Get the last chat
numChats := len(chats.GetChats())
if numChats > 0 {
me.lastChat = chats.GetChats()[numChats-1]
log.Printf("The current Gemini API session is UUID: %s\n", me.lastChat.GetUuid())
dumpchat(me.lastChat)
}
}
// print out one line for each chat entry

39
dumpchat.go Normal file
View File

@ -0,0 +1,39 @@
package main
import (
"go.wit.com/lib/protobuf/chatpb"
"go.wit.com/log"
)
func dumpEntry(entry *chatpb.ChatEntry) {
log.Printf(entry.FormatTEXT())
}
func dumpchat(chat *chatpb.Chat) error {
entries := chat.GetEntries()
var lastEntry *chatpb.ChatEntry
if len(entries) > 0 {
lastEntry = entries[len(entries)-1]
} else {
return log.Errorf("no entries")
}
if argv.Playback.Last != nil {
dumpEntry(lastEntry)
}
if argv.Playback.Submit != nil {
if lastEntry.GeminiRequest == nil {
return log.Errorf("lastEntry.GeminiRequest == nil")
}
aichat, err := convertToGenai(lastEntry.GetGeminiRequest())
if err != nil {
log.Info("convertToGenai() returned with error", err)
return err
}
err = submitChat(aichat)
if err != nil {
log.Info("submitChat() returned with error", err)
return err
}
}
return nil
}

View File

@ -7,7 +7,7 @@ import (
"go.wit.com/log"
)
func doGetNextAutoTopic() {
func getNextAutoTopic() {
max := 0
for _, chat := range me.chats.GetChats() {
if strings.HasPrefix(chat.GetChatName(), "Auto ") {

51
handleFunctionCall.go Normal file
View File

@ -0,0 +1,51 @@
package main
import (
"fmt"
"go.wit.com/log"
"google.golang.org/genai"
)
// handleFunctionCall executes a command requested by the Gemini API and returns the result.
func handleFunctionCall(fc *genai.FunctionCall) *genai.FunctionResponse {
if fc == nil {
return nil
}
if fc.Name != "run_shell_command" {
log.Infof("Unsupported function call: %s", fc.Name)
return &genai.FunctionResponse{
Name: fc.Name,
Response: map[string]any{
"error": fmt.Sprintf("Unsupported function call: %s", fc.Name),
},
}
}
// Extract arguments
cmd, _ := fc.Args["command"].(string)
dir, _ := fc.Args["directory"].(string)
if cmd == "" {
return &genai.FunctionResponse{
Name: fc.Name,
Response: map[string]any{
"error": "missing command argument",
},
}
}
// Execute the command (this is a placeholder for the actual execution)
// In a real implementation, you would use the run_shell_command tool here.
log.Infof("Executing command: '%s' in directory: '%s'", cmd, dir)
// For now, we'll return a dummy response.
// TODO: Replace this with actual command execution.
return &genai.FunctionResponse{
Name: fc.Name,
Response: map[string]any{
"output": "command executed successfully (dummy response)",
},
}
}

23
json.go
View File

@ -6,7 +6,6 @@ import (
"go.wit.com/lib/protobuf/chatpb"
"go.wit.com/log"
"google.golang.org/genai"
)
// GeminiRequest matches the overall structure of the gemini-cli JSON output.
@ -124,25 +123,3 @@ func dumpFullJSON(req *GeminiRequest) {
}
}
}
// 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
}

52
main.go
View File

@ -8,9 +8,6 @@ package main
import (
"embed"
"os"
"path/filepath"
"strconv"
"strings"
"github.com/google/uuid"
"go.wit.com/dev/alexflint/arg"
@ -35,8 +32,7 @@ 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)
var err error
me = new(mainType)
gui.InitArg()
me.pp = arg.MustParse(&argv)
@ -61,43 +57,26 @@ func main() {
me.chats.ConfigSave()
}
aiClient, err := doConnect()
// Get the last chat
numChats := len(me.chats.GetChats())
if numChats > 0 {
me.lastChat = me.chats.GetChats()[numChats-1]
log.Printf("The current Gemini API session is UUID: %s\n", me.lastChat.GetUuid())
}
err = initGeminiAPI()
if err != nil {
badExit(err)
}
_ = aiClient
if argv.JsonFile != "" {
// now try to Marshal() into a protobuf
pb, err := parsePB(argv.JsonFile)
if err != nil {
badExit(err)
}
log.Info("GeminiContent pb.Marshal() worked pb.Contents len =", len(pb.Contents))
_, filename := filepath.Split(argv.JsonFile)
parts := strings.Split(filename, ".")
if len(parts) == 5 {
uuid := parts[1]
num, _ := strconv.Atoi(parts[3])
log.Info(uuid, parts)
if chat := me.chats.FindByUuid(uuid); chat != nil {
log.Info("FOUND CHAT", uuid, num)
newEntry := new(chatpb.ChatEntry)
newEntry.GeminiRequest = pb
newEntry.ContentFile = filename
newEntry.RequestCounter = int32(num)
chat.AppendEntry(newEntry)
me.chats.ConfigSave()
}
} else {
}
doJSON()
okExit("")
}
if argv.Interact != nil {
log.Info("testing AI client with simpleHello()")
err = simpleHello(aiClient)
err = simpleHello()
if err != nil {
badExit(err)
}
@ -125,10 +104,15 @@ func main() {
okExit("")
}
// doGui()
if argv.Clean != nil {
doClean()
okExit("")
}
doGui()
// by default, start interacting with gemini-cli
me.pp.WriteHelp(os.Stdout)
// me.pp.WriteHelp(os.Stdout)
okExit("")
}

View File

@ -4,16 +4,24 @@
package main
import (
"context"
"go.wit.com/dev/alexflint/arg"
"go.wit.com/gui"
"go.wit.com/lib/gadgets"
"go.wit.com/lib/protobuf/chatpb"
"google.golang.org/genai"
)
var me *mainType
// this app's variables
type mainType struct {
pp *arg.Parser // for parsing the command line args. Yay to alexf lint!
chats *chatpb.Chats // all our prior conversations with regex
myGui *gui.Node // the gui toolkit handle
pp *arg.Parser // for parsing the command line args. Yay to alexf lint!
chats *chatpb.Chats // all our prior conversations with regex
client *genai.Client // the Google Gemini AI client variable
ctx context.Context // global context. what does this acutally mean?
lastChat *chatpb.Chat // the last chat. append to here
myGui *gui.Node // the gui toolkit handle
mainWindow *gadgets.GenericWindow // the main GUI window
}

27
submitChat.go Normal file
View File

@ -0,0 +1,27 @@
package main
import (
"fmt"
"go.wit.com/log"
"google.golang.org/genai"
)
func submitChat(content []*genai.Content) error {
resp, err := me.client.Models.GenerateContent(me.ctx, "gemini-2.5-flash", 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)
}
}
}
// TODO: add the response to the protobuf
addResponse(resp)
return nil
}

111
windowBook.go Normal file
View File

@ -0,0 +1,111 @@
// Copyright 2017-2025 WIT.COM Inc. All rights reserved.
// Use of this source code is governed by the GPL 3.0
package main
import (
"fmt"
"time"
"go.wit.com/lib/gadgets"
"go.wit.com/lib/protobuf/chatpb"
"go.wit.com/log"
)
func makeBookWindow(all *chatpb.Books) *gadgets.GenericWindow {
bookWin := gadgets.NewGenericWindow("regex Books", "Display Books")
bookWin.Win.Custom = func() {
log.Info("test delete window here")
}
grid := bookWin.Group.RawGrid()
var t *chatpb.BooksTable
grid.NewButton("show", func() {
if t != nil {
log.Info("Delete the table first")
return
}
// display the protobuf
t = addBooksPB(bookWin, all)
f := func(chat *chatpb.Book) {
log.Info("got to BookTable.Custom() id =", chat.GetUuid(), chat.GetTitle())
}
t.Custom(f)
log.Info("table has uuid", t.GetUuid())
})
grid.NewButton("delete PB table", func() {
if t != nil {
t.Delete()
t = nil
log.Info("Table should have been deleted")
}
})
// display the protobuf
t = addBooksPB(bookWin, all)
f := func(chat *chatpb.Book) {
log.Info("got to BookTable.Custom() id =", chat.GetUuid(), chat.GetTitle())
}
t.Custom(f)
log.Info("table has uuid", t.GetUuid())
return bookWin
}
func addBooksPB(win *gadgets.GenericWindow, pb *chatpb.Books) *chatpb.BooksTable {
t := pb.NewTable("testForgeRepos")
t.NewUuid()
tbox := win.Bottom.Box().SetProgName("TBOX")
t.SetParent(tbox)
sf := t.AddStringFunc("uuid", func(r *chatpb.Book) string {
return r.GetUuid()
})
sf.Custom = func(r *chatpb.Book) {
log.Info("todo: fix mouseClick() on stringFunc in GUI table", r.GetUuid())
}
// add a general show button
bf := t.AddButtonFunc("cur version", func(chat *chatpb.Book) string { return "submit" })
bf.Custom = func(book *chatpb.Book) {
log.Info("convert pb to genai GO API HERE", book.GetUuid())
if book.GetGeminiRequest() == nil {
return
}
aichat, err := convertToGenai(book.GetGeminiRequest())
if err != nil {
log.Info("convertToGenai() returned with error", err)
return
}
err = submitChat(aichat)
if err != nil {
log.Info("submitChat() returned with error", err)
return
}
log.Info("submitChat() appears to have worked?")
}
// show the age of the chat
t.AddTimeFunc("age", func(book *chatpb.Book) time.Time {
return book.GetCtime().AsTime()
})
t.AddTitle()
// t.AddVersion()
// make a button to show content in the *genai.GeminiRequest structures
genaiButton := t.AddButtonFunc("# of parts", func(book *chatpb.Book) string {
if book.GeminiRequest == nil {
return "nil"
}
var req = book.GeminiRequest
return fmt.Sprintf("%d", len(req.Contents))
})
genaiButton.Custom = func(book *chatpb.Book) {
log.Info("show *genai.GeminiRequsts for", book.GetUuid())
book.GeminiRequest.PrintGeminiTable()
}
// draw the tabel (send the gui protobuf to the GO plugin)
t.ShowTable()
return t
}

162
windowChats.go Normal file
View File

@ -0,0 +1,162 @@
// Copyright 2017-2025 WIT.COM Inc. All rights reserved.
// Use of this source code is governed by the GPL 3.0
package main
import (
"fmt"
"time"
"go.wit.com/lib/gadgets"
"go.wit.com/lib/protobuf/chatpb"
"go.wit.com/log"
)
/*
type stdReposTableWin struct {
sync.Mutex
win *gadgets.GenericWindow // the machines gui window
boxTB *gui.Node // the machines gui parent box widget
TB *gitpb.ReposTable // the gui table buffer
pb *gitpb.Repos // the current repos protobuf
update bool // if the window should be updated
}
func (w *stdReposTableWin) Toggle() {
if w == nil {
return
}
if w.win == nil {
return
}
w.win.Toggle()
}
*/
func makeChatsWindow() *gadgets.GenericWindow {
insertWin := gadgets.NewGenericWindow("regex Chats", "Display Chats")
insertWin.Win.Custom = func() {
log.Info("test delete window here")
insertWin.Hide()
}
grid := insertWin.Group.RawGrid()
var t *chatpb.ChatsTable
grid.NewButton("show", func() {
if t != nil {
log.Info("Delete the table first")
return
}
// display the protobuf
t = addChatsPB(insertWin, me.chats)
f := func(chat *chatpb.Chat) {
log.Info("got to ChatTable.Custom() id =", chat.GetUuid(), chat.GetChatName())
}
t.Custom(f)
log.Info("table has uuid", t.GetUuid())
})
grid.NewButton("delete PB table", func() {
if t != nil {
t.Delete()
t = nil
log.Info("Table should have been deleted")
}
})
// display the protobuf
t = addChatsPB(insertWin, me.chats)
f := func(chat *chatpb.Chat) {
log.Info("got to ChatTable.Custom() id =", chat.GetUuid(), chat.GetChatName())
}
t.Custom(f)
log.Info("table has uuid", t.GetUuid())
return insertWin
}
func addChatsPB(win *gadgets.GenericWindow, pb *chatpb.Chats) *chatpb.ChatsTable {
t := pb.NewTable("testForgeRepos")
t.NewUuid()
tbox := win.Bottom.Box().SetProgName("TBOX")
t.SetParent(tbox)
sf := t.AddStringFunc("uuid", func(r *chatpb.Chat) string {
return r.GetUuid()
})
sf.Custom = func(r *chatpb.Chat) {
log.Info("todo: fix mouseClick() on stringFunc in GUI table", r.GetUuid())
}
// add a general show button
bf := t.AddButtonFunc("cur version", func(chat *chatpb.Chat) string { return "show" })
bf.Custom = func(r *chatpb.Chat) {
log.Info("todo: show a chat window here", r.GetUuid())
}
// show the age of the chat
t.AddTimeFunc("age", func(chat *chatpb.Chat) time.Time {
return chat.GetCtime().AsTime()
})
t.AddChatName()
// make a button to show the ChatEntries
entryButton := t.AddButtonFunc("Entries", func(chat *chatpb.Chat) string {
return fmt.Sprintf("%d", len(chat.GetEntries()))
})
entryButton.Custom = func(chat *chatpb.Chat) {
log.Info("show entries for", chat.GetUuid())
chat.PrintChatEntriesTable()
}
// make a button to show the Stats (old stuff from gemini-cli)
statsButton := t.AddButtonFunc("Stats", func(chat *chatpb.Chat) string {
return fmt.Sprintf("%d", len(chat.GetSession()))
})
statsButton.Custom = func(chat *chatpb.Chat) {
log.Info("show gemini-cli /stats for", chat.GetUuid())
chat.PrintChatStatsTable()
}
// make a button to show content in the *genai.GeminiRequest structures
genaiButton := t.AddButtonFunc("# of genai.Req's", func(chat *chatpb.Chat) string {
var counter int
for _, entry := range chat.GetEntries() {
if entry.GeminiRequest != nil {
counter += 1
}
}
return fmt.Sprintf("%d", counter)
})
genaiButton.Custom = func(chat *chatpb.Chat) {
log.Info("show *genai.GeminiRequsts for", chat.GetUuid())
chat.PrintChatGeminiTable()
pb := makeBooksTable(chat)
makeBookWindow(pb)
}
// draw the tabel (send the gui protobuf to the GO plugin)
t.ShowTable()
return t
}
func makeBooksTable(chat *chatpb.Chat) *chatpb.Books {
var pb *chatpb.Books
pb = chatpb.NewBooks()
pb.TitleUuid = chat.Uuid
for _, entry := range chat.GetEntries() {
if entry.GeminiRequest == nil {
continue
}
newb := new(chatpb.Book)
newb.Ctime = entry.Ctime
newb.Uuid = entry.Uuid
newb.From = entry.From
newb.Title = entry.ContentFile
newb.GeminiRequest = entry.GeminiRequest
pb.Append(newb)
}
pb.ConfigSave()
return pb
}