package main

import (
	"encoding/json"
	"fmt"
	"os"
	"runtime"
	"strconv"
	"strings"

	"github.com/hanyuancheung/gpt-go"
)

func isDebug() bool {
	return os.Getenv("DEBUG") != ""
}

func getGPTHotkeys() []string {
	hotkeys := os.Getenv("GPT_HOTKEYS")
	if hotkeys == "" {
		if runtime.GOOS == "windows" {
			hotkeys = "shift+space"
		} else if runtime.GOOS == "darwin" {
			hotkeys = "shift+space"
		} else {
			hotkeys = "shift+space"
		}
	}
	return strings.Split(hotkeys, "+")
}

func getOpenAIkey() string {
	return os.Getenv("OPENAI_API_KEY")
}

func getOpenAIBaseUrl() string {
	if os.Getenv("OPENAI_API_BASE_URL") != "" {
		return os.Getenv("OPENAI_API_BASE_URL")
	}
	return "https://api.openai.com/v1"
}

func getMaxContext() int {
	maxContext := os.Getenv("MAX_CONTEXT")
	maxContextInt, err := strconv.Atoi(maxContext)
	if err != nil {
		fmt.Println("MAX_CONTEXT不是一个有效的数字")
		return 0
	} else {
		return maxContextInt
	}
}

func getModeList() []string {
	modesEnv := os.Getenv("MODES")
	if modesEnv == "" {
		modesEnv = "gpt-3.5-turbo"
	}

	modes := strings.Split(modesEnv, ",")
	if len(modes) == 0 {
		modes = []string{"gpt-3.5-turbo"}
	}
	fmt.Println(len(modes))
	return modes
}

type ModePrompt struct {
	Name         string                             `json:"name"`
	Model        string                             `json:"model"`
	HeadMessages []gpt.ChatCompletionRequestMessage `json:"headMessages"`
	MaxContext   int                                `json:"maxContext"`
}

func loadModePrompt(filepath string) (ModePrompt, error) {
	var prompt ModePrompt

	// Read the file contents
	data, err := os.ReadFile(filepath)
	if err != nil {
		return prompt, err
	}

	// Unmarshal the JSON data into the ModePrompt struct
	err = json.Unmarshal(data, &prompt)
	if err != nil {
		return prompt, err
	}

	return prompt, nil
}

func parseModePrompt(content string) (ModePrompt, error) {
	var prompt ModePrompt

	// Unmarshal the JSON data into the ModePrompt struct
	err := json.Unmarshal([]byte(content), &prompt)
	if err != nil {
		return prompt, err
	}

	return prompt, nil
}

func saveModePrompt(prompt ModePrompt, filepath string) error {
	// Marshal the ModePrompt struct into JSON data
	data, err := json.Marshal(prompt)
	if err != nil {
		return err
	}

	// Write the JSON data to the file
	err = os.WriteFile(filepath, data, 0644)
	if err != nil {
		return err
	}

	return nil
}

package main

import (
	"context"
	"fmt"
	"time"

	"github.com/hanyuancheung/gpt-go"
	"golang.design/x/clipboard"
)

type Core struct {
	u           *UserCore
	st          *SysTray
	txtChan     chan string
	ctx         context.Context
	cancel      context.CancelFunc
	lastHit     time.Time
	lastEscHit  time.Time
	escCnt      int
	queryString string
}

func NewCore(u *UserCore, st *SysTray) *Core {
	c := Core{u: u, st: st}
	c.init()
	return &c
}

func (c *Core) init() {
	c.ctx, c.cancel = context.WithCancel(context.Background())
	c.lastEscHit = time.Now()
	c.escCnt = 0
}

func (c *Core) queryHit() {
	err := clipboard.Init()
	if err != nil {
		panic(err)
	}

	c.queryString = string(clipboard.Read(clipboard.FmtText))
	fmt.Println(c.queryString)

	go func() {
		if time.Since(c.lastHit).Milliseconds() > 1000 {
			c.lastHit = time.Now()
		} else {
			return
		}

		if err != nil {
			fmt.Println("Failed to read clipboard content:", err)
			return
		}

		if len(c.queryString) < 1 {
			fmt.Println("Empty question")
			return
		}

		c.u.reloadMask()

		fmt.Println("### prompt:", c.u.mask)
		fmt.Println("### model:", c.u.model)
		fmt.Println("### user:")
		fmt.Println(c.queryString)
		prompts, new := c.u.GeneratePromptMessages(c.queryString)

		if len(prompts) == 0 {
			return
		}

		c.txtChan = make(chan string, 1024)
		fmt.Println("Generating...")
		c.cancel()
		c.ctx, c.cancel = context.WithCancel(context.Background())

		TypeStr("⏳")
		workDone := make(chan struct{}, 2)
		go c.st.ShowRunningIcon(c.ctx, workDone)
		go c.u.QueryGPT(c.ctx, c.txtChan, prompts)

		assistantAns := ""
		fmt.Print("### Assistant:\n")

		defer func() {
			//recover clipboard
			clipboard.Write(clipboard.FmtText, []byte(c.queryString))
		}()

		tmpText := ""
		nextType := time.Now()
		stop := false
		for {
			if t, ok := <-c.txtChan; ok {
				fmt.Print(t)
				tmpText = tmpText + t
			} else {
				stop = true
				if time.Since(nextType).Microseconds() < 0 {
					time.Sleep(time.Since(nextType) * -1)
				}
			}

			if time.Since(nextType).Microseconds() > 0 {
				if assistantAns == "" {
					for i := 0; i < len([]rune("⏳")); i++ {
						TypeBackspace()
						time.Sleep(time.Millisecond * 10)
					}
				}
				assistantAns = assistantAns + tmpText
				if tmpText != "" {
					TypeStr(tmpText)
					tmpText = ""
					nextType = time.Now().Add(time.Millisecond * 100) //write interavl 100 milliseconds
				}

				if stop {
					fmt.Print("\n")
					new = append(new, gpt.ChatCompletionRequestMessage{
						Role:    "assistant",
						Content: assistantAns,
					})
					c.u.AddNewMessages(new)
					workDone <- struct{}{}
					break
				}
			}
		}
	}()
}

func (c *Core) escapeHit() {
	if time.Since(c.lastEscHit).Milliseconds() < 500 {
		c.escCnt++
		fmt.Println("increase escCnt to", c.escCnt)
		if c.escCnt == 2 { //triple 'esc' click for quick clean context
			c.u.ClearContext()
			c.escCnt = 0
		}
	} else {
		c.escCnt = 0
	}
	c.lastEscHit = time.Now()
	c.cancel()
}

//go:build darwin
// +build darwin

package main

import (
	"fmt"
	"io"
	"os"
	"path/filepath"

	"github.com/joho/godotenv"
)

func OSDepCheck() {
	// change working directory
	userDir, _ := os.UserConfigDir()
	APP_SUPPORT_DIR := filepath.Join(userDir, "InputGPT")
	os.Chdir(APP_SUPPORT_DIR)
	promptsDir := filepath.Join(APP_SUPPORT_DIR, "prompts")

	if _, err := os.Stat(promptsDir); os.IsNotExist(err) {
		err := os.MkdirAll(promptsDir, 0755)
		if err != nil {
			fmt.Printf("unable to create：%s\n", err)
		}
		os.Chdir(APP_SUPPORT_DIR)
		fmt.Println("promptsDir created")
	}

	envFile := filepath.Join(APP_SUPPORT_DIR, "env.txt")
	if _, err := os.Stat(envFile); os.IsNotExist(err) {
		exePath, err := os.Executable()
		if err == nil {
			appPath := filepath.Dir(filepath.Dir(exePath))
			sourceEnvFile := filepath.Join(appPath, "Resources", "env.txt")
			copyFile(sourceEnvFile, envFile)
			godotenv.Load(envFile)
		}
	}

	godotenv.Load("env.txt")
	os.Setenv("LANG", "en_US.UTF-8")
	return
}

// 复制文件
func copyFile(src, dst string) error {
	srcFile, err := os.Open(src)
	if err != nil {
		return err
	}
	defer srcFile.Close()

	dstFile, err := os.Create(dst)
	if err != nil {
		return err
	}
	defer dstFile.Close()

	_, err = io.Copy(dstFile, srcFile)
	if err != nil {
		return err
	}

	err = dstFile.Sync()
	if err != nil {
		return err
	}

	return nil
}

package main

import (
	"time"

	"github.com/micmonay/keybd_event"
	"golang.design/x/clipboard"
	"golang.design/x/hotkey"
)

func escKey() {
	kb, _ := keybd_event.NewKeyBonding()
	kb.SetKeys(keybd_event.VK_ESC)
	if err := kb.Launching(); err != nil {
		panic(err)
	}
	time.Sleep(time.Millisecond * 50)
}

func paste() {
	kb, _ := keybd_event.NewKeyBonding()
	kb.HasSuper(true)
	kb.SetKeys(keybd_event.VK_V)
	if err := kb.Launching(); err != nil {
		panic(err)
	}
	//time.Sleep(time.Millisecond * 10)
}

func backspace() {
	kb, _ := keybd_event.NewKeyBonding()
	kb.SetKeys(keybd_event.VK_DELETE)
	if err := kb.Launching(); err != nil {
		panic(err)
	}
	//time.Sleep(time.Millisecond * 10)
}

func enter() {
	kb, _ := keybd_event.NewKeyBonding()
	kb.SetKeys(keybd_event.VK_ENTER)
	if err := kb.Launching(); err != nil {
		panic(err)
	}
	time.Sleep(time.Millisecond * 10)
}

func TypeStr(s string) {
	clipboard.Write(clipboard.FmtText, []byte(s))
	paste()
}

func TypeEnter() {
	enter()
}

func TypeBackspace() {
	backspace()
}

func keyNameToModifier(kn string) hotkey.Modifier {
	switch kn {
	case "shift":
		return hotkey.ModShift
	case "ctrl":
		return hotkey.ModCtrl
	case "ModOption":
		return hotkey.ModOption
	case "command":
		return hotkey.ModCmd
	default:
		return hotkey.ModShift
	}
}

func keyNamesToKey(kn string) hotkey.Key {
	switch kn {
	case "space":
		return hotkey.KeySpace
	case "1":
		return hotkey.Key1
	case "2":
		return hotkey.Key2
	case "3":
		return hotkey.Key3
	case "4":
		return hotkey.Key4
	case "5":
		return hotkey.Key5
	case "6":
		return hotkey.Key6
	case "7":
		return hotkey.Key7
	case "8":
		return hotkey.Key8
	case "9":
		return hotkey.Key9
	case "0":
		return hotkey.Key0
	case "a":
		return hotkey.KeyA
	case "b":
		return hotkey.KeyB
	case "c":
		return hotkey.KeyC
	case "d":
		return hotkey.KeyD
	case "e":
		return hotkey.KeyE
	case "f":
		return hotkey.KeyF
	case "g":
		return hotkey.KeyG
	case "h":
		return hotkey.KeyH
	case "i":
		return hotkey.KeyI
	case "j":
		return hotkey.KeyJ
	case "k":
		return hotkey.KeyK
	case "l":
		return hotkey.KeyL
	case "m":
		return hotkey.KeyM
	case "n":
		return hotkey.KeyN
	case "o":
		return hotkey.KeyO
	case "p":
		return hotkey.KeyP
	case "q":
		return hotkey.KeyQ
	case "r":
		return hotkey.KeyR
	case "s":
		return hotkey.KeyS
	case "t":
		return hotkey.KeyT
	case "u":
		return hotkey.KeyU
	case "v":
		return hotkey.KeyV
	case "w":
		return hotkey.KeyW
	case "x":
		return hotkey.KeyX
	case "y":
		return hotkey.KeyY
	case "z":
		return hotkey.KeyZ
	case "enter":
		return hotkey.KeyReturn
	case "esc":
		return hotkey.KeyEscape
	case "delete":
		return hotkey.KeyDelete
	case "tab":
		return hotkey.KeyTab
	case "left":
		return hotkey.KeyLeft
	case "right":
		return hotkey.KeyRight
	case "up":
		return hotkey.KeyUp
	case "down":
		return hotkey.KeyDown
	case "f1":
		return hotkey.KeyF1
	case "f2":
		return hotkey.KeyF2
	case "f3":
		return hotkey.KeyF3
	case "f4":
		return hotkey.KeyF4
	case "f5":
		return hotkey.KeyF5
	case "f6":
		return hotkey.KeyF6
	case "f7":
		return hotkey.KeyF7
	case "f8":
		return hotkey.KeyF8
	case "f9":
		return hotkey.KeyF9
	case "f10":
		return hotkey.KeyF10
	case "f11":
		return hotkey.KeyF11
	case "f12":
		return hotkey.KeyF12
	case "f13":
		return hotkey.KeyF13
	case "f14":
		return hotkey.KeyF14
	case "f15":
		return hotkey.KeyF15
	case "f16":
		return hotkey.KeyF16
	case "f17":
		return hotkey.KeyF17
	case "f18":
		return hotkey.KeyF18
	case "f19":
		return hotkey.KeyF19
	case "f20":
		return hotkey.KeyF20
	default:
		return hotkey.KeySpace
	}
}

package main

import (
	"time"

	"github.com/micmonay/keybd_event"
	"golang.design/x/clipboard"
	"golang.design/x/hotkey"
)

func escKey() {
	kb, _ := keybd_event.NewKeyBonding()
	kb.SetKeys(keybd_event.VK_ESC)
	if err := kb.Launching(); err != nil {
		panic(err)
	}
	time.Sleep(time.Millisecond * 50)
}

func paste() {
	kb, _ := keybd_event.NewKeyBonding()
	kb.HasCTRL(true)
	kb.SetKeys(keybd_event.VK_V)
	if err := kb.Launching(); err != nil {
		panic(err)
	}
	time.Sleep(time.Millisecond * 10)
}

func backspace() {
	kb, _ := keybd_event.NewKeyBonding()
	kb.SetKeys(keybd_event.VK_BACKSPACE)
	if err := kb.Launching(); err != nil {
		panic(err)
	}
	time.Sleep(time.Millisecond * 10)
}

func enter() {
	kb, _ := keybd_event.NewKeyBonding()
	kb.SetKeys(keybd_event.VK_ENTER)
	if err := kb.Launching(); err != nil {
		panic(err)
	}
	time.Sleep(time.Millisecond * 10)
}

func TypeStr(s string) {
	clipboard.Write(clipboard.FmtText, []byte(s))
	paste()
}

func TypeEnter() {
	enter()
}

func TypeBackspace() {
	backspace()
}

func keyNameToModifier(kn string) hotkey.Modifier {
	switch kn {
	case "shift":
		return hotkey.ModShift
	case "ctrl":
		return hotkey.ModCtrl
	case "alt":
		return hotkey.ModAlt
	case "windows":
		return hotkey.ModWin
	default:
		return hotkey.ModShift
	}
}

func keyNamesToKey(kn string) hotkey.Key {
	switch kn {
	case "space":
		return hotkey.KeySpace
	case "1":
		return hotkey.Key1
	case "2":
		return hotkey.Key2
	case "3":
		return hotkey.Key3
	case "4":
		return hotkey.Key4
	case "5":
		return hotkey.Key5
	case "6":
		return hotkey.Key6
	case "7":
		return hotkey.Key7
	case "8":
		return hotkey.Key8
	case "9":
		return hotkey.Key9
	case "0":
		return hotkey.Key0
	case "a":
		return hotkey.KeyA
	case "b":
		return hotkey.KeyB
	case "c":
		return hotkey.KeyC
	case "d":
		return hotkey.KeyD
	case "e":
		return hotkey.KeyE
	case "f":
		return hotkey.KeyF
	case "g":
		return hotkey.KeyG
	case "h":
		return hotkey.KeyH
	case "i":
		return hotkey.KeyI
	case "j":
		return hotkey.KeyJ
	case "k":
		return hotkey.KeyK
	case "l":
		return hotkey.KeyL
	case "m":
		return hotkey.KeyM
	case "n":
		return hotkey.KeyN
	case "o":
		return hotkey.KeyO
	case "p":
		return hotkey.KeyP
	case "q":
		return hotkey.KeyQ
	case "r":
		return hotkey.KeyR
	case "s":
		return hotkey.KeyS
	case "t":
		return hotkey.KeyT
	case "u":
		return hotkey.KeyU
	case "v":
		return hotkey.KeyV
	case "w":
		return hotkey.KeyW
	case "x":
		return hotkey.KeyX
	case "y":
		return hotkey.KeyY
	case "z":
		return hotkey.KeyZ
	case "enter":
		return hotkey.KeyReturn
	case "esc":
		return hotkey.KeyEscape
	case "delete":
		return hotkey.KeyDelete
	case "tab":
		return hotkey.KeyTab
	case "left":
		return hotkey.KeyLeft
	case "right":
		return hotkey.KeyRight
	case "up":
		return hotkey.KeyUp
	case "down":
		return hotkey.KeyDown
	case "f1":
		return hotkey.KeyF1
	case "f2":
		return hotkey.KeyF2
	case "f3":
		return hotkey.KeyF3
	case "f4":
		return hotkey.KeyF4
	case "f5":
		return hotkey.KeyF5
	case "f6":
		return hotkey.KeyF6
	case "f7":
		return hotkey.KeyF7
	case "f8":
		return hotkey.KeyF8
	case "f9":
		return hotkey.KeyF9
	case "f10":
		return hotkey.KeyF10
	case "f11":
		return hotkey.KeyF11
	case "f12":
		return hotkey.KeyF12
	case "f13":
		return hotkey.KeyF13
	case "f14":
		return hotkey.KeyF14
	case "f15":
		return hotkey.KeyF15
	case "f16":
		return hotkey.KeyF16
	case "f17":
		return hotkey.KeyF17
	case "f18":
		return hotkey.KeyF18
	case "f19":
		return hotkey.KeyF19
	case "f20":
		return hotkey.KeyF20
	default:
		return hotkey.KeySpace
	}
}

package main

import (
	"embed"
	"encoding/json"
	"fmt"
	"log"
	"os"
	"path/filepath"

	"github.com/jeandeaual/go-locale"
)

//go:embed languages/*
var langDatas embed.FS

var MYLANG = "en"

type Language struct {
	Data map[string]map[string]string
}

func NewLanguage() *Language {
	var l = Language{Data: make(map[string]map[string]string)}
	l.SetLang()
	l.Load()
	return &l
}

func (l *Language) UText(langText string) string {
	translated := langText
	if lang, ok := l.Data[langText]; ok {
		if text, ok := lang[MYLANG]; ok {
			translated = text
		}
	} else {
		if MYLANG != "en" {
			fmt.Printf("unmatch:\"%s\" \"%s\"\n", langText, MYLANG)
		}
	}

	return translated
}

func (l *Language) UTextWithLangCode(langText, lcode string) string {
	translated := langText
	if lang, ok := l.Data[langText]; ok {
		if text, ok := lang[lcode]; ok {
			translated = text
		}
	} else {
		if lcode != "en" {
			fmt.Printf("unmatch:\"%s\" \"%s\"\n", langText, lcode)
			return ""
		}
	}

	return translated
}

func (l *Language) SetLang() {
	if lang := os.Getenv("MYLANG"); lang != "" {
		fmt.Println("using user lang:", lang)
		MYLANG = lang
		return
	}
	userLanguage, err := locale.GetLanguage()
	if err == nil {
		fmt.Println("Current Language:", userLanguage)
		MYLANG = userLanguage
	}
}

func (l *Language) Load() {
	files, err := langDatas.ReadDir("languages")
	if err != nil {
		log.Fatal(err)
	}

	languages := make([]map[string]map[string]string, 0)

	for _, file := range files {
		if filepath.Ext(file.Name()) == ".json" {
			fmt.Println("load language:", file.Name())
			data, err := langDatas.ReadFile("languages/" + file.Name())
			if err != nil {
				log.Println("Error reading file:", err)
				continue
			}

			languageJson := make(map[string]map[string]string)
			err = json.Unmarshal(data, &languageJson)
			if err != nil {
				log.Println("Error unmarshaling JSON:", err)
				continue
			}

			languages = append(languages, languageJson)
		}
	}

	l.combineLanguages(languages)
}

func (l *Language) combineLanguages(languages []map[string]map[string]string) {
	for _, language := range languages {
		for key, value := range language {
			if _, ok := l.Data[key]; !ok {
				l.Data[key] = make(map[string]string)
			}

			for k, v := range value {
				l.Data[key][k] = v
			}
		}
	}
}

//go:build linux
// +build linux

package main

func OSDepCheck() {
	return
}

package main

import (
	"fmt"

	"github.com/joho/godotenv"
	"golang.design/x/hotkey"
	"golang.design/x/hotkey/mainthread"
)

func init() {
	godotenv.Load("env.txt")
}

var UText func(string) string
var UMenuText func(string) string
var gCore *Core

func initUText(l *Language) {
	UText = l.UText
}

func initUMenuText(l *Language) {
	UMenuText = func(s string) string {
		return l.UTextWithLangCode(s, "emoji") + l.UText(s)
	}
}

func queryHotkey() {
	gptHK := getGPTHotkeys()
	modKey, key := keyNameToModifier(gptHK[0]), keyNamesToKey(gptHK[1])
	hk := hotkey.New([]hotkey.Modifier{modKey}, key)
	err := hk.Register()
	if err != nil {
		fmt.Printf("hotkey: failed to register hotkey: %v", err)
		return
	}

	for {
		<-hk.Keyup()
		fmt.Printf("hotkey: %v is up\n", hk)
		gCore.queryHit()
	}
}

func escapeHotkey() {
	hk := hotkey.New([]hotkey.Modifier{}, hotkey.KeyEscape)
	err := hk.Register()
	if err != nil {
		fmt.Printf("hotkey: failed to register hotkey: %v\n", err)
		return
	}

	<-hk.Keyup()
	hk.Unregister()
	escKey()
	gCore.escapeHit()
	mainthread.Init(escapeHotkey)
}

func main() {
	var uc UserCore
	var l *Language
	l = NewLanguage()
	OSDepCheck()
	initUText(l)
	initUMenuText(l)
	uc.initUserCore()
	st := SysTray{userCore: &uc}

	gCore = NewCore(&uc, &st)
	go mainthread.Init(queryHotkey)
	go mainthread.Init(escapeHotkey)
	st.Run()
}

package main

import (
	"time"

	"github.com/hanyuancheung/gpt-go"
	"github.com/jeandeaual/go-locale"
)

func renderMessages(messages []gpt.ChatCompletionRequestMessage, newMsg string) (retMsgs []gpt.ChatCompletionRequestMessage) {
	loclang, _ := locale.GetLocale()
	vars := map[string]string{
		"msg":    newMsg,
		"date":   time.Now().Format("2006-01-02"),
		"time":   time.Now().Format("15:04:05"),
		"mylang": loclang,
	}

	for _, message := range messages {
		m := message
		m.Content = applyStringTemplate(message.Content, vars)
		retMsgs = append(retMsgs, m)
	}

	return retMsgs
}

package main

import (
	"context"
	"fmt"
	"os"
	"path/filepath"
	"strings"
	"time"

	"github.com/getlantern/systray"
	"github.com/joho/godotenv"
	icons "github.com/linexjlin/systray-icons/dynamic/ball-triangle"
	icon "github.com/linexjlin/systray-icons/enter-the-keyboard"
	"github.com/skratchdot/open-golang/open"
	"golang.design/x/clipboard"
)

type SysTray struct {
	userCore          *UserCore
	updateHotKeyTitle func(string)
}

func (st *SysTray) Run() {
	systray.Run(st.onReady, st.onExit)
}

func (st *SysTray) ShowRunningIcon(ctx context.Context, done <-chan struct{}) {
	defer systray.SetIcon(icon.Data)
	i := 0
	for {
		select {
		case <-ctx.Done():
			return
		case <-done:
			return
		default:
			systray.SetIcon(icons.Datas[i])
			time.Sleep(time.Millisecond * 120)
			i++
			if i == len(icons.Datas)-1 {
				i = 0
			}
		}
	}
}

func (st *SysTray) getMasks() (masks []string) {
	// Define the directory path and file extension
	dir := "prompts"
	ext := ".json"

	// Use the filepath package to get a list of all files with the specified extension
	files, err := filepath.Glob(filepath.Join(dir, "*"+ext))
	if err != nil {
		panic(err)
	}

	for _, file := range files {
		filename := filepath.Base(file)
		filenameWithoutExt := filename[:len(filename)-len(ext)]
		fmt.Println(filenameWithoutExt)
		masks = append(masks, filenameWithoutExt)
	}
	return
}

func (st *SysTray) onExit() {
	now := time.Now()
	fmt.Println("exit", now)
}

func monitorFileModification(filepath string) {
	fileInfo, err := os.Stat(filepath)
	if err != nil {
		fmt.Println("Error:", err)
		return
	}

	modTime := fileInfo.ModTime()
	fmt.Println("Initial modification time:", modTime)

	// Calculate the end time after 30 minutes
	endTime := time.Now().Add(30 * time.Minute)

	for time.Now().Before(endTime) {
		fileInfo, err = os.Stat(filepath)
		if err != nil {
			fmt.Println("Error:", err)
			return
		}

		if modTime != fileInfo.ModTime() {
			fmt.Println("Modification time changed:", fileInfo.ModTime())
			modTime = fileInfo.ModTime()
			godotenv.Load(filepath)
		}

		time.Sleep(time.Second * 3) // Sleep for 3 seconds before checking again
	}

	fmt.Println("Monitoring completed.")
}

func (st *SysTray) onReady() {
	systray.SetTemplateIcon(icon.Data, icon.Data)

	mQuitOrig := systray.AddMenuItem(UMenuText("Exit"), UMenuText("Quit the whole app"))
	go func() {
		<-mQuitOrig.ClickedCh
		fmt.Println("Requesting quit")
		systray.Quit()
		fmt.Println("Finished quitting")
	}()

	mHotKey := systray.AddMenuItem("", UMenuText("Click to active GPT"))
	st.updateHotKeyTitle = mHotKey.SetTitle

	mAbout := systray.AddMenuItem(UMenuText("About"), UMenuText("Open the project page"))
	go func() {
		for {
			<-mAbout.ClickedCh
			open.Start("https://github.com/linexjlin/inputGPT")
		}
	}()

	systray.AddSeparator()

	mSetting := systray.AddMenuItem(UMenuText("Setting"), UMenuText(""))

	mSetKey := mSetting.AddSubMenuItem(UMenuText("Set API KEY"), UMenuText("Set the OpenAI KEY, baseurl etc.."))
	go func() {
		for {
			<-mSetKey.ClickedCh
			open.Start("env.txt")
			go monitorFileModification("env.txt")
		}
	}()

	mManager := mSetting.AddSubMenuItem(UMenuText("Manage Prompts"), UMenuText("Modify, Delete prompts"))
	go func() {
		for {
			<-mManager.ClickedCh
			open.Start("prompts")
		}
	}()

	mImport := mSetting.AddSubMenuItem(UMenuText("Import"), UMenuText("Import a prompt from clipboard"))

	systray.AddSeparator()

	var modesMenus []*systray.MenuItem
	modes := getModeList()
	for i, mode := range modes {
		m := systray.AddMenuItem(UMenuText(mode), UMenuText("Chose "+mode))
		go func(i int, mode string) {
			for {
				<-m.ClickedCh
				fmt.Println(i, mode)
				st.userCore.SetDefaultMode(mode)

				for ii, mm := range modesMenus {
					if ii == i {
						mm.Check()
					} else {
						mm.Uncheck()
					}
				}
			}
		}(i, mode)
		if i == 0 {
			m.Check()
			st.userCore.SetDefaultMode(mode)
		}
		modesMenus = append(modesMenus, m)
	}

	systray.AddSeparator()
	mClearContext := systray.AddMenuItem(UMenuText("Clear Context"), UMenuText("Clear Context"))
	st.userCore.AddSetContextMenuFunc(mClearContext.SetTitle)

	go func() {
		for {
			select {
			case <-mClearContext.ClickedCh:
				fmt.Println("Clear Context")
				st.userCore.ClearContext()
			}
		}
	}()

	systray.SetTemplateIcon(icon.Data, icon.Data)
	//	systray.SetTitle(UMenuText("InputGPT"))
	systray.SetTooltip(UMenuText("InputGPT a Helpful input Assistant"))

	systray.AddSeparator()

	var maskMenus []*systray.MenuItem
	masks := st.getMasks()

	masks = append(masks, UMenuText("Default"))
	maskCnt := 0

	for i, msk := range masks {
		m := systray.AddMenuItemCheckbox(UMenuText(fmt.Sprintf("%s", msk)), UMenuText("Select this prompt"), false)
		filepath := fmt.Sprintf("prompts/%s.json", msk)
		mk := msk
		if i == len(masks)-1 {
			m.Check()
			filepath = ""
		}
		idx := i
		maskCnt = i
		go func() {
			for {
				select {
				case <-m.ClickedCh:
					fmt.Println(idx, filepath, mk)
					if filepath == "" {
						st.userCore.initUserCore()
					} else {
						if p, e := loadModePrompt(filepath); e != nil {
							fmt.Println(e)
							continue
						} else {
							st.userCore.SetMask(mk)
							st.userCore.SetModePrompt(p)
						}
					}

					for ii, mm := range maskMenus {
						if ii == idx {
							mm.Check()
						} else {
							mm.Uncheck()
						}
					}
				}
			}
		}()
		maskMenus = append(maskMenus, m)
	}

	// Handling import events
	go func() {
		for {
			select {
			case <-mImport.ClickedCh:
				fmt.Println(UText("Import"))
				clipboardContent := string(clipboard.Read(clipboard.FmtText))
				fmt.Println(clipboardContent)
				if p, e := parseModePrompt(clipboardContent); e != nil {
					fmt.Println(e)
				} else {
					if p.Name != "" {
						promptFilePath := fmt.Sprintf("prompts/%s.json", p.Name)
						if _, err := os.Stat(promptFilePath); err == nil {
							fmt.Println("File exists.", promptFilePath)
						} else {
							fmt.Println("create new", promptFilePath)
							m := systray.AddMenuItemCheckbox(fmt.Sprintf("%s", p.Name), "Check Me", false)
							maskCnt++
							idx := maskCnt
							go func() {
								for {
									<-m.ClickedCh
									st.userCore.SetMask(p.Name)
									st.userCore.SetModePrompt(p)

									for ii, mm := range maskMenus {
										if ii == idx {
											mm.Check()
										} else {
											mm.Uncheck()
										}
									}
								}
							}()
							maskMenus = append(maskMenus, m)
						}
						saveModePrompt(p, promptFilePath)
					}
				}

			}
		}
	}()

	st.updateHotKeyTitle(fmt.Sprintf(UMenuText("Copy the question then click \"%s\" to query GPT"), strings.ToUpper(strings.Join(getGPTHotkeys(), "+"))))
}

package main

import (
	"context"
	"fmt"
	"io"
	"time"

	"github.com/hanyuancheung/gpt-go"
)

type UserCore struct {
	mask           string
	model          string
	defaultMode    string
	maxConext      int
	msgCnt         int
	headMessages   []gpt.ChatCompletionRequestMessage
	histMessages   []gpt.ChatCompletionRequestMessage
	setContextMenu func(string)
}

func (u *UserCore) initUserCore() {
	u.mask = "Default"
	u.model = u.defaultMode
	u.maxConext = getMaxContext()
	u.msgCnt = 0
	u.histMessages = []gpt.ChatCompletionRequestMessage{}
	u.headMessages = []gpt.ChatCompletionRequestMessage{
		{
			Role:    "system",
			Content: "Just complete the text I give you, do not explain.",
		},
	}
	u.updateContextMenu()
}

func (u *UserCore) reloadMask() {
	if u.mask == "Default" {
		return
	}
	filepath := fmt.Sprintf("prompts/%s.json", u.mask)
	if p, e := loadModePrompt(filepath); e != nil {
		fmt.Println(e)
	} else {
		u.SetModePrompt(p)
	}
}

func (u *UserCore) SetModePrompt(p ModePrompt) {
	u.initUserCore()
	u.headMessages = p.HeadMessages
	if p.Model != "" {
		u.model = p.Model
	}

	if p.MaxContext != 0 {
		u.maxConext = p.MaxContext
	}
	u.ClearContext()
}

func (u *UserCore) SetMask(m string) {
	u.mask = m
}

func (u *UserCore) AddNewMessages(msgs []gpt.ChatCompletionRequestMessage) {
	u.msgCnt++
	u.histMessages = append(u.histMessages, msgs...)
	u.updateContextMenu()
}

func (u *UserCore) generateZeroContextPromptMessages(newQuery string) []gpt.ChatCompletionRequestMessage {
	if len(u.headMessages) == 0 {
		return []gpt.ChatCompletionRequestMessage{{
			Role:    "user",
			Content: newQuery,
		}}
	} else {
		renderedMessages := renderMessages(u.headMessages, newQuery)
		if renderedMessages[len(renderedMessages)-1].Role == "user" {
			return renderedMessages
		} else {
			return append(renderedMessages, gpt.ChatCompletionRequestMessage{
				Role:    "user",
				Content: newQuery,
			})
		}
	}
}

func (u *UserCore) GeneratePromptMessages(newQuery string) (prompts, new []gpt.ChatCompletionRequestMessage) {
	if u.maxConext == 0 {
		prompts = u.generateZeroContextPromptMessages(newQuery)
		return prompts, prompts
	} else {
		if len(u.histMessages) == 0 {
			prompts = u.generateZeroContextPromptMessages(newQuery)
			return prompts, prompts
		} else {
			new = append(new, gpt.ChatCompletionRequestMessage{
				Role:    "user",
				Content: newQuery,
			})
			if u.msgCnt < u.maxConext {
				prompts = append(prompts, u.histMessages...)
				prompts = append(prompts, new...)
				return prompts, new
			} else {
				prompts = append(prompts, u.histMessages[:len(u.headMessages)]...)
				if u.histMessages[len(u.headMessages)].Role == "assistant" {
					prompts = append(prompts, u.histMessages[len(u.headMessages)])
					prompts = append(prompts, u.histMessages[(len(u.histMessages)-(u.maxConext-1)*2):len(u.histMessages)]...)
				} else {
					prompts = append(prompts, u.histMessages[(len(u.histMessages)-u.maxConext*2):len(u.histMessages)]...)
				}
				prompts = append(prompts, new...)
				return prompts, new
			}
		}
	}
}

func (u *UserCore) AddSetContextMenuFunc(f func(string)) {
	u.setContextMenu = f
	u.updateContextMenu()
}

func (u *UserCore) updateContextMenu() {
	if u.setContextMenu == nil {
		return
	}
	u.setContextMenu(fmt.Sprintf(UText("Clear Context ")+"%s (%d/%d)", u.model, u.msgCnt, u.maxConext))
}

func (u *UserCore) ClearContext() {
	fmt.Println("clean all context")
	u.histMessages = u.histMessages[:0]
	u.msgCnt = 0
	u.updateContextMenu()
}

func (u *UserCore) SetDefaultMode(mode string) {
	fmt.Println("Set default mode to", mode)
	u.defaultMode = mode
	u.model = mode
	u.updateContextMenu()
}

func (u *UserCore) QueryGPT(ctx context.Context, txtChan chan string, messages []gpt.ChatCompletionRequestMessage) {
	fmt.Println("Query messages:")
	showAsJson(messages)
	client := gpt.NewClient(
		getOpenAIkey(),
		gpt.WithBaseURL(getOpenAIBaseUrl()),
		gpt.WithTimeout(600*time.Second),
	)
	err := client.ChatCompletionStream(ctx, &gpt.ChatCompletionRequest{
		Model:    u.model,
		Messages: messages,
	}, func(response *gpt.ChatCompletionStreamResponse) {
		//fmt.Println(response.Choices)
		//fmt.Printf("%+v\n", response)
		if len(response.Choices) > 0 {
			if response.Choices[0].Delta.Content != "" {
				txtChan <- response.Choices[0].Delta.Content
			}
			if response.Choices[0].FinishReason == "stop" {
				fmt.Println("stop")
			}
		}
	})

	if err != nil && err != io.EOF {
		fmt.Printf("ChatCompletionStream error: %v\n", err)
		txtChan <- fmt.Sprintf("ChatCompletionStream error: %v\n", err)
	}
	close(txtChan)
}

package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"text/template"
)

func showAsJson(data interface{}) {
	jsonData, err := json.MarshalIndent(data, "", " ")
	if err != nil {
		fmt.Println("Error converting data to JSON:", err)
		return
	}
	fmt.Println(string(jsonData))
}

func applyStringTemplate(temp string, vars map[string]string) string {
	tmpl, err := template.New("tmpl").Parse(temp)
	if err != nil {
		fmt.Println(err)
	}

	var buf bytes.Buffer
	err = tmpl.Execute(&buf, vars)
	if err != nil {
		fmt.Println(err)
	}

	result := buf.String()
	return result
}

//go:build windows
// +build windows

package main

import "github.com/lxn/win"

func OSDepCheck() {
	if !isDebug() {
		win.ShowWindow(win.GetConsoleWindow(), win.SW_HIDE)
	}
}
--------------------------------------------------
make a python program in one file that does the exact same thing as the thing above