summaryrefslogtreecommitdiff
path: root/main.go
blob: 59f36483ab673b4f6c927ac438822d3ba537dc32 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
package main

import (
	"bufio"
	"bytes"
	"encoding/json"
	"flag"
	"io"
	"log"
	"net/http"
	"os"
	"strings"
	"text/template"
)

var (
	token     = flag.String("token", os.Getenv("FASTGPT_API_TOKEN"), "API-Token, default from FASTGPT_API_TOKEN environment")
	stdin     = flag.Bool("stdin", true, "Use stdin instead of command-line arguments for search")
	cache     = flag.Bool("cache", false, "Whether to allow cached requests & responses.")
	websearch = flag.Bool("websearch", true, "Whether to perform web searches to enrich answers. MUST   be true for now.")
	raw       = flag.Bool("raw", false, "Output raw json result")
)

type Query struct {
	Query     string `json:"query"`      // A query to be answered.
	Cache     bool   `json:"cache"`      // Whether to allow cached requests & responses. (default true)
	WebSearch bool   `json:"web_search"` // Whether to perform web searches to enrich answers. MUST be true for now.
}

type Result struct {
	Meta struct {
		Id   string `json:"id"`
		Node string `json:"node"`
		MS   int    `json:"ms"`
	} `json:"meta"`
	Data struct {
		Query      string      `json:"-"`
		Output     string      `json:"output"`     // Answer output
		References []Reference `json:"references"` // The search results that are referred in the answer.
		Tokens     int         `json:"tokens"`     // Amount of tokens processed
	} `json:"data"`
}

type Reference struct {
	Title   string `json:"title"`   // Title of the referenced search result.
	Snippet string `json:"snippet"` // Snippet of the referenced search result.
	URL     string `json:"url"`     // URL of the referenced search result.
}

var fm = template.FuncMap{
	"quote": func(s string) string {
		buf := &bytes.Buffer{}
		scn := bufio.NewScanner(strings.NewReader(s))
		for scn.Scan() {
			buf.WriteString("> ")
			buf.WriteString(scn.Text())
			buf.WriteString("\n")
		}
		return buf.String()
	},
	"plusone": func(i int) int { return i + 1 },
}
var tmpl = template.Must(template.New("output").
	Funcs(fm).
	Parse(`Q: {{ .Query }}

{{.Output}}
{{ with .References }}{{- range $i, $ref := . }}
[{{ plusone $i }}]: [{{$ref.Title}}]({{$ref.URL}})
{{ quote $ref.Snippet }}
{{- end }}
{{- end }}
`))

func main() {

	q := Query{
		Cache:     *cache,
		WebSearch: *websearch,
	}
	buf := &bytes.Buffer{}

	flag.Parse()
	if *stdin {
		if _, e := io.Copy(buf, os.Stdin); e != nil {
			panic(e)
		}
		q.Query = buf.String()
		buf.Reset()
	} else {
		q.Query = strings.Join(os.Args, " ")
	}

	if e := json.NewEncoder(buf).Encode(&q); e != nil {
		panic(e)
	}

	req, e := http.NewRequest("POST",
		"https://kagi.com/api/v0/fastgpt",
		buf)
	if e != nil {
		panic(e)
	}
	req.Header.Add("Authorization", "Bot "+*token)
	req.Header.Add("Content-Type", "application/json")

	r, e := http.DefaultClient.Do(req)
	if e != nil {
		panic(e)
	}

	if r.StatusCode != 200 {
		log.Printf("Non-200 status code: %d\nBody: \n", r.StatusCode)
		io.Copy(os.Stdout, r.Body)
		os.Exit(r.StatusCode)
	}

	buf.Reset()
	res := &Result{}
	if e := json.NewDecoder(io.TeeReader(r.Body, buf)).Decode(res); e != nil {
		log.Printf("Couldn't decode result: %v\nBody:\n", e)
		io.Copy(os.Stdout, buf)
	}
	res.Data.Query = q.Query

	if *raw {
		io.Copy(os.Stdout, buf)
	} else {
		tmpl.Execute(os.Stdout, res.Data)
	}
}