Compare commits
10 Commits
b3bcb42974
...
69b3c7c57b
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
69b3c7c57b | ||
|
|
0abe4f621b | ||
|
|
3c5b52c294 | ||
|
|
f5f443abb8 | ||
|
|
c61a2cb2b7 | ||
|
|
be91357b6f | ||
| fbc40bbf9b | |||
| f62eff64a0 | |||
|
|
679059a360 | ||
|
|
fb895435a1 |
5
.dockerignore
Normal file
5
.dockerignore
Normal file
@@ -0,0 +1,5 @@
|
||||
node_modules
|
||||
dist
|
||||
.env
|
||||
.DS_Store
|
||||
.vscode
|
||||
56
.github/workflows/docker-build.yml
vendored
Normal file
56
.github/workflows/docker-build.yml
vendored
Normal file
@@ -0,0 +1,56 @@
|
||||
#
|
||||
name: Create and publish a Docker image
|
||||
|
||||
# Configures this workflow to run every time a change is pushed to the branch called `release`.
|
||||
on:
|
||||
# Allows manual triggering of the workflow by default.
|
||||
# Remove line below and uncomment the `push` section
|
||||
# to enable automatic builds on pushes to the `main` branch.
|
||||
workflow_dispatch
|
||||
# push:
|
||||
# branches: ['main']
|
||||
|
||||
# Defines two custom environment variables for the workflow. These are used for the Container registry domain, and a name for the Docker image that this workflow builds.
|
||||
env:
|
||||
REGISTRY: git.keithsolomon.net
|
||||
IMAGE_NAME: ${{ github.repository }}
|
||||
|
||||
# There is a single job in this workflow. It's configured to run on the latest available version of Ubuntu.
|
||||
jobs:
|
||||
build-and-push-image:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
# Sets the permissions granted to the `GITHUB_TOKEN` for the actions in this job.
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
attestations: write
|
||||
id-token: write
|
||||
#
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
# Uses the `docker/login-action` action to log in to the Container registry registry using the account and password that will publish the packages. Once published, the packages are scoped to the account defined here.
|
||||
- name: Log in to the Container registry
|
||||
uses: docker/login-action@65b78e6e13532edd9afa3aa52ac7964289d1a9c1
|
||||
with:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.DOCKER_TOKEN }}
|
||||
# This step uses [docker/metadata-action](https://github.com/docker/metadata-action#about) to extract tags and labels that will be applied to the specified image. The `id` "meta" allows the output of this step to be referenced in a subsequent step. The `images` value provides the base name for the tags and labels.
|
||||
- name: Extract metadata (tags, labels) for Docker
|
||||
id: meta
|
||||
uses: docker/metadata-action@9ec57ed1fcdbf14dcef7dfbe97b2010124a938b7
|
||||
with:
|
||||
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
|
||||
# This step uses the `docker/build-push-action` action to build the image, based on your repository's `Dockerfile`. If the build succeeds, it pushes the image to GitHub Packages.
|
||||
# It uses the `context` parameter to define the build's context as the set of files located in the specified path. For more information, see "[Usage](https://github.com/docker/build-push-action#usage)" in the README of the `docker/build-push-action` repository.
|
||||
# It uses the `tags` and `labels` parameters to tag and label the image with the output from the "meta" step.
|
||||
- name: Build and push Docker image
|
||||
id: push
|
||||
uses: docker/build-push-action@f2a1d5e99d037542a71f64918e516c093c6f3fc4
|
||||
with:
|
||||
context: .
|
||||
push: true
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
15
Dockerfile
Normal file
15
Dockerfile
Normal file
@@ -0,0 +1,15 @@
|
||||
FROM node:20-alpine
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY app/package*.json ./
|
||||
RUN npm install
|
||||
|
||||
COPY app .
|
||||
|
||||
# Add this debug line:
|
||||
RUN echo "Build starting..." && npm run build && echo "Build finished." && ls -la dist
|
||||
|
||||
RUN npm install -g serve
|
||||
|
||||
CMD ["serve", "dist", "-l", "4321"]
|
||||
54
README.md
54
README.md
@@ -1,8 +1,8 @@
|
||||
# Prompt Catalog
|
||||
# PromptBase
|
||||
|
||||
## Overview
|
||||
|
||||
The Prompt Catalog is a centralized, searchable repository for storing and managing prompts used in AI models, chatbots, or any generative application. It provides a structured way to organize, tag, search, and reuse prompts—streamlining prompt engineering for developers, teams, and enthusiasts.
|
||||
PromptBase is a centralized, searchable repository for storing and managing prompts used in AI models, chatbots, or any generative application. It provides a structured way to organize, tag, search, and reuse prompts—streamlining prompt engineering for developers, teams, and enthusiasts.
|
||||
|
||||
## Features
|
||||
|
||||
@@ -22,13 +22,44 @@ The Prompt Catalog is a centralized, searchable repository for storing and manag
|
||||
- User auth and profiles
|
||||
- Prompt favorites and contributions
|
||||
- Ratings, version history, and sharing
|
||||
- External API access
|
||||
|
||||
## Usage
|
||||
|
||||
You'll need a .env file with your Supabase credentials. See `supabase.env.example`.
|
||||
|
||||
### Docker
|
||||
|
||||
To run the app using Docker, you can use the provided `docker-compose.yml` file.
|
||||
|
||||
```yaml
|
||||
services:
|
||||
frontend:
|
||||
container_name: PromptBase
|
||||
image: git.keithsolomon.net/keith/promptbase:main
|
||||
restart: unless-stopped
|
||||
|
||||
environment:
|
||||
- NODE_ENV=production
|
||||
|
||||
ports:
|
||||
- "4321:4321"
|
||||
|
||||
volumes:
|
||||
- ./.env:/app/.env:ro # Bind-mount .env as read-only
|
||||
```
|
||||
|
||||
### To run locally (for development)
|
||||
|
||||
```bash
|
||||
npm install
|
||||
npm run dev
|
||||
```
|
||||
|
||||
## Tech Stack
|
||||
|
||||
- **Frontend**: Astro + HTMX + Alpine.js (AHA Stack)
|
||||
- **Frontend**: Astro js, Tailwind CSS
|
||||
- **Backend**: Supabase (self-hosted)
|
||||
- **Deployment**: Vercel or Netlify (frontend), Supabase (backend)
|
||||
- **Deployment**: Docker (frontend), Supabase (backend)
|
||||
|
||||
## Database Schema
|
||||
|
||||
@@ -48,7 +79,7 @@ The Prompt Catalog is a centralized, searchable repository for storing and manag
|
||||
|
||||
## Development Roadmap
|
||||
|
||||
Development is organized into phases. For details, see `Development Checklist.md`.
|
||||
Development is organized into phases. For details, see `checklist.md`.
|
||||
|
||||
### MVP Phases
|
||||
|
||||
@@ -66,17 +97,6 @@ Development is organized into phases. For details, see `Development Checklist.md
|
||||
- Ratings, version control, external API
|
||||
- Shareable links and embeds
|
||||
|
||||
## Usage
|
||||
|
||||
To run locally:
|
||||
|
||||
```bash
|
||||
npm install
|
||||
npm run dev
|
||||
```
|
||||
|
||||
You'll need a .env file with your Supabase credentials. See `supabase.env.example`.
|
||||
|
||||
## Contributing
|
||||
|
||||
Pull requests are welcome! Please keep contributions focused on core functionality and usability improvements.
|
||||
|
||||
0
package-lock.json → app/package-lock.json
generated
0
package-lock.json → app/package-lock.json
generated
@@ -3,7 +3,7 @@
|
||||
"type": "module",
|
||||
"version": "0.0.1",
|
||||
"scripts": {
|
||||
"dev": "astro dev",
|
||||
"dev": "astro dev --host 0.0.0.0",
|
||||
"build": "astro build",
|
||||
"preview": "astro preview",
|
||||
"astro": "astro"
|
||||
|
Before Width: | Height: | Size: 749 B After Width: | Height: | Size: 749 B |
143
app/public/scripts/fetch-prompts.js
Normal file
143
app/public/scripts/fetch-prompts.js
Normal file
@@ -0,0 +1,143 @@
|
||||
import { createClient } from 'https://cdn.jsdelivr.net/npm/@supabase/supabase-js/+esm';
|
||||
|
||||
const env = document.getElementById('supabase-env');
|
||||
const supabaseUrl = env.dataset.url;
|
||||
const supabaseKey = env.dataset.key;
|
||||
|
||||
const supabase = createClient(supabaseUrl, supabaseKey);
|
||||
|
||||
const promptContainer = document.getElementById('prompt-container');
|
||||
const errorBox = document.getElementById('prompt-error');
|
||||
|
||||
const formatDate = (dateStr) => {
|
||||
if (!dateStr) return "–";
|
||||
const date = new Date(dateStr);
|
||||
return isNaN(date.getTime())
|
||||
? "Invalid date"
|
||||
: date.toLocaleDateString('en-US', {
|
||||
month: 'short',
|
||||
day: 'numeric',
|
||||
year: 'numeric',
|
||||
});
|
||||
};
|
||||
|
||||
function renderPromptCard(prompt) {
|
||||
const sortedTags = (prompt.tags ?? []).slice().sort((a, b) =>
|
||||
a.toLowerCase().localeCompare(b.toLowerCase())
|
||||
);
|
||||
const tagSpans = sortedTags.map(tag =>
|
||||
`<span class="text-sm bg-gray-200 text-gray-800 px-2 py-1 pt-0 rounded">${tag}</span>`
|
||||
).join('');
|
||||
const escapedDescription = (prompt.description ?? '').replace(/\n/g, '<br />');
|
||||
const notes = prompt.notes ?? '';
|
||||
|
||||
return `
|
||||
<div class="prompt-card border border-gray-400 rounded p-4 bg-gray-700 text-gray-200 shadow-sm flex flex-col gap-2 min-h-[12rem] max-h-fit"
|
||||
data-type="${prompt.type}" data-tags="${(prompt.tags ?? []).join(',')}">
|
||||
<div class="flex items-center justify-between mb-2">
|
||||
<h3 class="text-xl font-semibold">${prompt.title}</h3>
|
||||
<span class="text-sm font-medium px-2 py-1 rounded ${
|
||||
prompt.type === 'System'
|
||||
? 'bg-blue-100 text-blue-700'
|
||||
: 'bg-green-100 text-green-700'
|
||||
}">${prompt.type}</span>
|
||||
</div>
|
||||
<p class="text-md mb-4">${notes}</p>
|
||||
<div class="flex flex-wrap gap-2 mt-2 mb-4">${tagSpans}</div>
|
||||
<details name="prompt-details">
|
||||
<summary class="cursor-pointer font-semibold mt-2 text-lg">View Details</summary>
|
||||
<div class="text-md border-t mt-2 pt-2">
|
||||
<div class="flex justify-between mx-2 items-center border-b border-b-gray-400 pb-2 mb-2">
|
||||
<h3 class="text-xl font-semibold">Prompt</h3>
|
||||
|
||||
<a class="bg-green-600 text-white px-2 py-0 rounded text-sm hover:bg-green-700 transition-colors duration-300" href="/edit?slug=${prompt.slug}">Edit</a>
|
||||
</div>
|
||||
|
||||
<p class="my-2 px-2 text-balance">${escapedDescription}</p>
|
||||
|
||||
<hr class="my-2" />
|
||||
<p class="text-sm"><strong>Created:</strong> ${formatDate(prompt.created_at)} • <strong>Updated:</strong> ${formatDate(prompt.updated_at)}</p>
|
||||
</div>
|
||||
</details>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
function updateFiltersFromTags(prompts) {
|
||||
const sortedTags = Array.from(new Set(
|
||||
prompts.flatMap(p => p.tags ?? [])
|
||||
)).sort((a, b) => a.toLowerCase().localeCompare(b.toLowerCase()));
|
||||
const tagSet = new Set(sortedTags);
|
||||
const tagContainer = document.querySelector('#filters fieldset > div');
|
||||
if (!tagContainer) return;
|
||||
|
||||
tagContainer.innerHTML = '';
|
||||
|
||||
tagSet.forEach(tag => {
|
||||
const label = document.createElement('label');
|
||||
label.className = "relative inline-flex items-center cursor-pointer";
|
||||
label.innerHTML = `
|
||||
<input type="checkbox" name="tag" value="${tag}" class="sr-only peer">
|
||||
<span class="px-3 py-1 pt-0 rounded-full border border-gray-300 peer-checked:bg-blue-600 peer-checked:text-white hover:bg-blue-600 hover:text-white transition-colors duration-300 text-sm">${tag}</span>
|
||||
`;
|
||||
tagContainer.appendChild(label);
|
||||
});
|
||||
}
|
||||
|
||||
function filterCards() {
|
||||
const form = document.getElementById('filter-form');
|
||||
const cards = document.querySelectorAll('.prompt-card');
|
||||
const params = new URLSearchParams(new FormData(form));
|
||||
const type = params.get('type');
|
||||
const query = params.get('q')?.toLowerCase() || '';
|
||||
const tagParams = params.getAll('tag');
|
||||
|
||||
let visibleCount = 0;
|
||||
|
||||
cards.forEach(card => {
|
||||
const cardType = card.dataset.type;
|
||||
const cardTags = card.dataset.tags.split(',');
|
||||
const cardText = card.innerText.toLowerCase();
|
||||
|
||||
const matchesType = !type || type === cardType;
|
||||
const matchesTags = tagParams.length === 0 || tagParams.some(t => cardTags.includes(t));
|
||||
const matchesSearch = !query || cardText.includes(query);
|
||||
|
||||
const show = matchesType && matchesTags && matchesSearch;
|
||||
card.style.display = show ? 'block' : 'none';
|
||||
if (show) visibleCount++;
|
||||
});
|
||||
|
||||
const countLabel = document.getElementById('prompt-count');
|
||||
if (countLabel) {
|
||||
countLabel.textContent = visibleCount === 1
|
||||
? '1 prompt shown'
|
||||
: `${visibleCount} prompts shown`;
|
||||
}
|
||||
}
|
||||
|
||||
async function loadPrompts() {
|
||||
const { data: prompts, error } = await supabase.from('prompts').select('*').order('title');
|
||||
|
||||
if (error) {
|
||||
promptContainer.innerHTML = '';
|
||||
errorBox.textContent = error.message;
|
||||
errorBox.classList.remove('hidden');
|
||||
return;
|
||||
}
|
||||
|
||||
promptContainer.innerHTML = prompts.map(renderPromptCard).join('');
|
||||
updateFiltersFromTags(prompts);
|
||||
filterCards(); // initial run
|
||||
|
||||
const form = document.getElementById('filter-form');
|
||||
form?.addEventListener('input', filterCards);
|
||||
|
||||
const clearBtn = document.getElementById('clear-filters');
|
||||
clearBtn?.addEventListener('click', () => {
|
||||
form.reset();
|
||||
filterCards();
|
||||
});
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', loadPrompts);
|
||||
44
app/src/pages/index.astro
Normal file
44
app/src/pages/index.astro
Normal file
@@ -0,0 +1,44 @@
|
||||
---
|
||||
// index.astro
|
||||
import MainLayout from '../layouts/MainLayout.astro';
|
||||
import SearchBar from '../components/SearchBar.astro';
|
||||
|
||||
const supabaseUrl = import.meta.env.PUBLIC_SUPABASE_URL;
|
||||
const supabaseKey = import.meta.env.PUBLIC_SUPABASE_ANON_KEY;
|
||||
---
|
||||
|
||||
<MainLayout page="Home">
|
||||
<div
|
||||
id="supabase-env"
|
||||
data-url={supabaseUrl}
|
||||
data-key={supabaseKey}
|
||||
hidden
|
||||
></div>
|
||||
|
||||
<header class="border-b p-4">
|
||||
<h1 class="text-2xl font-bold"><a href="/">Prompt Catalog</a></h1>
|
||||
<p class="text-sm mt-1">Save and explore AI prompts</p>
|
||||
</header>
|
||||
|
||||
<main class="flex flex-col lg:flex-row">
|
||||
<SearchBar allTags={[]} /> <!-- leave empty, we'll hydrate it in JS -->
|
||||
|
||||
<div class="border-b lg:border-b-0 lg:border-l flex-1 flex flex-col overflow-hidden">
|
||||
<div class="flex-1 overflow-y-auto px-4 lg:px-6 pt-2 pb-4">
|
||||
<div id="prompt-error" class="text-red-500 hidden"></div>
|
||||
<div class="border-b mb-4 flex justify-between items-center">
|
||||
<h2 id="prompt-count" class="text-xl font-semibold text-gray-300 mb-2"></h2>
|
||||
<a href="#filters" class="block lg:hidden bg-blue-600 text-white px-2 py-0 pb-1 mb-2 rounded cursor-pointer hover:bg-blue-700 transition-colors duration-300">Filters ↓</a>
|
||||
</div>
|
||||
|
||||
<div id="prompt-container" class="grid grid-cols-1 gap-4 md:grid-cols-2 xl:grid-cols-3 items-start">Loading prompts…</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<footer class="border-t p-4 text-center text-sm">
|
||||
© {new Date().getFullYear()} Prompt Catalog
|
||||
</footer>
|
||||
|
||||
<script type="module" src="/scripts/fetch-prompts.js"></script>
|
||||
</MainLayout>
|
||||
87
checklist.md
87
checklist.md
@@ -1,6 +1,10 @@
|
||||
# ✅ Prompt Catalog Development Checklist
|
||||
# Prompt Catalog Development Checklist
|
||||
|
||||
## 🔧 Phase 1: Planning & Setup
|
||||
## MVP Phases
|
||||
|
||||
This checklist outlines the phases for developing the Prompt Catalog MVP. Each phase includes specific tasks to complete.
|
||||
|
||||
### Phase 1: Planning & Setup
|
||||
|
||||
- [x] Review and finalize requirements from `Prompt Catalog Features.md`
|
||||
- [x] Choose JavaScript framework (React, Vue, etc.)
|
||||
@@ -13,7 +17,7 @@
|
||||
|
||||
---
|
||||
|
||||
## 🧱 Phase 2: Database & API
|
||||
### Phase 2: Database & API
|
||||
|
||||
- [x] Define and implement Supabase schema
|
||||
- [x] Set up Supabase RLS rules (if applicable)
|
||||
@@ -21,30 +25,51 @@
|
||||
|
||||
---
|
||||
|
||||
## 🖼 Phase 3: Front-End Interface
|
||||
### Phase 3: Front-End Interface
|
||||
|
||||
- [ ] Build static UI from `Front End Interface.png`
|
||||
- [ ] Sidebar navigation (System / Task)
|
||||
- [ ] Search bar with filters
|
||||
- [ ] Prompt list display
|
||||
- [ ] Prompt detail view
|
||||
- [ ] Tags display and interaction
|
||||
- [ ] Integrate UI with Supabase for live data
|
||||
- [ ] Implement CRUD operations for prompts
|
||||
- [x] Build static UI from `Front End Interface.png`
|
||||
- [x] Sidebar navigation (Search / filter prompts)
|
||||
- [x] Prompt list display
|
||||
- [x] Prompt detail view
|
||||
- [x] Tags display and interaction
|
||||
- [x] Integrate UI with Supabase for live data
|
||||
- [x] Implement CRUD operations for prompts
|
||||
|
||||
---
|
||||
|
||||
## 🔍 Phase 4: Search & Tagging
|
||||
### Phase 4: Search & Tagging
|
||||
|
||||
- [ ] Implement keyword and full-text search
|
||||
- [ ] Add filter by:
|
||||
- [ ] Type (System, Task)
|
||||
- [ ] Tags (multi-select)
|
||||
- [ ] Create tag suggestion/autocomplete
|
||||
- [x] Implement keyword and full-text search
|
||||
- [x] Add filter by:
|
||||
- [x] Type (System, Task)
|
||||
- [x] Tags (multi-select)
|
||||
- [x] Create tag suggestion/autocomplete
|
||||
|
||||
---
|
||||
|
||||
## 🤖 Phase 5: AI Integration
|
||||
### Phase 5: Import/Export
|
||||
|
||||
- [x] Implement prompt export to JSON
|
||||
- [x] Implement prompt import from JSON with validation
|
||||
|
||||
---
|
||||
|
||||
### Phase 6: Deployment & QA
|
||||
|
||||
- [x] Set up frontend docker image and deployment compose file
|
||||
- [x] Set up Supabase production environment
|
||||
- [x] QA Testing:
|
||||
- [x] UI functionality
|
||||
- [x] Prompt CRUD operations
|
||||
- [x] Search and filtering
|
||||
- [x] Import/export behavior
|
||||
- [ ] Write usage documentation
|
||||
|
||||
---
|
||||
|
||||
## Post-MVP Phases
|
||||
|
||||
### Phase 7: AI Integration
|
||||
|
||||
- [ ] Set up API key management (e.g., OpenAI, Together, Ollama)
|
||||
- [ ] Add prompt suggestion UI for user input
|
||||
@@ -52,14 +77,7 @@
|
||||
|
||||
---
|
||||
|
||||
## 📦 Phase 6: Import/Export
|
||||
|
||||
- [ ] Implement prompt export to JSON
|
||||
- [ ] Implement prompt import from JSON with validation
|
||||
|
||||
---
|
||||
|
||||
## 🔐 Phase 7: Authentication & User Features (Future)
|
||||
### Phase 8: Authentication & User Features (Future)
|
||||
|
||||
- [ ] Add Supabase Auth for login/register
|
||||
- [ ] Create user profile UI
|
||||
@@ -68,20 +86,7 @@
|
||||
|
||||
---
|
||||
|
||||
## 🚀 Phase 8: Deployment & QA
|
||||
|
||||
- [ ] Deploy frontend to hosting platform
|
||||
- [ ] Set up Supabase production environment
|
||||
- [ ] QA Testing:
|
||||
- [ ] UI functionality
|
||||
- [ ] Prompt CRUD operations
|
||||
- [ ] Search and filtering
|
||||
- [ ] Import/export behavior
|
||||
- [ ] Write usage documentation
|
||||
|
||||
---
|
||||
|
||||
## 🌱 Phase 9: Post-MVP Enhancements
|
||||
### Phase 9: Future Enhancements
|
||||
|
||||
- [ ] Add prompt rating system
|
||||
- [ ] Implement version history tracking
|
||||
|
||||
14
docker-compose.yml
Normal file
14
docker-compose.yml
Normal file
@@ -0,0 +1,14 @@
|
||||
services:
|
||||
frontend:
|
||||
container_name: PromptBase
|
||||
image: git.keithsolomon.net/keith/promptbase:main
|
||||
restart: unless-stopped
|
||||
|
||||
environment:
|
||||
- NODE_ENV=production
|
||||
|
||||
ports:
|
||||
- "4321:4321"
|
||||
|
||||
volumes:
|
||||
- ./.env:/app/.env:ro # Bind-mount .env as read-only
|
||||
@@ -1,90 +0,0 @@
|
||||
# ✅ Prompt Catalog Development Checklist
|
||||
|
||||
## 🔧 Phase 1: Planning & Setup
|
||||
|
||||
- [x] Review and finalize requirements from `Prompt Catalog Features.md`
|
||||
- [x] Choose JavaScript framework (React, Vue, etc.)
|
||||
- [x] Set up Supabase project
|
||||
- [x] Create `prompts` table
|
||||
- [x] Create `users` table (future)
|
||||
- [x] Create `user_prompts` table (future)
|
||||
- [x] Define JSON structure for import/export
|
||||
- [x] Choose hosting platform (Vercel, Netlify, etc.)
|
||||
|
||||
---
|
||||
|
||||
## 🧱 Phase 2: Database & API
|
||||
|
||||
- [x] Define and implement Supabase schema
|
||||
- [x] Set up Supabase RLS rules (if applicable)
|
||||
- [x] Connect frontend to Supabase using client API
|
||||
|
||||
---
|
||||
|
||||
## 🖼 Phase 3: Front-End Interface
|
||||
|
||||
- [ ] Build static UI from `Front End Interface.png`
|
||||
- [ ] Sidebar navigation (System / Task)
|
||||
- [ ] Search bar with filters
|
||||
- [ ] Prompt list display
|
||||
- [ ] Prompt detail view
|
||||
- [ ] Tags display and interaction
|
||||
- [ ] Integrate UI with Supabase for live data
|
||||
- [ ] Implement CRUD operations for prompts
|
||||
|
||||
---
|
||||
|
||||
## 🔍 Phase 4: Search & Tagging
|
||||
|
||||
- [ ] Implement keyword and full-text search
|
||||
- [ ] Add filter by:
|
||||
- [ ] Type (System, Task)
|
||||
- [ ] Tags (multi-select)
|
||||
- [ ] Create tag suggestion/autocomplete
|
||||
|
||||
---
|
||||
|
||||
## 🤖 Phase 5: AI Integration
|
||||
|
||||
- [ ] Set up API key management (e.g., OpenAI, Together, Ollama)
|
||||
- [ ] Add prompt suggestion UI for user input
|
||||
- [ ] Integrate with AI API to return prompt suggestions
|
||||
|
||||
---
|
||||
|
||||
## 📦 Phase 6: Import/Export
|
||||
|
||||
- [ ] Implement prompt export to JSON
|
||||
- [ ] Implement prompt import from JSON with validation
|
||||
|
||||
---
|
||||
|
||||
## 🔐 Phase 7: Authentication & User Features (Future)
|
||||
|
||||
- [ ] Add Supabase Auth for login/register
|
||||
- [ ] Create user profile UI
|
||||
- [ ] Track user-owned prompts
|
||||
- [ ] Enable user favorites system
|
||||
|
||||
---
|
||||
|
||||
## 🚀 Phase 8: Deployment & QA
|
||||
|
||||
- [ ] Deploy frontend to hosting platform
|
||||
- [ ] Set up Supabase production environment
|
||||
- [ ] QA Testing:
|
||||
- [ ] UI functionality
|
||||
- [ ] Prompt CRUD operations
|
||||
- [ ] Search and filtering
|
||||
- [ ] Import/export behavior
|
||||
- [ ] Write usage documentation
|
||||
|
||||
---
|
||||
|
||||
## 🌱 Phase 9: Post-MVP Enhancements
|
||||
|
||||
- [ ] Add prompt rating system
|
||||
- [ ] Implement version history tracking
|
||||
- [ ] Add social sharing (links, embed)
|
||||
- [ ] Provide external API for prompt access
|
||||
- [ ] Improve AI integration with context-aware suggestions
|
||||
@@ -1,51 +0,0 @@
|
||||
# Prompt Catalog
|
||||
|
||||
- June 21, 2025: Initial planning
|
||||
|
||||
## Overview & Purpose
|
||||
|
||||
The Prompt Catalog is a centralized repository for prompts used in various applications, such as AI models or chatbots. It aims to provide a structured way to store, search, and categorize prompts for easy access and management. It will serve as a comprehensive and user-friendly catalog of prompts that can be easily searched, categorized, and tagged, enhancing the usability and discoverability of prompts for developers and users alike.
|
||||
|
||||
## Features
|
||||
|
||||
- **Storage**: Store prompts, metadata, and (future) users in Supabase (self-hosted or cloud).
|
||||
- **Prompt Metadata**: Include metadata for each prompt:
|
||||
- Type (System, Task)
|
||||
- Title
|
||||
- Description
|
||||
- Tags
|
||||
- Creation date
|
||||
- Last modified date
|
||||
- **Categorization**: Organize prompts into types (System, Tasks) for easier navigation.
|
||||
- **Tagging System**: Use tags to describe prompts for better filtering.
|
||||
- **Search Functionality**: Quickly find prompts by keywords, tags, or text search.
|
||||
- **AI Integration**: Connect to AI models via API (OpenAI, Together, Ollama, etc.) to suggest prompts based on user input or context.
|
||||
- **Export/Import**: Allow users to export prompts in JSON format and import them back.
|
||||
|
||||
## Technical Details
|
||||
|
||||
- **Web Interface**: A user-friendly web interface to view, search, and manage prompts.
|
||||
- See `notes/Front End Interface.png` for mockup.
|
||||
- **Database**: Use Supabase for storing prompts and metadata.
|
||||
- Tables:
|
||||
- `prompts`: Stores prompt details (id, type, title, description, tags, created_at, updated_at).
|
||||
- `users`: (Future) Stores user information (id, username, email, created_at).
|
||||
- `user_prompts`: (Future) Stores user contributions and favorites (user_id, prompt_id, created_at).
|
||||
- **Authentication**: Implement user authentication for future features (e.g., user profiles, contributions).
|
||||
- **Deployment**: Host the application on a platform like Vercel or Netlify for the front end, and Supabase for the backend.
|
||||
- **Front End**: Use AHA Stack (Astro, HTMX and Alpine.js) for a modern, responsive user interface.
|
||||
- **Astro**: For static site generation and routing.
|
||||
- **HTMX**: For dynamic content loading and interactions without full page reloads.
|
||||
- **Alpine.js**: For lightweight interactivity and state management.
|
||||
- **Back End**: Use Supabase's built-in API for database interactions.
|
||||
- **AI Integration**: Use OpenAI or other AI APIs to suggest prompts based on user input.
|
||||
|
||||
## Future Enhancements
|
||||
|
||||
- **User Profiles**: Create profiles for users to manage their contributions and favorites.
|
||||
- **User Contributions**: Allow users to submit their own prompts to the catalog.
|
||||
- **Favorites**: Users can mark prompts as favorites for easy access later.
|
||||
- **Rating System**: Rate prompts to help others find the best ones.
|
||||
- **Version Control**: Track changes to prompts over time.
|
||||
- **API Access**: Provide an API for external applications to access the catalog.
|
||||
- **Sharing**: Enable sharing of prompts via links or social media.
|
||||
@@ -1,32 +0,0 @@
|
||||
const prompts = [
|
||||
{
|
||||
slug: "summarize-document",
|
||||
title: "Summarize Document",
|
||||
type: "System",
|
||||
description: "Summarizes a document or long input using GPT-4.",
|
||||
tags: ["summary", "long-form", "NLP"],
|
||||
createdAt: "2025-06-01",
|
||||
updatedAt: "2025-07-10",
|
||||
notes: "Summarizes input using GPT-4 with smart chunking."
|
||||
},
|
||||
{
|
||||
slug: "translate-text",
|
||||
title: "Translate Text",
|
||||
type: "Task",
|
||||
description: "Translate English text into French, Spanish, or Japanese.",
|
||||
tags: ["translate", "language"],
|
||||
createdAt: "2025-05-15",
|
||||
updatedAt: "2025-06-22",
|
||||
notes: "Uses multilingual model for more accurate translation."
|
||||
},
|
||||
{
|
||||
slug: "generate-code",
|
||||
title: "Generate Code",
|
||||
type: "Task",
|
||||
description: "Generate Python or JavaScript functions from descriptions.",
|
||||
tags: ["code", "generation", "devtools"],
|
||||
createdAt: "2025-06-05",
|
||||
updatedAt: "2025-07-01",
|
||||
notes: "Includes language detection and function wrapping."
|
||||
}
|
||||
];
|
||||
@@ -1,71 +0,0 @@
|
||||
---
|
||||
import PromptCard from './PromptCard.astro';
|
||||
|
||||
const { prompts = [] } = Astro.props;
|
||||
|
||||
type Prompt = {
|
||||
slug: string;
|
||||
title: string;
|
||||
type: string;
|
||||
description: string;
|
||||
tags?: string[];
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
notes?: string;
|
||||
};
|
||||
---
|
||||
|
||||
<div class="border-b mb-4 flex justify-between items-center">
|
||||
<h2 id="prompt-count" class="text-xl font-semibold text-gray-300 mb-2"></h2>
|
||||
<a href="#filters" class="block lg:hidden bg-blue-600 text-white px-2 py-0 pb-1 mb-2 rounded cursor-pointer hover:bg-blue-700 transition-colors duration-300">Filters ↓</a>
|
||||
</div>
|
||||
|
||||
<div id="prompt-grid" class="grid grid-cols-1 gap-4 md:grid-cols-2 xl:grid-cols-3">
|
||||
{prompts.map((p: Prompt) => (
|
||||
<div class="prompt-card" data-type={p.type} data-tags={(p.tags ?? []).join(',')}>
|
||||
<PromptCard {...p} />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<script is:inline>
|
||||
const form = document.getElementById('filter-form');
|
||||
const cards = document.querySelectorAll('.prompt-card');
|
||||
|
||||
const clearBtn = document.getElementById('clear-filters');
|
||||
clearBtn?.addEventListener('click', () => {
|
||||
form.reset();
|
||||
filterCards();
|
||||
});
|
||||
|
||||
function filterCards() {
|
||||
const params = new URLSearchParams(new FormData(form));
|
||||
const type = params.get('type');
|
||||
const query = params.get('q')?.toLowerCase() || '';
|
||||
const tagParams = params.getAll('tag');
|
||||
|
||||
cards.forEach(card => {
|
||||
const cardType = card.dataset.type;
|
||||
const cardTags = card.dataset.tags.split(',');
|
||||
const cardText = card.innerText.toLowerCase();
|
||||
|
||||
const matchesType = !type || type === cardType;
|
||||
const matchesTags = tagParams.length === 0 || tagParams.some(t => cardTags.includes(t));
|
||||
const matchesSearch = !query || cardText.includes(query);
|
||||
|
||||
card.style.display = matchesType && matchesTags && matchesSearch ? 'block' : 'none';
|
||||
|
||||
const visibleCount = Array.from(cards).filter(c => c.style.display !== 'none').length;
|
||||
|
||||
document.getElementById('prompt-count').textContent =
|
||||
visibleCount === 1
|
||||
? "1 prompt shown"
|
||||
: `${visibleCount} prompts shown`;
|
||||
});
|
||||
}
|
||||
|
||||
form.addEventListener('input', filterCards);
|
||||
|
||||
// Trigger filter once on page load
|
||||
filterCards();
|
||||
</script>
|
||||
@@ -1,59 +0,0 @@
|
||||
---
|
||||
const {
|
||||
slug,
|
||||
title,
|
||||
type,
|
||||
description,
|
||||
tags = [],
|
||||
created_at,
|
||||
updated_at,
|
||||
notes,
|
||||
} = Astro.props;
|
||||
|
||||
const formatDate = (dateStr: string | undefined) => {
|
||||
if (!dateStr) return "–";
|
||||
const date = new Date(dateStr);
|
||||
return isNaN(date.getTime())
|
||||
? "Invalid date"
|
||||
: date.toLocaleDateString('en-US', {
|
||||
month: 'short',
|
||||
day: 'numeric',
|
||||
year: 'numeric',
|
||||
});
|
||||
};
|
||||
---
|
||||
|
||||
<div class="border border-gray-400 rounded p-4 bg-gray-700 text-gray-200 shadow-sm flex flex-col gap-2 min-h-[12rem]">
|
||||
<div class="flex items-center justify-between">
|
||||
<h3 class="text-xl font-semibold">{title}</h3>
|
||||
<span class={`text-sm font-medium px-2 py-1 rounded ${
|
||||
type === 'System' ? 'bg-blue-100 text-blue-700' : 'bg-green-100 text-green-700'
|
||||
}`}>
|
||||
{type}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<p class="text-md">{notes}</p>
|
||||
|
||||
<div class="flex flex-wrap gap-2 mt-2">
|
||||
{tags.map((tag: string) => (
|
||||
<span class="text-sm bg-gray-200 text-gray-800 px-2 py-1 pt-0 rounded">{tag}</span>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<details name="prompt-details">
|
||||
<summary class="cursor-pointer font-semibold mt-2 text-lg">View Details</summary>
|
||||
<div class="text-md border-t mt-2 pt-2">
|
||||
<div class="flex justify-between items-center">
|
||||
<h3 class="text-xl font-semibold px-2">Prompt</h3>
|
||||
|
||||
<a class="bg-green-600 text-white px-2 py-0 rounded text-sm hover:bg-green-700 transition-colors duration-300" href={`/edit?slug=${slug}`}->Edit</a>
|
||||
</div>
|
||||
|
||||
<p class="my-2 px-2 text-balance" set:html={description.replace(/\n/g, '<br />')} />
|
||||
|
||||
<hr class="my-2" />
|
||||
<p class="text-sm"><strong>Created:</strong> {formatDate(created_at)} • <strong>Updated:</strong> {formatDate(updated_at)}</p>
|
||||
</div>
|
||||
</details>
|
||||
</div>
|
||||
@@ -1,70 +0,0 @@
|
||||
---
|
||||
import PromptCard from './PromptCard.astro';
|
||||
|
||||
const { prompts, error } = Astro.props;
|
||||
|
||||
const typeFilter = typeof window !== "undefined"
|
||||
? new URLSearchParams(window.location.search).get("type")
|
||||
: null;
|
||||
|
||||
const tagFilter = typeof window !== "undefined"
|
||||
? new URLSearchParams(window.location.search).get("tag")
|
||||
: null;
|
||||
|
||||
console.log("🔍 searchParams:", typeFilter, tagFilter);
|
||||
|
||||
type Prompt = {
|
||||
slug: string;
|
||||
title: string;
|
||||
type: string;
|
||||
description: string;
|
||||
tags?: string[];
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
notes?: string;
|
||||
};
|
||||
|
||||
const filtered = prompts?.filter((p: Prompt) => {
|
||||
return (!typeFilter || p.type === typeFilter) &&
|
||||
(!tagFilter || p.tags?.includes(tagFilter));
|
||||
});
|
||||
---
|
||||
|
||||
{error ? (
|
||||
<p class="text-red-500">Failed to load prompts: {error.message}</p>
|
||||
) : (
|
||||
<form class="mb-4 flex gap-4 items-end" method="GET">
|
||||
<div>
|
||||
<label for="type" class="block text-sm font-medium">Type</label>
|
||||
<select name="type" id="type" class="border p-2 rounded w-full bg-gray-800">
|
||||
<option value="">All</option>
|
||||
<option value="System" selected={typeFilter === 'System'}>System</option>
|
||||
<option value="Task" selected={typeFilter === 'Task'}>Task</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label for="tag" class="block text-sm font-medium">Tag</label>
|
||||
<input type="text" name="tag" id="tag" class="border p-2 rounded w-full" value={tagFilter || ''} />
|
||||
</div>
|
||||
|
||||
<button type="submit" class="bg-blue-600 text-white px-4 py-2 rounded">
|
||||
Filter
|
||||
</button>
|
||||
</form>
|
||||
|
||||
<div class="grid grid-cols-1 gap-4 md:grid-cols-2 lg:grid-cols-3">
|
||||
{filtered?.map((prompt: Prompt) => (
|
||||
<PromptCard
|
||||
slug={prompt.slug}
|
||||
title={prompt.title}
|
||||
type={prompt.type}
|
||||
description={prompt.description}
|
||||
tags={prompt.tags}
|
||||
createdAt={prompt.created_at}
|
||||
updatedAt={prompt.updated_at}
|
||||
notes={prompt.notes}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
@@ -1,40 +0,0 @@
|
||||
---
|
||||
import MainLayout from '../layouts/MainLayout.astro';
|
||||
import SearchBar from '../components/SearchBar.astro';
|
||||
import FilteredPromptList from '../components/FilteredPromptList.astro';
|
||||
import { supabase } from '../lib/supabase';
|
||||
|
||||
const { data: prompts, error } = await supabase
|
||||
.from('prompts')
|
||||
.select('*')
|
||||
.order('title', { ascending: true });
|
||||
|
||||
const allTags = Array.from(
|
||||
new Set(
|
||||
prompts?.flatMap((p) => p.tags ?? [])
|
||||
)
|
||||
).sort((a, b) => a.toLowerCase().localeCompare(b.toLowerCase()));
|
||||
---
|
||||
|
||||
<MainLayout page="Home">
|
||||
<header class="border-b p-4">
|
||||
<h1 class="text-2xl font-bold"><a href="/">Prompt Catalog</a></h1>
|
||||
<p class="text-sm mt-1">Save and explore AI prompts</p>
|
||||
</header>
|
||||
|
||||
<main class="flex flex-col lg:flex-row">
|
||||
<SearchBar allTags={allTags} />
|
||||
|
||||
<div class="border-b lg:border-l flex-1 flex flex-col overflow-hidden">
|
||||
<div class="flex-1 overflow-y-auto px-4 lg:px-6 pt-2 pb-4">
|
||||
{error
|
||||
? <p class="text-red-500">Supabase error: {error.message}</p>
|
||||
: <FilteredPromptList prompts={prompts} />}
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<footer class="border-t p-4 text-center text-sm">
|
||||
© {new Date().getFullYear()} Prompt Catalog
|
||||
</footer>
|
||||
</MainLayout>
|
||||
Reference in New Issue
Block a user