rony-chat-bot/internal/server/server.go
Victor Hugo Vargas f33708534a feat: bootstrap rony-chat-bot Go module
Initial implementation of the bot:

- cmd/chat-bot: CLI entrypoint (serve, reindex, ask, version)
- internal/agent: LLM provider client + agent runner with RAG injection
- internal/config: YAML config loader (providers, RAG, persona, server)
- internal/i18n: response-language detection (EN/ES)
- internal/persona: persona system prompt assembly from YAML
- internal/portfolio: heading-based chunker + SQLite FTS5 indexer
- internal/server: chi router with /api/chat (SSE), /api/health, /api/info,
  /api/reindex, middleware (RequestID, Logging, CORS, RateLimit)
- internal/streaming: SSE protocol helpers (start, chunk, sources, done, error)
- web/: drop-in vanilla-JS chat widget (no build, no deps) + demo + README
- bench/: reproducible driver benchmark (modernc vs mattn SQLite)
- configs/portfolio-bot.yaml: llama.cpp default provider, SQLite RAG, canine persona
- docs/architecture.md / .es.md: aligned with SQLite FTS5 + llama.cpp decisions
- data/projects/README*.md: project data documentation
- README.md / .es.md: updated for current implementation

All tests pass (go test ./...). Bot is functional end-to-end with the
configured LLM provider.
2026-07-17 00:56:06 -07:00

62 lines
No EOL
1.4 KiB
Go

package server
import (
"context"
"errors"
"log/slog"
"net/http"
"time"
"github.com/go-chi/chi/v5"
"github.com/VictorVargas/rony-chat-bot/internal/config"
)
type Server struct {
httpSrv *http.Server
}
func New(cfg *config.Config, h *Handlers) *Server {
r := chi.NewRouter()
r.Use(RequestID)
r.Use(Logging)
r.Use(CORS(cfg.Server.CORSOrigins))
if cfg.Server.RateLimit.RequestsPerMinute > 0 {
r.Use(RateLimit(cfg.Server.RateLimit))
}
r.Get("/", func(w http.ResponseWriter, _ *http.Request) {
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
_, _ = w.Write([]byte("Rony Chat Bot — see /api/health, /api/info, POST /api/chat\n"))
})
r.Route("/api", func(r chi.Router) {
r.Post("/chat", h.Chat)
r.Post("/reindex", h.Reindex)
r.Get("/health", h.Health)
r.Get("/info", h.Info)
})
srv := &http.Server{
Addr: cfg.Addr(),
Handler: r,
ReadTimeout: time.Duration(cfg.Server.ReadTimeoutMS) * time.Millisecond,
WriteTimeout: 0, // SSE streams must not be cut off by WriteTimeout
IdleTimeout: 120 * time.Second,
}
return &Server{httpSrv: srv}
}
func (s *Server) Start() error {
slog.Info("http server starting", "addr", s.httpSrv.Addr)
if err := s.httpSrv.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) {
return err
}
return nil
}
func (s *Server) Shutdown(ctx context.Context) error {
return s.httpSrv.Shutdown(ctx)
}