diff --git a/.cursorrules b/.cursorrules deleted file mode 100644 index c25eec68..00000000 --- a/.cursorrules +++ /dev/null @@ -1,24 +0,0 @@ -# Project Configuration - -## Project Type -- Static Site Generator: Jekyll -- Hosting: GitHub Pages - - - -## Build Commands -- `bundle install`: Install dependencies -- `bundle exec jekyll serve`: Run development server -- `bundle exec jekyll build`: Build site for production - -## Important Files -- `_config.yml`: Jekyll configuration -- `Gemfile`: Ruby dependencies -- `_site/`: Build output directory (generated) -- `_posts/`: Blog posts directory -- `_layouts/`: Template layouts -- `_includes/`: Reusable components - -## GitHub Pages Settings -- Branch: gh-pages (or main/master, depending on your setup) -- Build Source: Jekyll \ No newline at end of file diff --git a/.env.example b/.env.example new file mode 100644 index 00000000..5786fec3 --- /dev/null +++ b/.env.example @@ -0,0 +1,20 @@ +# Database +DATABASE_URL="postgresql://postgres:postgres@localhost:5432/prompts_chat?schema=public" + +# NextAuth +NEXTAUTH_URL="http://localhost:3000" +NEXTAUTH_SECRET="your-super-secret-key-change-in-production" + +# OAuth Providers (optional - enable in prompts.config.ts) +# GOOGLE_CLIENT_ID="" +# GOOGLE_CLIENT_SECRET="" +# AZURE_AD_CLIENT_ID="" +# AZURE_AD_CLIENT_SECRET="" +# AZURE_AD_TENANT_ID="" + +# Storage Providers (optional - enable in prompts.config.ts) +# S3_BUCKET="" +# S3_REGION="" +# S3_ACCESS_KEY_ID="" +# S3_SECRET_ACCESS_KEY="" +# S3_ENDPOINT="" # For S3-compatible services like MinIO diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml index 613dd3bc..d83f13a2 100644 --- a/.github/FUNDING.yml +++ b/.github/FUNDING.yml @@ -1,3 +1 @@ -# These are supported funding model platforms - github: [f] diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md deleted file mode 100644 index d0bea980..00000000 --- a/.github/pull_request_template.md +++ /dev/null @@ -1,20 +0,0 @@ -# Add New Prompt - -You'll need to add your prompt into README.md, and to the `prompts.csv` file. If -your prompt includes quotes, you will need to double-quote them to escape in CSV -file. - -If the prompt is generated by AI, please add `Generated by AI` to -the end of the contribution line. - -- [ ] I've confirmed the prompt works well -- [ ] I've added - `Contributed by: [@yourusername](https://github.com/yourusername)` -- [ ] I've added to the README.md -- [ ] I've added to the `prompts.csv` - - [ ] Escaped quotes by double-quoting them - - [ ] No spaces after commas after double quotes. e.g. `"Hello","hi"`, not - `"Hello", "hi"` - - [ ] Removed "Act as" from the title on CSV - -Please make sure you've completed all the checklist. diff --git a/.github/workflows/ai_bot.yml b/.github/workflows/ai_bot.yml deleted file mode 100644 index 4dfa6735..00000000 --- a/.github/workflows/ai_bot.yml +++ /dev/null @@ -1,623 +0,0 @@ -name: AI Bot - -on: - issues: - types: [opened] - issue_comment: - types: [created] - pull_request_review_comment: - types: [created] - pull_request: - types: [opened, edited, synchronize] - -jobs: - respond-to-commands: - runs-on: ubuntu-latest - if: | - (github.actor == 'f') && - ((github.event_name == 'issues' && contains(github.event.issue.body, '/ai')) || - (github.event_name == 'issue_comment' && contains(github.event.comment.body, '/ai')) || - (github.event_name == 'pull_request_review_comment' && contains(github.event.comment.body, '/ai')) || - (github.event_name == 'pull_request' && contains(github.event.pull_request.body, '/ai'))) - permissions: - contents: write - pull-requests: write - issues: write - - steps: - - uses: actions/checkout@v3 - with: - fetch-depth: 0 - token: ${{ secrets.PAT_TOKEN }} - - - name: Setup Node.js - uses: actions/setup-node@v3 - with: - node-version: "18" - - - name: Install dependencies - run: npm install openai@^4.0.0 @octokit/rest@^19.0.0 - - - name: Process command - id: process - env: - GH_TOKEN: ${{ secrets.PAT_TOKEN }} - OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} - run: | - node << 'EOF' - const OpenAI = require('openai'); - const { Octokit } = require('@octokit/rest'); - - async function main() { - const openai = new OpenAI({ - apiKey: process.env.OPENAI_API_KEY - }); - - const octokit = new Octokit({ - auth: process.env.GH_TOKEN - }); - - const eventName = process.env.GITHUB_EVENT_NAME; - const eventPath = process.env.GITHUB_EVENT_PATH; - const event = require(eventPath); - - // Double check user authorization - const actor = event.sender?.login || event.pull_request?.user?.login || event.issue?.user?.login; - if (actor !== 'f') { - console.log('Unauthorized user attempted to use the bot:', actor); - return; - } - - // Get command and context - let command = ''; - let issueNumber = null; - let isPullRequest = false; - - if (eventName === 'issues') { - command = event.issue.body; - issueNumber = event.issue.number; - } else if (eventName === 'issue_comment') { - command = event.comment.body; - issueNumber = event.issue.number; - isPullRequest = !!event.issue.pull_request; - } else if (eventName === 'pull_request_review_comment') { - command = event.comment.body; - issueNumber = event.pull_request.number; - isPullRequest = true; - } else if (eventName === 'pull_request') { - command = event.pull_request.body; - issueNumber = event.pull_request.number; - isPullRequest = true; - } - - if (!command.startsWith('/ai')) { - return; - } - - // Extract the actual command after /ai - const aiCommand = command.substring(3).trim(); - - // Handle resolve conflicts command - if (aiCommand === 'resolve' || aiCommand === 'fix conflicts') { - if (!isPullRequest) { - console.log('Command rejected: Not a pull request'); - await octokit.issues.createComment({ - owner: event.repository.owner.login, - repo: event.repository.name, - issue_number: issueNumber, - body: '❌ The resolve command can only be used on pull requests.' - }); - return; - } - - try { - console.log('Starting resolve command execution...'); - - // Get PR details - console.log('Fetching PR details...'); - const { data: pr } = await octokit.pulls.get({ - owner: event.repository.owner.login, - repo: event.repository.name, - pull_number: issueNumber - }); - console.log(`Original PR found: #${issueNumber} from ${pr.user.login}`); - - // Get the PR diff to extract the new prompt - console.log('Fetching PR file changes...'); - const { data: files } = await octokit.pulls.listFiles({ - owner: event.repository.owner.login, - repo: event.repository.name, - pull_number: issueNumber - }); - console.log(`Found ${files.length} changed files`); - - // Extract prompt from changes - console.log('Analyzing changes to extract prompt information...'); - const prompts = new Map(); // Use Map to deduplicate prompts by title - - // Helper function to normalize prompt titles - const normalizeTitle = (title) => { - title = title.trim(); - // Remove "Act as" or "Act as a" or "Act as an" from start if present - title = title.replace(/^Act as (?:a |an )?/i, ''); - - // Capitalize each word except common articles and prepositions - const lowercaseWords = ['a', 'an', 'the', 'and', 'but', 'or', 'for', 'nor', 'on', 'at', 'to', 'for', 'with', 'in']; - const capitalized = title.toLowerCase().split(' ').map((word, index) => { - // Always capitalize first and last word - if (index === 0 || !lowercaseWords.includes(word)) { - return word.charAt(0).toUpperCase() + word.slice(1); - } - return word; - }).join(' '); - - // Add "Act as" prefix - return `Act as ${capitalized}`; - }; - - // First try to find prompts in README - let foundInReadme = false; - for (const file of files) { - console.log(`Processing file: ${file.filename}`); - if (file.filename === 'README.md') { - const patch = file.patch || ''; - const addedLines = patch.split('\n') - .filter(line => line.startsWith('+')) - .map(line => line.substring(1)) - .join('\n'); - - console.log('Attempting to extract prompts from README changes...'); - const promptMatches = [...addedLines.matchAll(/## (?:Act as (?:a |an )?)?([^\n]+)\n(?:Contributed by:[^\n]*\n)?(?:> )?([^#]+?)(?=\n##|\n\n##|$)/ig)]; - - for (const match of promptMatches) { - const actName = normalizeTitle(match[1]); - const promptText = match[2].trim() - .replace(/^(?:Contributed by:?[^\n]*\n\s*)+/i, '') - .trim(); - const contributorLine = addedLines.match(/Contributed by: \[@([^\]]+)\]\(https:\/\/github\.com\/([^\)]+)\)/); - const contributorInfo = contributorLine - ? `Contributed by: [@${contributorLine[1]}](https://github.com/${contributorLine[2]})` - : `Contributed by: [@${pr.user.login}](https://github.com/${pr.user.login})`; - - prompts.set(actName.toLowerCase(), { actName, promptText, contributorInfo }); - console.log(`Found prompt in README: "${actName}"`); - foundInReadme = true; - } - } - } - - // Only look in CSV if we didn't find anything in README - if (!foundInReadme) { - console.log('No prompts found in README, checking CSV...'); - for (const file of files) { - if (file.filename === 'prompts.csv') { - const patch = file.patch || ''; - const addedLines = patch.split('\n') - .filter(line => line.startsWith('+')) - .map(line => line.substring(1)) - .filter(line => line.trim()); // Remove empty lines - - console.log('Attempting to extract prompts from CSV changes...'); - for (const line of addedLines) { - // Parse CSV line considering escaped quotes - const matches = [...line.matchAll(/"([^"]*(?:""[^"]*)*)"/g)]; - if (matches.length >= 2) { - const actName = normalizeTitle(matches[0][1].replace(/""/g, '"').trim()); - const promptText = matches[1][1].replace(/""/g, '"').trim() - .replace(/^(?:Contributed by:?[^\n]*\n\s*)+/i, '') - .trim(); - - const contributorInfo = `Contributed by: [@${pr.user.login}](https://github.com/${pr.user.login})`; - prompts.set(actName.toLowerCase(), { actName, promptText, contributorInfo }); - console.log(`Found prompt in CSV: "${actName}"`); - } - } - } - } - } - - if (prompts.size === 0) { - console.log('Failed to extract prompt information'); - await octokit.issues.createComment({ - owner: event.repository.owner.login, - repo: event.repository.name, - issue_number: issueNumber, - body: '❌ Could not extract prompt information from changes' - }); - return; - } - - // Get content from main branch - console.log('Fetching current content from main branch...'); - const { data: readmeFile } = await octokit.repos.getContent({ - owner: event.repository.owner.login, - repo: event.repository.name, - path: 'README.md', - ref: 'main' - }); - - const { data: csvFile } = await octokit.repos.getContent({ - owner: event.repository.owner.login, - repo: event.repository.name, - path: 'prompts.csv', - ref: 'main' - }); - - // Prepare new content - console.log('Preparing content updates...'); - let readmeContent = Buffer.from(readmeFile.content, 'base64').toString('utf-8'); - let csvContent = Buffer.from(csvFile.content, 'base64').toString('utf-8'); - if (!csvContent.endsWith('\n')) csvContent += '\n'; - - // Convert Map to array for processing - const promptsArray = Array.from(prompts.values()); - - // Process each prompt - for (const { actName, promptText, contributorInfo } of promptsArray) { - // Remove markdown quote character and trim whitespace - const cleanPrompt = promptText.replace(/^>\s*/gm, '').trim(); - - // For README: Add quote to each line - const readmePrompt = cleanPrompt.split('\n') - .map(line => `> ${line.trim()}`) - .join('\n'); - const newSection = `## ${actName}\n${contributorInfo}\n\n${readmePrompt}\n\n`; - - // For CSV: Convert to single paragraph - const csvPrompt = cleanPrompt.replace(/\n+/g, ' ').trim(); - - // Insert the new section before Contributors in README - const contributorsIndex = readmeContent.indexOf('## Contributors'); - if (contributorsIndex === -1) { - console.log('Contributors section not found, appending to end'); - readmeContent += newSection; - } else { - console.log('Inserting before Contributors section'); - readmeContent = readmeContent.slice(0, contributorsIndex) + newSection + readmeContent.slice(contributorsIndex); - } - - // Add to CSV content - csvContent += `"${actName.replace(/^Act as an?/i, '').replace(/"/g, '""')}","${csvPrompt.replace(/"/g, '""')}"\n`; - } - - // Create new branch - const branchName = `prompt/${promptsArray.map(p => p.actName.toLowerCase().replace(/[^a-z0-9]+/g, '-')).join('-')}`; - console.log(`Creating new branch: ${branchName}`); - - // Check if branch exists and delete it - try { - console.log('Checking if branch already exists...'); - const { data: existingRef } = await octokit.git.getRef({ - owner: event.repository.owner.login, - repo: event.repository.name, - ref: `heads/${branchName}` - }); - - if (existingRef) { - // Check for existing PRs from this branch - console.log('Checking for existing PRs from this branch...'); - const { data: existingPRs } = await octokit.pulls.list({ - owner: event.repository.owner.login, - repo: event.repository.name, - head: `${event.repository.owner.login}:${branchName}`, - state: 'open' - }); - - // Close any existing PRs - for (const pr of existingPRs) { - console.log(`Closing existing PR #${pr.number}...`); - await octokit.pulls.update({ - owner: event.repository.owner.login, - repo: event.repository.name, - pull_number: pr.number, - state: 'closed' - }); - } - - console.log('Branch exists, deleting it...'); - await octokit.git.deleteRef({ - owner: event.repository.owner.login, - repo: event.repository.name, - ref: `heads/${branchName}` - }); - console.log('Existing branch deleted'); - } - } catch (error) { - // 404 means branch doesn't exist, which is fine - if (error.status !== 404) { - throw error; - } - console.log('Branch does not exist, proceeding with creation'); - } - - // Get main branch ref - const { data: mainRef } = await octokit.git.getRef({ - owner: event.repository.owner.login, - repo: event.repository.name, - ref: 'heads/main' - }); - - // Create new branch - await octokit.git.createRef({ - owner: event.repository.owner.login, - repo: event.repository.name, - ref: `refs/heads/${branchName}`, - sha: mainRef.object.sha - }); - - // Get current files from the new branch - console.log('Getting current file SHAs...'); - const { data: currentReadme } = await octokit.repos.getContent({ - owner: event.repository.owner.login, - repo: event.repository.name, - path: 'README.md', - ref: branchName - }); - - const { data: currentCsv } = await octokit.repos.getContent({ - owner: event.repository.owner.login, - repo: event.repository.name, - path: 'prompts.csv', - ref: branchName - }); - - // Update files with correct author - console.log('Updating README.md...'); - await octokit.repos.createOrUpdateFileContents({ - owner: event.repository.owner.login, - repo: event.repository.name, - path: 'README.md', - message: promptsArray.length === 1 - ? `feat: Add "${promptsArray[0].actName}" to README` - : `feat: Add multiple prompts to README`, - content: Buffer.from(readmeContent).toString('base64'), - branch: branchName, - sha: currentReadme.sha, - committer: { - name: pr.user.login, - email: `${pr.user.login}@users.noreply.github.com` - }, - author: { - name: pr.user.login, - email: `${pr.user.login}@users.noreply.github.com` - } - }); - - console.log('Updating prompts.csv...'); - await octokit.repos.createOrUpdateFileContents({ - owner: event.repository.owner.login, - repo: event.repository.name, - path: 'prompts.csv', - message: promptsArray.length === 1 - ? `feat: Add "${promptsArray[0].actName}" to prompts.csv` - : `feat: Add multiple prompts to prompts.csv`, - content: Buffer.from(csvContent).toString('base64'), - branch: branchName, - sha: currentCsv.sha, - committer: { - name: pr.user.login, - email: `${pr.user.login}@users.noreply.github.com` - }, - author: { - name: pr.user.login, - email: `${pr.user.login}@users.noreply.github.com` - } - }); - - // Create new PR - const prTitle = promptsArray.length === 1 - ? `feat: Add "${promptsArray[0].actName}"` - : `feat: Add multiple prompts (${promptsArray.map(p => `"${p.actName}"`).join(', ')})`; - - const prBody = promptsArray.length === 1 - ? `This PR supersedes #${issueNumber} with proper formatting. Original PR by @${pr.user.login}. Added "${promptsArray[0].actName}" to README.md and prompts.csv, preserving original attribution.` - : `This PR supersedes #${issueNumber} with proper formatting. Original PR by @${pr.user.login}.\n\nAdded the following prompts:\n${promptsArray.map(p => `- "${p.actName}"`).join('\n')}\n\nAll prompts have been added to README.md and prompts.csv, preserving original attribution.`; - - const { data: newPr } = await octokit.pulls.create({ - owner: event.repository.owner.login, - repo: event.repository.name, - title: prTitle, - head: branchName, - base: 'main', - body: prBody - }); - - // Comment on original PR - await octokit.issues.createComment({ - owner: event.repository.owner.login, - repo: event.repository.name, - issue_number: issueNumber, - body: `I've created a new PR #${newPr.number} with your contribution properly formatted. This PR will be closed in favor of the new one.` - }); - - // Close original PR - await octokit.pulls.update({ - owner: event.repository.owner.login, - repo: event.repository.name, - pull_number: issueNumber, - state: 'closed' - }); - - console.log(`Created new PR #${newPr.number} and closed original PR #${issueNumber}`); - - } catch (error) { - console.error('Error details:', error); - await octokit.issues.createComment({ - owner: event.repository.owner.login, - repo: event.repository.name, - issue_number: issueNumber, - body: `❌ Error while trying to create new PR:\n\`\`\`\n${error.message}\n\`\`\`` - }); - } - return; - } - - // Handle rename command specifically - if (aiCommand.startsWith('rename') || aiCommand === 'suggest title') { - if (!isPullRequest) { - await octokit.issues.createComment({ - owner: event.repository.owner.login, - repo: event.repository.name, - issue_number: issueNumber, - body: '❌ The rename command can only be used on pull requests.' - }); - return; - } - - // Get PR details for context - const { data: pr } = await octokit.pulls.get({ - owner: event.repository.owner.login, - repo: event.repository.name, - pull_number: issueNumber - }); - - // Get the list of files changed in the PR - const { data: files } = await octokit.pulls.listFiles({ - owner: event.repository.owner.login, - repo: event.repository.name, - pull_number: issueNumber - }); - - // Process file changes - const fileChanges = await Promise.all(files.map(async file => { - if (file.status === 'removed') { - return `Deleted: ${file.filename}`; - } - - // Get file content for added or modified files - if (file.status === 'added' || file.status === 'modified') { - const patch = file.patch || ''; - return `${file.status === 'added' ? 'Added' : 'Modified'}: ${file.filename}\nChanges:\n${patch}`; - } - - return `${file.status}: ${file.filename}`; - })); - - const completion = await openai.chat.completions.create({ - model: "gpt-3.5-turbo", - messages: [ - { - role: "system", - content: "You are a helpful assistant that generates clear and concise pull request titles. Follow these rules:\n1. Use conventional commit style (feat:, fix:, docs:, etc.)\n2. Focus on WHAT changed, not HOW or WHERE\n3. Keep it short and meaningful\n4. Don't mention file names or technical implementation details\n5. Return ONLY the new title, nothing else\n\nGood examples:\n- feat: Add \"Act as a Career Coach\"\n- fix: Correct typo in Linux Terminal prompt\n- docs: Update installation instructions\n- refactor: Improve error handling" - }, - { - role: "user", - content: `Based on these file changes, generate a concise PR title:\n\n${fileChanges.join('\n\n')}` - } - ], - temperature: 0.5, - max_tokens: 60 - }); - - const newTitle = completion.choices[0].message.content.trim(); - - // Update PR title - await octokit.pulls.update({ - owner: event.repository.owner.login, - repo: event.repository.name, - pull_number: issueNumber, - title: newTitle - }); - - // Add comment about the rename - await octokit.issues.createComment({ - owner: event.repository.owner.login, - repo: event.repository.name, - issue_number: issueNumber, - body: `✨ Updated PR title to: "${newTitle}"\n\nBased on the following changes:\n\`\`\`diff\n${fileChanges.join('\n')}\n\`\`\`` - }); - return; - } - - // Handle other commands - const completion = await openai.chat.completions.create({ - model: "gpt-3.5-turbo", - messages: [ - { - role: "system", - content: "You are a helpful AI assistant that helps with GitHub repositories. You can suggest code changes, fix issues, and improve code quality." - }, - { - role: "user", - content: aiCommand - } - ], - temperature: 0.7, - max_tokens: 2000 - }); - - const response = completion.choices[0].message.content; - - // If response contains code changes, create a new branch and PR - if (response.includes('```')) { - const branchName = `ai-bot/fix-${issueNumber}`; - - // Create new branch - const defaultBranch = event.repository.default_branch; - const ref = await octokit.git.getRef({ - owner: event.repository.owner.login, - repo: event.repository.name, - ref: `heads/${defaultBranch}` - }); - - await octokit.git.createRef({ - owner: event.repository.owner.login, - repo: event.repository.name, - ref: `refs/heads/${branchName}`, - sha: ref.data.object.sha - }); - - // Extract code changes and file paths from response - const codeBlocks = response.match(/```[\s\S]*?```/g); - for (const block of codeBlocks) { - const [_, filePath, ...codeLines] = block.split('\n'); - const content = Buffer.from(codeLines.join('\n')).toString('base64'); - - await octokit.repos.createOrUpdateFileContents({ - owner: event.repository.owner.login, - repo: event.repository.name, - path: filePath, - message: `AI Bot: Apply suggested changes for #${issueNumber}`, - content, - branch: branchName - }); - } - - // Create PR - await octokit.pulls.create({ - owner: event.repository.owner.login, - repo: event.repository.name, - title: `AI Bot: Fix for #${issueNumber}`, - body: `This PR was automatically generated in response to #${issueNumber}\n\nChanges proposed:\n${response}`, - head: branchName, - base: defaultBranch - }); - } - - // Add comment with response - await octokit.issues.createComment({ - owner: event.repository.owner.login, - repo: event.repository.name, - issue_number: issueNumber, - body: `AI Bot Response:\n\n${response}` - }); - } - - main().catch(error => { - console.error('Error:', error); - process.exit(1); - }); - EOF - - - name: Handle errors - if: failure() - uses: actions/github-script@v6 - with: - script: | - const issueNumber = context.issue.number || context.payload.pull_request.number; - await github.rest.issues.createComment({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: issueNumber, - body: '❌ Sorry, there was an error processing your command. Please try again or contact the repository maintainers.' - }); diff --git a/.github/workflows/auto_commands.yml b/.github/workflows/auto_commands.yml deleted file mode 100644 index 6ec9006e..00000000 --- a/.github/workflows/auto_commands.yml +++ /dev/null @@ -1,43 +0,0 @@ -name: Auto AI Commands - -on: - pull_request: - types: [opened, reopened, synchronize] - pull_request_target: - types: [opened, reopened, synchronize] - -jobs: - check-and-comment: - runs-on: ubuntu-latest - permissions: - pull-requests: write - issues: write - - steps: - - name: Check PR status and comment - uses: actions/github-script@v6 - with: - script: | - const pr = context.payload.pull_request; - - // Check if PR has conflicts - if (pr.mergeable === false) { - console.log('PR has conflicts, commenting /ai resolve'); - await github.rest.issues.createComment({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: pr.number, - body: '/ai resolve' - }); - } - - // Check if PR title starts with "updated" - if (pr.title.toLowerCase().startsWith('updated')) { - console.log('PR title starts with "updated", commenting /ai suggest title'); - await github.rest.issues.createComment({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: pr.number, - body: '/ai suggest title' - }); - } diff --git a/.github/workflows/csv_linter.yml b/.github/workflows/csv_linter.yml deleted file mode 100644 index 3e0c7219..00000000 --- a/.github/workflows/csv_linter.yml +++ /dev/null @@ -1,82 +0,0 @@ -name: CSV Linter and Trailing Whitespaces - -on: - push: - pull_request: - -jobs: - lint_and_check_trailing_whitespaces: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - - name: Set up Python - uses: actions/setup-python@v2 - with: - python-version: "3.8" - - - name: Install dependencies - run: | - python -m pip install --upgrade pip - python -m pip install csvkit - - - name: Validate CSV structure - run: | - echo "Checking CSV structure..." - if ! csvclean -n prompts.csv 2>&1 | tee /tmp/csv_errors.log; then - echo "::error::CSV validation failed" - cat /tmp/csv_errors.log - exit 1 - fi - - - name: Check CSV format - run: | - echo "Checking CSV format..." - if ! python -c ' - import csv - with open("prompts.csv", "r", encoding="utf-8") as f: - reader = csv.reader(f) - headers = next(reader) - if headers != ["act", "prompt", "for_devs", "type"]: - print("Error: CSV headers must be exactly [act, prompt, for_devs, type]") - exit(1) - valid_types = ["TEXT", "JSON"] - for row_num, row in enumerate(reader, 2): - if len(row) != 4: - print(f"Error: Row {row_num} has {len(row)} columns, expected 4") - exit(1) - if not row[0] or not row[1] or not row[2] or not row[3]: - print(f"Error: Row {row_num} has empty values") - exit(1) - if row[3] not in valid_types: - print(f"Error: Row {row_num} has invalid type \"{row[3]}\". Must be TEXT or JSON") - exit(1) - print("CSV format OK") - '; then - echo "::error::CSV format check failed" - exit 1 - fi - - - name: Check Trailing Whitespaces - run: | - echo "Checking for trailing whitespaces..." - if grep -q "[[:space:]]$" prompts.csv; then - echo "::error::Found trailing whitespaces in prompts.csv" - grep -n "[[:space:]]$" prompts.csv | while read -r line; do - echo "Line with trailing whitespace: $line" - done - exit 0 - fi - echo "No trailing whitespaces found" - - - name: Check for UTF-8 BOM and line endings - run: | - echo "Checking for UTF-8 BOM and line endings..." - if file prompts.csv | grep -q "with BOM"; then - echo "::error::File contains UTF-8 BOM marker" - exit 1 - fi - if file prompts.csv | grep -q "CRLF"; then - echo "::error::File contains Windows-style (CRLF) line endings" - exit 1 - fi diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml deleted file mode 100644 index d02d795e..00000000 --- a/.github/workflows/publish.yml +++ /dev/null @@ -1,29 +0,0 @@ -name: Publish to GitHub Pages - -on: - push: - branches: - - main - -jobs: - build: - runs-on: ubuntu-latest - - steps: - - name: Checkout repository - uses: actions/checkout@v3 - - - name: Set up Ruby - uses: ruby/setup-ruby@v1 - with: - ruby-version: "3.2" - bundler-cache: true - - - name: Build site - run: bundle exec jekyll build - - - name: Deploy to GitHub Pages - uses: peaceiris/actions-gh-pages@v3 - with: - github_token: ${{ secrets.GITHUB_TOKEN }} - publish_dir: ./_site diff --git a/.gitignore b/.gitignore index 6e3613bb..d4b7827a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,46 @@ -.idea/ -.vscode/ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +/node_modules +/.pnp +.pnp.* +.yarn/* +!.yarn/patches +!.yarn/plugins +!.yarn/releases +!.yarn/versions + +# testing +/coverage + +# next.js +/.next/ +/out/ + +# production +/build + +# misc .DS_Store -_site/ \ No newline at end of file +*.pem + +# debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* +.pnpm-debug.log* + +# env files (can opt-in for committing if needed) +.env +.env.local +.env.*.local +!.env.example + +# vercel +.vercel + +# typescript +*.tsbuildinfo +next-env.d.ts + +/src/generated/prisma diff --git a/.windsurfrules b/.windsurfrules deleted file mode 120000 index 161303b6..00000000 --- a/.windsurfrules +++ /dev/null @@ -1 +0,0 @@ -.cursorrules \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 00000000..417de10c --- /dev/null +++ b/Dockerfile @@ -0,0 +1,49 @@ +# Build stage +FROM node:20-alpine AS builder + +WORKDIR /app + +# Install dependencies +COPY package*.json ./ +RUN npm ci + +# Copy source +COPY . . + +# Generate Prisma client +RUN npx prisma generate + +# Build the application +RUN npm run build + +# Production stage +FROM node:20-alpine AS runner + +WORKDIR /app + +ENV NODE_ENV=production +ENV NEXT_TELEMETRY_DISABLED=1 + +# Create non-root user +RUN addgroup --system --gid 1001 nodejs +RUN adduser --system --uid 1001 nextjs + +# Copy necessary files from builder +COPY --from=builder /app/public ./public +COPY --from=builder /app/.next/standalone ./ +COPY --from=builder /app/.next/static ./.next/static +COPY --from=builder /app/prisma ./prisma +COPY --from=builder /app/node_modules/.prisma ./node_modules/.prisma + +# Set correct permissions +RUN chown -R nextjs:nodejs /app + +USER nextjs + +EXPOSE 3000 + +ENV PORT=3000 +ENV HOSTNAME="0.0.0.0" + +# Run database migrations and start the server +CMD ["sh", "-c", "npx prisma migrate deploy && node server.js"] diff --git a/Gemfile b/Gemfile deleted file mode 100644 index 84800ed7..00000000 --- a/Gemfile +++ /dev/null @@ -1,4 +0,0 @@ -source "https://rubygems.org" - -gem "jekyll" -gem "github-pages", group: :jekyll_plugins \ No newline at end of file diff --git a/Gemfile.lock b/Gemfile.lock deleted file mode 100644 index a6cd4477..00000000 --- a/Gemfile.lock +++ /dev/null @@ -1,309 +0,0 @@ -GEM - remote: https://rubygems.org/ - specs: - activesupport (8.1.1) - base64 - bigdecimal - concurrent-ruby (~> 1.0, >= 1.3.1) - connection_pool (>= 2.2.5) - drb - i18n (>= 1.6, < 2) - json - logger (>= 1.4.2) - minitest (>= 5.1) - securerandom (>= 0.3) - tzinfo (~> 2.0, >= 2.0.5) - uri (>= 0.13.1) - addressable (2.8.8) - public_suffix (>= 2.0.2, < 8.0) - base64 (0.3.0) - bigdecimal (3.3.1) - coffee-script (2.4.1) - coffee-script-source - execjs - coffee-script-source (1.12.2) - colorator (1.1.0) - commonmarker (0.23.12) - concurrent-ruby (1.3.5) - connection_pool (2.5.5) - csv (3.3.5) - dnsruby (1.73.1) - base64 (>= 0.2) - logger (~> 1.6) - simpleidn (~> 0.2.1) - drb (2.2.3) - em-websocket (0.5.3) - eventmachine (>= 0.12.9) - http_parser.rb (~> 0) - ethon (0.15.0) - ffi (>= 1.15.0) - eventmachine (1.2.7) - execjs (2.10.0) - faraday (2.14.0) - faraday-net_http (>= 2.0, < 3.5) - json - logger - faraday-net_http (3.4.2) - net-http (~> 0.5) - ffi (1.17.2-aarch64-linux-gnu) - ffi (1.17.2-aarch64-linux-musl) - ffi (1.17.2-arm-linux-gnu) - ffi (1.17.2-arm-linux-musl) - ffi (1.17.2-arm64-darwin) - ffi (1.17.2-x86_64-darwin) - ffi (1.17.2-x86_64-linux-gnu) - ffi (1.17.2-x86_64-linux-musl) - forwardable-extended (2.6.0) - gemoji (4.1.0) - github-pages (232) - github-pages-health-check (= 1.18.2) - jekyll (= 3.10.0) - jekyll-avatar (= 0.8.0) - jekyll-coffeescript (= 1.2.2) - jekyll-commonmark-ghpages (= 0.5.1) - jekyll-default-layout (= 0.1.5) - jekyll-feed (= 0.17.0) - jekyll-gist (= 1.5.0) - jekyll-github-metadata (= 2.16.1) - jekyll-include-cache (= 0.2.1) - jekyll-mentions (= 1.6.0) - jekyll-optional-front-matter (= 0.3.2) - jekyll-paginate (= 1.1.0) - jekyll-readme-index (= 0.3.0) - jekyll-redirect-from (= 0.16.0) - jekyll-relative-links (= 0.6.1) - jekyll-remote-theme (= 0.4.3) - jekyll-sass-converter (= 1.5.2) - jekyll-seo-tag (= 2.8.0) - jekyll-sitemap (= 1.4.0) - jekyll-swiss (= 1.0.0) - jekyll-theme-architect (= 0.2.0) - jekyll-theme-cayman (= 0.2.0) - jekyll-theme-dinky (= 0.2.0) - jekyll-theme-hacker (= 0.2.0) - jekyll-theme-leap-day (= 0.2.0) - jekyll-theme-merlot (= 0.2.0) - jekyll-theme-midnight (= 0.2.0) - jekyll-theme-minimal (= 0.2.0) - jekyll-theme-modernist (= 0.2.0) - jekyll-theme-primer (= 0.6.0) - jekyll-theme-slate (= 0.2.0) - jekyll-theme-tactile (= 0.2.0) - jekyll-theme-time-machine (= 0.2.0) - jekyll-titles-from-headings (= 0.5.3) - jemoji (= 0.13.0) - kramdown (= 2.4.0) - kramdown-parser-gfm (= 1.1.0) - liquid (= 4.0.4) - mercenary (~> 0.3) - minima (= 2.5.1) - nokogiri (>= 1.16.2, < 2.0) - rouge (= 3.30.0) - terminal-table (~> 1.4) - webrick (~> 1.8) - github-pages-health-check (1.18.2) - addressable (~> 2.3) - dnsruby (~> 1.60) - octokit (>= 4, < 8) - public_suffix (>= 3.0, < 6.0) - typhoeus (~> 1.3) - html-pipeline (2.14.3) - activesupport (>= 2) - nokogiri (>= 1.4) - http_parser.rb (0.8.0) - i18n (1.14.7) - concurrent-ruby (~> 1.0) - jekyll (3.10.0) - addressable (~> 2.4) - colorator (~> 1.0) - csv (~> 3.0) - em-websocket (~> 0.5) - i18n (>= 0.7, < 2) - jekyll-sass-converter (~> 1.0) - jekyll-watch (~> 2.0) - kramdown (>= 1.17, < 3) - liquid (~> 4.0) - mercenary (~> 0.3.3) - pathutil (~> 0.9) - rouge (>= 1.7, < 4) - safe_yaml (~> 1.0) - webrick (>= 1.0) - jekyll-avatar (0.8.0) - jekyll (>= 3.0, < 5.0) - jekyll-coffeescript (1.2.2) - coffee-script (~> 2.2) - coffee-script-source (~> 1.12) - jekyll-commonmark (1.4.0) - commonmarker (~> 0.22) - jekyll-commonmark-ghpages (0.5.1) - commonmarker (>= 0.23.7, < 1.1.0) - jekyll (>= 3.9, < 4.0) - jekyll-commonmark (~> 1.4.0) - rouge (>= 2.0, < 5.0) - jekyll-default-layout (0.1.5) - jekyll (>= 3.0, < 5.0) - jekyll-feed (0.17.0) - jekyll (>= 3.7, < 5.0) - jekyll-gist (1.5.0) - octokit (~> 4.2) - jekyll-github-metadata (2.16.1) - jekyll (>= 3.4, < 5.0) - octokit (>= 4, < 7, != 4.4.0) - jekyll-include-cache (0.2.1) - jekyll (>= 3.7, < 5.0) - jekyll-mentions (1.6.0) - html-pipeline (~> 2.3) - jekyll (>= 3.7, < 5.0) - jekyll-optional-front-matter (0.3.2) - jekyll (>= 3.0, < 5.0) - jekyll-paginate (1.1.0) - jekyll-readme-index (0.3.0) - jekyll (>= 3.0, < 5.0) - jekyll-redirect-from (0.16.0) - jekyll (>= 3.3, < 5.0) - jekyll-relative-links (0.6.1) - jekyll (>= 3.3, < 5.0) - jekyll-remote-theme (0.4.3) - addressable (~> 2.0) - jekyll (>= 3.5, < 5.0) - jekyll-sass-converter (>= 1.0, <= 3.0.0, != 2.0.0) - rubyzip (>= 1.3.0, < 3.0) - jekyll-sass-converter (1.5.2) - sass (~> 3.4) - jekyll-seo-tag (2.8.0) - jekyll (>= 3.8, < 5.0) - jekyll-sitemap (1.4.0) - jekyll (>= 3.7, < 5.0) - jekyll-swiss (1.0.0) - jekyll-theme-architect (0.2.0) - jekyll (> 3.5, < 5.0) - jekyll-seo-tag (~> 2.0) - jekyll-theme-cayman (0.2.0) - jekyll (> 3.5, < 5.0) - jekyll-seo-tag (~> 2.0) - jekyll-theme-dinky (0.2.0) - jekyll (> 3.5, < 5.0) - jekyll-seo-tag (~> 2.0) - jekyll-theme-hacker (0.2.0) - jekyll (> 3.5, < 5.0) - jekyll-seo-tag (~> 2.0) - jekyll-theme-leap-day (0.2.0) - jekyll (> 3.5, < 5.0) - jekyll-seo-tag (~> 2.0) - jekyll-theme-merlot (0.2.0) - jekyll (> 3.5, < 5.0) - jekyll-seo-tag (~> 2.0) - jekyll-theme-midnight (0.2.0) - jekyll (> 3.5, < 5.0) - jekyll-seo-tag (~> 2.0) - jekyll-theme-minimal (0.2.0) - jekyll (> 3.5, < 5.0) - jekyll-seo-tag (~> 2.0) - jekyll-theme-modernist (0.2.0) - jekyll (> 3.5, < 5.0) - jekyll-seo-tag (~> 2.0) - jekyll-theme-primer (0.6.0) - jekyll (> 3.5, < 5.0) - jekyll-github-metadata (~> 2.9) - jekyll-seo-tag (~> 2.0) - jekyll-theme-slate (0.2.0) - jekyll (> 3.5, < 5.0) - jekyll-seo-tag (~> 2.0) - jekyll-theme-tactile (0.2.0) - jekyll (> 3.5, < 5.0) - jekyll-seo-tag (~> 2.0) - jekyll-theme-time-machine (0.2.0) - jekyll (> 3.5, < 5.0) - jekyll-seo-tag (~> 2.0) - jekyll-titles-from-headings (0.5.3) - jekyll (>= 3.3, < 5.0) - jekyll-watch (2.2.1) - listen (~> 3.0) - jemoji (0.13.0) - gemoji (>= 3, < 5) - html-pipeline (~> 2.2) - jekyll (>= 3.0, < 5.0) - json (2.16.0) - kramdown (2.4.0) - rexml - kramdown-parser-gfm (1.1.0) - kramdown (~> 2.0) - liquid (4.0.4) - listen (3.9.0) - rb-fsevent (~> 0.10, >= 0.10.3) - rb-inotify (~> 0.9, >= 0.9.10) - logger (1.7.0) - mercenary (0.3.6) - minima (2.5.1) - jekyll (>= 3.5, < 5.0) - jekyll-feed (~> 0.9) - jekyll-seo-tag (~> 2.1) - minitest (5.26.2) - net-http (0.8.0) - uri (>= 0.11.1) - nokogiri (1.18.10-aarch64-linux-gnu) - racc (~> 1.4) - nokogiri (1.18.10-aarch64-linux-musl) - racc (~> 1.4) - nokogiri (1.18.10-arm-linux-gnu) - racc (~> 1.4) - nokogiri (1.18.10-arm-linux-musl) - racc (~> 1.4) - nokogiri (1.18.10-arm64-darwin) - racc (~> 1.4) - nokogiri (1.18.10-x86_64-darwin) - racc (~> 1.4) - nokogiri (1.18.10-x86_64-linux-gnu) - racc (~> 1.4) - nokogiri (1.18.10-x86_64-linux-musl) - racc (~> 1.4) - octokit (4.25.1) - faraday (>= 1, < 3) - sawyer (~> 0.9) - pathutil (0.16.2) - forwardable-extended (~> 2.6) - public_suffix (5.1.1) - racc (1.8.1) - rb-fsevent (0.11.2) - rb-inotify (0.11.1) - ffi (~> 1.0) - rexml (3.4.4) - rouge (3.30.0) - rubyzip (2.4.1) - safe_yaml (1.0.5) - sass (3.7.4) - sass-listen (~> 4.0.0) - sass-listen (4.0.0) - rb-fsevent (~> 0.9, >= 0.9.4) - rb-inotify (~> 0.9, >= 0.9.7) - sawyer (0.9.3) - addressable (>= 2.3.5) - faraday (>= 0.17.3, < 3) - securerandom (0.4.1) - simpleidn (0.2.3) - terminal-table (1.8.0) - unicode-display_width (~> 1.1, >= 1.1.1) - typhoeus (1.5.0) - ethon (>= 0.9.0, < 0.16.0) - tzinfo (2.0.6) - concurrent-ruby (~> 1.0) - unicode-display_width (1.8.0) - uri (1.1.1) - webrick (1.9.2) - -PLATFORMS - aarch64-linux-gnu - aarch64-linux-musl - arm-linux-gnu - arm-linux-musl - arm64-darwin - x86_64-darwin - x86_64-linux-gnu - x86_64-linux-musl - -DEPENDENCIES - github-pages - jekyll - -BUNDLED WITH - 2.5.23 diff --git a/_config.yml b/_config.yml deleted file mode 100644 index 324b5973..00000000 --- a/_config.yml +++ /dev/null @@ -1,7 +0,0 @@ -name: prompts.chat -title: prompts.chat -subtitle: World's First & Most Famous Prompts Directory - -github_pages: - url: "https://prompts.chat" - branch: "main" diff --git a/_layouts/default.html b/_layouts/default.html deleted file mode 100644 index ff03bf6a..00000000 --- a/_layouts/default.html +++ /dev/null @@ -1,380 +0,0 @@ - - -
- - - - -{{ page.subtitle | default: site.subtitle }} - {% if page.subpage != true %} - - Vibe Coding Prompts - - - ♥️ Improve Your GitHub Sponsors Profile - - {% endif %} -
- {% if page.hide_platform_selector != true %} -Choose your AI platform
-Design and share AI prompts
-- Share your creative prompts with the community! Submit a pull request to add your prompts to the collection. -
- Contribute Now - -`; - promptsGrid.appendChild(contributeCard); - - // Fetch prompts.csv to get for_devs information - fetch("/prompts.csv") - .then((response) => response.text()) - .then((csvText) => { - const prompts = parseCSV(csvText); - const isDevMode = document.getElementById("audienceSelect").value === "developers"; - - const promptElements = document.querySelectorAll( - "h2[id^=act] + p + blockquote" - ); - - promptElements.forEach((blockquote) => { - const title = - blockquote.previousElementSibling.previousElementSibling.textContent.trim(); - const content = blockquote.textContent.trim(); - - // Find matching prompt in CSV - const matchingPrompt = prompts.find((p) => { - const csvTitle = p.act - .replace(/\s+/g, " ") - .replace(/[\n\r]/g, "") - .trim(); - const elementTitle = title - .replace(/\s+/g, " ") - .replace(/[\n\r]/g, "") - .trim(); - return ( - csvTitle.toLowerCase() === elementTitle.toLowerCase() || - csvTitle.toLowerCase().includes(elementTitle.toLowerCase()) || - elementTitle.toLowerCase().includes(csvTitle.toLowerCase()) - ); - }); - - // Extract contributor from the paragraph element - const contributorParagraph = blockquote.previousElementSibling; - const contributorText = contributorParagraph.textContent; - let contributor = null; - - // Try different contributor formats - const formats = [ - /Contributed by: \[([^\]]+)\]/i, - /Contributed by \[([^\]]+)\]/i, - /Contributed by: @([^\s]+)/i, - /Contributed by @([^\s]+)/i, - /Contributed by: \[@([^\]]+)\]/i, - /Contributed by \[@([^\]]+)\]/i, - ]; - - for (const format of formats) { - const match = contributorText.match(format); - if (match) { - contributor = match[1]; - // Remove @ if it exists at the start - contributor = contributor.replace(/^@/, ""); - break; - } - } - - // Set default contributor to 'f' if none found - if (!contributor) { - contributor = "f"; - } - - const card = document.createElement("div"); - card.className = "prompt-card"; - - // Set initial visibility based on dev mode - if (isDevMode && (!matchingPrompt || !matchingPrompt.for_devs)) { - card.style.display = "none"; - } - - // Determine prompt type from CSV data - const promptType = matchingPrompt && matchingPrompt.type ? matchingPrompt.type : 'TEXT'; - const isJsonPrompt = promptType === 'JSON'; - - // Render content based on type - let renderedContent; - if (isJsonPrompt) { - renderedContent = `${formatJsonWithHighlighting(content)}`;
- } else {
- renderedContent = `${updatePromptPreview(content)}
`; - } - - // JSON icon HTML - only show for JSON prompts - const jsonIconHtml = isJsonPrompt ? ` - - ` : ''; - - card.innerHTML = ` -${formatJsonWithHighlighting(content)}`;
- } else {
- modalTitle.textContent = title;
- modalContent.textContent = content;
- }
-
- const modalCopyButton = modalOverlay.querySelector(".modal-copy-button");
- const modalContributor = modalOverlay.querySelector(".modal-contributor");
- const modalChatButton = modalOverlay.querySelector(".modal-chat-button");
-
- if (!modalTitle || !modalContent) return;
-
- // Update chat button text with platform name and handle visibility
- const platform = document.querySelector(".platform-tag.active");
- const isDevMode = document.getElementById("audienceSelect").value === "developers";
-
- if (platform) {
- const shouldHideChat = ["gemini", "llama"].includes(
- platform.dataset.platform
- );
- modalChatButton.style.display = shouldHideChat ? "none" : "flex";
-
- if (!shouldHideChat) {
- const chatIcon = modalChatButton.querySelector(".chat-icon");
- const terminalIcon = modalChatButton.querySelector(".terminal-icon");
-
- if (chatIcon && terminalIcon) {
- chatIcon.style.display = isDevMode ? "none" : "block";
- terminalIcon.style.display = isDevMode ? "block" : "none";
- }
-
- modalChatButton.innerHTML = `
-
-
- Chat with ${platform.textContent}
- `;
- }
- }
-
- // Store content for chat button
- modalChatButton.dataset.content = modalContent.textContent;
-
- // Find the contributor for this prompt
- const promptCard = Array.from(document.querySelectorAll(".prompt-card")).find(
- (card) =>
- card.querySelector(".prompt-title").textContent.trim() === title.trim()
- );
-
- if (promptCard) {
- const contributorBadge = promptCard.querySelector(".contributor-badge");
- if (contributorBadge) {
- modalContributor.href = contributorBadge.href;
- modalContributor.textContent = `Contributed by ${contributorBadge.textContent}`;
- }
- }
-
- // Add copy functionality
- modalCopyButton.addEventListener("click", async () => {
- try {
- copyPrompt(modalCopyButton, modalContent.textContent);
- modalCopyButton.innerHTML = `
-
- `;
- setTimeout(() => {
- modalCopyButton.innerHTML = `
-
- `;
- }, 2000);
- } catch (err) {
- alert("Failed to copy prompt to clipboard");
- }
- });
-
- modalOverlay.style.display = "block";
- document.body.style.overflow = "hidden";
-}
-
-function hideModal() {
- const modalOverlay = document.getElementById("modalOverlay");
- if (!modalOverlay) return;
-
- modalOverlay.style.display = "none";
- document.body.style.overflow = "";
-
- // Optional: Remove modal from DOM when hidden
- modalOverlay.remove();
-}
-
-let selectedPlatform =
- localStorage.getItem("selected-platform") || "github-copilot"; // Get from localStorage or default to github
-
-// Platform toggle functionality
-document.querySelectorAll(".platform-tag").forEach((button) => {
- button.addEventListener("click", () => {
- document
- .querySelectorAll(".platform-tag")
- .forEach((btn) => btn.classList.remove("active"));
- button.classList.add("active");
- selectedPlatform = button.dataset.platform;
- localStorage.setItem("selected-platform", selectedPlatform);
-
- // Hide/show chat buttons based on platform
- const chatButtons = document.querySelectorAll(
- ".chat-button, .modal-chat-button"
- );
- const shouldHideChat = ["gemini", "llama"].includes(selectedPlatform);
- chatButtons.forEach((btn) => {
- btn.style.display = shouldHideChat ? "none" : "flex";
- });
-
- // Auto-select technical tone and developers audience for GitHub Copilot
- if (selectedPlatform === "github-copilot") {
- const toneSelect = document.getElementById('toneSelect');
- const audienceSelect = document.getElementById('audienceSelect');
-
- // Set tone to technical
- toneSelect.value = 'technical';
- localStorage.setItem('selected-tone', 'technical');
-
- // Set audience to developers
- audienceSelect.value = 'developers';
- localStorage.setItem('audience', 'developers');
-
- // Update dev mode class on body
- document.body.classList.add('dev-mode');
-
- // Update chat button icons
- updateChatButtonIcons(true);
-
- // Trigger prompt filtering for dev mode
- filterPrompts();
- }
- });
-});
-
-// Set active platform from localStorage and handle initial button visibility
-const platformToActivate =
- document.querySelector(`[data-platform="${selectedPlatform}"]`) ||
- document.querySelector('[data-platform="github-copilot"]');
-platformToActivate.classList.add("active");
-
-// Set initial chat button visibility
-const shouldHideChat = ["gemini", "llama"].includes(selectedPlatform);
-document.querySelectorAll(".chat-button, .modal-chat-button").forEach((btn) => {
- btn.style.display = shouldHideChat ? "none" : "flex";
-});
-
-// Function to open prompt in selected AI chat platform
-function openInChat(button, encodedPrompt) {
- const promptText = buildPrompt(encodedPrompt);
- const platform = document.querySelector(".platform-tag.active");
-
- if (!platform) return;
-
- const baseUrl = platform.dataset.url;
- console.log(baseUrl);
- let url;
-
- switch (platform.dataset.platform) {
- case "github-copilot":
- url = `${baseUrl}?prompt=${encodeURIComponent(promptText)}`;
- break;
- case "chatgpt":
- url = `${baseUrl}?prompt=${encodeURIComponent(promptText)}`;
- break;
- case "grok":
- url = `${baseUrl}&q=${encodeURIComponent(promptText)}`;
- break;
- case "claude":
- url = `${baseUrl}?q=${encodeURIComponent(promptText)}`;
- break;
- case "perplexity":
- url = `${baseUrl}/search?q=${encodeURIComponent(promptText)}`;
- break;
- case "mistral":
- url = `${baseUrl}?q=${encodeURIComponent(promptText)}`;
- break;
- default:
- url = `${baseUrl}?q=${encodeURIComponent(promptText)}`;
- }
-
- window.open(url, "_blank");
-}
-
-function buildPrompt(encodedPrompt) {
- let promptText = decodeURIComponent(encodedPrompt);
-
- // Check if this is a JSON prompt
- const isJsonPrompt = isValidJson(promptText);
-
- // Get language, tone and audience preferences (shared for both JSON and TEXT)
- const languageSelect = document.getElementById('languageSelect');
- const customLanguage = document.getElementById('customLanguage');
- const toneSelect = document.getElementById('toneSelect');
- const customTone = document.getElementById('customTone');
- const audienceSelect = document.getElementById('audienceSelect');
-
- const language = languageSelect.value === 'custom' ? customLanguage.value : languageSelect.value;
- const tone = toneSelect.value === 'custom' ? customTone.value : toneSelect.value;
- const audience = audienceSelect.value;
-
- // Handle JSON prompts differently
- if (isJsonPrompt) {
- try {
- // Clean up curly quotes if present
- let cleanedJson = promptText
- .replace(/[\u201C\u201D]/g, '"')
- .replace(/[\u2018\u2019]/g, "'");
-
- const data = JSON.parse(cleanedJson);
-
- // Add preferences as a structured JSON object
- data.preferences = {
- language: language,
- tone: tone,
- for_developers: audience === 'developers'
- };
-
- // Return prettified JSON
- return JSON.stringify(data, null, 2);
- } catch (e) {
- console.warn('Failed to merge preferences into JSON prompt:', e);
- // Fallback: return original prompt if JSON parsing fails
- return promptText;
- }
- }
-
- // TEXT prompt handling (existing logic)
- // If there's a modal open, use the current state of variables
- const modalContent = document.querySelector(".modal-content");
- if (modalContent) {
- // Get all variable inputs
- const form = document.querySelector(".variable-form");
- if (form) {
- const inputs = form.querySelectorAll(".variable-input");
- inputs.forEach((input) => {
- const value = input.value.trim();
- const variable = input.dataset.variable;
- const defaultValue = input.dataset.default;
- const pattern = new RegExp(`\\$\{${variable}[^}]*\}`, "g");
-
- // Use value or default value
- const replacement = value || defaultValue || variable;
- promptText = promptText.replace(pattern, replacement);
- });
- }
- }
-
- // Clean up newlines and normalize whitespace
- promptText = promptText.replace(/\s+/g, ' ').trim();
-
- // Append preferences as a new line for TEXT prompts
- promptText += ` Reply in ${language} using ${tone} tone for ${audience}.`;
-
- return promptText;
-}
-
-// Existing copy function
-async function copyPrompt(button, encodedPrompt) {
- try {
- const promptText = buildPrompt(encodedPrompt);
- await navigator.clipboard.writeText(promptText);
- const originalHTML = button.innerHTML;
- button.innerHTML = `
-
- `;
- setTimeout(() => {
- button.innerHTML = originalHTML;
- }, 2000);
- } catch (err) {
- console.error("Failed to copy text: ", err);
- }
-}
-
-// Function to handle chat button click in modal
-function openModalChat() {
- const modalContent = document.querySelector(".modal-content");
- if (modalContent) {
- const content = modalContent.textContent;
- openInChat(null, encodeURIComponent(content.trim()));
- }
-}
-
-// Function to handle embed button click in modal
-function openEmbedDesigner() {
- const modalContent = document.querySelector(".modal-content");
- if (modalContent) {
- let content = modalContent.textContent || modalContent.innerText;
-
- // If there's a variable form, get the processed content with variables
- const form = document.querySelector(".variable-form");
- if (form) {
- content = buildPrompt(encodeURIComponent(content.trim()));
- // Remove the added language/tone preferences for embed
- content = content.replace(/\s*Reply in .+ using .+ tone for .+\.$/, '').trim();
- }
-
- // Build the embed URL
- const embedUrl = `/embed/?prompt=${encodeURIComponent(content)}&context=https://prompts.chat&model=gpt-4o&agentMode=chat&thinking=false&max=false&height=400`;
-
- // Open in new tab
- window.open(embedUrl, '_blank');
- }
-}
-
-// Add these functions before the closing script tag
-function showCopilotSuggestion() {
- const modal = document.getElementById("copilotSuggestionModal");
- const backdrop = document.querySelector(".copilot-suggestion-backdrop");
- if (modal) {
- if (!backdrop) {
- const backdropDiv = document.createElement("div");
- backdropDiv.className = "copilot-suggestion-backdrop";
- document.body.appendChild(backdropDiv);
- }
- modal.style.display = "block";
- backdrop.style.display = "block";
- document.body.style.overflow = "hidden";
- }
-}
-
-function hideCopilotSuggestion(switchToCopilot) {
- const modal = document.getElementById("copilotSuggestionModal");
- const backdrop = document.querySelector(".copilot-suggestion-backdrop");
- const doNotShowCheckbox = document.getElementById("doNotShowAgain");
-
- if (doNotShowCheckbox && doNotShowCheckbox.checked) {
- localStorage.setItem("copilot-suggestion-hidden", "true");
- }
-
- if (switchToCopilot) {
- const copilotButton = document.querySelector(
- '[data-platform="github-copilot"]'
- );
- if (copilotButton) {
- copilotButton.click();
- }
- }
-
- if (modal) {
- modal.style.display = "none";
- if (backdrop) {
- backdrop.style.display = "none";
- }
- document.body.style.overflow = "";
- }
-}
-
-// Function to update chat button icons based on dev mode
-function updateChatButtonIcons(isDevMode) {
- document
- .querySelectorAll(".chat-button, .modal-chat-button")
- .forEach((button) => {
- const chatIcon = button.querySelector(".chat-icon");
- const terminalIcon = button.querySelector(".terminal-icon");
- if (chatIcon && terminalIcon) {
- chatIcon.style.display = isDevMode ? "none" : "block";
- terminalIcon.style.display = isDevMode ? "block" : "none";
- }
- });
-}
-
-// Language and Tone Selection
-function initializeLanguageAndTone() {
- const languageSelect = document.getElementById('languageSelect');
- const customLanguage = document.getElementById('customLanguage');
- const toneSelect = document.getElementById('toneSelect');
- const customTone = document.getElementById('customTone');
-
- // Load saved preferences
- const savedLanguage = localStorage.getItem('selected-language');
- const savedCustomLanguage = localStorage.getItem('custom-language');
- const savedTone = localStorage.getItem('selected-tone');
- const savedCustomTone = localStorage.getItem('custom-tone');
-
- if (savedLanguage) {
- languageSelect.value = savedLanguage;
- if (savedLanguage === 'custom' && savedCustomLanguage) {
- customLanguage.value = savedCustomLanguage;
- customLanguage.style.display = 'inline-block';
- }
- }
-
- if (savedTone) {
- toneSelect.value = savedTone;
- if (savedTone === 'custom' && savedCustomTone) {
- customTone.value = savedCustomTone;
- customTone.style.display = 'inline-block';
- }
- }
-
- // Language select handler
- languageSelect.addEventListener('change', (e) => {
- const isCustom = e.target.value === 'custom';
- customLanguage.style.display = isCustom ? 'inline-block' : 'none';
- localStorage.setItem('selected-language', e.target.value);
-
- if (!isCustom) {
- customLanguage.value = '';
- localStorage.removeItem('custom-language');
- }
- });
-
- // Custom language input handler
- customLanguage.addEventListener('input', (e) => {
- localStorage.setItem('custom-language', e.target.value);
- });
-
- // Tone select handler
- toneSelect.addEventListener('change', (e) => {
- const isCustom = e.target.value === 'custom';
- customTone.style.display = isCustom ? 'inline-block' : 'none';
- localStorage.setItem('selected-tone', e.target.value);
-
- if (!isCustom) {
- customTone.value = '';
- localStorage.removeItem('custom-tone');
- }
- });
-
- // Custom tone input handler
- customTone.addEventListener('input', (e) => {
- localStorage.setItem('custom-tone', e.target.value);
- });
-}
-
-// Function to show a modal with YAML format and GitHub create button
-function showYamlModal(event, title, content) {
- event.stopPropagation();
- let modalOverlay = document.getElementById("yamlModalOverlay");
- if (!modalOverlay) {
- const modalHTML = `
-
- `;
- document.body.insertAdjacentHTML("beforeend", modalHTML);
- modalOverlay = document.getElementById("yamlModalOverlay");
-
- // Add event listeners
- const modalClose = modalOverlay.querySelector(".modal-close");
- modalClose.addEventListener("click", () => {
- modalOverlay.style.display = "none";
- document.body.style.overflow = "";
- modalOverlay.remove();
- });
-
- modalOverlay.addEventListener("click", (e) => {
- if (e.target === modalOverlay) {
- modalOverlay.style.display = "none";
- document.body.style.overflow = "";
- modalOverlay.remove();
- }
- });
- }
-
- const yamlContent = modalOverlay.querySelector(".yaml-content");
- const modalCopyButton = modalOverlay.querySelector(".modal-copy-button");
- const createYamlButton = modalOverlay.querySelector(".create-yaml-button");
-
- // Create the YAML content
- const cleanTitle = decodeURIComponent(title).trim().replace(/^Act as a?n?\s*/ig, '');
- const cleanContent = decodeURIComponent(content).trim().replace(/\n+/g, ' ');
-
- // Convert variables from ${Variable: Default} format to {{Variable}} format
- const convertedContent = cleanContent.replace(/\${([^:}]+)(?::[^}]*)?}/g, (match, varName) => {
- return `{{${varName.trim()}}}`;
- });
-
- const yamlText = `name: ${cleanTitle}
-model: gpt-4o-mini
-modelParameters:
- temperature: 0.5
-messages:
- - role: system
- content: |
- ${convertedContent.replace(/\n/g, '\n ')}`;
-
- // Apply basic syntax highlighting
- const highlightedYaml = yamlText
- .replace(/(name|model|modelParameters|temperature|messages|role|content):/g, '$1:')
- .replace(/(gpt-4o-mini)/g, '$1')
- .replace(/([0-9]\.?[0-9]*)/g, '$1')
- .replace(/(true|false)/g, '$1')
- .replace(/(\{\{[^}]+\}\})/g, '$1'); // Highlight the new variable format
-
- yamlContent.innerHTML = highlightedYaml;
-
- // Add copy functionality - use the plain text version for clipboard
- modalCopyButton.addEventListener("click", async () => {
- try {
- await navigator.clipboard.writeText(yamlText);
- modalCopyButton.innerHTML = `
-
- `;
- setTimeout(() => {
- modalCopyButton.innerHTML = `
-
- `;
- }, 2000);
- } catch (err) {
- alert("Failed to copy YAML to clipboard");
- }
- });
-
- // Add create functionality
- createYamlButton.addEventListener("click", () => {
- const orgName = document.getElementById('github-org').value.trim();
- const repoName = document.getElementById('github-repo').value.trim();
- const branchName = document.getElementById('github-branch').value.trim();
- const filename = cleanTitle.toLocaleLowerCase().replace(/\s+/g, '-') + '.prompt.yml';
- const encodedYaml = encodeURIComponent(yamlText);
- const githubUrl = `https://github.com/${orgName}/${repoName}/new/${branchName}?filename=${filename}&value=${encodedYaml}`;
- window.open(githubUrl, '_blank');
- });
-
- modalOverlay.style.display = "block";
- document.body.style.overflow = "hidden";
-}
diff --git a/scripts/find-prompt b/scripts/find-prompt
deleted file mode 100755
index 07fe008d..00000000
--- a/scripts/find-prompt
+++ /dev/null
@@ -1,3 +0,0 @@
-#!/usr/bin/env bash
-
-curl -s https://raw.githubusercontent.com/f/awesome-chatgpt-prompts/main/prompts.csv | tail +2 | ruby -rcsv -e 'CSV.parse(STDIN.read) {|row| puts row.join("\t")}' | fzf -1 -q "$1" --with-nth 1 --delimiter "\t" --preview 'echo {2} | fold -s -w $(tput cols)' | cut -d" " -f2
diff --git a/sponsors/index.md b/sponsors/index.md
deleted file mode 100644
index e0a39c1d..00000000
--- a/sponsors/index.md
+++ /dev/null
@@ -1,11 +0,0 @@
----
-title: /github sponsors profile
-subtitle: Improve your GitHub Sponsors profile with AI-powered prompts
-hide_platform_selector: true
-hide_extension_link: true
-hide_tone_selector: true
-subpage: true
-body_class: sponsors
----
-
-
diff --git a/sponsors/prompts.json b/sponsors/prompts.json
deleted file mode 100644
index d84f46d0..00000000
--- a/sponsors/prompts.json
+++ /dev/null
@@ -1,142 +0,0 @@
-[
- {
- "id": 1,
- "category": "Crafting a Compelling Bio",
- "title": "Create a Professional Bio",
- "prompt": "Write a GitHub Sponsors bio for my profile that highlights my experience in [your field], the impact of my open source work, and my commitment to community growth.",
- "tags": ["bio", "profile", "introduction"]
- },
- {
- "id": 2,
- "category": "Crafting a Compelling Bio",
- "title": "Showcase Top Repositories",
- "prompt": "Summarize my top three repositories ([repo1], [repo2], [repo3]) in a way that inspires potential sponsors to support my work.",
- "tags": ["bio", "repositories", "showcase"]
- },
- {
- "id": 3,
- "category": "Explaining Why Funding Matters",
- "title": "Explain Funding Impact",
- "prompt": "Create a section for my Sponsors page that explains how funding will help me dedicate more time to [project/topics], support new contributors, and ensure the sustainability of my open source work.",
- "tags": ["funding", "impact", "sustainability"]
- },
- {
- "id": 4,
- "category": "Explaining Why Funding Matters",
- "title": "Show Direct Impact",
- "prompt": "Write a paragraph that shows sponsors the direct impact their funding will have on my projects and the wider community.",
- "tags": ["funding", "impact", "community"]
- },
- {
- "id": 5,
- "category": "Unlocking New Pricing Tiers",
- "title": "Suggest Pricing Tiers",
- "prompt": "Suggest ideas for pricing tiers on GitHub Sponsors, including unique benefits at each level for individuals and companies.",
- "tags": ["pricing", "tiers", "benefits"]
- },
- {
- "id": 6,
- "category": "Unlocking New Pricing Tiers",
- "title": "Write Tier Descriptions",
- "prompt": "Write descriptions for three GitHub Sponsors tiers ($5, $25, $100) that offer increasing value and recognition to supporters.",
- "tags": ["pricing", "tiers", "descriptions"]
- },
- {
- "id": 7,
- "category": "Spotlighting Your Project",
- "title": "Create Project Spotlight",
- "prompt": "Draft a brief 'Project Spotlight' section for my Sponsors page, showcasing the goals, achievements, and roadmap of [project name].",
- "tags": ["project", "spotlight", "roadmap"]
- },
- {
- "id": 8,
- "category": "Spotlighting Your Project",
- "title": "Announce Milestone",
- "prompt": "Write an announcement for my Sponsors page about a new milestone or feature in [project], encouraging new and existing sponsors to get involved.",
- "tags": ["project", "milestone", "announcement"]
- },
- {
- "id": 9,
- "category": "Enhancing Community Engagement",
- "title": "Recognize Sponsors",
- "prompt": "List ways I can recognize or involve sponsors in my project's community (e.g., special Discord roles, early feature access, private Q&A sessions).",
- "tags": ["community", "engagement", "recognition"]
- },
- {
- "id": 10,
- "category": "Enhancing Community Engagement",
- "title": "Creative Perks",
- "prompt": "Suggest creative perks or acknowledgments for sponsors to foster a sense of belonging and appreciation.",
- "tags": ["community", "perks", "appreciation"]
- },
- {
- "id": 11,
- "category": "Crafting a Compelling Bio",
- "title": "Tell Your Story",
- "prompt": "Write a personal story about why I started contributing to open source, what drives me, and how sponsorship helps me continue this journey in [field/technology].",
- "tags": ["bio", "story", "personal"]
- },
- {
- "id": 12,
- "category": "Explaining Why Funding Matters",
- "title": "Break Down Costs",
- "prompt": "Create a transparent breakdown of how sponsor funds will be used (e.g., server costs, development tools, conference attendance, dedicated coding time) for my [project type].",
- "tags": ["funding", "transparency", "costs"]
- },
- {
- "id": 13,
- "category": "Unlocking New Pricing Tiers",
- "title": "Enterprise Sponsorship",
- "prompt": "Design enterprise-level sponsorship tiers ($500, $1000, $5000) with benefits like priority support, custom features, and brand visibility for my [project].",
- "tags": ["pricing", "enterprise", "tiers"]
- },
- {
- "id": 14,
- "category": "Spotlighting Your Project",
- "title": "Impact Metrics",
- "prompt": "Create a compelling data-driven section showing the impact of [project name]: downloads, users helped, issues resolved, and community growth statistics.",
- "tags": ["project", "metrics", "impact"]
- },
- {
- "id": 15,
- "category": "Enhancing Community Engagement",
- "title": "Sponsor Hall of Fame",
- "prompt": "Design a 'Sponsor Hall of Fame' section for my README and Sponsors page that creatively showcases and thanks all contributors at different tiers.",
- "tags": ["community", "recognition", "readme"]
- },
- {
- "id": 16,
- "category": "Crafting a Compelling Bio",
- "title": "Future Vision",
- "prompt": "Write a compelling vision statement about where I see [project/work] going in the next 2-3 years and how sponsors can be part of that journey.",
- "tags": ["bio", "vision", "future"]
- },
- {
- "id": 17,
- "category": "Explaining Why Funding Matters",
- "title": "Time Commitment",
- "prompt": "Explain how sponsorship would allow me to dedicate [X hours/days] per week/month to open source, comparing current volunteer time vs. potential sponsored time.",
- "tags": ["funding", "time", "commitment"]
- },
- {
- "id": 18,
- "category": "Unlocking New Pricing Tiers",
- "title": "Student Tier",
- "prompt": "Create a special $1-2 student sponsorship tier with meaningful benefits that acknowledges their support while respecting their budget.",
- "tags": ["pricing", "students", "accessibility"]
- },
- {
- "id": 19,
- "category": "Spotlighting Your Project",
- "title": "Success Stories",
- "prompt": "Write 3-5 brief success stories or testimonials from users who have benefited from [project name], showing real-world impact.",
- "tags": ["project", "testimonials", "success"]
- },
- {
- "id": 20,
- "category": "Enhancing Community Engagement",
- "title": "Monthly Updates",
- "prompt": "Create a template for monthly sponsor updates that includes progress, challenges, wins, and upcoming features for [project].",
- "tags": ["community", "updates", "communication"]
- }
-]
diff --git a/sponsors/script.js b/sponsors/script.js
deleted file mode 100644
index 0070c395..00000000
--- a/sponsors/script.js
+++ /dev/null
@@ -1,208 +0,0 @@
-// GitHub Sponsors Profile Improver Script
-
-// Dark mode functionality (global scope for onclick handler)
-window.toggleDarkMode = function() {
- const body = document.body;
- const toggle = document.querySelector(".dark-mode-toggle");
- const sunIcon = toggle.querySelector(".sun-icon");
- const moonIcon = toggle.querySelector(".moon-icon");
-
- body.classList.toggle("dark-mode");
- const isDarkMode = body.classList.contains("dark-mode");
-
- localStorage.setItem("dark-mode", isDarkMode);
- sunIcon.style.display = isDarkMode ? "none" : "block";
- moonIcon.style.display = isDarkMode ? "block" : "none";
-};
-
-(function() {
- let sponsorPrompts = [];
-
- // Load prompts from JSON file
- async function loadSponsorsPrompts() {
- try {
- const response = await fetch('/sponsors/prompts.json');
- sponsorPrompts = await response.json();
- renderSponsorsPrompts();
- renderSidebarPrompts();
- } catch (error) {
- console.error('Error loading prompts:', error);
- const container = document.querySelector('#promptContent');
- if (container) {
- container.innerHTML = 'Error loading prompts. Please try again later.
'; - } - } - } - - // Copy prompt to clipboard - window.copySponsorPrompt = function(button, promptText) { - navigator.clipboard.writeText(promptText).then(() => { - const originalHTML = button.innerHTML; - button.innerHTML = ` - - `; - button.style.background = '#10b981'; - - setTimeout(() => { - button.innerHTML = originalHTML; - button.style.background = ''; - }, 2000); - }).catch(err => { - console.error('Failed to copy:', err); - }); - } - - // Open prompt in GitHub Copilot - window.openInCopilot = function(promptText) { - const encodedPrompt = encodeURIComponent(promptText); - const copilotUrl = `https://github.com/copilot?prompt=${encodedPrompt}`; - window.open(copilotUrl, '_blank'); - } - - // Render prompts in the sidebar - function renderSidebarPrompts() { - const searchResults = document.getElementById('searchResults'); - if (searchResults && sponsorPrompts.length > 0) { - searchResults.innerHTML = sponsorPrompts.map(prompt => ` -${prompt.prompt}
- -{t("loginDescription")}
++ {t("noAccount")}{" "} + {t("register")} +
+{t("registerDescription")}
++ {t("hasAccount")}{" "} + {t("login")} +
+@{user.username}
+{t("noPromptsOwner")}
+ +{tChanges("noRequests")}
+{cr.prompt.title}
++ {formatDistanceToNow(cr.createdAt, locale)} +
+{t("noPrompts")}
+{t("description")}
++ {category.description} +
+ )} +{t("description")}
+{t("noCategories")}
++ {category.description} +
+ )} ++ {t("description")} +
++ {t("helpfulLinks")} +
++ {t("feedDescription")} +
++ {t("subscribeToCategories")} +
+ + {/* Category suggestions */} ++ {tHomepage("heroDescription")} +
+{tHomepage("freeAndOpen")}
+{t("reason")}
+{changeRequest.reason}
+{t("titleChange")}
+{t("contentChanges")}
+{t("reviewNote")}
+{changeRequest.reviewNote}
++ {prompt.title} +
+Update your prompt details
+{prompt.description}
+ )} +
+ {prompt.content}
+
+ )}
+ {t("noVersions")}
+ ) : ( ++ {version.changeNote} +
+ )} ++ {cr.reason} +
+ )} ++ {t("description")} +
++ {tPrompts("noPrompts")} +
+{t("description")}
+{t("noTags")}
+{t("description")}
+{t("description")}
+{t("description")}
+{t("placeholders")}:
+{user.name}
++ @{user.username} +
+{t("reviewActions")}
++ {t("noPromptsDescription")} +
+{t("noMorePrompts")}
+ )} +{prompt.description}
+ )} + + {/* Content Preview - smaller for image prompts */} +
+ {prompt.content}
+
+ )}
+ + {t("noPromptsDescription")} +
+
+
+ {displayLines.map((line, i) => (
+
+
+ {i + 1}
+
+
+ {highlightLine(line, language)}
+
+
+ ))}
+ {hasMore && (
+
+
+ ...
+
+
+ {t("moreLines", { count: lines.length - (maxLines || 0) })}
+
+
+ )}
+
+
+
+ {line.text || " "}
+
+ + {body} +
+ ) +} + +export { + useFormField, + Form, + FormItem, + FormLabel, + FormControl, + FormDescription, + FormMessage, + FormField, +} diff --git a/src/components/ui/input.tsx b/src/components/ui/input.tsx new file mode 100644 index 00000000..7e17f821 --- /dev/null +++ b/src/components/ui/input.tsx @@ -0,0 +1,21 @@ +import * as React from "react" + +import { cn } from "@/lib/utils" + +function Input({ className, type, ...props }: React.ComponentProps<"input">) { + return ( + + ) +} + +export { Input } diff --git a/src/components/ui/label.tsx b/src/components/ui/label.tsx new file mode 100644 index 00000000..fb5fbc3e --- /dev/null +++ b/src/components/ui/label.tsx @@ -0,0 +1,24 @@ +"use client" + +import * as React from "react" +import * as LabelPrimitive from "@radix-ui/react-label" + +import { cn } from "@/lib/utils" + +function Label({ + className, + ...props +}: React.ComponentProps${prompt.replace(/\\n/g, '
')}