Compare commits

...

10 Commits

Author SHA1 Message Date
Keith Solomon
69b3c7c57b 📄 docs: Update dev checklist 2025-07-29 13:51:56 -05:00
Keith Solomon
0abe4f621b 📄 docs: Update readme and dev checklist
🐞 fix: Clean up non-used files
2025-07-29 12:29:50 -05:00
Keith Solomon
3c5b52c294 🐞 fix: Order tag pills, make closed cards not expand to fill row when one is opened 2025-07-29 10:38:17 -05:00
Keith Solomon
f5f443abb8 🐞 fix: Format prompt text 2025-07-29 10:02:24 -05:00
Keith Solomon
c61a2cb2b7 🐞 fix: Wire up filtering and count after switch to client-side pull for prompts 2025-07-29 08:48:12 -05:00
Keith Solomon
be91357b6f 🐞 fix: Reorder docker-compose.yml and remove unused mounts 2025-07-28 12:04:58 -05:00
fbc40bbf9b feature: Add client-side fetch javascript 2025-07-28 17:02:38 +00:00
f62eff64a0 🐞 fix: Update docker config 2025-07-28 17:01:24 +00:00
Keith Solomon
679059a360 feature: Add docker support files 2025-07-27 13:10:05 -05:00
Keith Solomon
fb895435a1 feature: Change name, set up for docker build 2025-07-27 12:10:49 -05:00
31 changed files with 361 additions and 472 deletions

5
.dockerignore Normal file
View File

@@ -0,0 +1,5 @@
node_modules
dist
.env
.DS_Store
.vscode

56
.github/workflows/docker-build.yml vendored Normal file
View 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
View 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"]

View File

@@ -1,8 +1,8 @@
# Prompt Catalog # PromptBase
## Overview ## 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 ## Features
@@ -22,13 +22,44 @@ The Prompt Catalog is a centralized, searchable repository for storing and manag
- User auth and profiles - User auth and profiles
- Prompt favorites and contributions - Prompt favorites and contributions
- Ratings, version history, and sharing - 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 ## Tech Stack
- **Frontend**: Astro + HTMX + Alpine.js (AHA Stack) - **Frontend**: Astro js, Tailwind CSS
- **Backend**: Supabase (self-hosted) - **Backend**: Supabase (self-hosted)
- **Deployment**: Vercel or Netlify (frontend), Supabase (backend) - **Deployment**: Docker (frontend), Supabase (backend)
## Database Schema ## Database Schema
@@ -48,7 +79,7 @@ The Prompt Catalog is a centralized, searchable repository for storing and manag
## Development Roadmap ## 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 ### MVP Phases
@@ -66,17 +97,6 @@ Development is organized into phases. For details, see `Development Checklist.md
- Ratings, version control, external API - Ratings, version control, external API
- Shareable links and embeds - 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 ## Contributing
Pull requests are welcome! Please keep contributions focused on core functionality and usability improvements. Pull requests are welcome! Please keep contributions focused on core functionality and usability improvements.

View File

@@ -3,7 +3,7 @@
"type": "module", "type": "module",
"version": "0.0.1", "version": "0.0.1",
"scripts": { "scripts": {
"dev": "astro dev", "dev": "astro dev --host 0.0.0.0",
"build": "astro build", "build": "astro build",
"preview": "astro preview", "preview": "astro preview",
"astro": "astro" "astro": "astro"

View File

Before

Width:  |  Height:  |  Size: 749 B

After

Width:  |  Height:  |  Size: 749 B

View 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)} &bull; <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
View 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 &downarrow;</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">
&copy; {new Date().getFullYear()} Prompt Catalog
</footer>
<script type="module" src="/scripts/fetch-prompts.js"></script>
</MainLayout>

View File

@@ -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] Review and finalize requirements from `Prompt Catalog Features.md`
- [x] Choose JavaScript framework (React, Vue, etc.) - [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] Define and implement Supabase schema
- [x] Set up Supabase RLS rules (if applicable) - [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` - [x] Build static UI from `Front End Interface.png`
- [ ] Sidebar navigation (System / Task) - [x] Sidebar navigation (Search / filter prompts)
- [ ] Search bar with filters - [x] Prompt list display
- [ ] Prompt list display - [x] Prompt detail view
- [ ] Prompt detail view - [x] Tags display and interaction
- [ ] Tags display and interaction - [x] Integrate UI with Supabase for live data
- [ ] Integrate UI with Supabase for live data - [x] Implement CRUD operations for prompts
- [ ] Implement CRUD operations for prompts
--- ---
## 🔍 Phase 4: Search & Tagging ### Phase 4: Search & Tagging
- [ ] Implement keyword and full-text search - [x] Implement keyword and full-text search
- [ ] Add filter by: - [x] Add filter by:
- [ ] Type (System, Task) - [x] Type (System, Task)
- [ ] Tags (multi-select) - [x] Tags (multi-select)
- [ ] Create tag suggestion/autocomplete - [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) - [ ] Set up API key management (e.g., OpenAI, Together, Ollama)
- [ ] Add prompt suggestion UI for user input - [ ] Add prompt suggestion UI for user input
@@ -52,14 +77,7 @@
--- ---
## 📦 Phase 6: Import/Export ### Phase 8: Authentication & User Features (Future)
- [ ] Implement prompt export to JSON
- [ ] Implement prompt import from JSON with validation
---
## 🔐 Phase 7: Authentication & User Features (Future)
- [ ] Add Supabase Auth for login/register - [ ] Add Supabase Auth for login/register
- [ ] Create user profile UI - [ ] Create user profile UI
@@ -68,20 +86,7 @@
--- ---
## 🚀 Phase 8: Deployment & QA ### Phase 9: Future Enhancements
- [ ] 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 - [ ] Add prompt rating system
- [ ] Implement version history tracking - [ ] Implement version history tracking

14
docker-compose.yml Normal file
View 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

View File

@@ -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

View File

@@ -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.

View File

@@ -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."
}
];

View File

View File

@@ -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 &downarrow;</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>

View File

@@ -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)} &bull; <strong>Updated:</strong> {formatDate(updated_at)}</p>
</div>
</details>
</div>

View File

@@ -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>
)}

View File

@@ -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">
&copy; {new Date().getFullYear()} Prompt Catalog
</footer>
</MainLayout>