86 lines
2.4 KiB
Go
86 lines
2.4 KiB
Go
// Package main — strutil.go holds small, receiver-free string utilities used
|
|
// across the voice reply paths: trimming a wake token, pulling out the first
|
|
// word or first line, and a minimal JSON string encoder for the one payload
|
|
// shape that needs it. Extend this file rather than voice.go for anything in
|
|
// that shape.
|
|
package main
|
|
|
|
import (
|
|
"fmt"
|
|
"strings"
|
|
|
|
"github.com/kami/maven/internal/router"
|
|
)
|
|
|
|
// stripWake removes a leading wake token (any script the STT phonetically
|
|
// transcribes "Maven" as) so the verb is the first word.
|
|
func stripWake(u string) string {
|
|
stripped, had := router.StripWakeToken(u)
|
|
if !had {
|
|
return strings.TrimSpace(u)
|
|
}
|
|
return stripped
|
|
}
|
|
|
|
// firstWord returns the first whitespace-delimited token (lowercased) — the
|
|
// proposed tool's name.
|
|
func firstWord(s string) string {
|
|
f := strings.Fields(s)
|
|
if len(f) == 0 {
|
|
return ""
|
|
}
|
|
return strings.ToLower(f[0])
|
|
}
|
|
|
|
// firstLine — the first non-empty line of a tool's output, for a short spoken
|
|
// reply (the full output goes to the log, not the TTS). Trimmed to keep the
|
|
// utterance sane if a command dumps a wall of text.
|
|
func firstLine(s string) string {
|
|
for _, line := range strings.Split(s, "\n") {
|
|
line = strings.TrimSpace(line)
|
|
if line != "" {
|
|
if len(line) > 200 {
|
|
line = line[:200]
|
|
}
|
|
return line
|
|
}
|
|
}
|
|
return ""
|
|
}
|
|
|
|
// jsonString — a one-line JSON string encoder without dragging encoding/json
|
|
// into the top of this file. Used to wrap a reminder payload's text field;
|
|
// the router's reminder Slots are already absolute (DateTimeParser resolved
|
|
// relative→absolute), the payload shape is conventional {"text":...}.
|
|
func jsonString(s string) string {
|
|
// minimal JSON string escape — quotes + backslash + control chars.
|
|
// adequate for the reminder payload's text field; not a general JSON
|
|
// encoder. The chroma / RAG modules (when they land) use a real json
|
|
// encoder for richer payloads. Keep it inline here so the import
|
|
// direction stays narrow.
|
|
var b []byte
|
|
b = append(b, '"')
|
|
for _, r := range s {
|
|
switch r {
|
|
case '"':
|
|
b = append(b, '\\', '"')
|
|
case '\\':
|
|
b = append(b, '\\', '\\')
|
|
case '\n':
|
|
b = append(b, '\\', 'n')
|
|
case '\r':
|
|
b = append(b, '\\', 'r')
|
|
case '\t':
|
|
b = append(b, '\\', 't')
|
|
default:
|
|
if r < 0x20 {
|
|
b = append(b, []byte(fmt.Sprintf("\\u%04x", r))...)
|
|
} else {
|
|
b = append(b, []byte(string(r))...)
|
|
}
|
|
}
|
|
}
|
|
b = append(b, '"')
|
|
return string(b)
|
|
}
|