mirror of
https://github.com/YspCoder/clawgo.git
synced 2026-05-06 10:17:29 +08:00
refactor api server into focused modules
This commit is contained in:
179
pkg/api/server_webui.go
Normal file
179
pkg/api/server_webui.go
Normal file
@@ -0,0 +1,179 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime/debug"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
func (s *Server) handleWebUI(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodGet {
|
||||
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
if !s.checkAuth(r) {
|
||||
http.Error(w, "unauthorized", http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
if s.token != "" {
|
||||
http.SetCookie(w, &http.Cookie{
|
||||
Name: "clawgo_webui_token",
|
||||
Value: s.token,
|
||||
Path: "/",
|
||||
HttpOnly: true,
|
||||
SameSite: http.SameSiteLaxMode,
|
||||
MaxAge: 86400,
|
||||
})
|
||||
}
|
||||
if s.tryServeWebUIDist(w, r, "/index.html") {
|
||||
return
|
||||
}
|
||||
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||
_, _ = w.Write([]byte(webUIHTML))
|
||||
}
|
||||
|
||||
func (s *Server) handleWebUIAsset(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodGet {
|
||||
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
if !s.checkAuth(r) {
|
||||
http.Error(w, "unauthorized", http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
if strings.HasPrefix(r.URL.Path, "/api/") {
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
if r.URL.Path == "/" {
|
||||
s.handleWebUI(w, r)
|
||||
return
|
||||
}
|
||||
if s.tryServeWebUIDist(w, r, r.URL.Path) {
|
||||
return
|
||||
}
|
||||
if s.tryServeWebUIDist(w, r, "/index.html") {
|
||||
return
|
||||
}
|
||||
http.NotFound(w, r)
|
||||
}
|
||||
|
||||
func (s *Server) tryServeWebUIDist(w http.ResponseWriter, r *http.Request, reqPath string) bool {
|
||||
dir := strings.TrimSpace(s.webUIDir)
|
||||
if dir == "" {
|
||||
return false
|
||||
}
|
||||
p := strings.TrimPrefix(reqPath, "/")
|
||||
if reqPath == "/" || reqPath == "/index.html" {
|
||||
p = "index.html"
|
||||
}
|
||||
p = filepath.Clean(strings.TrimPrefix(p, "/"))
|
||||
if strings.HasPrefix(p, "..") {
|
||||
return false
|
||||
}
|
||||
full := filepath.Join(dir, p)
|
||||
fi, err := os.Stat(full)
|
||||
if err != nil || fi.IsDir() {
|
||||
return false
|
||||
}
|
||||
http.ServeFile(w, r, full)
|
||||
return true
|
||||
}
|
||||
|
||||
func (s *Server) handleWebUIUpload(w http.ResponseWriter, r *http.Request) {
|
||||
if !s.checkAuth(r) {
|
||||
http.Error(w, "unauthorized", http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
if r.Method != http.MethodPost {
|
||||
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
if err := r.ParseMultipartForm(32 << 20); err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
f, h, err := r.FormFile("file")
|
||||
if err != nil {
|
||||
http.Error(w, "file required", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
defer f.Close()
|
||||
dir := filepath.Join(os.TempDir(), "clawgo_webui_uploads")
|
||||
_ = os.MkdirAll(dir, 0755)
|
||||
name := fmt.Sprintf("%d_%s", time.Now().UnixNano(), filepath.Base(h.Filename))
|
||||
path := filepath.Join(dir, name)
|
||||
out, err := os.Create(path)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
defer out.Close()
|
||||
if _, err := io.Copy(out, f); err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
writeJSON(w, map[string]interface{}{"ok": true, "path": path, "name": h.Filename})
|
||||
}
|
||||
|
||||
func gatewayBuildVersion() string {
|
||||
if bi, ok := debug.ReadBuildInfo(); ok && bi != nil {
|
||||
ver := strings.TrimSpace(bi.Main.Version)
|
||||
rev := ""
|
||||
for _, s := range bi.Settings {
|
||||
if s.Key == "vcs.revision" {
|
||||
rev = s.Value
|
||||
break
|
||||
}
|
||||
}
|
||||
if len(rev) > 8 {
|
||||
rev = rev[:8]
|
||||
}
|
||||
if ver == "" || ver == "(devel)" {
|
||||
ver = "devel"
|
||||
}
|
||||
if rev != "" {
|
||||
return ver + "+" + rev
|
||||
}
|
||||
return ver
|
||||
}
|
||||
return "unknown"
|
||||
}
|
||||
|
||||
func detectWebUIVersion(webUIDir string) string {
|
||||
_ = webUIDir
|
||||
return "dev"
|
||||
}
|
||||
|
||||
const webUIHTML = `<!doctype html>
|
||||
<html><head><meta charset="utf-8"/><meta name="viewport" content="width=device-width,initial-scale=1"/>
|
||||
<title>ClawGo WebUI</title>
|
||||
<style>body{font-family:system-ui;margin:20px;max-width:980px}textarea{width:100%;min-height:220px}#chatlog{white-space:pre-wrap;border:1px solid #ddd;padding:12px;min-height:180px}</style>
|
||||
</head><body>
|
||||
<h2>ClawGo WebUI</h2>
|
||||
<p>Token: <input id="token" placeholder="gateway token" style="width:320px"/></p>
|
||||
<h3>Config (dynamic + hot reload)</h3>
|
||||
<button onclick="loadCfg()">Load Config</button>
|
||||
<button onclick="saveCfg()">Save + Reload</button>
|
||||
<textarea id="cfg"></textarea>
|
||||
<h3>Chat (supports media upload)</h3>
|
||||
<div>Session: <input id="session" value="webui:default"/> <input id="msg" placeholder="message" style="width:420px"/> <input id="file" type="file"/> <button onclick="sendChat()">Send</button></div>
|
||||
<div id="chatlog"></div>
|
||||
<script>
|
||||
function auth(){const t=document.getElementById('token').value.trim();return t?('?token='+encodeURIComponent(t)):''}
|
||||
async function loadCfg(){let r=await fetch('/api/config'+auth());document.getElementById('cfg').value=await r.text()}
|
||||
async function saveCfg(){let j=JSON.parse(document.getElementById('cfg').value);let r=await fetch('/api/config'+auth(),{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify(j)});alert(await r.text())}
|
||||
async function sendChat(){
|
||||
let media='';const f=document.getElementById('file').files[0];
|
||||
if(f){let fd=new FormData();fd.append('file',f);let ur=await fetch('/api/upload'+auth(),{method:'POST',body:fd});let uj=await ur.json();media=uj.path||''}
|
||||
const payload={session:document.getElementById('session').value,message:document.getElementById('msg').value,media};
|
||||
let r=await fetch('/api/chat'+auth(),{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify(payload)});let t=await r.text();
|
||||
document.getElementById('chatlog').textContent += '\nUSER> '+payload.message+(media?(' [file:'+media+']'):'')+'\nBOT> '+t+'\n';
|
||||
}
|
||||
loadCfg();
|
||||
</script></body></html>`
|
||||
Reference in New Issue
Block a user