larkup 0.1.28 → 0.1.30
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.next/BUILD_ID +1 -1
- package/.next/build-manifest.json +3 -3
- package/.next/diagnostics/route-bundle-stats.json +22 -22
- package/.next/fallback-build-manifest.json +3 -3
- package/.next/required-server-files.js +1 -1
- package/.next/required-server-files.json +1 -1
- package/.next/server/app/_global-error.html +1 -1
- package/.next/server/app/_global-error.rsc +1 -1
- package/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
- package/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/.next/server/app/_not-found/page.js.nft.json +1 -1
- package/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/.next/server/app/_not-found.html +1 -1
- package/.next/server/app/_not-found.rsc +16 -16
- package/.next/server/app/_not-found.segments/_full.segment.rsc +16 -16
- package/.next/server/app/_not-found.segments/_head.segment.rsc +4 -4
- package/.next/server/app/_not-found.segments/_index.segment.rsc +11 -11
- package/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +2 -2
- package/.next/server/app/_not-found.segments/_not-found.segment.rsc +3 -3
- package/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
- package/.next/server/app/analytics/page.js.nft.json +1 -1
- package/.next/server/app/analytics/page_client-reference-manifest.js +1 -1
- package/.next/server/app/analytics.html +1 -1
- package/.next/server/app/analytics.rsc +17 -17
- package/.next/server/app/analytics.segments/_full.segment.rsc +17 -17
- package/.next/server/app/analytics.segments/_head.segment.rsc +4 -4
- package/.next/server/app/analytics.segments/_index.segment.rsc +11 -11
- package/.next/server/app/analytics.segments/_tree.segment.rsc +1 -1
- package/.next/server/app/analytics.segments/analytics/__PAGE__.segment.rsc +3 -3
- package/.next/server/app/analytics.segments/analytics.segment.rsc +3 -3
- package/.next/server/app/api/chat/route.js +8 -9
- package/.next/server/app/api/chat/route.js.nft.json +1 -1
- package/.next/server/app/api/config/test-provider/route.js +4 -4
- package/.next/server/app/api/config/test-provider/route.js.nft.json +1 -1
- package/.next/server/app/api/config/verify/route.js +3 -3
- package/.next/server/app/api/config/verify/route.js.nft.json +1 -1
- package/.next/server/app/api/marketplace/[toolId]/route.js +1 -1
- package/.next/server/app/api/marketplace/[toolId]/route.js.nft.json +1 -1
- package/.next/server/app/api/marketplace/route.js +1 -1
- package/.next/server/app/api/marketplace/route.js.nft.json +1 -1
- package/.next/server/app/api/media/process/route.js +2 -2
- package/.next/server/app/api/media/process/route.js.nft.json +1 -1
- package/.next/server/app/api/media/route.js +1 -1
- package/.next/server/app/api/media/route.js.nft.json +1 -1
- package/.next/server/app/chat/page.js.nft.json +1 -1
- package/.next/server/app/chat/page_client-reference-manifest.js +1 -1
- package/.next/server/app/chat.html +1 -1
- package/.next/server/app/chat.rsc +17 -17
- package/.next/server/app/chat.segments/_full.segment.rsc +17 -17
- package/.next/server/app/chat.segments/_head.segment.rsc +4 -4
- package/.next/server/app/chat.segments/_index.segment.rsc +11 -11
- package/.next/server/app/chat.segments/_tree.segment.rsc +1 -1
- package/.next/server/app/chat.segments/chat/__PAGE__.segment.rsc +3 -3
- package/.next/server/app/chat.segments/chat.segment.rsc +3 -3
- package/.next/server/app/data/page.js.nft.json +1 -1
- package/.next/server/app/data/page_client-reference-manifest.js +1 -1
- package/.next/server/app/data.html +1 -1
- package/.next/server/app/data.rsc +17 -17
- package/.next/server/app/data.segments/_full.segment.rsc +17 -17
- package/.next/server/app/data.segments/_head.segment.rsc +4 -4
- package/.next/server/app/data.segments/_index.segment.rsc +11 -11
- package/.next/server/app/data.segments/_tree.segment.rsc +1 -1
- package/.next/server/app/data.segments/data/__PAGE__.segment.rsc +3 -3
- package/.next/server/app/data.segments/data.segment.rsc +3 -3
- package/.next/server/app/index.html +1 -1
- package/.next/server/app/index.rsc +16 -16
- package/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
- package/.next/server/app/index.segments/_full.segment.rsc +16 -16
- package/.next/server/app/index.segments/_head.segment.rsc +4 -4
- package/.next/server/app/index.segments/_index.segment.rsc +11 -11
- package/.next/server/app/index.segments/_tree.segment.rsc +1 -1
- package/.next/server/app/page.js.nft.json +1 -1
- package/.next/server/app/page_client-reference-manifest.js +1 -1
- package/.next/server/app/settings/page.js.nft.json +1 -1
- package/.next/server/app/settings/page_client-reference-manifest.js +1 -1
- package/.next/server/app/settings.html +1 -1
- package/.next/server/app/settings.rsc +18 -18
- package/.next/server/app/settings.segments/_full.segment.rsc +18 -18
- package/.next/server/app/settings.segments/_head.segment.rsc +4 -4
- package/.next/server/app/settings.segments/_index.segment.rsc +11 -11
- package/.next/server/app/settings.segments/_tree.segment.rsc +1 -1
- package/.next/server/app/settings.segments/settings/__PAGE__.segment.rsc +4 -4
- package/.next/server/app/settings.segments/settings.segment.rsc +3 -3
- package/.next/server/chunks/0598_next_dist_esm_build_templates_app-route_0kbcuk-.js +4 -3
- package/.next/server/chunks/0598_next_dist_esm_build_templates_app-route_1f4h7rr.js +1 -1
- package/.next/server/chunks/[root-of-the-server]__067hiof._.js +18 -6
- package/.next/server/chunks/[root-of-the-server]__0a_0ywc._.js +18 -6
- package/.next/server/chunks/[root-of-the-server]__0c80bj0._.js +3 -0
- package/.next/server/chunks/{[root-of-the-server]__1m7m2x7._.js → [root-of-the-server]__0drkoru._.js} +2 -2
- package/.next/server/chunks/{[root-of-the-server]__0vnm6_8._.js → [root-of-the-server]__0kmh740._.js} +6 -6
- package/.next/server/chunks/{[root-of-the-server]__0ou70-v._.js → [root-of-the-server]__0nfenje._.js} +1 -1
- package/.next/server/chunks/[root-of-the-server]__0omd-x0._.js +3 -0
- package/.next/server/chunks/[root-of-the-server]__0u17zp8._.js +18 -6
- package/.next/server/chunks/{[root-of-the-server]__1s1zui-._.js → [root-of-the-server]__10v5qsi._.js} +1 -1
- package/.next/server/chunks/{[root-of-the-server]__1xuhh_i._.js → [root-of-the-server]__16jk_se._.js} +2 -2
- package/.next/server/chunks/[root-of-the-server]__1ahdfq9._.js +19 -7
- package/.next/server/chunks/[root-of-the-server]__1idgvjh._.js +172 -0
- package/.next/server/chunks/[root-of-the-server]__1ip05r1._.js +1 -1
- package/.next/server/chunks/[root-of-the-server]__1jc54o2._.js +3 -0
- package/.next/server/chunks/{[root-of-the-server]__0748x7h._.js → [root-of-the-server]__1lhls88._.js} +1 -1
- package/.next/server/chunks/[root-of-the-server]__1o3rcbz._.js +2 -1
- package/.next/server/chunks/{[root-of-the-server]__10gcb7d._.js → [root-of-the-server]__1qh2h5v._.js} +2 -2
- package/.next/server/chunks/[root-of-the-server]__1s9is1o._.js +2 -1
- package/.next/server/chunks/[root-of-the-server]__1vsvck0._.js +18 -6
- package/.next/server/chunks/{[root-of-the-server]__0g_q-44._.js → [root-of-the-server]__1wy6wes._.js} +2 -2
- package/.next/server/chunks/[root-of-the-server]__1zptg4z._.js +2 -1
- package/.next/server/chunks/{packages_1f4dx4h._.js → packages_1ggqion._.js} +2 -2
- package/.next/server/chunks/packages_core_src_generator_server-runtime_ts_1_68l7t._.js +4 -4
- package/.next/server/chunks/packages_marketplace_src_125_g2p._.js +1 -1
- package/.next/server/chunks/{packages_vector-stores_src_064-9ay._.js → packages_vector-stores_src_1fib7u-._.js} +2 -2
- package/.next/server/chunks/ssr/{[root-of-the-server]__0wdatpx._.js → [root-of-the-server]__0m21fli._.js} +1 -1
- package/.next/server/chunks/ssr/{[root-of-the-server]__1r9ap67._.js → [root-of-the-server]__0t8ud_u._.js} +1 -1
- package/.next/server/chunks/ssr/{[root-of-the-server]__046ldu_._.js → [root-of-the-server]__0xyo3ni._.js} +1 -1
- package/.next/server/chunks/ssr/{_0k81--x._.js → _0ckxy33._.js} +2 -2
- package/.next/server/chunks/ssr/_169pdlf._.js +6 -6
- package/.next/server/chunks/ssr/_1hetwwu._.js +4 -3
- package/.next/server/chunks/ssr/_1yqlx1m._.js +1 -1
- package/.next/server/chunks/ssr/apps_web_components_analytics_analytics-dashboard_tsx_1056-z3._.js +1 -1
- package/.next/server/chunks/ssr/apps_web_lib_pdf-images_ts_13-l5wa._.js +1 -1
- package/.next/server/functions-config-manifest.json +3 -1
- package/.next/server/middleware-build-manifest.js +3 -3
- package/.next/server/pages/404.html +1 -1
- package/.next/server/pages/500.html +1 -1
- package/.next/static/chunks/{3bpr59xr-_rv7.js → 0ge3nirnmx_np.js} +1 -1
- package/.next/static/chunks/{0rgt7-bqxonef.js → 1uzf7x2okeuab.js} +6 -6
- package/.next/static/chunks/{3g_wl1fri6jb-.js → 21_6-9hjj2i68.js} +1 -1
- package/.next/static/chunks/2ib3e0xrrdait.js +1 -0
- package/.next/static/chunks/2jrpgzxyrmuqp.js +121 -0
- package/.next/static/chunks/390ds7l_plpmd.js +1 -0
- package/.next/static/chunks/{3een1tiktxr6a.js → 3zw6tgf7mxo39.js} +1 -1
- package/CHANGELOG.md +22 -0
- package/app/api/chat/route.ts +1 -0
- package/app/api/chat/tools.ts +5 -1
- package/app/api/config/test-provider/route.ts +16 -18
- package/app/api/config/verify/route.ts +5 -7
- package/app/api/describe-image/route.ts +12 -4
- package/app/api/firecrawl/local/route.ts +1 -1
- package/app/api/jobs/route.ts +1 -2
- package/app/api/marketplace/[toolId]/route.ts +1 -0
- package/components/analytics/analytics-dashboard.tsx +1 -1
- package/components/data/firecrawl-notice.tsx +49 -98
- package/components/data/media-panel.tsx +34 -5
- package/components/data/scrape-panel.tsx +17 -1
- package/components/data/upload-panel.tsx +2 -1
- package/components/onboarding/welcome-screen.tsx +45 -15
- package/components/settings/general-section.tsx +15 -10
- package/components/settings/marketplace-section.tsx +14 -2
- package/components/settings/server-section.tsx +3 -3
- package/lib/error-formatter.ts +19 -4
- package/lib/pdf-images.ts +20 -5
- package/package.json +7 -7
- package/.next/server/chunks/[root-of-the-server]__00r1kgp._.js +0 -143
- package/.next/server/chunks/[root-of-the-server]__064gb96._.js +0 -3
- package/.next/server/chunks/_0ce9vcg._.js +0 -32
- package/.next/static/chunks/00z1f-adeq1lg.js +0 -1
- package/.next/static/chunks/0k405nomf6s4e.js +0 -120
- package/.next/static/chunks/13hmtkhrbm4t3.js +0 -1
- /package/.next/static/{bDwMIFYSTVBKUJkJJx77o → Laf7YhpONHOZXSCw6POiL}/_buildManifest.js +0 -0
- /package/.next/static/{bDwMIFYSTVBKUJkJJx77o → Laf7YhpONHOZXSCw6POiL}/_clientMiddlewareManifest.js +0 -0
- /package/.next/static/{bDwMIFYSTVBKUJkJJx77o → Laf7YhpONHOZXSCw6POiL}/_ssgManifest.js +0 -0
|
@@ -1,21 +1,12 @@
|
|
|
1
|
-
|
|
1
|
+
'use client';
|
|
2
2
|
|
|
3
|
-
import { useState, useEffect } from
|
|
4
|
-
import
|
|
5
|
-
import
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
Server,
|
|
11
|
-
Square,
|
|
12
|
-
TriangleAlert,
|
|
13
|
-
} from "lucide-react";
|
|
14
|
-
import { toast } from "sonner";
|
|
15
|
-
import { formatErrorMessage } from "@/lib/error-formatter";
|
|
16
|
-
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
|
|
17
|
-
import { Button } from "@/components/ui/button";
|
|
18
|
-
import { Badge } from "@/components/ui/badge";
|
|
3
|
+
import { useState, useEffect } from 'react';
|
|
4
|
+
import { useRouter } from 'next/navigation';
|
|
5
|
+
import useSWR from 'swr';
|
|
6
|
+
import { Loader2, Play, Square } from 'lucide-react';
|
|
7
|
+
import { toast } from 'sonner';
|
|
8
|
+
import { formatErrorMessage } from '@/lib/error-formatter';
|
|
9
|
+
import { Button } from '@/components/ui/button';
|
|
19
10
|
|
|
20
11
|
interface LocalState {
|
|
21
12
|
running: boolean;
|
|
@@ -33,7 +24,7 @@ interface DockerState {
|
|
|
33
24
|
interface LocalResponse {
|
|
34
25
|
state: LocalState;
|
|
35
26
|
docker: DockerState;
|
|
36
|
-
runtimeEnv?:
|
|
27
|
+
runtimeEnv?: 'web' | 'desktop' | 'docker';
|
|
37
28
|
}
|
|
38
29
|
|
|
39
30
|
const fetcher = (url: string) => fetch(url).then((r) => r.json());
|
|
@@ -57,19 +48,15 @@ export function FirecrawlNotice({
|
|
|
57
48
|
onChange?: () => void;
|
|
58
49
|
onErrorChange?: (hasError: boolean) => void;
|
|
59
50
|
}) {
|
|
60
|
-
const
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
d?.state.running || d?.state.startedAt ? 8000 : 0,
|
|
66
|
-
},
|
|
67
|
-
);
|
|
68
|
-
const [busy, setBusy] = useState<"start" | "stop" | null>(null);
|
|
51
|
+
const router = useRouter();
|
|
52
|
+
const { data, mutate, isLoading } = useSWR<LocalResponse>('/api/firecrawl/local', fetcher, {
|
|
53
|
+
refreshInterval: (d) => (d?.state.running || d?.state.startedAt ? 8000 : 0),
|
|
54
|
+
});
|
|
55
|
+
const [busy, setBusy] = useState<'start' | 'stop' | null>(null);
|
|
69
56
|
|
|
70
57
|
const state = data?.state;
|
|
71
58
|
const docker = data?.docker;
|
|
72
|
-
const runtimeEnv = data?.runtimeEnv ??
|
|
59
|
+
const runtimeEnv = data?.runtimeEnv ?? 'web';
|
|
73
60
|
const running = state?.running ?? false;
|
|
74
61
|
const dockerReady = docker?.compose ?? false;
|
|
75
62
|
const hasError = !cloudConfigured && (!dockerReady || !!state?.lastError);
|
|
@@ -80,49 +67,45 @@ export function FirecrawlNotice({
|
|
|
80
67
|
}
|
|
81
68
|
}, [hasError, isLoading, onErrorChange]);
|
|
82
69
|
|
|
83
|
-
async function control(action:
|
|
70
|
+
async function control(action: 'start' | 'stop') {
|
|
84
71
|
setBusy(action);
|
|
85
|
-
if (action ===
|
|
72
|
+
if (action === 'start') {
|
|
86
73
|
toast.info(
|
|
87
|
-
runtimeEnv ===
|
|
88
|
-
? "Connecting to crawler service…"
|
|
89
|
-
: "Launching web crawler…",
|
|
74
|
+
runtimeEnv === 'docker' ? 'Connecting to crawler service…' : 'Launching web crawler…',
|
|
90
75
|
{
|
|
91
76
|
description:
|
|
92
|
-
runtimeEnv ===
|
|
93
|
-
?
|
|
94
|
-
:
|
|
77
|
+
runtimeEnv === 'docker'
|
|
78
|
+
? 'Checking if the crawler service is available on the network.'
|
|
79
|
+
: 'Pulling images and starting containers. This can take a minute on first run.',
|
|
95
80
|
},
|
|
96
81
|
);
|
|
97
82
|
}
|
|
98
83
|
try {
|
|
99
|
-
const res = await fetch(
|
|
100
|
-
method:
|
|
101
|
-
headers: {
|
|
84
|
+
const res = await fetch('/api/firecrawl/local', {
|
|
85
|
+
method: 'POST',
|
|
86
|
+
headers: { 'Content-Type': 'application/json' },
|
|
102
87
|
body: JSON.stringify({ action }),
|
|
103
88
|
});
|
|
104
89
|
const json = (await res.json()) as { state?: LocalState; error?: string };
|
|
105
|
-
if (!res.ok) throw new Error(json.error ||
|
|
90
|
+
if (!res.ok) throw new Error(json.error || 'Request failed');
|
|
106
91
|
|
|
107
|
-
if (action ===
|
|
92
|
+
if (action === 'start') {
|
|
108
93
|
if (json.state?.running) {
|
|
109
|
-
toast.success(
|
|
110
|
-
description:
|
|
94
|
+
toast.success('Web crawler is running', {
|
|
95
|
+
description: 'Web scraping is now active.',
|
|
111
96
|
});
|
|
112
97
|
} else {
|
|
113
|
-
toast.warning(
|
|
114
|
-
description:
|
|
115
|
-
json.state?.lastError ??
|
|
116
|
-
"Waiting for the service to become healthy.",
|
|
98
|
+
toast.warning('Web crawler is starting', {
|
|
99
|
+
description: 'It may take a moment to become available. Try again shortly.',
|
|
117
100
|
});
|
|
118
101
|
}
|
|
119
102
|
} else {
|
|
120
|
-
toast.success(
|
|
103
|
+
toast.success('Web crawler stopped');
|
|
121
104
|
}
|
|
122
105
|
await mutate();
|
|
123
106
|
onChange?.();
|
|
124
107
|
} catch (err) {
|
|
125
|
-
toast.error(
|
|
108
|
+
toast.error('Could not connect to the web crawler', {
|
|
126
109
|
description: formatErrorMessage(err),
|
|
127
110
|
});
|
|
128
111
|
} finally {
|
|
@@ -130,31 +113,16 @@ export function FirecrawlNotice({
|
|
|
130
113
|
}
|
|
131
114
|
}
|
|
132
115
|
|
|
133
|
-
// Both cloud and local options are kept available.
|
|
134
|
-
|
|
135
|
-
// Description text based on environment
|
|
136
|
-
const idleDescription = (() => {
|
|
137
|
-
if (runtimeEnv === "docker") {
|
|
138
|
-
return dockerReady
|
|
139
|
-
? "A crawler service was found on the network. Click to connect."
|
|
140
|
-
: "Start your deployment with the crawler profile to enable web scraping: docker compose --profile crawler up";
|
|
141
|
-
}
|
|
142
|
-
if (cloudConfigured) {
|
|
143
|
-
return "You have a cloud key set. You can also run the crawler locally via Docker.";
|
|
144
|
-
}
|
|
145
|
-
return "Launch a self-hosted web crawler via Docker. A key is generated automatically — no signup required. Pasting and uploading work without it.";
|
|
146
|
-
})();
|
|
147
|
-
|
|
148
116
|
return (
|
|
149
117
|
<div className="flex items-center gap-2">
|
|
150
118
|
{running ? (
|
|
151
119
|
<Button
|
|
152
120
|
variant="outline"
|
|
153
121
|
className="h-10 gap-1.5 rounded-md bg-red-500 hover:bg-red-600 text-white hover:text-white hover:border-red-200 text-sm px-4 shadow-none"
|
|
154
|
-
onClick={() => control(
|
|
122
|
+
onClick={() => control('stop')}
|
|
155
123
|
disabled={busy !== null}
|
|
156
124
|
>
|
|
157
|
-
{busy ===
|
|
125
|
+
{busy === 'stop' ? (
|
|
158
126
|
<Loader2 className="size-3 animate-spin" />
|
|
159
127
|
) : (
|
|
160
128
|
<Square className="size-3" />
|
|
@@ -164,45 +132,28 @@ export function FirecrawlNotice({
|
|
|
164
132
|
) : (
|
|
165
133
|
<Button
|
|
166
134
|
variant="outline"
|
|
167
|
-
onClick={() =>
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
135
|
+
onClick={() => {
|
|
136
|
+
if (runtimeEnv === 'docker' && !dockerReady) {
|
|
137
|
+
router.push('/settings?section=general#web-crawler');
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
control('start');
|
|
141
|
+
}}
|
|
142
|
+
disabled={busy !== null || isLoading}
|
|
173
143
|
className="h-10 gap-1.5 rounded-md text-sm px-4 shadow-none bg-orange-500 text-white hover:bg-orange-600 hover:text-white"
|
|
174
144
|
>
|
|
175
|
-
{busy ===
|
|
145
|
+
{busy === 'start' ? (
|
|
176
146
|
<Loader2 className="size-3 animate-spin text-muted-foreground" />
|
|
177
147
|
) : (
|
|
178
|
-
<img
|
|
179
|
-
src="/icons/firecrawl2.png"
|
|
180
|
-
alt=""
|
|
181
|
-
className="size-3 object-contain"
|
|
182
|
-
/>
|
|
148
|
+
<img src="/icons/firecrawl2.png" alt="" className="size-3 object-contain" />
|
|
183
149
|
)}
|
|
184
|
-
{runtimeEnv ===
|
|
150
|
+
{runtimeEnv === 'docker' && !dockerReady
|
|
151
|
+
? 'Crawler settings'
|
|
152
|
+
: runtimeEnv === 'docker'
|
|
153
|
+
? 'Connect Crawler'
|
|
154
|
+
: 'Launch Crawler'}
|
|
185
155
|
</Button>
|
|
186
156
|
)}
|
|
187
|
-
|
|
188
|
-
{state?.lastError && !running && (
|
|
189
|
-
<span
|
|
190
|
-
className="text-[10px] text-destructive flex items-center gap-1 max-w-[200px] truncate"
|
|
191
|
-
title={state.lastError}
|
|
192
|
-
>
|
|
193
|
-
<TriangleAlert className="size-3 shrink-0" />
|
|
194
|
-
<span className="truncate">Error starting crawler</span>
|
|
195
|
-
</span>
|
|
196
|
-
)}
|
|
197
|
-
{!isLoading && !dockerReady && !running && docker?.message && (
|
|
198
|
-
<span
|
|
199
|
-
className="text-[10px] text-destructive flex items-center gap-1 max-w-[200px] truncate"
|
|
200
|
-
title={docker.message}
|
|
201
|
-
>
|
|
202
|
-
<TriangleAlert className="size-3 shrink-0" />
|
|
203
|
-
<span className="truncate">Docker issue</span>
|
|
204
|
-
</span>
|
|
205
|
-
)}
|
|
206
157
|
</div>
|
|
207
158
|
);
|
|
208
159
|
}
|
|
@@ -304,6 +304,12 @@ export function MediaPanel({
|
|
|
304
304
|
const videoAudioTool = toolsData?.tools?.find((t: any) => t.id === 'video-audio');
|
|
305
305
|
const isToolInstalled = videoAudioTool?.status === 'installed';
|
|
306
306
|
const enabledTools = configData?.config?.enabledTools;
|
|
307
|
+
const videoAudioConfig = configData?.config?.toolConfigs?.['video-audio'] ?? {};
|
|
308
|
+
const audioProvider =
|
|
309
|
+
typeof videoAudioConfig.audioProvider === 'string' ? videoAudioConfig.audioProvider : '';
|
|
310
|
+
const audioApiKey =
|
|
311
|
+
typeof videoAudioConfig.audioApiKey === 'string' ? videoAudioConfig.audioApiKey : '';
|
|
312
|
+
const audioConfigured = Boolean(audioProvider && (audioProvider === 'local' || audioApiKey));
|
|
307
313
|
const isToolEnabled = enabledTools
|
|
308
314
|
? enabledTools.length === 0 || enabledTools.includes('video-audio')
|
|
309
315
|
: true;
|
|
@@ -391,6 +397,8 @@ export function MediaPanel({
|
|
|
391
397
|
mutate();
|
|
392
398
|
onAdded();
|
|
393
399
|
}}
|
|
400
|
+
audioConfigured={audioConfigured}
|
|
401
|
+
onConfigureAudio={() => router.push('/settings?section=tool-settings')}
|
|
394
402
|
/>
|
|
395
403
|
)}
|
|
396
404
|
</div>
|
|
@@ -428,7 +436,13 @@ function InstallPrompt({
|
|
|
428
436
|
const err = await res.json();
|
|
429
437
|
throw new Error(err.error || 'Install failed');
|
|
430
438
|
}
|
|
431
|
-
toast.success('
|
|
439
|
+
toast.success('Video & Audio installed', {
|
|
440
|
+
description: 'Choose an audio provider before indexing video or audio.',
|
|
441
|
+
action: {
|
|
442
|
+
label: 'Set up audio',
|
|
443
|
+
onClick: () => window.location.assign('/settings?section=tool-settings'),
|
|
444
|
+
},
|
|
445
|
+
});
|
|
432
446
|
onInstallComplete();
|
|
433
447
|
} catch (err) {
|
|
434
448
|
toast.error(err instanceof Error ? err.message : 'Failed to install tool');
|
|
@@ -509,6 +523,8 @@ function MediaContent({
|
|
|
509
523
|
serverId,
|
|
510
524
|
onMutate,
|
|
511
525
|
onUploadComplete,
|
|
526
|
+
audioConfigured,
|
|
527
|
+
onConfigureAudio,
|
|
512
528
|
}: {
|
|
513
529
|
mediaType: 'image' | 'video' | 'audio';
|
|
514
530
|
tab: MediaSubTab;
|
|
@@ -518,6 +534,8 @@ function MediaContent({
|
|
|
518
534
|
serverId?: string;
|
|
519
535
|
onMutate: () => void | Promise<unknown>;
|
|
520
536
|
onUploadComplete: () => void;
|
|
537
|
+
audioConfigured: boolean;
|
|
538
|
+
onConfigureAudio: () => void;
|
|
521
539
|
}) {
|
|
522
540
|
const inputRef = useRef<HTMLInputElement>(null);
|
|
523
541
|
const router = useRouter();
|
|
@@ -587,7 +605,8 @@ function MediaContent({
|
|
|
587
605
|
if (
|
|
588
606
|
msg.toLowerCase().includes('api key') ||
|
|
589
607
|
msg.toLowerCase().includes('configuration') ||
|
|
590
|
-
msg.toLowerCase().includes('missing provider')
|
|
608
|
+
msg.toLowerCase().includes('missing provider') ||
|
|
609
|
+
msg.toLowerCase().includes('choose an audio provider')
|
|
591
610
|
) {
|
|
592
611
|
toast.error(msg, {
|
|
593
612
|
duration: 10000,
|
|
@@ -601,6 +620,16 @@ function MediaContent({
|
|
|
601
620
|
}
|
|
602
621
|
}
|
|
603
622
|
|
|
623
|
+
function ensureAudioConfiguration(): boolean {
|
|
624
|
+
if (mediaType === 'image' || audioConfigured) return true;
|
|
625
|
+
toast.error('Set up an audio provider to index video or audio.', {
|
|
626
|
+
description: 'Select a provider and add its API key in Installed Tools.',
|
|
627
|
+
duration: 10_000,
|
|
628
|
+
action: { label: 'Set up audio', onClick: onConfigureAudio },
|
|
629
|
+
});
|
|
630
|
+
return false;
|
|
631
|
+
}
|
|
632
|
+
|
|
604
633
|
async function processAssets(assetIds: string[]) {
|
|
605
634
|
const PROCESS_BATCH_SIZE = 4;
|
|
606
635
|
for (let i = 0; i < assetIds.length; i += PROCESS_BATCH_SIZE) {
|
|
@@ -635,6 +664,7 @@ function MediaContent({
|
|
|
635
664
|
async function reviewRemoteEstimate() {
|
|
636
665
|
const urls = parsedUrls();
|
|
637
666
|
if (urls.length === 0) return;
|
|
667
|
+
if (!ensureAudioConfiguration()) return;
|
|
638
668
|
setCheckingUrls(true);
|
|
639
669
|
try {
|
|
640
670
|
const res = await fetch(mediaApiUrl, {
|
|
@@ -655,6 +685,7 @@ function MediaContent({
|
|
|
655
685
|
async function importRemoteUrls() {
|
|
656
686
|
const urls = parsedUrls();
|
|
657
687
|
if (urls.length === 0 || !remoteEstimates) return;
|
|
688
|
+
if (!ensureAudioConfiguration()) return;
|
|
658
689
|
setImportingUrls(true);
|
|
659
690
|
try {
|
|
660
691
|
const res = await fetch(mediaApiUrl, {
|
|
@@ -681,6 +712,7 @@ function MediaContent({
|
|
|
681
712
|
|
|
682
713
|
async function uploadAll() {
|
|
683
714
|
if (staged.length === 0) return;
|
|
715
|
+
if (!ensureAudioConfiguration()) return;
|
|
684
716
|
setUploading(true);
|
|
685
717
|
|
|
686
718
|
const BATCH_SIZE = 5;
|
|
@@ -1602,9 +1634,6 @@ function ActiveIndexingList({
|
|
|
1602
1634
|
{asset.processingMessage || 'Preparing media indexing...'}
|
|
1603
1635
|
</p>
|
|
1604
1636
|
</div>
|
|
1605
|
-
{asset.processingSteps?.length ? (
|
|
1606
|
-
<PipelineSteps steps={asset.processingSteps} />
|
|
1607
|
-
) : null}
|
|
1608
1637
|
</div>
|
|
1609
1638
|
);
|
|
1610
1639
|
})}
|
|
@@ -36,6 +36,7 @@ import { GenericAlert } from '@/components/alerts/generic-alert';
|
|
|
36
36
|
import { cn } from '@/lib/utils';
|
|
37
37
|
import { Tabs, TabsList, TabsTrigger } from '@/components/ui/tabs';
|
|
38
38
|
import useSWR from 'swr';
|
|
39
|
+
import { useRouter } from 'next/navigation';
|
|
39
40
|
import type { RagConfig } from '@larkup/core/types';
|
|
40
41
|
|
|
41
42
|
const fetcher = (url: string) => fetch(url).then((r) => r.json() as Promise<{ config: RagConfig }>);
|
|
@@ -85,6 +86,7 @@ export function ScrapePanel({
|
|
|
85
86
|
onStarted: () => void;
|
|
86
87
|
crawlerControl?: React.ReactNode;
|
|
87
88
|
}) {
|
|
89
|
+
const router = useRouter();
|
|
88
90
|
const {
|
|
89
91
|
query,
|
|
90
92
|
setQuery,
|
|
@@ -124,6 +126,15 @@ export function ScrapePanel({
|
|
|
124
126
|
const inputRef = useRef<HTMLInputElement>(null);
|
|
125
127
|
const dropdownRef = useRef<HTMLDivElement>(null);
|
|
126
128
|
|
|
129
|
+
function showCrawlerSettings() {
|
|
130
|
+
toast.error('Web crawler is not available. Choose a crawler provider in Settings.', {
|
|
131
|
+
action: {
|
|
132
|
+
label: 'Open settings',
|
|
133
|
+
onClick: () => router.push('/settings?section=general#web-crawler'),
|
|
134
|
+
},
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
|
|
127
138
|
useEffect(() => {
|
|
128
139
|
function handleClickOutside(event: MouseEvent) {
|
|
129
140
|
if (
|
|
@@ -222,7 +233,12 @@ export function ScrapePanel({
|
|
|
222
233
|
fetchProviderTotalCount(q, activeProvider);
|
|
223
234
|
}
|
|
224
235
|
} catch (err) {
|
|
225
|
-
|
|
236
|
+
const message = formatErrorMessage(err);
|
|
237
|
+
if (/crawler|firecrawl|not running|api key/i.test(message)) {
|
|
238
|
+
showCrawlerSettings();
|
|
239
|
+
} else {
|
|
240
|
+
toast.error(message);
|
|
241
|
+
}
|
|
226
242
|
}
|
|
227
243
|
}
|
|
228
244
|
|
|
@@ -314,7 +314,7 @@ export function UploadPanel({ onAdded }: { onAdded: () => void }) {
|
|
|
314
314
|
|
|
315
315
|
const res = await fetch(img.base64);
|
|
316
316
|
const blob = await res.blob();
|
|
317
|
-
const file = new File([blob], `image-${img.index}.
|
|
317
|
+
const file = new File([blob], `image-${img.index}.jpg`, { type: 'image/jpeg' });
|
|
318
318
|
|
|
319
319
|
const formData = new FormData();
|
|
320
320
|
formData.append('file', file);
|
|
@@ -386,6 +386,7 @@ export function UploadPanel({ onAdded }: { onAdded: () => void }) {
|
|
|
386
386
|
imageUrl: img.imageUrl,
|
|
387
387
|
pageNumber: img.pageNumber,
|
|
388
388
|
index: img.index,
|
|
389
|
+
description: img.description,
|
|
389
390
|
})),
|
|
390
391
|
}
|
|
391
392
|
: undefined,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
3
|
import { useState } from 'react';
|
|
4
|
-
import { ArrowRight, Loader2, ChevronDown, ChevronUp, Pencil } from 'lucide-react';
|
|
4
|
+
import { ArrowRight, Loader2, ChevronDown, ChevronUp, Pencil, Eye, EyeOff } from 'lucide-react';
|
|
5
5
|
import { Input } from '@/components/ui/input';
|
|
6
6
|
import { Button } from '@/components/ui/button';
|
|
7
7
|
import { useWorkspace } from '@/components/workspace/workspace-provider';
|
|
@@ -42,6 +42,8 @@ export function WelcomeScreen() {
|
|
|
42
42
|
// UI state
|
|
43
43
|
const [saving, setSaving] = useState(false);
|
|
44
44
|
const [showSeparateChat, setShowSeparateChat] = useState(false);
|
|
45
|
+
const [showEmbeddingApiKey, setShowEmbeddingApiKey] = useState(false);
|
|
46
|
+
const [showChatApiKey, setShowChatApiKey] = useState(false);
|
|
45
47
|
const [customModalOpen, setCustomModalOpen] = useState<'embedding' | 'chat' | null>(null);
|
|
46
48
|
|
|
47
49
|
const isCustomEmbedding = embeddingProvider === 'custom';
|
|
@@ -326,13 +328,27 @@ export function WelcomeScreen() {
|
|
|
326
328
|
</span>
|
|
327
329
|
)}
|
|
328
330
|
</label>
|
|
329
|
-
<
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
331
|
+
<div className="relative">
|
|
332
|
+
<Input
|
|
333
|
+
type={showEmbeddingApiKey ? 'text' : 'password'}
|
|
334
|
+
value={embeddingApiKey}
|
|
335
|
+
onChange={(e) => setEmbeddingApiKey(e.target.value)}
|
|
336
|
+
placeholder="sk-..."
|
|
337
|
+
className="bg-background/50 pr-10"
|
|
338
|
+
/>
|
|
339
|
+
<button
|
|
340
|
+
type="button"
|
|
341
|
+
aria-label={showEmbeddingApiKey ? 'Hide API key' : 'Show API key'}
|
|
342
|
+
onClick={() => setShowEmbeddingApiKey((visible) => !visible)}
|
|
343
|
+
className="absolute inset-y-0 right-0 flex items-center px-3 text-muted-foreground hover:text-foreground"
|
|
344
|
+
>
|
|
345
|
+
{showEmbeddingApiKey ? (
|
|
346
|
+
<EyeOff className="size-4" />
|
|
347
|
+
) : (
|
|
348
|
+
<Eye className="size-4" />
|
|
349
|
+
)}
|
|
350
|
+
</button>
|
|
351
|
+
</div>
|
|
336
352
|
</div>
|
|
337
353
|
)}
|
|
338
354
|
|
|
@@ -368,13 +384,27 @@ export function WelcomeScreen() {
|
|
|
368
384
|
{!isCustomChat && (
|
|
369
385
|
<div className="space-y-1.5">
|
|
370
386
|
<label className="text-xs font-medium text-foreground">Chat API Key</label>
|
|
371
|
-
<
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
387
|
+
<div className="relative">
|
|
388
|
+
<Input
|
|
389
|
+
type={showChatApiKey ? 'text' : 'password'}
|
|
390
|
+
value={chatApiKey}
|
|
391
|
+
onChange={(e) => setChatApiKey(e.target.value)}
|
|
392
|
+
placeholder="sk-..."
|
|
393
|
+
className="bg-background/50 pr-10"
|
|
394
|
+
/>
|
|
395
|
+
<button
|
|
396
|
+
type="button"
|
|
397
|
+
aria-label={showChatApiKey ? 'Hide chat API key' : 'Show chat API key'}
|
|
398
|
+
onClick={() => setShowChatApiKey((visible) => !visible)}
|
|
399
|
+
className="absolute inset-y-0 right-0 flex items-center px-3 text-muted-foreground hover:text-foreground"
|
|
400
|
+
>
|
|
401
|
+
{showChatApiKey ? (
|
|
402
|
+
<EyeOff className="size-4" />
|
|
403
|
+
) : (
|
|
404
|
+
<Eye className="size-4" />
|
|
405
|
+
)}
|
|
406
|
+
</button>
|
|
407
|
+
</div>
|
|
378
408
|
</div>
|
|
379
409
|
)}
|
|
380
410
|
</div>
|
|
@@ -201,7 +201,7 @@ export function GeneralSection() {
|
|
|
201
201
|
</div>
|
|
202
202
|
|
|
203
203
|
{/* Username */}
|
|
204
|
-
<Card className="">
|
|
204
|
+
<Card id="web-crawler" className="">
|
|
205
205
|
<CardHeader className="pb-3">
|
|
206
206
|
<CardTitle className="text-sm">Your Name</CardTitle>
|
|
207
207
|
<CardDescription className="text-xs">Displayed in the workspace header.</CardDescription>
|
|
@@ -427,7 +427,7 @@ export function GeneralSection() {
|
|
|
427
427
|
<CardHeader className="pb-3">
|
|
428
428
|
<CardTitle className="text-sm">Web Scraper</CardTitle>
|
|
429
429
|
<CardDescription className="text-xs">
|
|
430
|
-
|
|
430
|
+
Choose the web crawler used to search and import websites.
|
|
431
431
|
</CardDescription>
|
|
432
432
|
</CardHeader>
|
|
433
433
|
<CardContent className="space-y-4">
|
|
@@ -442,30 +442,35 @@ export function GeneralSection() {
|
|
|
442
442
|
>
|
|
443
443
|
<SelectTrigger className="w-full">
|
|
444
444
|
<div className="flex items-center gap-2">
|
|
445
|
-
<Image src="/icons/firecrawl.png" alt="
|
|
445
|
+
<Image src="/icons/firecrawl.png" alt="Web crawler" width={16} height={16} />
|
|
446
446
|
<span>
|
|
447
447
|
{form.webCrawlerProvider === 'cloud'
|
|
448
|
-
? '
|
|
449
|
-
: 'Local
|
|
448
|
+
? 'Cloud web crawler'
|
|
449
|
+
: 'Local web crawler'}
|
|
450
450
|
</span>
|
|
451
451
|
</div>
|
|
452
452
|
</SelectTrigger>
|
|
453
453
|
<SelectContent>
|
|
454
454
|
<SelectItem value="local">
|
|
455
455
|
<div className="flex items-center gap-2">
|
|
456
|
-
<Image
|
|
457
|
-
|
|
456
|
+
<Image
|
|
457
|
+
src="/icons/firecrawl.png"
|
|
458
|
+
alt="Local web crawler"
|
|
459
|
+
width={16}
|
|
460
|
+
height={16}
|
|
461
|
+
/>
|
|
462
|
+
<span>Local web crawler</span>
|
|
458
463
|
</div>
|
|
459
464
|
</SelectItem>
|
|
460
465
|
<SelectItem value="cloud">
|
|
461
466
|
<div className="flex items-center gap-2">
|
|
462
467
|
<Image
|
|
463
468
|
src="/icons/firecrawl.png"
|
|
464
|
-
alt="
|
|
469
|
+
alt="Cloud web crawler"
|
|
465
470
|
width={16}
|
|
466
471
|
height={16}
|
|
467
472
|
/>
|
|
468
|
-
<span>
|
|
473
|
+
<span>Cloud web crawler</span>
|
|
469
474
|
</div>
|
|
470
475
|
</SelectItem>
|
|
471
476
|
</SelectContent>
|
|
@@ -475,7 +480,7 @@ export function GeneralSection() {
|
|
|
475
480
|
{form.webCrawlerProvider === 'cloud' && (
|
|
476
481
|
<div className="space-y-1.5 pt-2">
|
|
477
482
|
<div className="flex items-center justify-between">
|
|
478
|
-
<Label className="text-xs">
|
|
483
|
+
<Label className="text-xs">Web crawler API key</Label>
|
|
479
484
|
{verifyCrawlerStatus.status === 'success' && (
|
|
480
485
|
<span className="text-[10px] text-green-500 font-medium">✓ Verified</span>
|
|
481
486
|
)}
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import { useState, useCallback, useMemo } from 'react';
|
|
4
4
|
import useSWR from 'swr';
|
|
5
|
+
import { useRouter } from 'next/navigation';
|
|
5
6
|
import {
|
|
6
7
|
Download,
|
|
7
8
|
Trash2,
|
|
@@ -88,6 +89,7 @@ const fetcher = (url: string) =>
|
|
|
88
89
|
fetch(url).then((r) => r.json() as Promise<{ tools: MarketplaceTool[] }>);
|
|
89
90
|
|
|
90
91
|
export function MarketplaceSection() {
|
|
92
|
+
const router = useRouter();
|
|
91
93
|
const { data, isLoading, mutate } = useSWR('/api/marketplace', fetcher);
|
|
92
94
|
const [installing, setInstalling] = useState<string | null>(null);
|
|
93
95
|
const [searchQuery, setSearchQuery] = useState('');
|
|
@@ -104,7 +106,17 @@ export function MarketplaceSection() {
|
|
|
104
106
|
const err = await res.json();
|
|
105
107
|
throw new Error(err.error || 'Install failed');
|
|
106
108
|
}
|
|
107
|
-
|
|
109
|
+
if (toolId === 'video-audio') {
|
|
110
|
+
toast.success('Video & Audio installed', {
|
|
111
|
+
description: 'Choose an audio provider before you index media.',
|
|
112
|
+
action: {
|
|
113
|
+
label: 'Set up audio',
|
|
114
|
+
onClick: () => router.push('/settings?section=tool-settings'),
|
|
115
|
+
},
|
|
116
|
+
});
|
|
117
|
+
} else {
|
|
118
|
+
toast.success('Tool installed successfully');
|
|
119
|
+
}
|
|
108
120
|
mutate();
|
|
109
121
|
} catch (err) {
|
|
110
122
|
toast.error(err instanceof Error ? err.message : 'Failed to install tool');
|
|
@@ -112,7 +124,7 @@ export function MarketplaceSection() {
|
|
|
112
124
|
setInstalling(null);
|
|
113
125
|
}
|
|
114
126
|
},
|
|
115
|
-
[mutate],
|
|
127
|
+
[mutate, router],
|
|
116
128
|
);
|
|
117
129
|
|
|
118
130
|
const handleUninstall = useCallback(
|
|
@@ -758,9 +758,9 @@ export function ServerSection() {
|
|
|
758
758
|
))}
|
|
759
759
|
</div>
|
|
760
760
|
) : (
|
|
761
|
-
<div className="flex flex-col
|
|
762
|
-
<div className="bg-white p-1 border border-border rounded-md">
|
|
763
|
-
<ServerIcon className="size-5" />
|
|
761
|
+
<div className="flex flex-col rounded-md items-center px-4 py-6 justify-center gap-2 mt-5">
|
|
762
|
+
<div className="bg-white p-1.5 border border-border rounded-md">
|
|
763
|
+
<ServerIcon className="size-4.5" />
|
|
764
764
|
</div>
|
|
765
765
|
<p className=" rounded-lg text-sm text-muted-foreground text-center mx-auto">
|
|
766
766
|
No cloud deployments yet. Deploy to a provider above to add one here.
|
package/lib/error-formatter.ts
CHANGED
|
@@ -1,12 +1,27 @@
|
|
|
1
1
|
export function formatErrorMessage(error: unknown): string {
|
|
2
2
|
const message = error instanceof Error ? error.message : String(error);
|
|
3
|
+
const normalized = message.toLowerCase();
|
|
3
4
|
|
|
4
|
-
|
|
5
|
-
|
|
5
|
+
// Provider SDKs sometimes echo the submitted key or a URL-encoded request
|
|
6
|
+
// URL in authentication failures. Besides being unreadable, that can expose
|
|
7
|
+
// credential material in a toast. Keep the actionable part instead.
|
|
8
|
+
if (
|
|
9
|
+
normalized.includes('authentication failed') ||
|
|
10
|
+
normalized.includes('invalid api key') ||
|
|
11
|
+
normalized.includes('invalid api_key') ||
|
|
12
|
+
normalized.includes('invalid token') ||
|
|
13
|
+
normalized.includes('unauthorized') ||
|
|
14
|
+
normalized.includes('status code 401')
|
|
15
|
+
) {
|
|
16
|
+
return 'Authentication failed. Check the API key for the selected provider.';
|
|
6
17
|
}
|
|
7
18
|
|
|
8
|
-
if (message.includes(
|
|
9
|
-
return
|
|
19
|
+
if (message.includes('Is the docker daemon running?')) {
|
|
20
|
+
return 'Docker is not running. Please make sure the Docker daemon is started.';
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
if (message.includes('fetch failed') || message.includes('Failed to fetch')) {
|
|
24
|
+
return 'Network error. Please check your connection and try again.';
|
|
10
25
|
}
|
|
11
26
|
|
|
12
27
|
// Add more common error mappings here as needed
|