basic ui
This commit is contained in:
parent
1a0bdce02d
commit
0e7d284f09
|
|
@ -0,0 +1,6 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="VcsDirectoryMappings">
|
||||||
|
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
21
main.go
21
main.go
|
|
@ -5,12 +5,12 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"html/template"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
"text/template"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
|
|
@ -230,6 +230,17 @@ func renderPrompt(tmplStr string, data any) (string, error) {
|
||||||
return buf.String(), nil
|
return buf.String(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var uiTemplate *template.Template
|
||||||
|
|
||||||
|
func loadUITemplate(path string) error {
|
||||||
|
tmpl, err := template.ParseFiles(path)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
uiTemplate = tmpl
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
if err := loadConfig("config.yaml"); err != nil {
|
if err := loadConfig("config.yaml"); err != nil {
|
||||||
log.Fatalf("Failed to load config.yaml: %v", err)
|
log.Fatalf("Failed to load config.yaml: %v", err)
|
||||||
|
|
@ -239,12 +250,18 @@ func main() {
|
||||||
log.Fatalf("Failed to load db.yaml: %v", err)
|
log.Fatalf("Failed to load db.yaml: %v", err)
|
||||||
}
|
}
|
||||||
fmt.Printf("Loaded %d reasons from db.yaml\n", len(reasonsDB))
|
fmt.Printf("Loaded %d reasons from db.yaml\n", len(reasonsDB))
|
||||||
|
if err := loadUITemplate("ui.html"); err != nil {
|
||||||
|
log.Fatalf("Failed to load ui.html: %v", err)
|
||||||
|
}
|
||||||
llm := &LLMClient{
|
llm := &LLMClient{
|
||||||
APIKey: os.Getenv("OPENAI_API_KEY"),
|
APIKey: os.Getenv("OPENAI_API_KEY"),
|
||||||
BaseURL: os.Getenv("OPENAI_BASE_URL"), // e.g. http://localhost:1234/v1/completions
|
BaseURL: os.Getenv("OPENAI_BASE_URL"), // e.g. http://localhost:1234/v1/completions
|
||||||
}
|
}
|
||||||
r := gin.Default()
|
r := gin.Default()
|
||||||
|
r.GET("/", func(c *gin.Context) {
|
||||||
|
c.Status(200)
|
||||||
|
uiTemplate.Execute(c.Writer, nil)
|
||||||
|
})
|
||||||
r.POST("/chat", func(c *gin.Context) {
|
r.POST("/chat", func(c *gin.Context) {
|
||||||
var req ChatRequest
|
var req ChatRequest
|
||||||
if err := c.ShouldBindJSON(&req); err != nil {
|
if err := c.ShouldBindJSON(&req); err != nil {
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,69 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>Vet Clinic Chat Assistant</title>
|
||||||
|
<style>
|
||||||
|
body { font-family: sans-serif; margin: 2em; }
|
||||||
|
#chatbox { width: 100%; max-width: 600px; margin: 0 auto; }
|
||||||
|
#messages { border: 1px solid #ccc; min-height: 120px; padding: 1em; margin-bottom: 1em; background: #fafafa; }
|
||||||
|
.msg-user { color: #333; }
|
||||||
|
.msg-bot { color: #007a3d; margin-bottom: 1em; }
|
||||||
|
#input { width: 80%; padding: 0.5em; }
|
||||||
|
#send { padding: 0.5em 1em; }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="chatbox">
|
||||||
|
<h2>Vet Clinic Chat Assistant</h2>
|
||||||
|
<div id="messages"></div>
|
||||||
|
<input id="input" type="text" placeholder="Type your message..." autofocus />
|
||||||
|
<button id="send">Send</button>
|
||||||
|
</div>
|
||||||
|
<script>
|
||||||
|
const messages = document.getElementById('messages');
|
||||||
|
const input = document.getElementById('input');
|
||||||
|
const send = document.getElementById('send');
|
||||||
|
function appendMsg(text, who) {
|
||||||
|
const div = document.createElement('div');
|
||||||
|
div.className = who === 'user' ? 'msg-user' : 'msg-bot';
|
||||||
|
div.textContent = text;
|
||||||
|
messages.appendChild(div);
|
||||||
|
messages.scrollTop = messages.scrollHeight;
|
||||||
|
}
|
||||||
|
send.onclick = async function() {
|
||||||
|
const msg = input.value.trim();
|
||||||
|
if (!msg) return;
|
||||||
|
appendMsg(msg, 'user');
|
||||||
|
input.value = '';
|
||||||
|
appendMsg('Thinking...', 'bot');
|
||||||
|
const resp = await fetch('/chat', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({ message: msg })
|
||||||
|
});
|
||||||
|
const data = await resp.json();
|
||||||
|
messages.lastChild.remove(); // remove 'Thinking...'
|
||||||
|
if (data.match) {
|
||||||
|
var txt = "Match: " + data.match + "\n";
|
||||||
|
if (data.procedures) {
|
||||||
|
txt += "Procedures:\n";
|
||||||
|
data.procedures.forEach(function(p) {
|
||||||
|
txt += "- " + p.name + ": " + p.price + " Ft, " + p.duration_minutes + " perc\n";
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (data.total_price) txt += "Total: " + data.total_price + " Ft\n";
|
||||||
|
if (data.total_duration) txt += "Total duration: " + data.total_duration + " perc\n";
|
||||||
|
if (data.notes) txt += "Notes: " + data.notes + "\n";
|
||||||
|
appendMsg(txt, 'bot');
|
||||||
|
} else {
|
||||||
|
appendMsg('Sorry, no match found. Your message will be sent to the vet.', 'bot');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
input.addEventListener('keydown', function(e) {
|
||||||
|
if (e.key === 'Enter') send.onclick();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
||||||
Loading…
Reference in New Issue