chore(raycast-extension): Add initial project files and configurations
This commit is contained in:
@@ -0,0 +1,21 @@
|
||||
# Dependencies
|
||||
node_modules/
|
||||
|
||||
# Build output
|
||||
dist/
|
||||
|
||||
# Raycast
|
||||
.raycast/
|
||||
|
||||
# OS
|
||||
.DS_Store
|
||||
|
||||
# IDE
|
||||
.idea/
|
||||
.vscode/
|
||||
*.swp
|
||||
*.swo
|
||||
|
||||
# Logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
@@ -0,0 +1,52 @@
|
||||
# Prompts.chat
|
||||
|
||||
Search, browse, and run AI prompts from [prompts.chat](https://prompts.chat) — the open-source community platform for ChatGPT and AI prompts.
|
||||
|
||||
## Features
|
||||
|
||||
- **Search Prompts** — Quickly search through thousands of community AI prompts
|
||||
- **Browse Prompts** — Browse latest prompts with filtering by type and sorting options
|
||||
- **Random Prompt** — Get a random prompt for inspiration
|
||||
- **Run Prompts** — Run prompts directly in ChatGPT, Claude, Cursor, and 25+ other AI platforms
|
||||
- **Variable Support** — Fill in prompt variables before running
|
||||
|
||||
## Commands
|
||||
|
||||
| Command | Description |
|
||||
|---------|-------------|
|
||||
| Search Prompts | Search for AI prompts from the community |
|
||||
| Browse Prompts | Browse and filter latest AI prompts |
|
||||
| Random Prompt | Get a random AI prompt for inspiration |
|
||||
|
||||
## Running Prompts
|
||||
|
||||
Press **Enter** on any prompt to run it. You can choose from:
|
||||
|
||||
**Chat Platforms:** ChatGPT, Claude, Microsoft Copilot, DeepSeek, Gemini, Grok, Perplexity, and more
|
||||
|
||||
**Code Platforms:** Cursor, Windsurf, VS Code, GitHub Copilot, Bolt, Lovable, v0, and more
|
||||
|
||||
If a prompt contains variables (e.g., `${topic}`), you'll be prompted to fill them in first.
|
||||
|
||||
## Keyboard Shortcuts
|
||||
|
||||
| Action | Shortcut |
|
||||
|--------|----------|
|
||||
| Run Prompt | ↵ Enter |
|
||||
| Copy Prompt | ⌘ C |
|
||||
| Open in Browser | ⌘ O |
|
||||
| Copy URL | ⌘ ⇧ C |
|
||||
| Refresh | ⌘ R |
|
||||
|
||||
## Configuration
|
||||
|
||||
| Preference | Description | Default |
|
||||
|------------|-------------|---------|
|
||||
| Base URL | The base URL of the prompts.chat instance | `https://prompts.chat` |
|
||||
|
||||
You can use this extension with self-hosted instances of prompts.chat by changing the Base URL.
|
||||
|
||||
## Links
|
||||
|
||||
- [prompts.chat](https://prompts.chat) — Browse prompts on the web
|
||||
- [GitHub Repository](https://github.com/f/awesome-chatgpt-prompts) — Source code and contributions
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 51 KiB |
@@ -0,0 +1,8 @@
|
||||
import raycastConfig from "@raycast/eslint-config";
|
||||
|
||||
export default [
|
||||
{
|
||||
ignores: ["node_modules/**", "dist/**"],
|
||||
},
|
||||
...raycastConfig,
|
||||
];
|
||||
+3602
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,78 @@
|
||||
{
|
||||
"$schema": "https://www.raycast.com/schemas/extension.json",
|
||||
"name": "prompts-chat",
|
||||
"title": "Prompts.chat",
|
||||
"description": "Search, browse, and copy AI prompts from prompts.chat - the community platform for ChatGPT and AI prompts",
|
||||
"icon": "icon.png",
|
||||
"author": "fka",
|
||||
"categories": ["Productivity", "Developer Tools"],
|
||||
"license": "MIT",
|
||||
"platforms": ["macOS"],
|
||||
"commands": [
|
||||
{
|
||||
"name": "search-prompts",
|
||||
"title": "Search Prompts",
|
||||
"subtitle": "prompts.chat",
|
||||
"description": "Search for AI prompts from the community",
|
||||
"mode": "view"
|
||||
},
|
||||
{
|
||||
"name": "browse-prompts",
|
||||
"title": "Browse Prompts",
|
||||
"subtitle": "prompts.chat",
|
||||
"description": "Browse and filter latest AI prompts",
|
||||
"mode": "view"
|
||||
},
|
||||
{
|
||||
"name": "random-prompt",
|
||||
"title": "Random Prompt",
|
||||
"subtitle": "prompts.chat",
|
||||
"description": "Get a random AI prompt for inspiration",
|
||||
"mode": "view"
|
||||
},
|
||||
{
|
||||
"name": "download-prompts",
|
||||
"title": "Download All Prompts",
|
||||
"subtitle": "prompts.chat",
|
||||
"description": "Download and cache all prompts for offline use",
|
||||
"mode": "view"
|
||||
},
|
||||
{
|
||||
"name": "browse-categories",
|
||||
"title": "Browse Categories",
|
||||
"subtitle": "prompts.chat",
|
||||
"description": "Browse prompts by category",
|
||||
"mode": "view"
|
||||
}
|
||||
],
|
||||
"preferences": [
|
||||
{
|
||||
"name": "baseUrl",
|
||||
"title": "Base URL",
|
||||
"description": "The base URL of the prompts.chat instance",
|
||||
"type": "textfield",
|
||||
"default": "https://prompts.chat",
|
||||
"required": false
|
||||
}
|
||||
],
|
||||
"dependencies": {
|
||||
"@raycast/api": "^1.96.1",
|
||||
"@raycast/utils": "^1.19.1",
|
||||
"node-fetch": "^3.3.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@raycast/eslint-config": "^1.0.11",
|
||||
"@types/node": "22.13.14",
|
||||
"@types/react": "19.0.10",
|
||||
"eslint": "^8.57.0",
|
||||
"prettier": "^3.5.3",
|
||||
"typescript": "^5.8.2"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "ray build",
|
||||
"dev": "ray develop",
|
||||
"fix-lint": "ray lint --fix",
|
||||
"lint": "ray lint",
|
||||
"publish": "npx @raycast/api@latest publish"
|
||||
}
|
||||
}
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
/// <reference types="@raycast/api">
|
||||
|
||||
/* 🚧 🚧 🚧
|
||||
* This file is auto-generated from the extension's manifest.
|
||||
* Do not modify manually. Instead, update the `package.json` file.
|
||||
* 🚧 🚧 🚧 */
|
||||
|
||||
/* eslint-disable @typescript-eslint/ban-types */
|
||||
|
||||
type ExtensionPreferences = {
|
||||
/** Base URL - The base URL of the prompts.chat instance */
|
||||
"baseUrl": string
|
||||
}
|
||||
|
||||
/** Preferences accessible in all the extension's commands */
|
||||
declare type Preferences = ExtensionPreferences
|
||||
|
||||
declare namespace Preferences {
|
||||
/** Preferences accessible in the `search-prompts` command */
|
||||
export type SearchPrompts = ExtensionPreferences & {}
|
||||
/** Preferences accessible in the `browse-prompts` command */
|
||||
export type BrowsePrompts = ExtensionPreferences & {}
|
||||
/** Preferences accessible in the `random-prompt` command */
|
||||
export type RandomPrompt = ExtensionPreferences & {}
|
||||
/** Preferences accessible in the `download-prompts` command */
|
||||
export type DownloadPrompts = ExtensionPreferences & {}
|
||||
/** Preferences accessible in the `browse-categories` command */
|
||||
export type BrowseCategories = ExtensionPreferences & {}
|
||||
}
|
||||
|
||||
declare namespace Arguments {
|
||||
/** Arguments passed to the `search-prompts` command */
|
||||
export type SearchPrompts = {}
|
||||
/** Arguments passed to the `browse-prompts` command */
|
||||
export type BrowsePrompts = {}
|
||||
/** Arguments passed to the `random-prompt` command */
|
||||
export type RandomPrompt = {}
|
||||
/** Arguments passed to the `download-prompts` command */
|
||||
export type DownloadPrompts = {}
|
||||
/** Arguments passed to the `browse-categories` command */
|
||||
export type BrowseCategories = {}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,98 @@
|
||||
import { getPreferenceValues } from "@raycast/api";
|
||||
import type { Preferences, Prompt, PromptsResponse } from "./types";
|
||||
|
||||
function getBaseUrl(): string {
|
||||
const { baseUrl } = getPreferenceValues<Preferences>();
|
||||
return baseUrl?.replace(/\/$/, "") || "https://prompts.chat";
|
||||
}
|
||||
|
||||
export async function fetchPrompts(options: {
|
||||
page?: number;
|
||||
perPage?: number;
|
||||
type?: string;
|
||||
category?: string;
|
||||
tag?: string;
|
||||
sort?: string;
|
||||
q?: string;
|
||||
}): Promise<PromptsResponse> {
|
||||
const baseUrl = getBaseUrl();
|
||||
const params = new URLSearchParams();
|
||||
|
||||
if (options.page) params.set("page", options.page.toString());
|
||||
if (options.perPage) params.set("perPage", options.perPage.toString());
|
||||
if (options.type) params.set("type", options.type);
|
||||
if (options.category) params.set("category", options.category);
|
||||
if (options.tag) params.set("tag", options.tag);
|
||||
if (options.sort) params.set("sort", options.sort);
|
||||
if (options.q) params.set("q", options.q);
|
||||
|
||||
const url = `${baseUrl}/api/prompts?${params.toString()}`;
|
||||
const response = await fetch(url);
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`Failed to fetch prompts: ${response.statusText}`);
|
||||
}
|
||||
|
||||
return response.json() as Promise<PromptsResponse>;
|
||||
}
|
||||
|
||||
export async function fetchPromptBySlug(
|
||||
username: string,
|
||||
slug: string,
|
||||
): Promise<Prompt | null> {
|
||||
const baseUrl = getBaseUrl();
|
||||
|
||||
const response = await fetch(`${baseUrl}/api/prompts/${slug}`);
|
||||
|
||||
if (!response.ok) {
|
||||
if (response.status === 404) {
|
||||
return null;
|
||||
}
|
||||
throw new Error(`Failed to fetch prompt: ${response.statusText}`);
|
||||
}
|
||||
|
||||
return response.json() as Promise<Prompt>;
|
||||
}
|
||||
|
||||
export async function searchPrompts(
|
||||
query: string,
|
||||
limit: number = 20,
|
||||
): Promise<
|
||||
{ id: string; title: string; slug: string; author: { username: string } }[]
|
||||
> {
|
||||
if (query.length < 2) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const baseUrl = getBaseUrl();
|
||||
const params = new URLSearchParams({
|
||||
q: query,
|
||||
limit: limit.toString(),
|
||||
});
|
||||
|
||||
const url = `${baseUrl}/api/prompts/search?${params.toString()}`;
|
||||
const response = await fetch(url);
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`Failed to search prompts: ${response.statusText}`);
|
||||
}
|
||||
|
||||
const data = (await response.json()) as {
|
||||
prompts: {
|
||||
id: string;
|
||||
title: string;
|
||||
slug: string;
|
||||
author: { username: string };
|
||||
}[];
|
||||
};
|
||||
return data.prompts;
|
||||
}
|
||||
|
||||
export function getPromptUrl(username: string, slug: string): string {
|
||||
const baseUrl = getBaseUrl();
|
||||
return `${baseUrl}/${username}/${slug}`;
|
||||
}
|
||||
|
||||
export function getWebsiteUrl(): string {
|
||||
return getBaseUrl();
|
||||
}
|
||||
@@ -0,0 +1,264 @@
|
||||
import {
|
||||
Action,
|
||||
ActionPanel,
|
||||
Icon,
|
||||
List,
|
||||
showToast,
|
||||
Toast,
|
||||
Clipboard,
|
||||
openExtensionPreferences,
|
||||
} from "@raycast/api";
|
||||
import { useState, useEffect, useMemo } from "react";
|
||||
import type { Prompt } from "./types";
|
||||
import { getPromptUrl } from "./api";
|
||||
import { RunPromptForm } from "./components/run-prompt";
|
||||
import { PromptDetailView } from "./components/prompt-detail";
|
||||
import {
|
||||
getCachedPrompts,
|
||||
convertToPrompt,
|
||||
downloadAllPrompts,
|
||||
type CachedPrompt,
|
||||
} from "./cache";
|
||||
|
||||
interface Category {
|
||||
name: string;
|
||||
slug: string;
|
||||
count: number;
|
||||
}
|
||||
|
||||
export default function BrowseCategories() {
|
||||
const [allPrompts, setAllPrompts] = useState<CachedPrompt[]>([]);
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const [selectedCategory, setSelectedCategory] = useState<string | null>(null);
|
||||
const [searchText, setSearchText] = useState("");
|
||||
|
||||
async function handleDownload() {
|
||||
setIsLoading(true);
|
||||
try {
|
||||
await showToast({ style: Toast.Style.Animated, title: "Downloading prompts..." });
|
||||
const prompts = await downloadAllPrompts();
|
||||
setAllPrompts(prompts);
|
||||
await showToast({ style: Toast.Style.Success, title: `Downloaded ${prompts.length} prompts` });
|
||||
} catch (err) {
|
||||
await showToast({ style: Toast.Style.Failure, title: "Download failed", message: String(err) });
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
async function loadPrompts() {
|
||||
const cached = await getCachedPrompts();
|
||||
setAllPrompts(cached);
|
||||
setIsLoading(false);
|
||||
}
|
||||
loadPrompts();
|
||||
}, []);
|
||||
|
||||
const categories = useMemo(() => {
|
||||
const categoryMap = new Map<string, Category>();
|
||||
|
||||
for (const prompt of allPrompts) {
|
||||
if (prompt.category) {
|
||||
const existing = categoryMap.get(prompt.category.slug);
|
||||
if (existing) {
|
||||
existing.count++;
|
||||
} else {
|
||||
categoryMap.set(prompt.category.slug, {
|
||||
name: prompt.category.name,
|
||||
slug: prompt.category.slug,
|
||||
count: 1,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Array.from(categoryMap.values()).sort((a, b) => b.count - a.count);
|
||||
}, [allPrompts]);
|
||||
|
||||
const categoryPrompts = useMemo(() => {
|
||||
if (!selectedCategory) return [];
|
||||
|
||||
let result = allPrompts.filter((p) => p.category?.slug === selectedCategory);
|
||||
|
||||
if (searchText.trim()) {
|
||||
const query = searchText.toLowerCase();
|
||||
result = result.filter(
|
||||
(p) =>
|
||||
p.title.toLowerCase().includes(query) ||
|
||||
(p.description && p.description.toLowerCase().includes(query)) ||
|
||||
p.author.username.toLowerCase().includes(query)
|
||||
);
|
||||
}
|
||||
|
||||
return result.sort((a, b) => b.voteCount - a.voteCount);
|
||||
}, [allPrompts, selectedCategory, searchText]);
|
||||
|
||||
if (allPrompts.length === 0 && !isLoading) {
|
||||
return (
|
||||
<List>
|
||||
<List.EmptyView
|
||||
icon={Icon.Download}
|
||||
title="No prompts cached"
|
||||
description="Press Enter to download all prompts"
|
||||
actions={
|
||||
<ActionPanel>
|
||||
<Action
|
||||
title="Download All Prompts"
|
||||
icon={Icon.Download}
|
||||
onAction={handleDownload}
|
||||
/>
|
||||
</ActionPanel>
|
||||
}
|
||||
/>
|
||||
</List>
|
||||
);
|
||||
}
|
||||
|
||||
if (selectedCategory) {
|
||||
const categoryName = categories.find((c) => c.slug === selectedCategory)?.name || selectedCategory;
|
||||
|
||||
return (
|
||||
<List
|
||||
isLoading={isLoading}
|
||||
navigationTitle={categoryName}
|
||||
searchBarPlaceholder={`Search in ${categoryName}...`}
|
||||
onSearchTextChange={setSearchText}
|
||||
throttle
|
||||
>
|
||||
{categoryPrompts.length === 0 ? (
|
||||
<List.EmptyView
|
||||
icon={Icon.XMarkCircle}
|
||||
title="No prompts found"
|
||||
description={searchText ? `No prompts match "${searchText}"` : "No prompts in this category"}
|
||||
actions={
|
||||
<ActionPanel>
|
||||
<Action
|
||||
title="Back to Categories"
|
||||
icon={Icon.ArrowLeft}
|
||||
onAction={() => setSelectedCategory(null)}
|
||||
/>
|
||||
</ActionPanel>
|
||||
}
|
||||
/>
|
||||
) : (
|
||||
categoryPrompts.map((cached) => (
|
||||
<PromptListItem
|
||||
key={cached.id}
|
||||
prompt={convertToPrompt(cached)}
|
||||
onBack={() => setSelectedCategory(null)}
|
||||
/>
|
||||
))
|
||||
)}
|
||||
</List>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<List
|
||||
isLoading={isLoading}
|
||||
searchBarPlaceholder="Search categories..."
|
||||
onSearchTextChange={setSearchText}
|
||||
throttle
|
||||
>
|
||||
{categories.length === 0 ? (
|
||||
<List.EmptyView
|
||||
icon={Icon.Folder}
|
||||
title="No categories found"
|
||||
description="Prompts don't have categories assigned"
|
||||
/>
|
||||
) : (
|
||||
categories
|
||||
.filter((c) => !searchText || c.name.toLowerCase().includes(searchText.toLowerCase()))
|
||||
.map((category) => (
|
||||
<List.Item
|
||||
key={category.slug}
|
||||
title={category.name}
|
||||
subtitle={`${category.count} prompts`}
|
||||
icon={Icon.Folder}
|
||||
actions={
|
||||
<ActionPanel>
|
||||
<Action
|
||||
title="View Prompts"
|
||||
icon={Icon.List}
|
||||
onAction={() => {
|
||||
setSelectedCategory(category.slug);
|
||||
setSearchText("");
|
||||
}}
|
||||
/>
|
||||
</ActionPanel>
|
||||
}
|
||||
/>
|
||||
))
|
||||
)}
|
||||
</List>
|
||||
);
|
||||
}
|
||||
|
||||
function PromptListItem({ prompt, onBack }: { prompt: Prompt; onBack: () => void }) {
|
||||
const promptUrl = getPromptUrl(prompt.author.username, prompt.slug);
|
||||
|
||||
return (
|
||||
<List.Item
|
||||
title={prompt.title}
|
||||
subtitle={`@${prompt.author.username} • ${prompt.voteCount} upvotes`}
|
||||
accessories={[{ tag: prompt.type }]}
|
||||
actions={
|
||||
<ActionPanel>
|
||||
<ActionPanel.Section title="Run">
|
||||
<Action.Push
|
||||
title="Run Prompt"
|
||||
icon={Icon.Play}
|
||||
target={<RunPromptForm prompt={prompt} />}
|
||||
/>
|
||||
</ActionPanel.Section>
|
||||
<ActionPanel.Section>
|
||||
<Action.CopyToClipboard
|
||||
title="Copy Prompt"
|
||||
content={prompt.content}
|
||||
shortcut={{ modifiers: ["cmd"], key: "c" }}
|
||||
/>
|
||||
<Action.OpenInBrowser
|
||||
title="Open in Browser"
|
||||
url={promptUrl}
|
||||
shortcut={{ modifiers: ["cmd"], key: "o" }}
|
||||
/>
|
||||
<Action
|
||||
title="Copy URL"
|
||||
icon={Icon.Link}
|
||||
shortcut={{ modifiers: ["cmd", "shift"], key: "c" }}
|
||||
onAction={async () => {
|
||||
await Clipboard.copy(promptUrl);
|
||||
await showToast({
|
||||
style: Toast.Style.Success,
|
||||
title: "URL copied to clipboard",
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</ActionPanel.Section>
|
||||
<ActionPanel.Section>
|
||||
<Action.Push
|
||||
title="View Details"
|
||||
icon={Icon.Eye}
|
||||
target={<PromptDetailView prompt={prompt} />}
|
||||
shortcut={{ modifiers: ["cmd"], key: "d" }}
|
||||
/>
|
||||
</ActionPanel.Section>
|
||||
<ActionPanel.Section>
|
||||
<Action
|
||||
title="Back to Categories"
|
||||
icon={Icon.ArrowLeft}
|
||||
onAction={onBack}
|
||||
shortcut={{ modifiers: ["cmd"], key: "b" }}
|
||||
/>
|
||||
<Action
|
||||
title="Open Extension Preferences"
|
||||
icon={Icon.Gear}
|
||||
onAction={openExtensionPreferences}
|
||||
/>
|
||||
</ActionPanel.Section>
|
||||
</ActionPanel>
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,212 @@
|
||||
import {
|
||||
Action,
|
||||
ActionPanel,
|
||||
Icon,
|
||||
List,
|
||||
showToast,
|
||||
Toast,
|
||||
Clipboard,
|
||||
openExtensionPreferences,
|
||||
} from "@raycast/api";
|
||||
import { useState, useEffect, useMemo } from "react";
|
||||
import type { Prompt } from "./types";
|
||||
import { getPromptUrl } from "./api";
|
||||
import { RunPromptForm } from "./components/run-prompt";
|
||||
import { PromptDetailView } from "./components/prompt-detail";
|
||||
import {
|
||||
getCachedPrompts,
|
||||
convertToPrompt,
|
||||
type CachedPrompt,
|
||||
} from "./cache";
|
||||
|
||||
const TYPE_OPTIONS = [
|
||||
{ title: "All Types", value: "" },
|
||||
{ title: "Text", value: "TEXT" },
|
||||
{ title: "Image", value: "IMAGE" },
|
||||
{ title: "Video", value: "VIDEO" },
|
||||
{ title: "Audio", value: "AUDIO" },
|
||||
{ title: "Skill", value: "SKILL" },
|
||||
];
|
||||
|
||||
const SORT_OPTIONS = [
|
||||
{ title: "Most Upvoted", value: "upvotes" },
|
||||
{ title: "Most Recent", value: "recent" },
|
||||
{ title: "By Author", value: "author" },
|
||||
];
|
||||
|
||||
export default function BrowsePrompts() {
|
||||
const [searchText, setSearchText] = useState("");
|
||||
const [type, setType] = useState("");
|
||||
const [sort, setSort] = useState("upvotes");
|
||||
const [allPrompts, setAllPrompts] = useState<CachedPrompt[]>([]);
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
async function loadPrompts() {
|
||||
const cached = await getCachedPrompts();
|
||||
setAllPrompts(cached);
|
||||
setIsLoading(false);
|
||||
|
||||
if (cached.length === 0) {
|
||||
showToast({
|
||||
style: Toast.Style.Animated,
|
||||
title: "No prompts cached",
|
||||
message: "Run 'Download All Prompts' first",
|
||||
});
|
||||
}
|
||||
}
|
||||
loadPrompts();
|
||||
}, []);
|
||||
|
||||
const filteredPrompts = useMemo(() => {
|
||||
let result = [...allPrompts];
|
||||
|
||||
if (type) {
|
||||
result = result.filter((p) => p.type === type);
|
||||
}
|
||||
|
||||
if (searchText.trim()) {
|
||||
const query = searchText.toLowerCase();
|
||||
result = result.filter(
|
||||
(p) =>
|
||||
p.title.toLowerCase().includes(query) ||
|
||||
(p.description && p.description.toLowerCase().includes(query)) ||
|
||||
p.author.username.toLowerCase().includes(query) ||
|
||||
(p.author.name && p.author.name.toLowerCase().includes(query)) ||
|
||||
p.tags.some((tag) => tag.toLowerCase().includes(query)) ||
|
||||
(p.category && p.category.name.toLowerCase().includes(query))
|
||||
);
|
||||
}
|
||||
|
||||
// Sort
|
||||
if (sort === "upvotes") {
|
||||
result.sort((a, b) => b.voteCount - a.voteCount);
|
||||
} else if (sort === "recent") {
|
||||
// Keep original order (already sorted by newest from API)
|
||||
} else if (sort === "author") {
|
||||
result.sort((a, b) => a.author.username.localeCompare(b.author.username));
|
||||
}
|
||||
|
||||
return result;
|
||||
}, [allPrompts, type, searchText, sort]);
|
||||
|
||||
return (
|
||||
<List
|
||||
isLoading={isLoading}
|
||||
searchBarPlaceholder="Filter prompts..."
|
||||
onSearchTextChange={setSearchText}
|
||||
searchBarAccessory={
|
||||
<List.Dropdown
|
||||
tooltip="Filter & Sort"
|
||||
storeValue
|
||||
onChange={(value) => {
|
||||
if (value.startsWith("sort:")) {
|
||||
setSort(value.replace("sort:", ""));
|
||||
} else if (value.startsWith("type:")) {
|
||||
setType(value.replace("type:", ""));
|
||||
}
|
||||
}}
|
||||
>
|
||||
<List.Dropdown.Section title="Sort">
|
||||
{SORT_OPTIONS.map((option) => (
|
||||
<List.Dropdown.Item
|
||||
key={`sort:${option.value}`}
|
||||
title={option.title}
|
||||
value={`sort:${option.value}`}
|
||||
/>
|
||||
))}
|
||||
</List.Dropdown.Section>
|
||||
<List.Dropdown.Section title="Type">
|
||||
{TYPE_OPTIONS.map((option) => (
|
||||
<List.Dropdown.Item
|
||||
key={`type:${option.value}`}
|
||||
title={option.title}
|
||||
value={`type:${option.value}`}
|
||||
/>
|
||||
))}
|
||||
</List.Dropdown.Section>
|
||||
</List.Dropdown>
|
||||
}
|
||||
>
|
||||
{allPrompts.length === 0 ? (
|
||||
<List.EmptyView
|
||||
icon={Icon.Download}
|
||||
title="No prompts cached"
|
||||
description="Run 'Download All Prompts' command first"
|
||||
/>
|
||||
) : filteredPrompts.length === 0 ? (
|
||||
<List.EmptyView
|
||||
icon={Icon.Document}
|
||||
title="No prompts found"
|
||||
description="Try changing the filters"
|
||||
/>
|
||||
) : (
|
||||
filteredPrompts.map((cached) => (
|
||||
<PromptListItem key={cached.id} prompt={convertToPrompt(cached)} />
|
||||
))
|
||||
)}
|
||||
</List>
|
||||
);
|
||||
}
|
||||
|
||||
function PromptListItem({ prompt }: { prompt: Prompt }) {
|
||||
const promptUrl = getPromptUrl(prompt.author.username, prompt.slug);
|
||||
|
||||
return (
|
||||
<List.Item
|
||||
title={prompt.title}
|
||||
subtitle={`@${prompt.author.username} • ${prompt.voteCount} upvotes`}
|
||||
accessories={[{ tag: prompt.type }]}
|
||||
actions={
|
||||
<ActionPanel>
|
||||
<ActionPanel.Section title="Run">
|
||||
<Action.Push
|
||||
title="Run Prompt"
|
||||
icon={Icon.Play}
|
||||
target={<RunPromptForm prompt={prompt} />}
|
||||
/>
|
||||
</ActionPanel.Section>
|
||||
<ActionPanel.Section>
|
||||
<Action.CopyToClipboard
|
||||
title="Copy Prompt"
|
||||
content={prompt.content}
|
||||
shortcut={{ modifiers: ["cmd"], key: "c" }}
|
||||
/>
|
||||
<Action.OpenInBrowser
|
||||
title="Open in Browser"
|
||||
url={promptUrl}
|
||||
shortcut={{ modifiers: ["cmd"], key: "o" }}
|
||||
/>
|
||||
<Action
|
||||
title="Copy URL"
|
||||
icon={Icon.Link}
|
||||
shortcut={{ modifiers: ["cmd", "shift"], key: "c" }}
|
||||
onAction={async () => {
|
||||
await Clipboard.copy(promptUrl);
|
||||
await showToast({
|
||||
style: Toast.Style.Success,
|
||||
title: "URL copied to clipboard",
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</ActionPanel.Section>
|
||||
<ActionPanel.Section>
|
||||
<Action.Push
|
||||
title="View Details"
|
||||
icon={Icon.Eye}
|
||||
target={<PromptDetailView prompt={prompt} />}
|
||||
shortcut={{ modifiers: ["cmd"], key: "d" }}
|
||||
/>
|
||||
</ActionPanel.Section>
|
||||
<ActionPanel.Section>
|
||||
<Action
|
||||
title="Open Extension Preferences"
|
||||
icon={Icon.Gear}
|
||||
onAction={openExtensionPreferences}
|
||||
/>
|
||||
</ActionPanel.Section>
|
||||
</ActionPanel>
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,158 @@
|
||||
import { LocalStorage } from "@raycast/api";
|
||||
import type { Prompt } from "./types";
|
||||
import { getWebsiteUrl } from "./api";
|
||||
|
||||
const PROMPTS_CACHE_KEY = "cached_prompts";
|
||||
const CACHE_TIMESTAMP_KEY = "cached_prompts_timestamp";
|
||||
|
||||
export interface CachedPrompt {
|
||||
id: string;
|
||||
title: string;
|
||||
content: string;
|
||||
description?: string;
|
||||
type: string;
|
||||
author: {
|
||||
username: string;
|
||||
name?: string;
|
||||
};
|
||||
slug: string;
|
||||
category?: {
|
||||
name: string;
|
||||
slug: string;
|
||||
};
|
||||
tags: string[];
|
||||
voteCount: number;
|
||||
}
|
||||
|
||||
export async function getCachedPrompts(): Promise<CachedPrompt[]> {
|
||||
const cached = await LocalStorage.getItem<string>(PROMPTS_CACHE_KEY);
|
||||
if (!cached) return [];
|
||||
try {
|
||||
return JSON.parse(cached) as CachedPrompt[];
|
||||
} catch {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
export async function setCachedPrompts(prompts: CachedPrompt[]): Promise<void> {
|
||||
await LocalStorage.setItem(PROMPTS_CACHE_KEY, JSON.stringify(prompts));
|
||||
await LocalStorage.setItem(CACHE_TIMESTAMP_KEY, Date.now().toString());
|
||||
}
|
||||
|
||||
export async function getCacheTimestamp(): Promise<number | null> {
|
||||
const timestamp = await LocalStorage.getItem<string>(CACHE_TIMESTAMP_KEY);
|
||||
return timestamp ? parseInt(timestamp, 10) : null;
|
||||
}
|
||||
|
||||
export async function clearCache(): Promise<void> {
|
||||
await LocalStorage.removeItem(PROMPTS_CACHE_KEY);
|
||||
await LocalStorage.removeItem(CACHE_TIMESTAMP_KEY);
|
||||
}
|
||||
|
||||
interface PromptsJsonResponse {
|
||||
count: number;
|
||||
prompts: Array<{
|
||||
id: string;
|
||||
title: string;
|
||||
slug: string;
|
||||
description: string | null;
|
||||
content: string;
|
||||
type: string;
|
||||
voteCount: number;
|
||||
category: {
|
||||
id: string;
|
||||
name: string;
|
||||
slug: string;
|
||||
} | null;
|
||||
author: {
|
||||
username: string;
|
||||
name: string | null;
|
||||
};
|
||||
tags: Array<{
|
||||
id: string;
|
||||
name: string;
|
||||
slug: string;
|
||||
}>;
|
||||
}>;
|
||||
}
|
||||
|
||||
export async function downloadAllPrompts(): Promise<CachedPrompt[]> {
|
||||
const url = `${getWebsiteUrl()}/prompts.json`;
|
||||
const response = await fetch(url);
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`Failed to fetch prompts: ${response.statusText}`);
|
||||
}
|
||||
|
||||
const data = (await response.json()) as PromptsJsonResponse;
|
||||
|
||||
const prompts: CachedPrompt[] = data.prompts.map((item) => ({
|
||||
id: item.id,
|
||||
title: item.title,
|
||||
content: item.content,
|
||||
description: item.description || undefined,
|
||||
type: item.type,
|
||||
author: {
|
||||
username: item.author.username,
|
||||
name: item.author.name || undefined,
|
||||
},
|
||||
slug: item.slug,
|
||||
category: item.category
|
||||
? { name: item.category.name, slug: item.category.slug }
|
||||
: undefined,
|
||||
tags: item.tags.map((t) => t.name),
|
||||
voteCount: item.voteCount,
|
||||
}));
|
||||
|
||||
await setCachedPrompts(prompts);
|
||||
return prompts;
|
||||
}
|
||||
|
||||
export function searchPrompts(prompts: CachedPrompt[], query: string): CachedPrompt[] {
|
||||
if (!query.trim()) return prompts;
|
||||
|
||||
const lowerQuery = query.toLowerCase();
|
||||
return prompts.filter(
|
||||
(p) =>
|
||||
p.title.toLowerCase().includes(lowerQuery) ||
|
||||
p.content.toLowerCase().includes(lowerQuery) ||
|
||||
(p.description && p.description.toLowerCase().includes(lowerQuery)) ||
|
||||
p.author.username.toLowerCase().includes(lowerQuery) ||
|
||||
(p.author.name && p.author.name.toLowerCase().includes(lowerQuery)) ||
|
||||
(p.category && p.category.name.toLowerCase().includes(lowerQuery)) ||
|
||||
p.tags.some((tag) => tag.toLowerCase().includes(lowerQuery))
|
||||
);
|
||||
}
|
||||
|
||||
export function convertToPrompt(cached: CachedPrompt): Prompt {
|
||||
return {
|
||||
id: cached.id,
|
||||
title: cached.title,
|
||||
content: cached.content,
|
||||
description: cached.description || null,
|
||||
type: cached.type as Prompt["type"],
|
||||
mediaUrl: null,
|
||||
author: {
|
||||
id: "local",
|
||||
username: cached.author.username,
|
||||
name: cached.author.name || null,
|
||||
avatar: null,
|
||||
verified: false,
|
||||
},
|
||||
slug: cached.slug,
|
||||
category: cached.category
|
||||
? {
|
||||
id: "local",
|
||||
name: cached.category.name,
|
||||
slug: cached.category.slug,
|
||||
parent: null,
|
||||
}
|
||||
: null,
|
||||
tags: cached.tags.map((tag, i) => ({
|
||||
tag: { id: `tag-${i}`, name: tag, slug: tag.toLowerCase() },
|
||||
})),
|
||||
voteCount: cached.voteCount,
|
||||
createdAt: new Date().toISOString(),
|
||||
updatedAt: new Date().toISOString(),
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,127 @@
|
||||
import {
|
||||
Action,
|
||||
ActionPanel,
|
||||
Detail,
|
||||
Icon,
|
||||
showToast,
|
||||
Toast,
|
||||
Clipboard,
|
||||
} from "@raycast/api";
|
||||
import type { Prompt } from "../types";
|
||||
import { getPromptUrl } from "../api";
|
||||
import { RunPromptForm } from "./run-prompt";
|
||||
|
||||
interface PromptDetailViewProps {
|
||||
prompt: Prompt;
|
||||
onRefresh?: () => void;
|
||||
}
|
||||
|
||||
export function PromptDetailView({ prompt, onRefresh }: PromptDetailViewProps) {
|
||||
const promptUrl = getPromptUrl(prompt.author.username, prompt.slug);
|
||||
|
||||
const markdown = `# ${prompt.title}
|
||||
|
||||
${prompt.description ? `*${prompt.description}*\n\n---\n\n` : ""}
|
||||
|
||||
## Prompt
|
||||
|
||||
\`\`\`
|
||||
${prompt.content}
|
||||
\`\`\`
|
||||
|
||||
---
|
||||
|
||||
**Author:** ${prompt.author.name || prompt.author.username} (@${prompt.author.username})
|
||||
**Type:** ${prompt.type}
|
||||
**Votes:** ${prompt.voteCount}
|
||||
${prompt.category ? `**Category:** ${prompt.category.name} \n` : ""}
|
||||
${prompt.tags.length > 0 ? `**Tags:** ${prompt.tags.map((t) => t.tag.name).join(", ")}` : ""}
|
||||
`;
|
||||
|
||||
return (
|
||||
<Detail
|
||||
markdown={markdown}
|
||||
actions={
|
||||
<ActionPanel>
|
||||
<ActionPanel.Section title="Run">
|
||||
<Action.Push
|
||||
title="Run Prompt"
|
||||
icon={Icon.Play}
|
||||
target={<RunPromptForm prompt={prompt} />}
|
||||
/>
|
||||
</ActionPanel.Section>
|
||||
<ActionPanel.Section>
|
||||
<Action.CopyToClipboard
|
||||
title="Copy Prompt"
|
||||
content={prompt.content}
|
||||
shortcut={{ modifiers: ["cmd"], key: "c" }}
|
||||
/>
|
||||
<Action.OpenInBrowser
|
||||
title="Open in Browser"
|
||||
url={promptUrl}
|
||||
shortcut={{ modifiers: ["cmd"], key: "o" }}
|
||||
/>
|
||||
<Action
|
||||
title="Copy URL"
|
||||
icon={Icon.Link}
|
||||
shortcut={{ modifiers: ["cmd", "shift"], key: "c" }}
|
||||
onAction={async () => {
|
||||
await Clipboard.copy(promptUrl);
|
||||
await showToast({
|
||||
style: Toast.Style.Success,
|
||||
title: "URL copied to clipboard",
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</ActionPanel.Section>
|
||||
{onRefresh && (
|
||||
<ActionPanel.Section>
|
||||
<Action
|
||||
title="Refresh"
|
||||
icon={Icon.ArrowClockwise}
|
||||
onAction={onRefresh}
|
||||
shortcut={{ modifiers: ["cmd"], key: "r" }}
|
||||
/>
|
||||
</ActionPanel.Section>
|
||||
)}
|
||||
</ActionPanel>
|
||||
}
|
||||
metadata={
|
||||
<Detail.Metadata>
|
||||
<Detail.Metadata.Label title="Title" text={prompt.title} />
|
||||
<Detail.Metadata.Label
|
||||
title="Author"
|
||||
text={`@${prompt.author.username}`}
|
||||
/>
|
||||
<Detail.Metadata.TagList title="Type">
|
||||
<Detail.Metadata.TagList.Item text={prompt.type} color="#007AFF" />
|
||||
</Detail.Metadata.TagList>
|
||||
<Detail.Metadata.Label
|
||||
title="Votes"
|
||||
text={prompt.voteCount.toString()}
|
||||
icon={Icon.Heart}
|
||||
/>
|
||||
{prompt.category && (
|
||||
<Detail.Metadata.Label
|
||||
title="Category"
|
||||
text={prompt.category.name}
|
||||
/>
|
||||
)}
|
||||
{prompt.tags.length > 0 && (
|
||||
<Detail.Metadata.TagList title="Tags">
|
||||
{prompt.tags.map((t) => (
|
||||
<Detail.Metadata.TagList.Item key={t.tag.id} text={t.tag.name} />
|
||||
))}
|
||||
</Detail.Metadata.TagList>
|
||||
)}
|
||||
<Detail.Metadata.Separator />
|
||||
<Detail.Metadata.Link
|
||||
title="View on prompts.chat"
|
||||
text="Open"
|
||||
target={promptUrl}
|
||||
/>
|
||||
</Detail.Metadata>
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,244 @@
|
||||
import {
|
||||
Action,
|
||||
ActionPanel,
|
||||
Form,
|
||||
Icon,
|
||||
List,
|
||||
showToast,
|
||||
Toast,
|
||||
Clipboard,
|
||||
open,
|
||||
popToRoot,
|
||||
useNavigation,
|
||||
} from "@raycast/api";
|
||||
import { useState } from "react";
|
||||
import type { Prompt } from "../types";
|
||||
import {
|
||||
extractVariables,
|
||||
compilePrompt,
|
||||
chatPlatforms,
|
||||
codePlatforms,
|
||||
imagePlatforms,
|
||||
videoPlatforms,
|
||||
buildUrl,
|
||||
type Platform,
|
||||
} from "../utils";
|
||||
import { getPromptUrl } from "../api";
|
||||
|
||||
interface RunPromptProps {
|
||||
prompt: Prompt;
|
||||
}
|
||||
|
||||
export function RunPromptForm({ prompt }: RunPromptProps) {
|
||||
const variables = extractVariables(prompt.content);
|
||||
const [values, setValues] = useState<Record<string, string>>(() => {
|
||||
const initial: Record<string, string> = {};
|
||||
for (const v of variables) {
|
||||
initial[v.name] = v.defaultValue;
|
||||
}
|
||||
return initial;
|
||||
});
|
||||
const { push } = useNavigation();
|
||||
|
||||
const compiledContent = compilePrompt(prompt.content, values);
|
||||
|
||||
function handleSubmit() {
|
||||
push(<PlatformList prompt={prompt} content={compiledContent} />);
|
||||
}
|
||||
|
||||
if (variables.length === 0) {
|
||||
return <PlatformList prompt={prompt} content={compiledContent} />;
|
||||
}
|
||||
|
||||
return (
|
||||
<Form
|
||||
navigationTitle={`Fill Variables: ${prompt.title}`}
|
||||
actions={
|
||||
<ActionPanel>
|
||||
<Action.SubmitForm
|
||||
title="Run Prompt"
|
||||
icon={Icon.Play}
|
||||
onSubmit={handleSubmit}
|
||||
/>
|
||||
<Action.CopyToClipboard
|
||||
title="Copy Compiled Prompt"
|
||||
content={compiledContent}
|
||||
shortcut={{ modifiers: ["cmd"], key: "c" }}
|
||||
/>
|
||||
</ActionPanel>
|
||||
}
|
||||
>
|
||||
<Form.Description title="Prompt" text={prompt.title} />
|
||||
{prompt.description && (
|
||||
<Form.Description title="" text={prompt.description} />
|
||||
)}
|
||||
<Form.Separator />
|
||||
{variables.map((variable) => (
|
||||
<Form.TextField
|
||||
key={variable.name}
|
||||
id={variable.name}
|
||||
title={variable.name}
|
||||
placeholder={variable.defaultValue || `Enter ${variable.name}`}
|
||||
value={values[variable.name] || ""}
|
||||
onChange={(value) =>
|
||||
setValues((prev) => ({ ...prev, [variable.name]: value }))
|
||||
}
|
||||
/>
|
||||
))}
|
||||
<Form.Separator />
|
||||
<Form.Description
|
||||
title="Preview"
|
||||
text={compiledContent}
|
||||
/>
|
||||
</Form>
|
||||
);
|
||||
}
|
||||
|
||||
interface PlatformListProps {
|
||||
prompt: Prompt;
|
||||
content: string;
|
||||
}
|
||||
|
||||
function PlatformList({ prompt, content }: PlatformListProps) {
|
||||
const promptUrl = getPromptUrl(prompt.author.username, prompt.slug);
|
||||
|
||||
async function handleRun(platform: Platform) {
|
||||
if (platform.supportsQuerystring) {
|
||||
const url = buildUrl(
|
||||
platform.id,
|
||||
platform.baseUrl,
|
||||
content,
|
||||
prompt.title,
|
||||
prompt.description || undefined,
|
||||
);
|
||||
await open(url);
|
||||
await showToast({
|
||||
style: Toast.Style.Success,
|
||||
title: `Opening in ${platform.name}`,
|
||||
});
|
||||
} else {
|
||||
await Clipboard.copy(content);
|
||||
await open(platform.baseUrl);
|
||||
await showToast({
|
||||
style: Toast.Style.Success,
|
||||
title: "Prompt copied!",
|
||||
message: `Opening ${platform.name}... Paste with ⌘V`,
|
||||
});
|
||||
}
|
||||
await popToRoot();
|
||||
}
|
||||
|
||||
const isImagePrompt = prompt.type === "IMAGE";
|
||||
const isVideoPrompt = prompt.type === "VIDEO";
|
||||
const mediaPlatforms = isImagePrompt
|
||||
? imagePlatforms
|
||||
: isVideoPrompt
|
||||
? videoPlatforms
|
||||
: [];
|
||||
|
||||
return (
|
||||
<List navigationTitle={`Run: ${prompt.title}`}>
|
||||
<List.Section title="Chat Platforms">
|
||||
{chatPlatforms.map((platform) => (
|
||||
<List.Item
|
||||
key={platform.id}
|
||||
title={platform.name}
|
||||
icon={Icon.Message}
|
||||
accessories={[
|
||||
platform.supportsQuerystring
|
||||
? { icon: Icon.Play, tooltip: "Auto-fill supported" }
|
||||
: { icon: Icon.Clipboard, tooltip: "Will copy to clipboard" },
|
||||
]}
|
||||
actions={
|
||||
<ActionPanel>
|
||||
<Action
|
||||
title={`Run with ${platform.name}`}
|
||||
icon={Icon.Play}
|
||||
onAction={() => handleRun(platform)}
|
||||
/>
|
||||
<Action.CopyToClipboard
|
||||
title="Copy Prompt"
|
||||
content={content}
|
||||
shortcut={{ modifiers: ["cmd"], key: "c" }}
|
||||
/>
|
||||
<Action.OpenInBrowser
|
||||
title="View on prompts.chat"
|
||||
url={promptUrl}
|
||||
shortcut={{ modifiers: ["cmd"], key: "o" }}
|
||||
/>
|
||||
</ActionPanel>
|
||||
}
|
||||
/>
|
||||
))}
|
||||
</List.Section>
|
||||
<List.Section title="Code Platforms">
|
||||
{codePlatforms.map((platform) => (
|
||||
<List.Item
|
||||
key={platform.id}
|
||||
title={platform.name}
|
||||
icon={Icon.Terminal}
|
||||
accessories={[
|
||||
platform.supportsQuerystring
|
||||
? { icon: Icon.Play, tooltip: "Auto-fill supported" }
|
||||
: { icon: Icon.Clipboard, tooltip: "Will copy to clipboard" },
|
||||
]}
|
||||
actions={
|
||||
<ActionPanel>
|
||||
<Action
|
||||
title={`Run with ${platform.name}`}
|
||||
icon={Icon.Play}
|
||||
onAction={() => handleRun(platform)}
|
||||
/>
|
||||
<Action.CopyToClipboard
|
||||
title="Copy Prompt"
|
||||
content={content}
|
||||
shortcut={{ modifiers: ["cmd"], key: "c" }}
|
||||
/>
|
||||
<Action.OpenInBrowser
|
||||
title="View on prompts.chat"
|
||||
url={promptUrl}
|
||||
shortcut={{ modifiers: ["cmd"], key: "o" }}
|
||||
/>
|
||||
</ActionPanel>
|
||||
}
|
||||
/>
|
||||
))}
|
||||
</List.Section>
|
||||
{mediaPlatforms.length > 0 && (
|
||||
<List.Section title="Media Generation">
|
||||
{mediaPlatforms.map((platform) => (
|
||||
<List.Item
|
||||
key={platform.id}
|
||||
title={platform.name}
|
||||
icon={Icon.Image}
|
||||
accessories={[
|
||||
platform.supportsQuerystring
|
||||
? { icon: Icon.Play, tooltip: "Auto-fill supported" }
|
||||
: { icon: Icon.Clipboard, tooltip: "Will copy to clipboard" },
|
||||
]}
|
||||
actions={
|
||||
<ActionPanel>
|
||||
<Action
|
||||
title={`Run with ${platform.name}`}
|
||||
icon={Icon.Play}
|
||||
onAction={() => handleRun(platform)}
|
||||
/>
|
||||
<Action.CopyToClipboard
|
||||
title="Copy Prompt"
|
||||
content={content}
|
||||
shortcut={{ modifiers: ["cmd"], key: "c" }}
|
||||
/>
|
||||
<Action.OpenInBrowser
|
||||
title="View on prompts.chat"
|
||||
url={promptUrl}
|
||||
shortcut={{ modifiers: ["cmd"], key: "o" }}
|
||||
/>
|
||||
</ActionPanel>
|
||||
}
|
||||
/>
|
||||
))}
|
||||
</List.Section>
|
||||
)}
|
||||
</List>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,127 @@
|
||||
import { Action, ActionPanel, Detail, Icon, showToast, Toast } from "@raycast/api";
|
||||
import { useState, useEffect } from "react";
|
||||
import { downloadAllPrompts, getCacheTimestamp, getCachedPrompts, clearCache } from "./cache";
|
||||
|
||||
export default function DownloadPrompts() {
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [promptCount, setPromptCount] = useState<number | null>(null);
|
||||
const [lastUpdated, setLastUpdated] = useState<Date | null>(null);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
loadCacheInfo();
|
||||
}, []);
|
||||
|
||||
async function loadCacheInfo() {
|
||||
const prompts = await getCachedPrompts();
|
||||
setPromptCount(prompts.length);
|
||||
|
||||
const timestamp = await getCacheTimestamp();
|
||||
if (timestamp) {
|
||||
setLastUpdated(new Date(timestamp));
|
||||
}
|
||||
}
|
||||
|
||||
async function handleDownload() {
|
||||
setIsLoading(true);
|
||||
setError(null);
|
||||
|
||||
try {
|
||||
await showToast({
|
||||
style: Toast.Style.Animated,
|
||||
title: "Downloading prompts...",
|
||||
});
|
||||
|
||||
const prompts = await downloadAllPrompts();
|
||||
|
||||
await showToast({
|
||||
style: Toast.Style.Success,
|
||||
title: "Download complete!",
|
||||
message: `${prompts.length} prompts cached`,
|
||||
});
|
||||
|
||||
setPromptCount(prompts.length);
|
||||
setLastUpdated(new Date());
|
||||
} catch (err) {
|
||||
const message = err instanceof Error ? err.message : "Unknown error";
|
||||
setError(message);
|
||||
await showToast({
|
||||
style: Toast.Style.Failure,
|
||||
title: "Download failed",
|
||||
message,
|
||||
});
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
}
|
||||
|
||||
async function handleClear() {
|
||||
await clearCache();
|
||||
setPromptCount(0);
|
||||
setLastUpdated(null);
|
||||
await showToast({
|
||||
style: Toast.Style.Success,
|
||||
title: "Cache cleared",
|
||||
});
|
||||
}
|
||||
|
||||
const markdown = `# Download Prompts
|
||||
|
||||
Download all prompts from prompts.chat for offline access and faster searching.
|
||||
|
||||
## Cache Status
|
||||
|
||||
${promptCount !== null ? `**Cached Prompts:** ${promptCount}` : "**Cached Prompts:** Not loaded yet"}
|
||||
|
||||
${lastUpdated ? `**Last Updated:** ${lastUpdated.toLocaleString()}` : "**Last Updated:** Never"}
|
||||
|
||||
${error ? `\n\n## Error\n\n\`${error}\`` : ""}
|
||||
|
||||
---
|
||||
|
||||
Press **Enter** to download all prompts. This will fetch the latest prompts from prompts.chat and cache them locally for offline use.
|
||||
`;
|
||||
|
||||
return (
|
||||
<Detail
|
||||
isLoading={isLoading}
|
||||
markdown={markdown}
|
||||
actions={
|
||||
<ActionPanel>
|
||||
<Action
|
||||
title="Download All Prompts"
|
||||
icon={Icon.Download}
|
||||
onAction={handleDownload}
|
||||
/>
|
||||
{promptCount && promptCount > 0 && (
|
||||
<Action
|
||||
title="Clear Cache"
|
||||
icon={Icon.Trash}
|
||||
style={Action.Style.Destructive}
|
||||
onAction={handleClear}
|
||||
/>
|
||||
)}
|
||||
</ActionPanel>
|
||||
}
|
||||
metadata={
|
||||
<Detail.Metadata>
|
||||
<Detail.Metadata.Label
|
||||
title="Cached Prompts"
|
||||
text={promptCount !== null ? promptCount.toString() : "—"}
|
||||
icon={Icon.Document}
|
||||
/>
|
||||
<Detail.Metadata.Label
|
||||
title="Last Updated"
|
||||
text={lastUpdated ? lastUpdated.toLocaleString() : "Never"}
|
||||
icon={Icon.Clock}
|
||||
/>
|
||||
<Detail.Metadata.Separator />
|
||||
<Detail.Metadata.Label
|
||||
title="Source"
|
||||
text="prompts.chat/prompts.json"
|
||||
/>
|
||||
</Detail.Metadata>
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
import { Action, ActionPanel, Detail, Icon, showToast, Toast } from "@raycast/api";
|
||||
import { useState, useEffect, useCallback } from "react";
|
||||
import type { Prompt } from "./types";
|
||||
import { PromptDetailView } from "./components/prompt-detail";
|
||||
import { getCachedPrompts, convertToPrompt, type CachedPrompt } from "./cache";
|
||||
|
||||
export default function RandomPrompt() {
|
||||
const [allPrompts, setAllPrompts] = useState<CachedPrompt[]>([]);
|
||||
const [randomPrompt, setRandomPrompt] = useState<Prompt | null>(null);
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
|
||||
const pickRandom = useCallback((prompts: CachedPrompt[]) => {
|
||||
if (prompts.length === 0) return null;
|
||||
const randomIndex = Math.floor(Math.random() * prompts.length);
|
||||
return convertToPrompt(prompts[randomIndex]);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
async function loadPrompts() {
|
||||
const cached = await getCachedPrompts();
|
||||
setAllPrompts(cached);
|
||||
setRandomPrompt(pickRandom(cached));
|
||||
setIsLoading(false);
|
||||
|
||||
if (cached.length === 0) {
|
||||
showToast({
|
||||
style: Toast.Style.Animated,
|
||||
title: "No prompts cached",
|
||||
message: "Run 'Download All Prompts' first",
|
||||
});
|
||||
}
|
||||
}
|
||||
loadPrompts();
|
||||
}, [pickRandom]);
|
||||
|
||||
function handleRefresh() {
|
||||
setRandomPrompt(pickRandom(allPrompts));
|
||||
}
|
||||
|
||||
if (isLoading) {
|
||||
return <Detail isLoading={true} markdown="Loading random prompt..." />;
|
||||
}
|
||||
|
||||
if (allPrompts.length === 0) {
|
||||
return (
|
||||
<Detail
|
||||
markdown="# No Prompts Cached\n\nRun the **Download All Prompts** command first to cache prompts for offline use."
|
||||
actions={
|
||||
<ActionPanel>
|
||||
<Action.OpenInBrowser
|
||||
title="Open prompts.chat"
|
||||
url="https://prompts.chat"
|
||||
/>
|
||||
</ActionPanel>
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (!randomPrompt) {
|
||||
return <Detail isLoading={true} markdown="Picking a random prompt..." />;
|
||||
}
|
||||
|
||||
return <PromptDetailView prompt={randomPrompt} onRefresh={handleRefresh} />;
|
||||
}
|
||||
@@ -0,0 +1,158 @@
|
||||
import {
|
||||
Action,
|
||||
ActionPanel,
|
||||
Icon,
|
||||
List,
|
||||
showToast,
|
||||
Toast,
|
||||
Clipboard,
|
||||
openExtensionPreferences,
|
||||
} from "@raycast/api";
|
||||
import { useState, useEffect } from "react";
|
||||
import type { Prompt } from "./types";
|
||||
import { getPromptUrl } from "./api";
|
||||
import { RunPromptForm } from "./components/run-prompt";
|
||||
import { PromptDetailView } from "./components/prompt-detail";
|
||||
import {
|
||||
getCachedPrompts,
|
||||
searchPrompts,
|
||||
convertToPrompt,
|
||||
downloadAllPrompts,
|
||||
type CachedPrompt,
|
||||
} from "./cache";
|
||||
|
||||
export default function SearchPrompts() {
|
||||
const [searchText, setSearchText] = useState("");
|
||||
const [allPrompts, setAllPrompts] = useState<CachedPrompt[]>([]);
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
|
||||
async function handleDownload() {
|
||||
setIsLoading(true);
|
||||
try {
|
||||
await showToast({ style: Toast.Style.Animated, title: "Downloading prompts..." });
|
||||
const prompts = await downloadAllPrompts();
|
||||
setAllPrompts(prompts);
|
||||
await showToast({ style: Toast.Style.Success, title: `Downloaded ${prompts.length} prompts` });
|
||||
} catch (err) {
|
||||
await showToast({ style: Toast.Style.Failure, title: "Download failed", message: String(err) });
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
async function loadPrompts() {
|
||||
const cached = await getCachedPrompts();
|
||||
setAllPrompts(cached);
|
||||
setIsLoading(false);
|
||||
}
|
||||
loadPrompts();
|
||||
}, []);
|
||||
|
||||
const filteredPrompts = searchPrompts(allPrompts, searchText);
|
||||
|
||||
return (
|
||||
<List
|
||||
isLoading={isLoading}
|
||||
searchBarPlaceholder="Search prompts..."
|
||||
onSearchTextChange={setSearchText}
|
||||
throttle
|
||||
>
|
||||
{allPrompts.length === 0 ? (
|
||||
<List.EmptyView
|
||||
icon={Icon.Download}
|
||||
title="No prompts cached"
|
||||
description="Press Enter to download all prompts"
|
||||
actions={
|
||||
<ActionPanel>
|
||||
<Action
|
||||
title="Download All Prompts"
|
||||
icon={Icon.Download}
|
||||
onAction={handleDownload}
|
||||
/>
|
||||
</ActionPanel>
|
||||
}
|
||||
/>
|
||||
) : searchText.length < 2 ? (
|
||||
<List.EmptyView
|
||||
icon={Icon.MagnifyingGlass}
|
||||
title="Start typing to search"
|
||||
description={`${allPrompts.length} prompts available. Enter at least 2 characters to search.`}
|
||||
/>
|
||||
) : filteredPrompts.length === 0 ? (
|
||||
<List.EmptyView
|
||||
icon={Icon.XMarkCircle}
|
||||
title="No prompts found"
|
||||
description={`No prompts match "${searchText}"`}
|
||||
/>
|
||||
) : (
|
||||
filteredPrompts.map((cached) => (
|
||||
<PromptListItem key={cached.id} prompt={convertToPrompt(cached)} />
|
||||
))
|
||||
)}
|
||||
</List>
|
||||
);
|
||||
}
|
||||
|
||||
function PromptListItem({ prompt }: { prompt: Prompt }) {
|
||||
const promptUrl = getPromptUrl(prompt.author.username, prompt.slug);
|
||||
|
||||
return (
|
||||
<List.Item
|
||||
title={prompt.title}
|
||||
subtitle={`@${prompt.author.username} • ${prompt.voteCount} upvotes`}
|
||||
accessories={[{ tag: prompt.type }]}
|
||||
actions={
|
||||
<ActionPanel>
|
||||
<ActionPanel.Section title="Run">
|
||||
<Action.Push
|
||||
title="Run Prompt"
|
||||
icon={Icon.Play}
|
||||
target={<RunPromptForm prompt={prompt} />}
|
||||
/>
|
||||
</ActionPanel.Section>
|
||||
<ActionPanel.Section>
|
||||
<Action.CopyToClipboard
|
||||
title="Copy Prompt"
|
||||
content={prompt.content}
|
||||
shortcut={{ modifiers: ["cmd"], key: "c" }}
|
||||
/>
|
||||
<Action.OpenInBrowser
|
||||
title="Open in Browser"
|
||||
url={promptUrl}
|
||||
shortcut={{ modifiers: ["cmd"], key: "o" }}
|
||||
/>
|
||||
<Action
|
||||
title="Copy URL"
|
||||
icon={Icon.Link}
|
||||
shortcut={{ modifiers: ["cmd", "shift"], key: "c" }}
|
||||
onAction={async () => {
|
||||
await Clipboard.copy(promptUrl);
|
||||
await showToast({
|
||||
style: Toast.Style.Success,
|
||||
title: "URL copied to clipboard",
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</ActionPanel.Section>
|
||||
<ActionPanel.Section>
|
||||
<Action.Push
|
||||
title="View Details"
|
||||
icon={Icon.Eye}
|
||||
target={<PromptDetailView prompt={prompt} />}
|
||||
shortcut={{ modifiers: ["cmd"], key: "d" }}
|
||||
/>
|
||||
</ActionPanel.Section>
|
||||
<ActionPanel.Section>
|
||||
<Action
|
||||
title="Open Extension Preferences"
|
||||
icon={Icon.Gear}
|
||||
onAction={openExtensionPreferences}
|
||||
shortcut={{ modifiers: ["cmd"], key: "," }}
|
||||
/>
|
||||
</ActionPanel.Section>
|
||||
</ActionPanel>
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
export interface Author {
|
||||
id: string;
|
||||
name: string | null;
|
||||
username: string;
|
||||
avatar: string | null;
|
||||
verified: boolean;
|
||||
}
|
||||
|
||||
export interface Category {
|
||||
id: string;
|
||||
name: string;
|
||||
slug: string;
|
||||
parent?: {
|
||||
id: string;
|
||||
name: string;
|
||||
slug: string;
|
||||
} | null;
|
||||
}
|
||||
|
||||
export interface Tag {
|
||||
id: string;
|
||||
name: string;
|
||||
slug: string;
|
||||
}
|
||||
|
||||
export interface PromptTag {
|
||||
tag: Tag;
|
||||
}
|
||||
|
||||
export interface Prompt {
|
||||
id: string;
|
||||
title: string;
|
||||
slug: string;
|
||||
description: string | null;
|
||||
content: string;
|
||||
type: "TEXT" | "IMAGE" | "VIDEO" | "AUDIO" | "SKILL";
|
||||
mediaUrl: string | null;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
author: Author;
|
||||
category: Category | null;
|
||||
tags: PromptTag[];
|
||||
voteCount: number;
|
||||
viewCount?: number;
|
||||
isFeatured?: boolean;
|
||||
}
|
||||
|
||||
export interface PromptsResponse {
|
||||
prompts: Prompt[];
|
||||
total: number;
|
||||
page: number;
|
||||
perPage: number;
|
||||
totalPages: number;
|
||||
}
|
||||
|
||||
export interface SearchPromptsResponse {
|
||||
prompts: {
|
||||
id: string;
|
||||
title: string;
|
||||
slug: string;
|
||||
author: {
|
||||
username: string;
|
||||
};
|
||||
}[];
|
||||
}
|
||||
|
||||
export interface Preferences {
|
||||
baseUrl: string;
|
||||
}
|
||||
@@ -0,0 +1,320 @@
|
||||
export interface Variable {
|
||||
name: string;
|
||||
defaultValue: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract variables from prompt content in ${variableName} or ${variableName:default} format
|
||||
*/
|
||||
export function extractVariables(content: string): Variable[] {
|
||||
const regex = /\$\{([^}:]+)(?::([^}]*))?\}/g;
|
||||
const variables: Variable[] = [];
|
||||
const seen = new Set<string>();
|
||||
|
||||
let match;
|
||||
while ((match = regex.exec(content)) !== null) {
|
||||
const name = match[1].trim();
|
||||
const defaultValue = match[2]?.trim() || "";
|
||||
|
||||
if (!seen.has(name)) {
|
||||
seen.add(name);
|
||||
variables.push({ name, defaultValue });
|
||||
}
|
||||
}
|
||||
|
||||
return variables;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compile a prompt template with variable values
|
||||
*/
|
||||
export function compilePrompt(
|
||||
template: string,
|
||||
values: Record<string, string>,
|
||||
): string {
|
||||
return template.replace(
|
||||
/\$\{([^}:]+)(?::([^}]*))?\}/g,
|
||||
(match, name, defaultValue) => {
|
||||
const trimmedName = name.trim();
|
||||
return values[trimmedName] ?? defaultValue?.trim() ?? match;
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
export interface Platform {
|
||||
id: string;
|
||||
name: string;
|
||||
baseUrl: string;
|
||||
supportsQuerystring: boolean;
|
||||
isDeeplink?: boolean;
|
||||
}
|
||||
|
||||
// Chat platforms (AI assistants)
|
||||
export const chatPlatforms: Platform[] = [
|
||||
{
|
||||
id: "chatgpt",
|
||||
name: "ChatGPT",
|
||||
baseUrl: "https://chatgpt.com",
|
||||
supportsQuerystring: true,
|
||||
},
|
||||
{
|
||||
id: "claude",
|
||||
name: "Claude",
|
||||
baseUrl: "https://claude.ai/new",
|
||||
supportsQuerystring: true,
|
||||
},
|
||||
{
|
||||
id: "copilot",
|
||||
name: "Microsoft Copilot",
|
||||
baseUrl: "https://copilot.microsoft.com",
|
||||
supportsQuerystring: false,
|
||||
},
|
||||
{
|
||||
id: "deepseek",
|
||||
name: "DeepSeek",
|
||||
baseUrl: "https://chat.deepseek.com",
|
||||
supportsQuerystring: false,
|
||||
},
|
||||
{
|
||||
id: "fal",
|
||||
name: "fal.ai Sandbox",
|
||||
baseUrl: "https://fal.ai/sandbox",
|
||||
supportsQuerystring: true,
|
||||
},
|
||||
{
|
||||
id: "gemini",
|
||||
name: "Gemini",
|
||||
baseUrl: "https://gemini.google.com/app",
|
||||
supportsQuerystring: false,
|
||||
},
|
||||
{
|
||||
id: "goose-chat",
|
||||
name: "Goose",
|
||||
baseUrl: "goose://recipe",
|
||||
supportsQuerystring: true,
|
||||
isDeeplink: true,
|
||||
},
|
||||
{
|
||||
id: "grok",
|
||||
name: "Grok",
|
||||
baseUrl: "https://grok.com/chat?reasoningMode=none",
|
||||
supportsQuerystring: true,
|
||||
},
|
||||
{
|
||||
id: "huggingface",
|
||||
name: "HuggingChat",
|
||||
baseUrl: "https://huggingface.co/chat",
|
||||
supportsQuerystring: true,
|
||||
},
|
||||
{
|
||||
id: "llama",
|
||||
name: "Meta AI",
|
||||
baseUrl: "https://www.meta.ai",
|
||||
supportsQuerystring: false,
|
||||
},
|
||||
{
|
||||
id: "manus",
|
||||
name: "Manus",
|
||||
baseUrl: "https://manus.im/app",
|
||||
supportsQuerystring: false,
|
||||
},
|
||||
{
|
||||
id: "mistral",
|
||||
name: "Le Chat",
|
||||
baseUrl: "https://chat.mistral.ai/chat",
|
||||
supportsQuerystring: true,
|
||||
},
|
||||
{
|
||||
id: "perplexity",
|
||||
name: "Perplexity",
|
||||
baseUrl: "https://www.perplexity.ai",
|
||||
supportsQuerystring: true,
|
||||
},
|
||||
{
|
||||
id: "phind",
|
||||
name: "Phind",
|
||||
baseUrl: "https://www.phind.com",
|
||||
supportsQuerystring: true,
|
||||
},
|
||||
{
|
||||
id: "pi",
|
||||
name: "Pi",
|
||||
baseUrl: "https://pi.ai",
|
||||
supportsQuerystring: false,
|
||||
},
|
||||
{
|
||||
id: "poe",
|
||||
name: "Poe",
|
||||
baseUrl: "https://poe.com",
|
||||
supportsQuerystring: false,
|
||||
},
|
||||
{
|
||||
id: "you",
|
||||
name: "You.com",
|
||||
baseUrl: "https://you.com",
|
||||
supportsQuerystring: true,
|
||||
},
|
||||
];
|
||||
|
||||
// Code platforms (IDEs + code generation tools)
|
||||
export const codePlatforms: Platform[] = [
|
||||
{
|
||||
id: "windsurf",
|
||||
name: "Windsurf",
|
||||
baseUrl: "windsurf://",
|
||||
supportsQuerystring: false,
|
||||
isDeeplink: true,
|
||||
},
|
||||
{
|
||||
id: "cursor",
|
||||
name: "Cursor",
|
||||
baseUrl: "cursor://anysphere.cursor-deeplink/prompt",
|
||||
supportsQuerystring: true,
|
||||
isDeeplink: true,
|
||||
},
|
||||
{
|
||||
id: "vscode",
|
||||
name: "VS Code",
|
||||
baseUrl: "vscode://",
|
||||
supportsQuerystring: false,
|
||||
isDeeplink: true,
|
||||
},
|
||||
{
|
||||
id: "vscode-insiders",
|
||||
name: "VS Code Insiders",
|
||||
baseUrl: "vscode-insiders://",
|
||||
supportsQuerystring: false,
|
||||
isDeeplink: true,
|
||||
},
|
||||
{
|
||||
id: "goose",
|
||||
name: "Goose",
|
||||
baseUrl: "goose://recipe",
|
||||
supportsQuerystring: true,
|
||||
isDeeplink: true,
|
||||
},
|
||||
{
|
||||
id: "github-copilot",
|
||||
name: "GitHub Copilot",
|
||||
baseUrl: "https://github.com/copilot",
|
||||
supportsQuerystring: true,
|
||||
},
|
||||
{
|
||||
id: "bolt",
|
||||
name: "Bolt",
|
||||
baseUrl: "https://bolt.new",
|
||||
supportsQuerystring: true,
|
||||
},
|
||||
{
|
||||
id: "lovable",
|
||||
name: "Lovable",
|
||||
baseUrl: "https://lovable.dev",
|
||||
supportsQuerystring: true,
|
||||
},
|
||||
{
|
||||
id: "v0",
|
||||
name: "v0",
|
||||
baseUrl: "https://v0.dev/chat",
|
||||
supportsQuerystring: true,
|
||||
},
|
||||
{
|
||||
id: "ai2sql",
|
||||
name: "AI2SQL",
|
||||
baseUrl: "https://builder.ai2sql.io/dashboard/builder-all-lp?tab=generate",
|
||||
supportsQuerystring: true,
|
||||
},
|
||||
];
|
||||
|
||||
// Image generation platforms
|
||||
export const imagePlatforms: Platform[] = [
|
||||
{
|
||||
id: "mitte-image",
|
||||
name: "Mitte.ai (Image)",
|
||||
baseUrl: "https://mitte.ai",
|
||||
supportsQuerystring: true,
|
||||
},
|
||||
];
|
||||
|
||||
// Video generation platforms
|
||||
export const videoPlatforms: Platform[] = [
|
||||
{
|
||||
id: "mitte-video",
|
||||
name: "Mitte.ai (Video)",
|
||||
baseUrl: "https://mitte.ai",
|
||||
supportsQuerystring: true,
|
||||
},
|
||||
];
|
||||
|
||||
export function buildUrl(
|
||||
platformId: string,
|
||||
baseUrl: string,
|
||||
promptText: string,
|
||||
promptTitle?: string,
|
||||
promptDescription?: string,
|
||||
): string {
|
||||
const encoded = encodeURIComponent(promptText);
|
||||
|
||||
switch (platformId) {
|
||||
// IDE deeplinks
|
||||
case "cursor":
|
||||
return `${baseUrl}?text=${encoded}`;
|
||||
case "goose":
|
||||
case "goose-chat": {
|
||||
const config = JSON.stringify({
|
||||
version: "1.0.0",
|
||||
title: promptTitle || "Prompt",
|
||||
description: promptDescription || "",
|
||||
instructions:
|
||||
"This is a prompt imported from prompts.chat. Follow the instructions below to complete the task.",
|
||||
prompt: promptText,
|
||||
activities: [
|
||||
"message:This prompt was imported from prompts.chat. Follow the instructions below to complete the task.",
|
||||
"Do it now",
|
||||
"Learn more about the instructions",
|
||||
],
|
||||
});
|
||||
const base64Config = Buffer.from(config).toString("base64");
|
||||
return `${baseUrl}?config=${base64Config}`;
|
||||
}
|
||||
// Web platforms
|
||||
case "ai2sql":
|
||||
return `${baseUrl}&prompt=${encoded}`;
|
||||
case "bolt":
|
||||
return `${baseUrl}?prompt=${encoded}`;
|
||||
case "chatgpt":
|
||||
return `${baseUrl}/?q=${encoded}`;
|
||||
case "claude":
|
||||
return `${baseUrl}?q=${encoded}`;
|
||||
case "copilot":
|
||||
return `${baseUrl}/?q=${encoded}`;
|
||||
case "deepseek":
|
||||
return `${baseUrl}/?q=${encoded}`;
|
||||
case "github-copilot":
|
||||
return `${baseUrl}?prompt=${encoded}`;
|
||||
case "grok":
|
||||
return `${baseUrl}&q=${encoded}`;
|
||||
case "fal":
|
||||
return `${baseUrl}?prompt=${encoded}`;
|
||||
case "huggingface":
|
||||
return `${baseUrl}/?prompt=${encoded}`;
|
||||
case "lovable":
|
||||
return `${baseUrl}/?autosubmit=true#prompt=${encoded}`;
|
||||
case "mistral":
|
||||
return `${baseUrl}?q=${encoded}`;
|
||||
case "perplexity":
|
||||
return `${baseUrl}/search?q=${encoded}`;
|
||||
case "phind":
|
||||
return `${baseUrl}/search?q=${encoded}`;
|
||||
case "poe":
|
||||
return `${baseUrl}/?q=${encoded}`;
|
||||
case "v0":
|
||||
return `${baseUrl}?q=${encoded}`;
|
||||
case "you":
|
||||
return `${baseUrl}/search?q=${encoded}`;
|
||||
case "mitte-image":
|
||||
case "mitte-video":
|
||||
return `${baseUrl}?prompt=${encoded}`;
|
||||
default:
|
||||
return `${baseUrl}?q=${encoded}`;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/tsconfig",
|
||||
"display": "Raycast Extension",
|
||||
"compilerOptions": {
|
||||
"strict": true,
|
||||
"lib": ["ES2022"],
|
||||
"module": "Node16",
|
||||
"target": "ES2022",
|
||||
"moduleResolution": "Node16",
|
||||
"resolveJsonModule": true,
|
||||
"esModuleInterop": true,
|
||||
"skipLibCheck": true,
|
||||
"jsx": "react-jsx",
|
||||
"outDir": "dist"
|
||||
},
|
||||
"include": ["src/**/*"]
|
||||
}
|
||||
Reference in New Issue
Block a user