mirror of
https://github.com/MODSetter/SurfSense.git
synced 2026-07-10 00:08:38 +00:00
feat(capabilities): add documentation URL to capabilities and enhance Reddit scrape capability
- Introduced a `docs_url` field to the `Capability` and `CapabilitySummary` classes for better documentation access. - Updated the `REDDIT_SCRAPE` capability to include a specific documentation link. - Enhanced the PlaygroundRunner component to display the documentation link when available, improving user guidance.
This commit is contained in:
parent
7268eae7fa
commit
3b9c806c90
9 changed files with 51 additions and 62 deletions
|
|
@ -71,6 +71,7 @@ class CapabilitySummary(BaseModel):
|
|||
|
||||
name: str
|
||||
description: str
|
||||
docs_url: str | None = None
|
||||
input_schema: dict
|
||||
output_schema: dict
|
||||
# Empty list = free (billing disabled or an unmetered verb).
|
||||
|
|
@ -145,6 +146,7 @@ def _register_capabilities_list(
|
|||
CapabilitySummary(
|
||||
name=capability.name,
|
||||
description=capability.description,
|
||||
docs_url=capability.docs_url,
|
||||
input_schema=capability.input_schema.model_json_schema(),
|
||||
output_schema=capability.output_schema.model_json_schema(),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -62,3 +62,4 @@ class Capability:
|
|||
output_schema: type[BaseModel]
|
||||
executor: Executor
|
||||
billing_unit: BillingUnit | None
|
||||
docs_url: str | None = None
|
||||
|
|
|
|||
|
|
@ -10,16 +10,14 @@ from app.capabilities.reddit.scrape.schemas import ScrapeInput, ScrapeOutput
|
|||
REDDIT_SCRAPE = Capability(
|
||||
name="reddit.scrape",
|
||||
description=(
|
||||
"Scrape public Reddit data. Give it Reddit URLs (post, subreddit, or "
|
||||
"user) and/or search terms, and it returns structured items — posts "
|
||||
"(title, body, score, comment count, subreddit, author), their comments, "
|
||||
"and community/user metadata. Use search_queries (optionally scoped to a "
|
||||
"community) to discover posts, or urls to pull a known post/subreddit/user."
|
||||
"Scrape public Reddit posts, comments, and metadata. Use urls or "
|
||||
"search_queries."
|
||||
),
|
||||
input_schema=ScrapeInput,
|
||||
output_schema=ScrapeOutput,
|
||||
executor=build_scrape_executor(),
|
||||
billing_unit=BillingUnit.REDDIT_ITEM,
|
||||
docs_url="/docs/connectors/native/reddit",
|
||||
)
|
||||
|
||||
register_capability(REDDIT_SCRAPE)
|
||||
|
|
|
|||
|
|
@ -22,10 +22,10 @@ function CopyButton({ text }: { text: string }) {
|
|||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={copy}
|
||||
className="absolute right-2 top-2 h-7 gap-1.5 px-2 text-xs"
|
||||
aria-label={copied ? "Copied" : "Copy"}
|
||||
className="absolute right-2 top-2 h-7 w-7 p-0"
|
||||
>
|
||||
{copied ? <Check className="h-3.5 w-3.5" /> : <Copy className="h-3.5 w-3.5" />}
|
||||
{copied ? "Copied" : "Copy"}
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
|
|
@ -91,11 +91,7 @@ export function ApiReference({
|
|||
<div>
|
||||
<h2 className="text-base font-semibold">API reference</h2>
|
||||
<p className="mt-1 text-sm text-muted-foreground">
|
||||
Call this API from your own project. Create a key in{" "}
|
||||
<span className="font-medium text-foreground">User settings</span> and enable API access
|
||||
for this workspace, then send it as a{" "}
|
||||
<code className="rounded bg-muted/40 px-1 py-0.5 text-xs">Authorization: Bearer</code>{" "}
|
||||
header.
|
||||
Create an API key, enable API access for this workspace, then use the examples below to call this endpoint.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
import { Check, Copy, Download } from "lucide-react";
|
||||
import { useMemo, useState } from "react";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
||||
import {
|
||||
Table,
|
||||
TableBody,
|
||||
|
|
@ -12,7 +13,6 @@ import {
|
|||
TableRow,
|
||||
} from "@/components/ui/table";
|
||||
import { downloadCsv, rowsToCsv } from "@/lib/playground/csv";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
const MAX_TABLE_ROWS = 200;
|
||||
|
||||
|
|
@ -109,34 +109,12 @@ export function OutputViewer({ data, filenameBase }: { data: unknown; filenameBa
|
|||
return (
|
||||
<div className="space-y-3">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="inline-flex rounded-md border border-border/60 p-0.5">
|
||||
{items && (
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setView("table")}
|
||||
className={cn(
|
||||
"rounded px-2.5 py-1 text-xs font-medium transition-colors",
|
||||
view === "table"
|
||||
? "bg-accent text-accent-foreground"
|
||||
: "text-muted-foreground hover:text-foreground"
|
||||
)}
|
||||
>
|
||||
Table
|
||||
</button>
|
||||
)}
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setView("json")}
|
||||
className={cn(
|
||||
"rounded px-2.5 py-1 text-xs font-medium transition-colors",
|
||||
view === "json"
|
||||
? "bg-accent text-accent-foreground"
|
||||
: "text-muted-foreground hover:text-foreground"
|
||||
)}
|
||||
>
|
||||
JSON
|
||||
</button>
|
||||
</div>
|
||||
<Tabs value={view} onValueChange={(value) => setView(value as "table" | "json")}>
|
||||
<TabsList className="h-auto">
|
||||
{items && <TabsTrigger value="table">Table</TabsTrigger>}
|
||||
<TabsTrigger value="json">JSON</TabsTrigger>
|
||||
</TabsList>
|
||||
</Tabs>
|
||||
<div className="flex items-center gap-1">
|
||||
{items && items.length > 0 && (
|
||||
<Button
|
||||
|
|
@ -150,9 +128,15 @@ export function OutputViewer({ data, filenameBase }: { data: unknown; filenameBa
|
|||
Export CSV
|
||||
</Button>
|
||||
)}
|
||||
<Button type="button" variant="ghost" size="sm" onClick={copy} className="gap-1.5">
|
||||
<Button
|
||||
type="button"
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={copy}
|
||||
aria-label={copied ? "Copied JSON" : "Copy JSON"}
|
||||
className="h-8 w-8 p-0"
|
||||
>
|
||||
{copied ? <Check className="h-3.5 w-3.5" /> : <Copy className="h-3.5 w-3.5" />}
|
||||
{copied ? "Copied" : "Copy JSON"}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -168,7 +152,7 @@ export function OutputViewer({ data, filenameBase }: { data: unknown; filenameBa
|
|||
<ResultTable items={items} />
|
||||
{truncated && (
|
||||
<p className="text-xs text-muted-foreground">
|
||||
Showing first {MAX_TABLE_ROWS} of {items.length} items. Use Copy JSON for the full
|
||||
Showing first {MAX_TABLE_ROWS} of {items.length} items. Switch to JSON for the full
|
||||
output.
|
||||
</p>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -2,15 +2,13 @@
|
|||
|
||||
import {
|
||||
Check,
|
||||
Coins,
|
||||
Copy,
|
||||
Hash,
|
||||
Info,
|
||||
Loader2,
|
||||
Play,
|
||||
Coins,
|
||||
Timer,
|
||||
X,
|
||||
} from "lucide-react";
|
||||
import Link from "next/link";
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
||||
import { toast } from "sonner";
|
||||
import { Alert, AlertDescription } from "@/components/ui/alert";
|
||||
|
|
@ -235,7 +233,21 @@ export function PlaygroundRunner({ workspaceId, platform, verb }: PlaygroundRunn
|
|||
<Alert>
|
||||
<Info />
|
||||
<AlertDescription>
|
||||
<p>{capability.description}</p>
|
||||
<p>
|
||||
{capability.description}
|
||||
{capability.docs_url ? (
|
||||
<>
|
||||
{" "}
|
||||
<Link
|
||||
href={capability.docs_url}
|
||||
className="font-medium text-foreground underline-offset-4 hover:underline"
|
||||
>
|
||||
Read docs
|
||||
</Link>
|
||||
.
|
||||
</>
|
||||
) : null}
|
||||
</p>
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
)}
|
||||
|
|
@ -243,8 +255,8 @@ export function PlaygroundRunner({ workspaceId, platform, verb }: PlaygroundRunn
|
|||
<div className="space-y-5">
|
||||
<div className="space-y-2">
|
||||
<EndpointCopyButton endpoint={endpoint} />
|
||||
<div className="flex items-center gap-1.5 text-xs text-muted-foreground">
|
||||
<Coins className="h-3.5 w-3.5" />
|
||||
<div className="text-xs text-muted-foreground">
|
||||
<span>Pricing: </span>
|
||||
<span className="font-medium tabular-nums text-foreground">
|
||||
{formatPricing(capability.pricing)}
|
||||
</span>
|
||||
|
|
@ -258,17 +270,12 @@ export function PlaygroundRunner({ workspaceId, platform, verb }: PlaygroundRunn
|
|||
/>
|
||||
|
||||
<div className="flex items-center gap-2">
|
||||
<Button type="button" onClick={handleRun} disabled={isRunning} className="gap-1.5">
|
||||
{isRunning ? (
|
||||
<Loader2 className="h-4 w-4 animate-spin" />
|
||||
) : (
|
||||
<Play className="h-4 w-4" />
|
||||
)}
|
||||
Run
|
||||
<Button type="button" onClick={handleRun} disabled={isRunning} className="relative">
|
||||
<span className={isRunning ? "opacity-0" : ""}>Run</span>
|
||||
{isRunning && <Spinner size="sm" className="absolute" />}
|
||||
</Button>
|
||||
{isRunning && (
|
||||
<Button type="button" variant="outline" onClick={run.cancel} className="gap-1.5">
|
||||
<X className="h-4 w-4" />
|
||||
<Button type="button" variant="secondary" onClick={run.cancel}>
|
||||
Cancel
|
||||
</Button>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -90,6 +90,6 @@ function getSelectedLabel(
|
|||
const group = groups.find((item) => item.items.some((child) => child.value === activeValue));
|
||||
const child = group?.items.find((item) => item.value === activeValue);
|
||||
|
||||
if (group && child) return `${group.label} / ${child.label}`;
|
||||
if (group && child) return `${group.label}: ${child.label}`;
|
||||
return "API Playground";
|
||||
}
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ const TabsTrigger = React.forwardRef<
|
|||
<TabsPrimitive.Trigger
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"inline-flex items-center justify-center whitespace-nowrap rounded-sm px-3 py-1.5 text-sm font-medium ring-offset-background transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:bg-background data-[state=active]:text-foreground data-[state=active]:shadow-sm",
|
||||
"inline-flex items-center justify-center whitespace-nowrap rounded-sm px-3 py-1.5 text-sm font-medium ring-offset-background transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:bg-popover data-[state=active]:text-popover-foreground data-[state=active]:shadow-sm",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ export const scraperPricingMeter = z.object({
|
|||
export const scraperCapability = z.object({
|
||||
name: z.string(),
|
||||
description: z.string(),
|
||||
docs_url: z.string().nullable().optional(),
|
||||
input_schema: z.record(z.string(), z.unknown()),
|
||||
// Optional so a backend that predates output schemas degrades to just not
|
||||
// showing the output-schema block instead of failing the whole fetch.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue