1
0
mirror of https://github.com/coalaura/whiskr.git synced 2025-09-08 17:06:42 +00:00
Files
whiskr/title.go
2025-08-30 14:15:22 +02:00

153 lines
3.0 KiB
Go

package main
import (
"encoding/json"
"fmt"
"net/http"
"strings"
"github.com/revrost/go-openrouter"
"github.com/revrost/go-openrouter/jsonschema"
)
type TitleRequest struct {
Title *string `json:"title"`
Messages []Message `json:"messages"`
}
type TitleResponse struct {
Title string `json:"title"`
}
var (
titleReplacer = strings.NewReplacer(
"\r", "",
"\n", "\\n",
"\t", "\\t",
)
titleSchema, _ = jsonschema.GenerateSchema[TitleResponse]()
)
func HandleTitle(w http.ResponseWriter, r *http.Request) {
debug("parsing title")
var raw TitleRequest
if err := json.NewDecoder(r.Body).Decode(&raw); err != nil {
RespondJson(w, http.StatusBadRequest, map[string]any{
"error": err.Error(),
})
return
}
debug("preparing request")
messages := make([]string, 0, len(raw.Messages))
for _, message := range raw.Messages {
switch message.Role {
case "system", "assistant", "user":
text := message.Text
if len(message.Files) != 0 {
if text != "" {
text += "\n"
}
files := make([]string, len(message.Files))
for i, file := range message.Files {
files[i] = file.Name
}
text += fmt.Sprintf("FILES: %s", strings.Join(files, ", "))
}
if text != "" {
text = strings.TrimSpace(text)
text = titleReplacer.Replace(text)
messages = append(messages, fmt.Sprintf("%s: %s", strings.ToUpper(message.Role), text))
}
}
}
if len(messages) == 0 {
RespondJson(w, http.StatusBadRequest, map[string]any{
"error": "no valid messages",
})
return
}
var prompt strings.Builder
if err := InternalTitleTmpl.Execute(&prompt, raw); err != nil {
RespondJson(w, http.StatusInternalServerError, map[string]any{
"error": err.Error(),
})
return
}
request := openrouter.ChatCompletionRequest{
Model: env.Settings.TitleModel,
Messages: []openrouter.ChatCompletionMessage{
openrouter.SystemMessage(prompt.String()),
openrouter.UserMessage(strings.Join(messages, "\n")),
},
Temperature: 0.25,
MaxTokens: 100,
ResponseFormat: &openrouter.ChatCompletionResponseFormat{
Type: openrouter.ChatCompletionResponseFormatTypeJSONSchema,
JSONSchema: &openrouter.ChatCompletionResponseFormatJSONSchema{
Name: "chat_title",
Schema: titleSchema,
Strict: true,
},
},
Usage: &openrouter.IncludeUsage{
Include: true,
},
}
if raw.Title != nil {
request.Temperature = 0.4
}
dump("title.json", request)
debug("generating title")
response, err := OpenRouterRun(r.Context(), request)
if err != nil {
RespondJson(w, http.StatusInternalServerError, map[string]any{
"error": err.Error(),
})
return
}
choice := response.Choices[0].Message.Content.Text
cost := response.Usage.Cost
var result TitleResponse
err = json.Unmarshal([]byte(choice), &result)
if err != nil {
RespondJson(w, http.StatusInternalServerError, map[string]any{
"error": err.Error(),
"cost": cost,
})
return
}
RespondJson(w, http.StatusOK, map[string]any{
"title": result.Title,
"cost": cost,
})
}