diff --git a/cmd/prompto/cmds/serve.go b/cmd/prompto/cmds/serve.go
index a106bee..bc12b2f 100644
--- a/cmd/prompto/cmds/serve.go
+++ b/cmd/prompto/cmds/serve.go
@@ -30,5 +30,5 @@ func (s *ServeCommand) run(cmd *cobra.Command, args []string) error {
port, _ := cmd.Flags().GetInt("port")
watching, _ := cmd.Flags().GetBool("watching")
- return server.Serve(port, watching)
+ return server.Serve(port, watching, s.repositories)
}
diff --git a/go.mod b/go.mod
index 83c583e..873fe89 100644
--- a/go.mod
+++ b/go.mod
@@ -1,6 +1,6 @@
module github.com/go-go-golems/prompto
-go 1.21
+go 1.22
toolchain go1.23.3
@@ -19,6 +19,7 @@ require (
github.com/Masterminds/goutils v1.1.1 // indirect
github.com/Masterminds/semver v1.5.0 // indirect
github.com/Masterminds/sprig v2.22.0+incompatible // indirect
+ github.com/a-h/templ v0.2.793 // indirect
github.com/adrg/frontmatter v0.2.0 // indirect
github.com/alecthomas/chroma/v2 v2.14.0 // indirect
github.com/araddon/dateparse v0.0.0-20210429162001-6b43995a97de // indirect
diff --git a/go.sum b/go.sum
index a8dc2f3..4383a7c 100644
--- a/go.sum
+++ b/go.sum
@@ -9,6 +9,8 @@ github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3Q
github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y=
github.com/Masterminds/sprig v2.22.0+incompatible h1:z4yfnGrZ7netVz+0EDJ0Wi+5VZCSYp4Z0m2dk6cEM60=
github.com/Masterminds/sprig v2.22.0+incompatible/go.mod h1:y6hNFY5UBTIWBxnzTeuNhlNS5hqE0NB0E6fgfo2Br3o=
+github.com/a-h/templ v0.2.793 h1:Io+/ocnfGWYO4VHdR0zBbf39PQlnzVCVVD+wEEs6/qY=
+github.com/a-h/templ v0.2.793/go.mod h1:lq48JXoUvuQrU0VThrK31yFwdRjTCnIE5bcPCM9IP1w=
github.com/adrg/frontmatter v0.2.0 h1:/DgnNe82o03riBd1S+ZDjd43wAmC6W35q67NHeLkPd4=
github.com/adrg/frontmatter v0.2.0/go.mod h1:93rQCj3z3ZlwyxxpQioRKC1wDLto4aXHrbqIsnH9wmE=
github.com/alecthomas/assert/v2 v2.7.0 h1:QtqSACNS3tF7oasA8CU6A6sXZSBDqnm7RfpLl9bZqbE=
diff --git a/pkg/server/handlers.go b/pkg/server/handlers.go
deleted file mode 100644
index 155affd..0000000
--- a/pkg/server/handlers.go
+++ /dev/null
@@ -1,101 +0,0 @@
-package server
-
-import (
- _ "embed"
- "html/template"
- "net/http"
- "strings"
-
- "github.com/go-go-golems/prompto/pkg"
-)
-
-//go:embed static/js/favorites.js
-var favoritesJS string
-
-//go:embed static/templates/root.html
-var rootTemplate string
-
-//go:embed static/templates/repoList.html
-var repoListTemplate string
-
-func rootHandler(state *ServerState) http.HandlerFunc {
- return func(w http.ResponseWriter, r *http.Request) {
- if r.URL.Path != "/" {
- http.NotFound(w, r)
- return
- }
-
- tmpl, err := state.CreateTemplateWithFuncs("root", rootTemplate+repoListTemplate)
- if err != nil {
- http.Error(w, err.Error(), http.StatusInternalServerError)
- return
- }
-
- data := struct {
- Groups []string
- FavoritesJS template.JS
- }{
- Groups: state.GetAllGroups(),
- FavoritesJS: template.JS(favoritesJS),
- }
-
- w.Header().Set("Content-Type", "text/html")
- err = tmpl.Execute(w, data)
- if err != nil {
- http.Error(w, err.Error(), http.StatusInternalServerError)
- return
- }
- }
-}
-
-func searchHandler(state *ServerState) http.HandlerFunc {
- return func(w http.ResponseWriter, r *http.Request) {
- if r.Method != http.MethodPost {
- http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
- return
- }
-
- query := r.FormValue("search")
- results := make(map[string][]pkg.Prompto)
-
- state.mu.RLock()
- for _, file := range state.GetAllPromptos() {
- if strings.Contains(strings.ToLower(file.Name), strings.ToLower(query)) {
- group := strings.SplitN(file.Name, "/", 2)[0]
- results[group] = append(results[group], file)
- }
- }
- state.mu.RUnlock()
-
- groups := make([]string, 0)
- for group := range results {
- groups = append(groups, group)
- }
-
- funcMap := template.FuncMap{
- "PromptosByGroup": func(group string) []pkg.Prompto {
- return results[group]
- },
- }
-
- tmpl, err := state.CreateTemplateWithFuncs("repoList", repoListTemplate)
- if err != nil {
- http.Error(w, err.Error(), http.StatusInternalServerError)
- return
- }
- tmpl = tmpl.Funcs(funcMap)
-
- data := struct {
- Groups []string
- }{
- Groups: groups,
- }
-
- w.Header().Set("Content-Type", "text/html")
- err = tmpl.ExecuteTemplate(w, "repoList", data)
- if err != nil {
- http.Error(w, err.Error(), http.StatusInternalServerError)
- return
- }
- }
-}
diff --git a/pkg/server/handlers/handlers.go b/pkg/server/handlers/handlers.go
new file mode 100644
index 0000000..eb2b489
--- /dev/null
+++ b/pkg/server/handlers/handlers.go
@@ -0,0 +1,15 @@
+package handlers
+
+import (
+ "github.com/go-go-golems/prompto/pkg/server/state"
+)
+
+type Handlers struct {
+ state *state.ServerState
+}
+
+func NewHandlers(state *state.ServerState) *Handlers {
+ return &Handlers{
+ state: state,
+ }
+}
diff --git a/pkg/server/handlers/index.go b/pkg/server/handlers/index.go
new file mode 100644
index 0000000..9395e36
--- /dev/null
+++ b/pkg/server/handlers/index.go
@@ -0,0 +1,22 @@
+package handlers
+
+import (
+ "net/http"
+
+ "github.com/go-go-golems/prompto/pkg/server/templates/pages"
+)
+
+func (h *Handlers) Index() http.HandlerFunc {
+ return func(w http.ResponseWriter, r *http.Request) {
+ if r.URL.Path != "/" {
+ http.NotFound(w, r)
+ return
+ }
+
+ component := pages.Index(h.state.GetAllRepositories(), h.state.Repos)
+ err := component.Render(r.Context(), w)
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ }
+ }
+}
diff --git a/pkg/server/handlers/prompt.go b/pkg/server/handlers/prompt.go
new file mode 100644
index 0000000..6b2f823
--- /dev/null
+++ b/pkg/server/handlers/prompt.go
@@ -0,0 +1,177 @@
+package handlers
+
+import (
+ "fmt"
+ "net/http"
+ "strings"
+
+ "github.com/go-go-golems/prompto/pkg"
+ "github.com/go-go-golems/prompto/pkg/server/templates/components"
+ "github.com/rs/zerolog/log"
+)
+
+func (h *Handlers) PromptList() http.HandlerFunc {
+ return func(w http.ResponseWriter, r *http.Request) {
+ parts := strings.Split(r.URL.Path, "/")
+ if len(parts) < 4 {
+ http.Error(w, "Invalid path", http.StatusBadRequest)
+ return
+ }
+
+ repo := parts[2]
+ group := parts[3]
+
+ repository, ok := h.state.Repos[repo]
+ if !ok {
+ http.Error(w, "Repository not found", http.StatusNotFound)
+ return
+ }
+
+ prompts := repository.GetPromptosByGroup(group)
+ component := components.PromptList(prompts)
+ err := component.Render(r.Context(), w)
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ }
+ }
+}
+
+func (h *Handlers) PromptContent() http.HandlerFunc {
+ return func(w http.ResponseWriter, r *http.Request) {
+ logger := log.With().Str("handler", "PromptContent").Logger()
+
+ name := r.PathValue("name")
+ logger.Debug().Str("path", name).Msg("handling prompt request")
+
+ if name == "" {
+ logger.Debug().Msg("invalid path: empty name")
+ http.Error(w, "Invalid path", http.StatusBadRequest)
+ return
+ }
+
+ // Handle root directory listing
+ if name == "" {
+ logger.Debug().Msg("rendering root directory listing")
+ var allPrompts []pkg.Prompto
+ for _, repo := range h.state.Repos {
+ allPrompts = append(allPrompts, repo.GetPromptos()...)
+ }
+ component := components.PromptList(allPrompts)
+ err := component.Render(r.Context(), w)
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ }
+ return
+ }
+
+ // Split the path into group and prompt path
+ parts := strings.SplitN(name, "/", 2)
+
+ // Handle group listing
+ if len(parts) == 1 {
+ group := parts[0]
+ logger = logger.With().Str("group", group).Logger()
+ logger.Debug().Msg("rendering group listing")
+
+ // Get all prompts from this group across all repositories
+ var prompts []pkg.Prompto
+ for _, repo := range h.state.Repos {
+ prompts = append(prompts, repo.GetPromptosByGroup(group)...)
+ }
+
+ component := components.PromptList(prompts)
+ err := component.Render(r.Context(), w)
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ }
+ return
+ }
+
+ group := parts[0]
+ promptPath := name
+ logger = logger.With().
+ Str("group", group).
+ Str("promptPath", promptPath).
+ Logger()
+
+ logger.Debug().Msg("looking up prompt")
+
+ // Get all prompts for this group and find the matching one
+ files := h.state.GetPromptosByGroup(group)
+ var foundFile pkg.Prompto
+ for _, file := range files {
+ if file.Name == promptPath {
+ foundFile = file
+ break
+ }
+ }
+
+ if foundFile.Name == "" {
+ logger.Debug().Msg("prompt not found")
+ http.Error(w, "Prompt not found", http.StatusNotFound)
+ return
+ }
+
+ logger = logger.With().
+ Str("repository", foundFile.Repository).
+ Str("prompt", foundFile.Name).
+ Logger()
+ logger.Debug().Msg("found prompt, rendering with args")
+
+ // Extract URL parameters
+ queryParams := r.URL.Query()
+ var restArgs []string
+ for key, values := range queryParams {
+ for _, value := range values {
+ if value == "" {
+ // pass non-keyword arguments as a straight string
+ restArgs = append(restArgs, key)
+ } else {
+ restArgs = append(restArgs, fmt.Sprintf("--%s", key), value)
+ }
+ }
+ }
+
+ content, err := foundFile.Render(foundFile.Repository, restArgs)
+ if err != nil {
+ logger.Debug().Err(err).Msg("error rendering prompt")
+ http.Error(w, "Error rendering prompt", http.StatusInternalServerError)
+ return
+ }
+
+ w.Header().Set("Content-Type", "text/markdown")
+ _, _ = w.Write([]byte(content))
+ }
+}
+
+func (h *Handlers) Search() http.HandlerFunc {
+ return func(w http.ResponseWriter, r *http.Request) {
+ query := r.URL.Query().Get("q")
+ var matchingPrompts []pkg.Prompto
+
+ if query == "" {
+ // Show all prompts when query is empty
+ repoNames := h.state.GetAllRepositories()
+ for _, repoName := range repoNames {
+ repo := h.state.Repos[repoName]
+ matchingPrompts = append(matchingPrompts, repo.GetPromptos()...)
+ }
+ } else {
+ // Search for matching prompts
+ for _, repo := range h.state.Repos {
+ for _, prompt := range repo.GetPromptos() {
+ if strings.Contains(strings.ToLower(prompt.Name), strings.ToLower(query)) ||
+ strings.Contains(strings.ToLower(prompt.Group), strings.ToLower(query)) {
+ matchingPrompts = append(matchingPrompts, prompt)
+ }
+ }
+ }
+ }
+
+ component := components.PromptList(matchingPrompts)
+ err := component.Render(r.Context(), w)
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ }
+ }
+}
diff --git a/pkg/server/refresh_handler.go b/pkg/server/handlers/refresh.go
similarity index 75%
rename from pkg/server/refresh_handler.go
rename to pkg/server/handlers/refresh.go
index 7f9db9b..8c7887e 100644
--- a/pkg/server/refresh_handler.go
+++ b/pkg/server/handlers/refresh.go
@@ -1,17 +1,17 @@
-package server
+package handlers
import (
"net/http"
)
-func refreshHandler(state *ServerState) http.HandlerFunc {
+func (h *Handlers) Refresh() http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
return
}
- if err := state.LoadRepositories(); err != nil {
+ if err := h.state.LoadRepositories(); err != nil {
http.Error(w, "Error refreshing repositories", http.StatusInternalServerError)
return
}
diff --git a/pkg/server/repositories_handler.go b/pkg/server/handlers/repositories.go
similarity index 67%
rename from pkg/server/repositories_handler.go
rename to pkg/server/handlers/repositories.go
index ddf68b9..aa816a6 100644
--- a/pkg/server/repositories_handler.go
+++ b/pkg/server/handlers/repositories.go
@@ -1,15 +1,13 @@
-package server
+package handlers
import (
"encoding/json"
"net/http"
)
-func repositoriesHandler(state *ServerState) http.HandlerFunc {
+func (h *Handlers) Repositories() http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
- state.mu.RLock()
- repos := state.Repositories
- state.mu.RUnlock()
+ repos := h.state.GetAllRepositories()
w.Header().Set("Content-Type", "application/json")
err := json.NewEncoder(w).Encode(repos)
diff --git a/pkg/server/prompt_handler.go b/pkg/server/prompt_handler.go
deleted file mode 100644
index 6f36676..0000000
--- a/pkg/server/prompt_handler.go
+++ /dev/null
@@ -1,89 +0,0 @@
-package server
-
-import (
- "fmt"
- "html/template"
- "net/http"
- "strings"
-
- "github.com/go-go-golems/prompto/pkg"
-)
-
-func promptHandler(state *ServerState) http.HandlerFunc {
- listTmpl := template.Must(template.New("promptList").Parse(`
-
- {{range .}}
- - {{.Name}}
- {{end}}
-
- `))
-
- return func(w http.ResponseWriter, r *http.Request) {
- path := strings.TrimSuffix(strings.TrimPrefix(r.URL.Path, "/prompts/"), "/")
- parts := strings.SplitN(path, "/", 2)
-
- if len(parts) == 1 {
- // Directory listing
- group := parts[0]
- state.mu.RLock()
- files := state.GetPromptosByGroup(group)
- state.mu.RUnlock()
-
- w.Header().Set("Content-Type", "text/html")
- err := listTmpl.Execute(w, files)
- if err != nil {
- http.Error(w, "Error rendering template", http.StatusInternalServerError)
- return
- }
- return
- }
-
- if len(parts) != 2 {
- http.Error(w, "Invalid path", http.StatusBadRequest)
- return
- }
-
- group := parts[0]
- promptName := path
-
- state.mu.RLock()
- files := state.GetPromptosByGroup(group)
- state.mu.RUnlock()
-
- var foundFile pkg.Prompto
- for _, file := range files {
- if file.Name == promptName {
- foundFile = file
- break
- }
- }
-
- if foundFile.Name == "" {
- http.Error(w, "Prompt not found", http.StatusNotFound)
- return
- }
-
- // Extract URL parameters
- queryParams := r.URL.Query()
- var restArgs []string
- for key, values := range queryParams {
- for _, value := range values {
- if value == "" {
- // pass non-keyword arguments as a straight string
- restArgs = append(restArgs, key)
- } else {
- restArgs = append(restArgs, fmt.Sprintf("--%s", key), value)
- }
- }
- }
-
- content, err := foundFile.Render(foundFile.Repository, restArgs)
- if err != nil {
- http.Error(w, "Error rendering prompt", http.StatusInternalServerError)
- return
- }
-
- w.Header().Set("Content-Type", "text/markdown")
- _, _ = w.Write([]byte(content))
- }
-}
diff --git a/pkg/server/serve.go b/pkg/server/serve.go
index f97bbc1..b3cf5a4 100644
--- a/pkg/server/serve.go
+++ b/pkg/server/serve.go
@@ -3,184 +3,15 @@ package server
import (
"context"
"fmt"
- "html/template"
"net/http"
- "path/filepath"
- "sort"
- "sync"
- "github.com/go-go-golems/clay/pkg/watcher"
- "github.com/go-go-golems/glazed/pkg/helpers/templating"
- "github.com/go-go-golems/prompto/pkg"
- "github.com/rs/zerolog/log"
- "github.com/spf13/viper"
+ "github.com/go-go-golems/prompto/pkg/server/handlers"
+ "github.com/go-go-golems/prompto/pkg/server/state"
)
-type ServerState struct {
- Repositories []string
- Repos map[string]*pkg.Repository
- mu sync.RWMutex
- Watching bool
-}
-
-func NewServerState(watching bool) *ServerState {
- return &ServerState{
- Repos: make(map[string]*pkg.Repository),
- Watching: watching,
- }
-}
-
-func (s *ServerState) LoadRepositories() error {
- s.mu.Lock()
- defer s.mu.Unlock()
-
- s.Repositories = viper.GetStringSlice("repositories")
- for _, repoPath := range s.Repositories {
- repo := pkg.NewRepository(repoPath)
- err := repo.LoadPromptos()
- if err != nil {
- return fmt.Errorf("error loading files from repository %s: %w", repoPath, err)
- }
- s.Repos[repoPath] = repo
- }
- return nil
-}
-
-func (s *ServerState) CreateTemplateWithFuncs(name, tmpl string) (*template.Template, error) {
- funcMap := template.FuncMap{
- "PromptosByGroup": func(group string) []pkg.Prompto {
- return s.GetPromptosByGroup(group)
- },
- "AllRepositories": func() []string {
- return s.GetAllRepositories()
- },
- "AllPromptos": func() []pkg.Prompto {
- return s.GetAllPromptos()
- },
- "AllGroups": func() []string {
- return s.GetAllGroups()
- },
- "PromptosByRepository": func(repo string) []pkg.Prompto {
- return s.Repos[repo].GetPromptos()
- },
- "GroupsByRepository": func(repo string) []string {
- return s.GetGroupsByRepository(repo)
- },
- "PromptosForRepositoryAndGroup": func(repo, group string) []pkg.Prompto {
- return s.GetPromptosForRepositoryAndGroup(repo, group)
- },
- }
-
- return templating.CreateHTMLTemplate(name).
- Funcs(funcMap).
- Parse(tmpl)
-}
-
-func (s *ServerState) GetAllRepositories() []string {
- s.mu.RLock()
- defer s.mu.RUnlock()
- return s.Repositories
-}
-
-func (s *ServerState) GetAllPromptos() []pkg.Prompto {
- s.mu.RLock()
- defer s.mu.RUnlock()
-
- var allPromptos []pkg.Prompto
- for _, repo := range s.Repos {
- allPromptos = append(allPromptos, repo.GetPromptos()...)
- }
-
- sort.Slice(allPromptos, func(i, j int) bool {
- return allPromptos[i].Name < allPromptos[j].Name
- })
-
- return allPromptos
-}
-
-func (s *ServerState) GetAllGroups() []string {
- s.mu.RLock()
- defer s.mu.RUnlock()
-
- groupSet := make(map[string]struct{})
- for _, repo := range s.Repos {
- for _, group := range repo.GetGroups() {
- groupSet[group] = struct{}{}
- }
- }
-
- var groups []string
- for group := range groupSet {
- groups = append(groups, group)
- }
-
- sort.Strings(groups)
- return groups
-}
-
-func (s *ServerState) GetPromptosByGroup(group string) []pkg.Prompto {
- s.mu.RLock()
- defer s.mu.RUnlock()
-
- var groupPromptos []pkg.Prompto
- for _, repo := range s.Repos {
- groupPromptos = append(groupPromptos, repo.GetPromptosByGroup(group)...)
- }
-
- sort.Slice(groupPromptos, func(i, j int) bool {
- return groupPromptos[i].Name < groupPromptos[j].Name
- })
-
- return groupPromptos
-}
-
-func (s *ServerState) GetGroupsByRepository(repo string) []string {
- s.mu.RLock()
- defer s.mu.RUnlock()
- return s.Repos[repo].GetGroups()
-}
-
-func (s *ServerState) GetPromptosForRepositoryAndGroup(repo, group string) []pkg.Prompto {
- s.mu.RLock()
- defer s.mu.RUnlock()
- return s.Repos[repo].GetPromptosByGroup(group)
-}
-
-func (s *ServerState) WatchRepositories(ctx context.Context) error {
- if !s.Watching {
- return nil
- }
-
- for repoPath, repo := range s.Repos {
- options := []watcher.Option{
- watcher.WithWriteCallback(func(path string) error {
- log.Info().Msgf("File %s changed, reloading...", path)
- s.mu.Lock()
- defer s.mu.Unlock()
- return repo.AddPrompto(path)
- }),
- watcher.WithRemoveCallback(func(path string) error {
- log.Info().Msgf("File %s removed, removing from repository...", path)
- s.mu.Lock()
- defer s.mu.Unlock()
- return repo.RemovePrompto(path)
- }),
- watcher.WithPaths(filepath.Join(repoPath, "prompto")),
- }
-
- w := watcher.NewWatcher(options...)
- go func() {
- if err := w.Run(ctx); err != nil {
- log.Error().Err(err).Msg("Watcher error")
- }
- }()
- }
-
- return nil
-}
-
-func Serve(port int, watching bool) error {
- state := NewServerState(watching)
+func Serve(port int, watching bool, repositories []string) error {
+ state := state.NewServerState(watching)
+ state.Repositories = repositories
if err := state.LoadRepositories(); err != nil {
return fmt.Errorf("error loading repositories: %w", err)
}
@@ -195,19 +26,20 @@ func Serve(port int, watching bool) error {
}
}
- http.HandleFunc("/", logHandler(rootHandler(state)))
- http.HandleFunc("/prompts/", logHandler(promptHandler(state)))
- http.HandleFunc("/search", logHandler(searchHandler(state)))
- http.HandleFunc("/refresh", logHandler(refreshHandler(state)))
- http.HandleFunc("/repositories", logHandler(repositoriesHandler(state)))
+ h := handlers.NewHandlers(state)
- fmt.Printf("Server is running on http://localhost:%d\n", port)
- return http.ListenAndServe(fmt.Sprintf(":%d", port), nil)
-}
+ // Set up routes
+ mux := http.NewServeMux()
+ mux.Handle("/", h.Index())
+ mux.Handle("/prompts/{name...}", h.PromptContent())
+ mux.Handle("/search", h.Search())
+ mux.Handle("/refresh", h.Refresh())
+ mux.Handle("/repositories", h.Repositories())
-func logHandler(next http.HandlerFunc) http.HandlerFunc {
- return func(w http.ResponseWriter, r *http.Request) {
- log.Printf("%s %s", r.Method, r.URL.Path)
- next(w, r)
- }
+ // Serve static files
+ fs := http.FileServer(http.Dir("pkg/server/static"))
+ mux.Handle("/static/", http.StripPrefix("/static/", fs))
+
+ fmt.Printf("Server is running on http://localhost:%d\n", port)
+ return http.ListenAndServe(fmt.Sprintf(":%d", port), mux)
}
diff --git a/pkg/server/state/state.go b/pkg/server/state/state.go
new file mode 100644
index 0000000..b26884b
--- /dev/null
+++ b/pkg/server/state/state.go
@@ -0,0 +1,177 @@
+package state
+
+import (
+ "context"
+ "fmt"
+ "html/template"
+ "path/filepath"
+ "sort"
+ "sync"
+
+ "github.com/go-go-golems/clay/pkg/watcher"
+ "github.com/go-go-golems/glazed/pkg/helpers/templating"
+ "github.com/go-go-golems/prompto/pkg"
+ "github.com/rs/zerolog/log"
+)
+
+type ServerState struct {
+ Repositories []string
+ Repos map[string]*pkg.Repository
+ mu sync.RWMutex
+ Watching bool
+}
+
+func NewServerState(watching bool) *ServerState {
+ return &ServerState{
+ Repos: make(map[string]*pkg.Repository),
+ Watching: watching,
+ }
+}
+
+func (s *ServerState) LoadRepositories() error {
+ s.mu.Lock()
+ defer s.mu.Unlock()
+
+ for _, repoPath := range s.Repositories {
+ repo := pkg.NewRepository(repoPath)
+ err := repo.LoadPromptos()
+ if err != nil {
+ return fmt.Errorf("error loading files from repository %s: %w", repoPath, err)
+ }
+ s.Repos[repoPath] = repo
+ }
+ return nil
+}
+
+func (s *ServerState) CreateTemplateWithFuncs(name, tmpl string) (*template.Template, error) {
+ funcMap := template.FuncMap{
+ "PromptosByGroup": func(group string) []pkg.Prompto {
+ return s.GetPromptosByGroup(group)
+ },
+ "AllRepositories": func() []string {
+ return s.GetAllRepositories()
+ },
+ "AllPromptos": func() []pkg.Prompto {
+ return s.GetAllPromptos()
+ },
+ "AllGroups": func() []string {
+ return s.GetAllGroups()
+ },
+ "PromptosByRepository": func(repo string) []pkg.Prompto {
+ return s.Repos[repo].GetPromptos()
+ },
+ "GroupsByRepository": func(repo string) []string {
+ return s.GetGroupsByRepository(repo)
+ },
+ "PromptosForRepositoryAndGroup": func(repo, group string) []pkg.Prompto {
+ return s.GetPromptosForRepositoryAndGroup(repo, group)
+ },
+ }
+
+ return templating.CreateHTMLTemplate(name).
+ Funcs(funcMap).
+ Parse(tmpl)
+}
+
+func (s *ServerState) GetAllRepositories() []string {
+ s.mu.RLock()
+ defer s.mu.RUnlock()
+ return s.Repositories
+}
+
+func (s *ServerState) GetAllPromptos() []pkg.Prompto {
+ s.mu.RLock()
+ defer s.mu.RUnlock()
+
+ var allPromptos []pkg.Prompto
+ for _, repo := range s.Repos {
+ allPromptos = append(allPromptos, repo.GetPromptos()...)
+ }
+
+ sort.Slice(allPromptos, func(i, j int) bool {
+ return allPromptos[i].Name < allPromptos[j].Name
+ })
+
+ return allPromptos
+}
+
+func (s *ServerState) GetAllGroups() []string {
+ s.mu.RLock()
+ defer s.mu.RUnlock()
+
+ groupSet := make(map[string]struct{})
+ for _, repo := range s.Repos {
+ for _, group := range repo.GetGroups() {
+ groupSet[group] = struct{}{}
+ }
+ }
+
+ var groups []string
+ for group := range groupSet {
+ groups = append(groups, group)
+ }
+
+ sort.Strings(groups)
+ return groups
+}
+
+func (s *ServerState) GetPromptosByGroup(group string) []pkg.Prompto {
+ s.mu.RLock()
+ defer s.mu.RUnlock()
+
+ var groupPromptos []pkg.Prompto
+ for _, repo := range s.Repos {
+ groupPromptos = append(groupPromptos, repo.GetPromptosByGroup(group)...)
+ }
+
+ sort.Slice(groupPromptos, func(i, j int) bool {
+ return groupPromptos[i].Name < groupPromptos[j].Name
+ })
+
+ return groupPromptos
+}
+
+func (s *ServerState) GetGroupsByRepository(repo string) []string {
+ s.mu.RLock()
+ defer s.mu.RUnlock()
+ return s.Repos[repo].GetGroups()
+}
+
+func (s *ServerState) GetPromptosForRepositoryAndGroup(repo, group string) []pkg.Prompto {
+ s.mu.RLock()
+ defer s.mu.RUnlock()
+ return s.Repos[repo].GetPromptosByGroup(group)
+}
+
+func (s *ServerState) WatchRepositories(ctx context.Context) error {
+ if !s.Watching {
+ return nil
+ }
+
+ for repoPath, repo := range s.Repos {
+ options := []watcher.Option{
+ watcher.WithWriteCallback(func(path string) error {
+ log.Info().Msgf("File %s changed, reloading...", path)
+ s.mu.Lock()
+ defer s.mu.Unlock()
+ return repo.AddPrompto(path)
+ }),
+ watcher.WithRemoveCallback(func(path string) error {
+ log.Info().Msgf("File %s removed, removing from repository...", path)
+ s.mu.Lock()
+ defer s.mu.Unlock()
+ return repo.RemovePrompto(path)
+ }),
+ watcher.WithPaths(filepath.Join(repoPath, "prompto")),
+ }
+
+ w := watcher.NewWatcher(options...)
+ go func() {
+ if err := w.Run(ctx); err != nil {
+ log.Error().Err(err).Msg("Watcher error")
+ }
+ }()
+ }
+
+ return nil
+}
diff --git a/pkg/server/static/js/favorites.js b/pkg/server/static/js/favorites.js
index aad24a0..a795617 100644
--- a/pkg/server/static/js/favorites.js
+++ b/pkg/server/static/js/favorites.js
@@ -1,36 +1,70 @@
-// static/js/favorites.js
-let favorites = [];
+// Utility functions for managing favorites
+function initFavorites() {
+ if (!localStorage.getItem('favorites')) {
+ localStorage.setItem('favorites', JSON.stringify([]));
+ }
+}
-function addToFavorites(promptName) {
- if (!favorites.includes(promptName)) {
- favorites.push(promptName);
+function getFavorites() {
+ return JSON.parse(localStorage.getItem('favorites') || '[]');
+}
+
+function copyToClipboard(text) {
+ fetch("/prompts/" + text)
+ .then(response => response.text())
+ .then(content => {
+ navigator.clipboard.writeText(content).then(() => {
+ const toastEl = document.getElementById('copyToast');
+ const toast = new bootstrap.Toast(toastEl);
+ toast.show();
+ });
+ });
+}
+
+function addToFavorites(name) {
+ const favorites = getFavorites();
+ if (!favorites.includes(name)) {
+ favorites.push(name);
+ localStorage.setItem('favorites', JSON.stringify(favorites));
renderFavorites();
+
+ const toastEl = document.getElementById('favToast');
+ const toast = new bootstrap.Toast(toastEl);
+ toast.show();
}
}
-function removeFromFavorites(promptName) {
- favorites = favorites.filter(fav => fav !== promptName);
+function removeFromFavorites(name) {
+ const favorites = getFavorites();
+ const newFavorites = favorites.filter(fav => fav !== name);
+ localStorage.setItem('favorites', JSON.stringify(newFavorites));
renderFavorites();
}
function renderFavorites() {
+ const favorites = getFavorites();
const favoritesList = document.getElementById('favorites-list');
- favoritesList.innerHTML = '';
- favorites.forEach(fav => {
- const li = document.createElement('li');
- li.innerHTML = `
- ${fav}
- 📋
- -
- `;
- favoritesList.appendChild(li);
- });
+ if (!favoritesList) return;
+
+ favoritesList.innerHTML = favorites.length === 0
+ ? 'No favorites yet
'
+ : favorites.map(fav => `
+
+
${fav}
+
+
+
+
+
+ `).join('');
}
-function copyToClipboard(text) {
- navigator.clipboard.writeText(text).then(function() {
- alert('Copied to clipboard');
- }, function(err) {
- alert('Failed to copy: ', err);
- });
-}
\ No newline at end of file
+// Initialize favorites when the DOM is loaded
+document.addEventListener('DOMContentLoaded', () => {
+ initFavorites();
+ renderFavorites();
+});
\ No newline at end of file
diff --git a/pkg/server/static/templates/repoList.html b/pkg/server/static/templates/repoList.html
deleted file mode 100644
index c32af21..0000000
--- a/pkg/server/static/templates/repoList.html
+++ /dev/null
@@ -1,14 +0,0 @@
-{{define "repoList"}}
- {{range $group := .Groups}}
- {{.}}
-
- {{ range (PromptosByGroup $group) }}
- -
- {{.Name}}
- 📋
- +
-
- {{end}}
-
- {{end}}
-{{end}}
\ No newline at end of file
diff --git a/pkg/server/static/templates/root.html b/pkg/server/static/templates/root.html
deleted file mode 100644
index e25a684..0000000
--- a/pkg/server/static/templates/root.html
+++ /dev/null
@@ -1,40 +0,0 @@
-
-
-
-
-
- Prompto Repositories
-
-
-
-
-
-
-
-
Prompto Repositories
-
Favorites
-
-
-
Searching...
-
- {{template "repoList" .}}
-
-
-
-
\ No newline at end of file
diff --git a/pkg/server/templates/components/prompt_list.templ b/pkg/server/templates/components/prompt_list.templ
new file mode 100644
index 0000000..9b86e5c
--- /dev/null
+++ b/pkg/server/templates/components/prompt_list.templ
@@ -0,0 +1,69 @@
+package components
+
+import "github.com/go-go-golems/prompto/pkg"
+
+script copyToClipboard(text string) {
+ copyToClipboard(text)
+}
+
+script addToFavorites(name string) {
+ addToFavorites(name)
+}
+
+templ PromptList(prompts []pkg.Prompto) {
+ for _, group := range getGroups(prompts) {
+
+
+
+ for _, prompt := range getPromptsByGroup(prompts, group) {
+
+ }
+
+
+ }
+}
+
+// Helper functions to group prompts
+func getGroups(prompts []pkg.Prompto) []string {
+ groups := make(map[string]bool)
+ var result []string
+ for _, p := range prompts {
+ if !groups[p.Group] {
+ groups[p.Group] = true
+ result = append(result, p.Group)
+ }
+ }
+ return result
+}
+
+func getPromptsByGroup(prompts []pkg.Prompto, group string) []pkg.Prompto {
+ var result []pkg.Prompto
+ for _, p := range prompts {
+ if p.Group == group {
+ result = append(result, p)
+ }
+ }
+ return result
+}
\ No newline at end of file
diff --git a/pkg/server/templates/components/prompt_list_templ.go b/pkg/server/templates/components/prompt_list_templ.go
new file mode 100644
index 0000000..48b7b10
--- /dev/null
+++ b/pkg/server/templates/components/prompt_list_templ.go
@@ -0,0 +1,166 @@
+// Code generated by templ - DO NOT EDIT.
+
+// templ: version: v0.2.793
+package components
+
+//lint:file-ignore SA4006 This context is only used if a nested component is present.
+
+import "github.com/a-h/templ"
+import templruntime "github.com/a-h/templ/runtime"
+
+import "github.com/go-go-golems/prompto/pkg"
+
+func copyToClipboard(text string) templ.ComponentScript {
+ return templ.ComponentScript{
+ Name: `__templ_copyToClipboard_9315`,
+ Function: `function __templ_copyToClipboard_9315(text){copyToClipboard(text)
+}`,
+ Call: templ.SafeScript(`__templ_copyToClipboard_9315`, text),
+ CallInline: templ.SafeScriptInline(`__templ_copyToClipboard_9315`, text),
+ }
+}
+
+func addToFavorites(name string) templ.ComponentScript {
+ return templ.ComponentScript{
+ Name: `__templ_addToFavorites_e022`,
+ Function: `function __templ_addToFavorites_e022(name){addToFavorites(name)
+}`,
+ Call: templ.SafeScript(`__templ_addToFavorites_e022`, name),
+ CallInline: templ.SafeScriptInline(`__templ_addToFavorites_e022`, name),
+ }
+}
+
+func PromptList(prompts []pkg.Prompto) templ.Component {
+ return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
+ templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
+ if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
+ return templ_7745c5c3_CtxErr
+ }
+ templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
+ if !templ_7745c5c3_IsBuffer {
+ defer func() {
+ templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
+ if templ_7745c5c3_Err == nil {
+ templ_7745c5c3_Err = templ_7745c5c3_BufErr
+ }
+ }()
+ }
+ ctx = templ.InitializeContext(ctx)
+ templ_7745c5c3_Var1 := templ.GetChildren(ctx)
+ if templ_7745c5c3_Var1 == nil {
+ templ_7745c5c3_Var1 = templ.NopComponent
+ }
+ ctx = templ.ClearChildren(ctx)
+ for _, group := range getGroups(prompts) {
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ for _, prompt := range getPromptsByGroup(prompts, group) {
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ var templ_7745c5c3_Var4 string
+ templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(prompt.Name)
+ if templ_7745c5c3_Err != nil {
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/server/templates/components/prompt_list.templ`, Line: 24, Col: 21}
+ }
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4))
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ templ_7745c5c3_Err = templ.RenderScriptItems(ctx, templ_7745c5c3_Buffer, copyToClipboard(prompt.Name))
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(" ")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ templ_7745c5c3_Err = templ.RenderScriptItems(ctx, templ_7745c5c3_Buffer, addToFavorites(prompt.Name))
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ }
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ }
+ return templ_7745c5c3_Err
+ })
+}
+
+// Helper functions to group prompts
+func getGroups(prompts []pkg.Prompto) []string {
+ groups := make(map[string]bool)
+ var result []string
+ for _, p := range prompts {
+ if !groups[p.Group] {
+ groups[p.Group] = true
+ result = append(result, p.Group)
+ }
+ }
+ return result
+}
+
+func getPromptsByGroup(prompts []pkg.Prompto, group string) []pkg.Prompto {
+ var result []pkg.Prompto
+ for _, p := range prompts {
+ if p.Group == group {
+ result = append(result, p)
+ }
+ }
+ return result
+}
+
+var _ = templruntime.GeneratedTemplate
diff --git a/pkg/server/templates/components/repository_list.templ b/pkg/server/templates/components/repository_list.templ
new file mode 100644
index 0000000..c564244
--- /dev/null
+++ b/pkg/server/templates/components/repository_list.templ
@@ -0,0 +1,32 @@
+package components
+
+import "github.com/go-go-golems/prompto/pkg"
+
+templ RepositoryList(repositories []string, repos map[string]*pkg.Repository) {
+
+ for _, repoPath := range repositories {
+
+
+
+
{repoPath}
+ if repo, ok := repos[repoPath]; ok {
+
+ for _, group := range repo.GetGroups() {
+
+ }
+
+ }
+
+
+
+ }
+
+}
\ No newline at end of file
diff --git a/pkg/server/templates/components/repository_list_templ.go b/pkg/server/templates/components/repository_list_templ.go
new file mode 100644
index 0000000..f90d586
--- /dev/null
+++ b/pkg/server/templates/components/repository_list_templ.go
@@ -0,0 +1,111 @@
+// Code generated by templ - DO NOT EDIT.
+
+// templ: version: v0.2.793
+package components
+
+//lint:file-ignore SA4006 This context is only used if a nested component is present.
+
+import "github.com/a-h/templ"
+import templruntime "github.com/a-h/templ/runtime"
+
+import "github.com/go-go-golems/prompto/pkg"
+
+func RepositoryList(repositories []string, repos map[string]*pkg.Repository) templ.Component {
+ return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
+ templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
+ if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
+ return templ_7745c5c3_CtxErr
+ }
+ templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
+ if !templ_7745c5c3_IsBuffer {
+ defer func() {
+ templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
+ if templ_7745c5c3_Err == nil {
+ templ_7745c5c3_Err = templ_7745c5c3_BufErr
+ }
+ }()
+ }
+ ctx = templ.InitializeContext(ctx)
+ templ_7745c5c3_Var1 := templ.GetChildren(ctx)
+ if templ_7745c5c3_Var1 == nil {
+ templ_7745c5c3_Var1 = templ.NopComponent
+ }
+ ctx = templ.ClearChildren(ctx)
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ for _, repoPath := range repositories {
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ var templ_7745c5c3_Var2 string
+ templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(repoPath)
+ if templ_7745c5c3_Err != nil {
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/server/templates/components/repository_list.templ`, Line: 11, Col: 38}
+ }
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2))
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ if repo, ok := repos[repoPath]; ok {
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ for _, group := range repo.GetGroups() {
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ }
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ }
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ }
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ return templ_7745c5c3_Err
+ })
+}
+
+var _ = templruntime.GeneratedTemplate
diff --git a/pkg/server/templates/layout.templ b/pkg/server/templates/layout.templ
new file mode 100644
index 0000000..8094298
--- /dev/null
+++ b/pkg/server/templates/layout.templ
@@ -0,0 +1,25 @@
+package templates
+
+templ Layout() {
+
+
+
+
+
+ Prompto
+
+
+
+
+
+
+
+
+ { children... }
+
+
+
+
+}
\ No newline at end of file
diff --git a/pkg/server/templates/layout_templ.go b/pkg/server/templates/layout_templ.go
new file mode 100644
index 0000000..6cae231
--- /dev/null
+++ b/pkg/server/templates/layout_templ.go
@@ -0,0 +1,48 @@
+// Code generated by templ - DO NOT EDIT.
+
+// templ: version: v0.2.793
+package templates
+
+//lint:file-ignore SA4006 This context is only used if a nested component is present.
+
+import "github.com/a-h/templ"
+import templruntime "github.com/a-h/templ/runtime"
+
+func Layout() templ.Component {
+ return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
+ templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
+ if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
+ return templ_7745c5c3_CtxErr
+ }
+ templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
+ if !templ_7745c5c3_IsBuffer {
+ defer func() {
+ templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
+ if templ_7745c5c3_Err == nil {
+ templ_7745c5c3_Err = templ_7745c5c3_BufErr
+ }
+ }()
+ }
+ ctx = templ.InitializeContext(ctx)
+ templ_7745c5c3_Var1 := templ.GetChildren(ctx)
+ if templ_7745c5c3_Var1 == nil {
+ templ_7745c5c3_Var1 = templ.NopComponent
+ }
+ ctx = templ.ClearChildren(ctx)
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("Prompto")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ templ_7745c5c3_Err = templ_7745c5c3_Var1.Render(ctx, templ_7745c5c3_Buffer)
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ return templ_7745c5c3_Err
+ })
+}
+
+var _ = templruntime.GeneratedTemplate
diff --git a/pkg/server/templates/pages/index.templ b/pkg/server/templates/pages/index.templ
new file mode 100644
index 0000000..f4c72b4
--- /dev/null
+++ b/pkg/server/templates/pages/index.templ
@@ -0,0 +1,82 @@
+package pages
+
+import (
+ "github.com/go-go-golems/prompto/pkg"
+ "github.com/go-go-golems/prompto/pkg/server/templates"
+ "github.com/go-go-golems/prompto/pkg/server/templates/components"
+)
+
+script copyToClipboard(text string) {
+ copyToClipboard(text)
+}
+
+script addToFavorites(name string) {
+ addToFavorites(name)
+}
+
+templ Index(repositories []string, repos map[string]*pkg.Repository) {
+ @templates.Layout() {
+
+
+
+
+
+ Prompt copied to clipboard!
+
+
+
+
+
+
+
+ Added to favorites!
+
+
+
+
+
+
+
+
+
+ for _, repo := range repositories {
+ @components.PromptList(repos[repo].GetPromptos())
+ }
+
+
+
+
+
+
Select a prompt to view its details
+
+
+
+
+
+ }
+}
diff --git a/pkg/server/templates/pages/index_templ.go b/pkg/server/templates/pages/index_templ.go
new file mode 100644
index 0000000..f5a7abd
--- /dev/null
+++ b/pkg/server/templates/pages/index_templ.go
@@ -0,0 +1,94 @@
+// Code generated by templ - DO NOT EDIT.
+
+// templ: version: v0.2.793
+package pages
+
+//lint:file-ignore SA4006 This context is only used if a nested component is present.
+
+import "github.com/a-h/templ"
+import templruntime "github.com/a-h/templ/runtime"
+
+import (
+ "github.com/go-go-golems/prompto/pkg"
+ "github.com/go-go-golems/prompto/pkg/server/templates"
+ "github.com/go-go-golems/prompto/pkg/server/templates/components"
+)
+
+func copyToClipboard(text string) templ.ComponentScript {
+ return templ.ComponentScript{
+ Name: `__templ_copyToClipboard_c982`,
+ Function: `function __templ_copyToClipboard_c982(text){copyToClipboard(text)
+}`,
+ Call: templ.SafeScript(`__templ_copyToClipboard_c982`, text),
+ CallInline: templ.SafeScriptInline(`__templ_copyToClipboard_c982`, text),
+ }
+}
+
+func addToFavorites(name string) templ.ComponentScript {
+ return templ.ComponentScript{
+ Name: `__templ_addToFavorites_543a`,
+ Function: `function __templ_addToFavorites_543a(name){addToFavorites(name)
+}`,
+ Call: templ.SafeScript(`__templ_addToFavorites_543a`, name),
+ CallInline: templ.SafeScriptInline(`__templ_addToFavorites_543a`, name),
+ }
+}
+
+func Index(repositories []string, repos map[string]*pkg.Repository) templ.Component {
+ return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
+ templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
+ if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
+ return templ_7745c5c3_CtxErr
+ }
+ templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
+ if !templ_7745c5c3_IsBuffer {
+ defer func() {
+ templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
+ if templ_7745c5c3_Err == nil {
+ templ_7745c5c3_Err = templ_7745c5c3_BufErr
+ }
+ }()
+ }
+ ctx = templ.InitializeContext(ctx)
+ templ_7745c5c3_Var1 := templ.GetChildren(ctx)
+ if templ_7745c5c3_Var1 == nil {
+ templ_7745c5c3_Var1 = templ.NopComponent
+ }
+ ctx = templ.ClearChildren(ctx)
+ templ_7745c5c3_Var2 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
+ templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
+ templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
+ if !templ_7745c5c3_IsBuffer {
+ defer func() {
+ templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
+ if templ_7745c5c3_Err == nil {
+ templ_7745c5c3_Err = templ_7745c5c3_BufErr
+ }
+ }()
+ }
+ ctx = templ.InitializeContext(ctx)
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(" Prompt copied to clipboard!
")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ for _, repo := range repositories {
+ templ_7745c5c3_Err = components.PromptList(repos[repo].GetPromptos()).Render(ctx, templ_7745c5c3_Buffer)
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ }
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
Select a prompt to view its details
")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ return templ_7745c5c3_Err
+ })
+ templ_7745c5c3_Err = templates.Layout().Render(templ.WithChildren(ctx, templ_7745c5c3_Var2), templ_7745c5c3_Buffer)
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ return templ_7745c5c3_Err
+ })
+}
+
+var _ = templruntime.GeneratedTemplate