fix(ui): update AMCS references and add status handling
* Corrected "Advanced Module Control System" to "Avalon Memory Control Service" in documentation and UI components. * Added status handling to the LoginInfoPanel and LoginPage components. * Implemented new endpoints for robots.txt and llms.txt.
This commit is contained in:
@@ -243,7 +243,11 @@ func routes(logger *slog.Logger, cfg *config.Config, info buildinfo.Info, db *st
|
||||
mux.HandleFunc("/images/project.jpg", serveHomeImage)
|
||||
mux.HandleFunc("/images/icon.png", serveIcon)
|
||||
mux.HandleFunc("/llm", serveLLMInstructions)
|
||||
mux.HandleFunc("/llms.txt", serveLLMSTXT)
|
||||
mux.HandleFunc("/.well-known/llms.txt", serveLLMSTXT)
|
||||
mux.HandleFunc("/robots.txt", serveRobotsTXT)
|
||||
mux.HandleFunc("/api/status", statusAPIHandler(info, accessTracker, oauthEnabled))
|
||||
mux.HandleFunc("/status", statusAPIHandler(info, accessTracker, oauthEnabled))
|
||||
|
||||
mux.HandleFunc("/healthz", func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
amcsllm "git.warky.dev/wdevs/amcs/llm"
|
||||
)
|
||||
@@ -20,3 +22,74 @@ func serveLLMInstructions(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
_, _ = w.Write(amcsllm.MemoryInstructions)
|
||||
}
|
||||
|
||||
func serveRobotsTXT(w http.ResponseWriter, r *http.Request) {
|
||||
if r.URL.Path != "/robots.txt" {
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
if r.Method != http.MethodGet && r.Method != http.MethodHead {
|
||||
w.Header().Set("Allow", "GET, HEAD")
|
||||
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
|
||||
w.Header().Set("Cache-Control", "public, max-age=300")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
if r.Method == http.MethodHead {
|
||||
return
|
||||
}
|
||||
|
||||
body := fmt.Sprintf("User-agent: *\nAllow: /\n\n# LLM-friendly docs\nLLM: %s/llm\nLLMS: %s/llms.txt\n", requestBaseURL(r), requestBaseURL(r))
|
||||
_, _ = w.Write([]byte(body))
|
||||
}
|
||||
|
||||
func serveLLMSTXT(w http.ResponseWriter, r *http.Request) {
|
||||
if r.URL.Path != "/llms.txt" && r.URL.Path != "/.well-known/llms.txt" {
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
if r.Method != http.MethodGet && r.Method != http.MethodHead {
|
||||
w.Header().Set("Allow", "GET, HEAD")
|
||||
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
|
||||
w.Header().Set("Cache-Control", "public, max-age=300")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
if r.Method == http.MethodHead {
|
||||
return
|
||||
}
|
||||
|
||||
base := requestBaseURL(r)
|
||||
body := fmt.Sprintf(
|
||||
"# AMCS\n\n> A memory server for AI assistants (MCP tools, semantic retrieval, and structured project memory).\n\n## Endpoints\n- %s/llm\n- %s/status\n- %s/mcp\n- %s/.well-known/oauth-authorization-server\n",
|
||||
base,
|
||||
base,
|
||||
base,
|
||||
base,
|
||||
)
|
||||
_, _ = w.Write([]byte(body))
|
||||
}
|
||||
|
||||
func requestBaseURL(r *http.Request) string {
|
||||
scheme := "http"
|
||||
if r != nil && r.TLS != nil {
|
||||
scheme = "https"
|
||||
}
|
||||
if r != nil {
|
||||
if proto := strings.TrimSpace(r.Header.Get("X-Forwarded-Proto")); proto != "" {
|
||||
scheme = proto
|
||||
}
|
||||
}
|
||||
|
||||
host := "localhost"
|
||||
if r != nil {
|
||||
if v := strings.TrimSpace(r.Host); v != "" {
|
||||
host = v
|
||||
}
|
||||
}
|
||||
return scheme + "://" + host
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package app
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
amcsllm "git.warky.dev/wdevs/amcs/llm"
|
||||
@@ -29,3 +30,70 @@ func TestServeLLMInstructions(t *testing.T) {
|
||||
t.Fatalf("body = %q, want embedded instructions", body)
|
||||
}
|
||||
}
|
||||
|
||||
func TestServeRobotsTXT(t *testing.T) {
|
||||
req := httptest.NewRequest(http.MethodGet, "/robots.txt", nil)
|
||||
req.Host = "amcs.example.com"
|
||||
req.Header.Set("X-Forwarded-Proto", "https")
|
||||
rec := httptest.NewRecorder()
|
||||
|
||||
serveRobotsTXT(rec, req)
|
||||
|
||||
res := rec.Result()
|
||||
defer func() {
|
||||
_ = res.Body.Close()
|
||||
}()
|
||||
|
||||
if res.StatusCode != http.StatusOK {
|
||||
t.Fatalf("status = %d, want %d", res.StatusCode, http.StatusOK)
|
||||
}
|
||||
if got := res.Header.Get("Content-Type"); got != "text/plain; charset=utf-8" {
|
||||
t.Fatalf("content-type = %q, want %q", got, "text/plain; charset=utf-8")
|
||||
}
|
||||
body := rec.Body.String()
|
||||
if !strings.Contains(body, "LLM: https://amcs.example.com/llm") {
|
||||
t.Fatalf("body = %q, want LLM link", body)
|
||||
}
|
||||
if !strings.Contains(body, "LLMS: https://amcs.example.com/llms.txt") {
|
||||
t.Fatalf("body = %q, want LLMS link", body)
|
||||
}
|
||||
}
|
||||
|
||||
func TestServeLLMSTXT(t *testing.T) {
|
||||
req := httptest.NewRequest(http.MethodGet, "/llms.txt", nil)
|
||||
req.Host = "amcs.example.com"
|
||||
req.Header.Set("X-Forwarded-Proto", "https")
|
||||
rec := httptest.NewRecorder()
|
||||
|
||||
serveLLMSTXT(rec, req)
|
||||
|
||||
res := rec.Result()
|
||||
defer func() {
|
||||
_ = res.Body.Close()
|
||||
}()
|
||||
|
||||
if res.StatusCode != http.StatusOK {
|
||||
t.Fatalf("status = %d, want %d", res.StatusCode, http.StatusOK)
|
||||
}
|
||||
if got := res.Header.Get("Content-Type"); got != "text/plain; charset=utf-8" {
|
||||
t.Fatalf("content-type = %q, want %q", got, "text/plain; charset=utf-8")
|
||||
}
|
||||
body := rec.Body.String()
|
||||
if !strings.Contains(body, "https://amcs.example.com/llm") {
|
||||
t.Fatalf("body = %q, want /llm link", body)
|
||||
}
|
||||
if !strings.Contains(body, "https://amcs.example.com/.well-known/oauth-authorization-server") {
|
||||
t.Fatalf("body = %q, want oauth discovery link", body)
|
||||
}
|
||||
}
|
||||
|
||||
func TestServeLLMSTXTWellKnownPath(t *testing.T) {
|
||||
req := httptest.NewRequest(http.MethodGet, "/.well-known/llms.txt", nil)
|
||||
rec := httptest.NewRecorder()
|
||||
|
||||
serveLLMSTXT(rec, req)
|
||||
|
||||
if rec.Code != http.StatusOK {
|
||||
t.Fatalf("status = %d, want %d", rec.Code, http.StatusOK)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,7 +55,7 @@ func fallback(value, defaultValue string) string {
|
||||
|
||||
func statusAPIHandler(info buildinfo.Info, tracker *auth.AccessTracker, oauthEnabled bool) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.URL.Path != "/api/status" {
|
||||
if r.URL.Path != "/api/status" && r.URL.Path != "/status" {
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -86,6 +86,26 @@ func TestStatusAPIHandlerReturnsJSON(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestStatusAPIHandlerSupportsStatusPath(t *testing.T) {
|
||||
handler := statusAPIHandler(buildinfo.Info{Version: "v1"}, auth.NewAccessTracker(), true)
|
||||
req := httptest.NewRequest(http.MethodGet, "/status", nil)
|
||||
rec := httptest.NewRecorder()
|
||||
|
||||
handler.ServeHTTP(rec, req)
|
||||
|
||||
if rec.Code != http.StatusOK {
|
||||
t.Fatalf("status = %d, want %d", rec.Code, http.StatusOK)
|
||||
}
|
||||
|
||||
var payload statusAPIResponse
|
||||
if err := json.Unmarshal(rec.Body.Bytes(), &payload); err != nil {
|
||||
t.Fatalf("json.Unmarshal() error = %v", err)
|
||||
}
|
||||
if payload.Version != "v1" {
|
||||
t.Fatalf("version = %q, want %q", payload.Version, "v1")
|
||||
}
|
||||
}
|
||||
|
||||
func TestHomeHandlerAllowsHead(t *testing.T) {
|
||||
handler := homeHandler(buildinfo.Info{Version: "v1"}, auth.NewAccessTracker(), false)
|
||||
req := httptest.NewRequest(http.MethodHead, "/", nil)
|
||||
|
||||
Reference in New Issue
Block a user