mirror of
https://github.com/coalaura/whiskr.git
synced 2025-09-09 09:19:54 +00:00
tweaks
This commit is contained in:
@@ -67,6 +67,7 @@ go build -o whiskr
|
|||||||
- Adjust model, temperature, prompt, or message role from the controls in the bottom-left
|
- Adjust model, temperature, prompt, or message role from the controls in the bottom-left
|
||||||
- Use the model search field to quickly find models (supports fuzzy matching)
|
- Use the model search field to quickly find models (supports fuzzy matching)
|
||||||
- Look for tags in the model list to see if a model supports tools, vision, or reasoning
|
- Look for tags in the model list to see if a model supports tools, vision, or reasoning
|
||||||
|
- Use `` in your message to display an image inline. If the model supports vision, the same image URL is passed to the model for multimodal input.
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
|
92
chat.go
92
chat.go
@@ -7,6 +7,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/revrost/go-openrouter"
|
"github.com/revrost/go-openrouter"
|
||||||
@@ -117,13 +118,26 @@ func (r *Request) Parse() (*openrouter.ChatCompletionRequest, error) {
|
|||||||
|
|
||||||
for index, message := range r.Messages {
|
for index, message := range r.Messages {
|
||||||
switch message.Role {
|
switch message.Role {
|
||||||
case "system", "user":
|
case "system":
|
||||||
request.Messages = append(request.Messages, openrouter.ChatCompletionMessage{
|
request.Messages = append(request.Messages, openrouter.ChatCompletionMessage{
|
||||||
Role: message.Role,
|
Role: message.Role,
|
||||||
Content: openrouter.Content{
|
Content: openrouter.Content{
|
||||||
Text: message.Text,
|
Text: message.Text,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
case "user":
|
||||||
|
var content openrouter.Content
|
||||||
|
|
||||||
|
if model.Vision && strings.Contains(message.Text, "![") {
|
||||||
|
content.Multi = SplitImagePairs(message.Text)
|
||||||
|
} else {
|
||||||
|
content.Text = message.Text
|
||||||
|
}
|
||||||
|
|
||||||
|
request.Messages = append(request.Messages, openrouter.ChatCompletionMessage{
|
||||||
|
Role: message.Role,
|
||||||
|
Content: content,
|
||||||
|
})
|
||||||
case "assistant":
|
case "assistant":
|
||||||
msg := openrouter.ChatCompletionMessage{
|
msg := openrouter.ChatCompletionMessage{
|
||||||
Role: openrouter.ChatMessageRoleAssistant,
|
Role: openrouter.ChatMessageRoleAssistant,
|
||||||
@@ -151,7 +165,7 @@ func (r *Request) Parse() (*openrouter.ChatCompletionRequest, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func HandleChat(w http.ResponseWriter, r *http.Request) {
|
func HandleChat(w http.ResponseWriter, r *http.Request) {
|
||||||
debug("new chat")
|
debug("parsing chat")
|
||||||
|
|
||||||
var raw Request
|
var raw Request
|
||||||
|
|
||||||
@@ -174,6 +188,9 @@ func HandleChat(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
request.Stream = true
|
request.Stream = true
|
||||||
|
|
||||||
|
dump("debug.json", request)
|
||||||
|
debug("preparing stream")
|
||||||
|
|
||||||
response, err := NewStream(w)
|
response, err := NewStream(w)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
RespondJson(w, http.StatusBadRequest, map[string]any{
|
RespondJson(w, http.StatusBadRequest, map[string]any{
|
||||||
@@ -325,3 +342,74 @@ func RunCompletion(ctx context.Context, response *Stream, request *openrouter.Ch
|
|||||||
|
|
||||||
return tool, result.String(), nil
|
return tool, result.String(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func SplitImagePairs(text string) []openrouter.ChatMessagePart {
|
||||||
|
rgx := regexp.MustCompile(`(?m)!\[[^\]]*]\((\S+?)\)`)
|
||||||
|
|
||||||
|
var (
|
||||||
|
index int
|
||||||
|
parts []openrouter.ChatMessagePart
|
||||||
|
)
|
||||||
|
|
||||||
|
push := func(str, end int) {
|
||||||
|
rest := text[str:end]
|
||||||
|
|
||||||
|
if rest == "" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
total := len(parts)
|
||||||
|
|
||||||
|
if total > 0 && parts[total-1].Type == openrouter.ChatMessagePartTypeText {
|
||||||
|
parts[total-1].Text += rest
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
parts = append(parts, openrouter.ChatMessagePart{
|
||||||
|
Type: openrouter.ChatMessagePartTypeText,
|
||||||
|
Text: rest,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
for {
|
||||||
|
location := rgx.FindStringSubmatchIndex(text[index:])
|
||||||
|
if location == nil {
|
||||||
|
push(index, len(text)-1)
|
||||||
|
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
start := index + location[0]
|
||||||
|
end := index + location[1]
|
||||||
|
|
||||||
|
urlStart := index + location[2]
|
||||||
|
urlEnd := index + location[3]
|
||||||
|
|
||||||
|
url := text[urlStart:urlEnd]
|
||||||
|
|
||||||
|
if !strings.HasPrefix(url, "https://") && !strings.HasPrefix(url, "http://") {
|
||||||
|
push(index, end)
|
||||||
|
|
||||||
|
index = end
|
||||||
|
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if start > index {
|
||||||
|
push(index, start)
|
||||||
|
}
|
||||||
|
|
||||||
|
parts = append(parts, openrouter.ChatMessagePart{
|
||||||
|
Type: openrouter.ChatMessagePartTypeImageURL,
|
||||||
|
ImageURL: &openrouter.ChatMessageImageURL{
|
||||||
|
Detail: openrouter.ImageURLDetailAuto,
|
||||||
|
URL: url,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
index = end
|
||||||
|
}
|
||||||
|
|
||||||
|
return parts
|
||||||
|
}
|
||||||
|
14
debug.go
14
debug.go
@@ -1,5 +1,19 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
func dump(name string, val any) {
|
||||||
|
if !Debug {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
b, _ := json.MarshalIndent(val, "", "\t")
|
||||||
|
os.WriteFile(name, b, 0644)
|
||||||
|
}
|
||||||
|
|
||||||
func debug(format string, args ...any) {
|
func debug(format string, args ...any) {
|
||||||
if !Debug {
|
if !Debug {
|
||||||
return
|
return
|
||||||
|
@@ -15,6 +15,7 @@ type Model struct {
|
|||||||
Tags []string `json:"tags,omitempty"`
|
Tags []string `json:"tags,omitempty"`
|
||||||
|
|
||||||
Reasoning bool `json:"-"`
|
Reasoning bool `json:"-"`
|
||||||
|
Vision bool `json:"-"`
|
||||||
JSON bool `json:"-"`
|
JSON bool `json:"-"`
|
||||||
Tools bool `json:"-"`
|
Tools bool `json:"-"`
|
||||||
}
|
}
|
||||||
@@ -78,6 +79,8 @@ func GetModelTags(model openrouter.Model, m *Model) {
|
|||||||
|
|
||||||
for _, modality := range model.Architecture.InputModalities {
|
for _, modality := range model.Architecture.InputModalities {
|
||||||
if modality == "image" {
|
if modality == "image" {
|
||||||
|
m.Vision = true
|
||||||
|
|
||||||
m.Tags = append(m.Tags, "vision")
|
m.Tags = append(m.Tags, "vision")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -10,7 +10,13 @@
|
|||||||
walkTokens: (token) => {
|
walkTokens: (token) => {
|
||||||
const { type, lang, text } = token;
|
const { type, lang, text } = token;
|
||||||
|
|
||||||
if (type !== "code") {
|
if (type === "html") {
|
||||||
|
token.text = token.text.replace(/&/g, "&")
|
||||||
|
token.text = token.text.replace(/</g, "<")
|
||||||
|
token.text = token.text.replace(/>/g, ">")
|
||||||
|
|
||||||
|
return;
|
||||||
|
} else if (type !== "code") {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user