mirror of
https://github.com/MODSetter/SurfSense.git
synced 2026-07-16 23:01:06 +02:00
chore: linting
This commit is contained in:
parent
1131da5ed7
commit
e8b3692b54
20 changed files with 74 additions and 82 deletions
|
|
@ -3,7 +3,6 @@
|
|||
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,6 +11,7 @@ import {
|
|||
TableHeader,
|
||||
TableRow,
|
||||
} from "@/components/ui/table";
|
||||
import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
||||
import { downloadCsv, rowsToCsv } from "@/lib/playground/csv";
|
||||
|
||||
const MAX_TABLE_ROWS = 200;
|
||||
|
|
@ -117,13 +117,7 @@ export function OutputViewer({ data, filenameBase }: { data: unknown; filenameBa
|
|||
</Tabs>
|
||||
<div className="flex items-center gap-1">
|
||||
{items && items.length > 0 && (
|
||||
<Button
|
||||
type="button"
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={exportCsv}
|
||||
className="gap-1.5"
|
||||
>
|
||||
<Button type="button" variant="ghost" size="sm" onClick={exportCsv} className="gap-1.5">
|
||||
<Download className="h-3.5 w-3.5" />
|
||||
Export CSV
|
||||
</Button>
|
||||
|
|
|
|||
|
|
@ -25,7 +25,8 @@ export function PlaygroundIndex({ workspaceId }: { workspaceId: number }) {
|
|||
<Info />
|
||||
<AlertDescription>
|
||||
<p>
|
||||
Manually run SurfSense's platform-native APIs and inspect their output. To use these APIs outside SurfSense,{" "}
|
||||
Manually run SurfSense's platform-native APIs and inspect their output. To use these
|
||||
APIs outside SurfSense,{" "}
|
||||
<Link
|
||||
href={`${base}/api-keys`}
|
||||
className="font-medium text-foreground underline-offset-4 hover:underline"
|
||||
|
|
|
|||
|
|
@ -35,13 +35,7 @@ function parseJsonl(text: string | null): { items: unknown[]; total: number } {
|
|||
return { items, total: lines.length };
|
||||
}
|
||||
|
||||
export function RunDetail({
|
||||
workspaceId,
|
||||
runId,
|
||||
}: {
|
||||
workspaceId: number;
|
||||
runId: string;
|
||||
}) {
|
||||
export function RunDetail({ workspaceId, runId }: { workspaceId: number; runId: string }) {
|
||||
const { data: run, isLoading, error } = useScraperRun(workspaceId, runId);
|
||||
|
||||
const parsed = useMemo(() => parseJsonl(run?.output_text ?? null), [run?.output_text]);
|
||||
|
|
|
|||
|
|
@ -9,7 +9,9 @@ import { formatDuration } from "@/lib/playground/format";
|
|||
function eventLabel(event: ScraperRunEvent): string {
|
||||
const base =
|
||||
event.message ||
|
||||
(event.phase ? event.phase.replace(/_/g, " ").replace(/^\w/, (c) => c.toUpperCase()) : "Working");
|
||||
(event.phase
|
||||
? event.phase.replace(/_/g, " ").replace(/^\w/, (c) => c.toUpperCase())
|
||||
: "Working");
|
||||
if (event.current !== undefined && event.current !== null) {
|
||||
const counter =
|
||||
event.total !== undefined && event.total !== null
|
||||
|
|
|
|||
|
|
@ -14,7 +14,10 @@ export function RunStatusBadge({ status }: { status: string }) {
|
|||
}
|
||||
if (normalized === "success") {
|
||||
return (
|
||||
<Badge variant="secondary" className="bg-emerald-500/15 text-emerald-600 dark:text-emerald-400">
|
||||
<Badge
|
||||
variant="secondary"
|
||||
className="bg-emerald-500/15 text-emerald-600 dark:text-emerald-400"
|
||||
>
|
||||
Success
|
||||
</Badge>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -71,7 +71,8 @@ export function RunsTable({ workspaceId }: { workspaceId: number }) {
|
|||
<Alert>
|
||||
<Info />
|
||||
<AlertDescription>
|
||||
View all API runs for this workspace, including runs from the playground, API keys, and agents.
|
||||
View all API runs for this workspace, including runs from the playground, API keys, and
|
||||
agents.
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
|
||||
|
|
|
|||
|
|
@ -43,12 +43,7 @@ function FieldControl({
|
|||
|
||||
if (field.kind === "boolean") {
|
||||
return (
|
||||
<Switch
|
||||
id={id}
|
||||
checked={Boolean(value)}
|
||||
onCheckedChange={onChange}
|
||||
disabled={disabled}
|
||||
/>
|
||||
<Switch id={id} checked={Boolean(value)} onCheckedChange={onChange} disabled={disabled} />
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -141,9 +136,7 @@ function FieldRow({
|
|||
{field.required ? "required" : "optional"}
|
||||
</Badge>
|
||||
</div>
|
||||
{field.description && (
|
||||
<p className="text-xs text-muted-foreground">{field.description}</p>
|
||||
)}
|
||||
{field.description && <p className="text-xs text-muted-foreground">{field.description}</p>}
|
||||
<FieldControl
|
||||
field={field}
|
||||
value={value}
|
||||
|
|
@ -156,13 +149,7 @@ function FieldRow({
|
|||
);
|
||||
}
|
||||
|
||||
export function SchemaForm({
|
||||
fields,
|
||||
values,
|
||||
onChange,
|
||||
disabled,
|
||||
fieldErrors,
|
||||
}: SchemaFormProps) {
|
||||
export function SchemaForm({ fields, values, onChange, disabled, fieldErrors }: SchemaFormProps) {
|
||||
const [showAdvanced, setShowAdvanced] = useState(false);
|
||||
|
||||
const { primary, advanced } = useMemo(() => {
|
||||
|
|
|
|||
|
|
@ -1018,10 +1018,7 @@ const ConnectedScraperIcons: FC<{ workspaceId: number }> = ({ workspaceId }) =>
|
|||
return (
|
||||
<Tooltip key={platform.id}>
|
||||
<TooltipTrigger asChild>
|
||||
<Avatar
|
||||
className="size-5"
|
||||
style={{ zIndex: platforms.length - i }}
|
||||
>
|
||||
<Avatar className="size-5" style={{ zIndex: platforms.length - i }}>
|
||||
<AvatarFallback className="bg-popover text-[10px]">
|
||||
<Icon className="size-3" />
|
||||
</AvatarFallback>
|
||||
|
|
|
|||
|
|
@ -27,7 +27,8 @@ export type HeroChatDemoScript = {
|
|||
|
||||
type Stage = "typing" | "steps" | "answer" | "done";
|
||||
|
||||
const PLACEHOLDER = "Track competitors, scrape platforms, automate briefs. Use / for prompts, @ for docs";
|
||||
const PLACEHOLDER =
|
||||
"Track competitors, scrape platforms, automate briefs. Use / for prompts, @ for docs";
|
||||
|
||||
/** Blinking caret for the typewriter (overlay only, never inside the real input). */
|
||||
function Caret() {
|
||||
|
|
|
|||
|
|
@ -717,10 +717,10 @@ export function HeroSection() {
|
|||
"relative mb-8 max-w-2xl text-left text-sm text-neutral-600 antialiased sm:text-base md:text-lg dark:text-neutral-400"
|
||||
)}
|
||||
>
|
||||
SurfSense is an open-source competitive intelligence platform, like NotebookLM but with
|
||||
live scraping connectors. Your AI agents monitor competitors, track rankings, and listen
|
||||
to your market with live data from platforms like Reddit, YouTube, Instagram, TikTok,
|
||||
Google Maps, Google Search, and the open web.
|
||||
SurfSense is an open-source competitive intelligence platform, like NotebookLM but
|
||||
with live scraping connectors. Your AI agents monitor competitors, track rankings, and
|
||||
listen to your market with live data from platforms like Reddit, YouTube, Instagram,
|
||||
TikTok, Google Maps, Google Search, and the open web.
|
||||
</p>
|
||||
|
||||
<div className="relative mb-4 flex w-full flex-col justify-center gap-y-2 sm:flex-row sm:justify-start sm:space-y-0 sm:space-x-4">
|
||||
|
|
|
|||
|
|
@ -324,9 +324,7 @@ export function NotificationsDropdown({
|
|||
)}
|
||||
>
|
||||
<span>{tab.label}</span>
|
||||
<span
|
||||
className="inline-flex h-5 min-w-5 items-center justify-center rounded-full bg-muted px-1.5 text-[11px] font-semibold text-muted-foreground"
|
||||
>
|
||||
<span className="inline-flex h-5 min-w-5 items-center justify-center rounded-full bg-muted px-1.5 text-[11px] font-semibold text-muted-foreground">
|
||||
{formatNotificationCount(tab.count)}
|
||||
</span>
|
||||
</button>
|
||||
|
|
|
|||
|
|
@ -198,8 +198,8 @@ export function AutoReloadSettings() {
|
|||
<AlertTriangle className="h-4 w-4" />
|
||||
<AlertTitle>Last top-up failed</AlertTitle>
|
||||
<AlertDescription>
|
||||
Your saved card was declined and top-ups were turned off. Update your card and
|
||||
re-enable top-ups below.
|
||||
Your saved card was declined and top-ups were turned off. Update your card and re-enable
|
||||
top-ups below.
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
)}
|
||||
|
|
@ -290,7 +290,11 @@ export function AutoReloadSettings() {
|
|||
</div>
|
||||
|
||||
<div className="flex justify-end">
|
||||
<Button className="w-full sm:w-auto" onClick={handleSave} disabled={saveMutation.isPending}>
|
||||
<Button
|
||||
className="w-full sm:w-auto"
|
||||
onClick={handleSave}
|
||||
disabled={saveMutation.isPending}
|
||||
>
|
||||
{saveMutation.isPending ? (
|
||||
<>
|
||||
<Spinner size="xs" />
|
||||
|
|
|
|||
|
|
@ -59,7 +59,12 @@ export function WorkspaceApiAccessControl({
|
|||
|
||||
if (isLoading) {
|
||||
return (
|
||||
<div className={cn("flex flex-col gap-3 md:flex-row md:items-center md:justify-between", className)}>
|
||||
<div
|
||||
className={cn(
|
||||
"flex flex-col gap-3 md:flex-row md:items-center md:justify-between",
|
||||
className
|
||||
)}
|
||||
>
|
||||
<div className="space-y-2">
|
||||
<Skeleton className="h-4 w-32" />
|
||||
<Skeleton className="h-3 w-56" />
|
||||
|
|
@ -71,7 +76,12 @@ export function WorkspaceApiAccessControl({
|
|||
|
||||
if (isError) {
|
||||
return (
|
||||
<div className={cn("flex flex-col gap-3 md:flex-row md:items-center md:justify-between", className)}>
|
||||
<div
|
||||
className={cn(
|
||||
"flex flex-col gap-3 md:flex-row md:items-center md:justify-between",
|
||||
className
|
||||
)}
|
||||
>
|
||||
<div className="space-y-1">
|
||||
<Label>API key access</Label>
|
||||
<p className="text-xs text-destructive">Failed to load workspace API access.</p>
|
||||
|
|
@ -84,7 +94,12 @@ export function WorkspaceApiAccessControl({
|
|||
}
|
||||
|
||||
return (
|
||||
<div className={cn("flex flex-col gap-3 md:flex-row md:items-center md:justify-between", className)}>
|
||||
<div
|
||||
className={cn(
|
||||
"flex flex-col gap-3 md:flex-row md:items-center md:justify-between",
|
||||
className
|
||||
)}
|
||||
>
|
||||
<div className="space-y-1">
|
||||
<Label htmlFor="api-access-enabled">API key access</Label>
|
||||
<p className="text-xs text-muted-foreground">Allow API keys to access this workspace.</p>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,13 @@
|
|||
{
|
||||
"title": "Native Connectors",
|
||||
"pages": ["reddit", "youtube", "instagram", "tiktok", "google-maps", "google-search", "web-crawl"],
|
||||
"pages": [
|
||||
"reddit",
|
||||
"youtube",
|
||||
"instagram",
|
||||
"tiktok",
|
||||
"google-maps",
|
||||
"google-search",
|
||||
"web-crawl"
|
||||
],
|
||||
"defaultOpen": false
|
||||
}
|
||||
|
|
|
|||
|
|
@ -89,17 +89,12 @@ export function useRunStream(workspaceId: number) {
|
|||
setState((s) => ({
|
||||
...s,
|
||||
latest: ev,
|
||||
events:
|
||||
ev.type === "run.progress"
|
||||
? [...s.events.slice(-(MAX_LOG - 1)), ev]
|
||||
: s.events,
|
||||
events: ev.type === "run.progress" ? [...s.events.slice(-(MAX_LOG - 1)), ev] : s.events,
|
||||
}));
|
||||
}
|
||||
} catch (e) {
|
||||
if (signal.aborted) return;
|
||||
setState((s) =>
|
||||
s.status === "running" ? { ...s, status: "error", error: e } : s
|
||||
);
|
||||
setState((s) => (s.status === "running" ? { ...s, status: "error", error: e } : s));
|
||||
}
|
||||
},
|
||||
[workspaceId, queryClient]
|
||||
|
|
@ -113,12 +108,7 @@ export function useRunStream(workspaceId: number) {
|
|||
startedAtRef.current = Date.now();
|
||||
setState({ ...INITIAL, status: "running" });
|
||||
try {
|
||||
const started = await scrapersApiService.runAsync(
|
||||
workspaceId,
|
||||
platform,
|
||||
verb,
|
||||
payload
|
||||
);
|
||||
const started = await scrapersApiService.runAsync(workspaceId, platform, verb, payload);
|
||||
runIdRef.current = started.run_id;
|
||||
trackWeeklyUser("api_run", workspaceId);
|
||||
setState((s) => ({ ...s, runId: started.run_id }));
|
||||
|
|
|
|||
|
|
@ -1,15 +1,15 @@
|
|||
import {
|
||||
type ListScraperRunsParams,
|
||||
type ScraperRunEvent,
|
||||
type StartAsyncRunResponse,
|
||||
listCapabilitiesResponse,
|
||||
listRunsResponse,
|
||||
type ScraperRunEvent,
|
||||
type StartAsyncRunResponse,
|
||||
scraperRunDetail,
|
||||
startAsyncRunResponse,
|
||||
} from "@/contracts/types/scraper.types";
|
||||
import { authenticatedFetch } from "@/lib/auth-fetch";
|
||||
import { readSSEStream } from "@/lib/chat/streaming-state";
|
||||
import { buildBackendUrl } from "@/lib/env-config";
|
||||
import { authenticatedFetch } from "@/lib/auth-fetch";
|
||||
import { baseApiService } from "./base-api.service";
|
||||
|
||||
const base = (workspaceId: number | string) => `/api/v1/workspaces/${workspaceId}/scrapers`;
|
||||
|
|
|
|||
|
|
@ -62,7 +62,8 @@ export const tiktok: ConnectorPageContent = {
|
|||
extractFields: [
|
||||
{
|
||||
label: "Videos",
|
||||
description: "Caption text, canonical web URL, duration, and cover image for each TikTok video.",
|
||||
description:
|
||||
"Caption text, canonical web URL, duration, and cover image for each TikTok video.",
|
||||
},
|
||||
{
|
||||
label: "Engagement",
|
||||
|
|
|
|||
|
|
@ -23,7 +23,11 @@ assert.deepEqual(payload, {
|
|||
query: "<query>",
|
||||
});
|
||||
|
||||
const snippets = buildSnippets("https://api.example.com", "/api/v1/workspaces/1/scrapers/x/y", payload);
|
||||
const snippets = buildSnippets(
|
||||
"https://api.example.com",
|
||||
"/api/v1/workspaces/1/scrapers/x/y",
|
||||
payload
|
||||
);
|
||||
|
||||
// Every popular language is present.
|
||||
assert.deepEqual(
|
||||
|
|
|
|||
|
|
@ -11,9 +11,7 @@ export function formatCost(costMicros: number | null | undefined): string {
|
|||
/** One meter as a per-1k rate, e.g. 3500 micros/place -> "$3.50 / 1k places". */
|
||||
export function formatRate(meter: ScraperPricingMeter): string {
|
||||
const perThousand = (meter.micros_per_unit * 1000) / 1_000_000;
|
||||
const dollars = Number.isInteger(perThousand)
|
||||
? perThousand.toString()
|
||||
: perThousand.toFixed(2);
|
||||
const dollars = Number.isInteger(perThousand) ? perThousand.toString() : perThousand.toFixed(2);
|
||||
return `$${dollars} / 1k ${meter.unit}s`;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -8,13 +8,7 @@
|
|||
|
||||
type JsonObject = Record<string, unknown>;
|
||||
|
||||
export type FieldKind =
|
||||
| "string"
|
||||
| "string_array"
|
||||
| "integer"
|
||||
| "number"
|
||||
| "boolean"
|
||||
| "enum";
|
||||
export type FieldKind = "string" | "string_array" | "integer" | "number" | "boolean" | "enum";
|
||||
|
||||
export interface FormField {
|
||||
name: string;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue