diff --git a/app/(main)/keystore/page.tsx b/app/(main)/keystore/page.tsx index b0985acb..07ce304d 100644 --- a/app/(main)/keystore/page.tsx +++ b/app/(main)/keystore/page.tsx @@ -13,6 +13,7 @@ import AddKeyModal from "@/app/components/keystore/AddKeyModal"; import { useAuth } from "@/app/lib/context/AuthContext"; import { useApp } from "@/app/lib/context/AppContext"; import { useToast } from "@/app/components/Toast"; +import { apiFetch } from "@/app/lib/apiClient"; import { APIKey } from "@/app/lib/types/credentials"; export const STORAGE_KEY = "kaapi_api_keys"; @@ -28,6 +29,7 @@ export default function KaapiKeystore() { const [newKeyProvider, setNewKeyProvider] = useState("Kaapi"); const [visibleKeys, setVisibleKeys] = useState>(new Set()); const [copiedKeyId, setCopiedKeyId] = useState(null); + const [isValidating, setIsValidating] = useState(false); const resetForm = () => { setNewKeyLabel(""); @@ -35,16 +37,28 @@ export default function KaapiKeystore() { setNewKeyProvider("Kaapi"); }; - const handleAddKey = () => { + const handleAddKey = async () => { if (!newKeyLabel.trim() || !newKeyValue.trim()) { toast.error("Please provide both a label and an API key"); return; } + const trimmedKey = newKeyValue.trim(); + setIsValidating(true); + + try { + await apiFetch("/api/apikeys/verify", trimmedKey); + } catch (err) { + console.error("API key validation failed:", err); + toast.error("Invalid API key. Please check the key and try again."); + setIsValidating(false); + return; + } + const newKey: APIKey = { id: Date.now().toString(), label: newKeyLabel.trim(), - key: newKeyValue.trim(), + key: trimmedKey, provider: newKeyProvider, createdAt: new Date().toISOString(), }; @@ -52,6 +66,7 @@ export default function KaapiKeystore() { addKey(newKey); resetForm(); setIsModalOpen(false); + setIsValidating(false); toast.success("API key added successfully"); }; @@ -122,6 +137,7 @@ export default function KaapiKeystore() { newKeyLabel={newKeyLabel} newKeyValue={newKeyValue} newKeyProvider={newKeyProvider} + isValidating={isValidating} onLabelChange={setNewKeyLabel} onValueChange={setNewKeyValue} onProviderChange={setNewKeyProvider} diff --git a/app/(main)/knowledge-base/page.tsx b/app/(main)/knowledge-base/page.tsx index a30a30da..5e0ab1e8 100644 --- a/app/(main)/knowledge-base/page.tsx +++ b/app/(main)/knowledge-base/page.tsx @@ -37,6 +37,7 @@ export default function KnowledgeBasePage() { ); const [showDocPreviewModal, setShowDocPreviewModal] = useState(false); const [previewDoc, setPreviewDoc] = useState(null); + const [isPreviewLoading, setIsPreviewLoading] = useState(false); const [collectionName, setCollectionName] = useState(""); const [collectionDescription, setCollectionDescription] = useState(""); @@ -103,14 +104,24 @@ export default function KnowledgeBasePage() { const handlePreviewDocument = async (firstDocument: Document) => { setShowDocPreviewModal(true); setPreviewDoc(firstDocument); - const enriched = await fetchAndPreviewDoc(firstDocument); - setPreviewDoc(enriched); + setIsPreviewLoading(true); + try { + const enriched = await fetchAndPreviewDoc(firstDocument); + setPreviewDoc(enriched); + } finally { + setIsPreviewLoading(false); + } }; const handleSelectPreviewDoc = async (doc: Document) => { setPreviewDoc(doc); - const enriched = await fetchAndPreviewDoc(doc); - setPreviewDoc(enriched); + setIsPreviewLoading(true); + try { + const enriched = await fetchAndPreviewDoc(doc); + setPreviewDoc(enriched); + } finally { + setIsPreviewLoading(false); + } }; return ( @@ -153,7 +164,7 @@ export default function KnowledgeBasePage() { ) : selectedCollection ? ( -
+
diff --git a/app/api/collections/route.ts b/app/api/collections/route.ts index 0c40145d..20ee2cff 100644 --- a/app/api/collections/route.ts +++ b/app/api/collections/route.ts @@ -3,7 +3,7 @@ import { apiClient } from "@/app/lib/apiClient"; export async function GET(request: Request) { try { - const { status, data } = await apiClient(request, "/api/v1/collections/"); + const { status, data } = await apiClient(request, "/api/v1/collections"); return NextResponse.json(data, { status }); } catch (error: unknown) { return NextResponse.json( @@ -22,7 +22,7 @@ export async function POST(request: NextRequest) { // Get the JSON body from the request const body = await request.json(); - const { status, data } = await apiClient(request, "/api/v1/collections/", { + const { status, data } = await apiClient(request, "/api/v1/collections", { method: "POST", body: JSON.stringify(body), }); diff --git a/app/api/configs/route.ts b/app/api/configs/route.ts index d0f6016d..3fda9fa3 100644 --- a/app/api/configs/route.ts +++ b/app/api/configs/route.ts @@ -5,7 +5,7 @@ export async function GET(request: Request) { try { const { searchParams } = new URL(request.url); const queryString = searchParams.toString(); - const endpoint = `/api/v1/configs/${queryString ? `?${queryString}` : ""}`; + const endpoint = `/api/v1/configs${queryString ? `?${queryString}` : ""}`; const { status, data } = await apiClient(request, endpoint); return NextResponse.json(data, { status }); } catch (error) { @@ -24,7 +24,7 @@ export async function POST(request: Request) { try { const body = await request.json(); - const { status, data } = await apiClient(request, "/api/v1/configs/", { + const { status, data } = await apiClient(request, "/api/v1/configs", { method: "POST", body: JSON.stringify(body), }); diff --git a/app/api/credentials/route.ts b/app/api/credentials/route.ts index 627f2b2a..06c76646 100644 --- a/app/api/credentials/route.ts +++ b/app/api/credentials/route.ts @@ -3,7 +3,7 @@ import { NextResponse, NextRequest } from "next/server"; export async function GET(request: NextRequest) { try { - const { status, data } = await apiClient(request, "/api/v1/credentials/"); + const { status, data } = await apiClient(request, "/api/v1/credentials"); return NextResponse.json(data, { status }); } catch (e: unknown) { return NextResponse.json( @@ -16,7 +16,7 @@ export async function GET(request: NextRequest) { export async function POST(request: NextRequest) { try { const body = await request.json(); - const { status, data } = await apiClient(request, "/api/v1/credentials/", { + const { status, data } = await apiClient(request, "/api/v1/credentials", { method: "POST", body: JSON.stringify(body), }); @@ -32,7 +32,7 @@ export async function POST(request: NextRequest) { export async function PATCH(request: NextRequest) { try { const body = await request.json(); - const { status, data } = await apiClient(request, "/api/v1/credentials/", { + const { status, data } = await apiClient(request, "/api/v1/credentials", { method: "PATCH", body: JSON.stringify(body), }); diff --git a/app/api/document/route.ts b/app/api/document/route.ts index fc4291ca..3eac9795 100644 --- a/app/api/document/route.ts +++ b/app/api/document/route.ts @@ -5,7 +5,7 @@ export async function GET(request: Request) { try { const { searchParams } = new URL(request.url); const queryString = searchParams.toString(); - const endpoint = `/api/v1/documents/${queryString ? `?${queryString}` : ""}`; + const endpoint = `/api/v1/documents${queryString ? `?${queryString}` : ""}`; const { status, data } = await apiClient(request, endpoint); return NextResponse.json(data, { status }); } catch (error: unknown) { @@ -78,7 +78,7 @@ export async function POST(request: NextRequest) { }, }); - const { status, data } = await apiClient(request, "/api/v1/documents/", { + const { status, data } = await apiClient(request, "/api/v1/documents", { method: "POST", body: uploadBody, headers: { "Content-Type": contentType }, diff --git a/app/api/organization/route.ts b/app/api/organization/route.ts index f04430d7..1a155321 100644 --- a/app/api/organization/route.ts +++ b/app/api/organization/route.ts @@ -8,7 +8,7 @@ export async function GET(request: NextRequest) { const { status, data } = await apiClient( request, - `/api/v1/organizations/${queryString ? `?${queryString}` : ""}`, + `/api/v1/organizations${queryString ? `?${queryString}` : ""}`, ); return NextResponse.json(data, { status }); } catch { diff --git a/app/api/projects/route.ts b/app/api/projects/route.ts index ae734776..406a0c67 100644 --- a/app/api/projects/route.ts +++ b/app/api/projects/route.ts @@ -4,7 +4,7 @@ import { apiClient } from "@/app/lib/apiClient"; export async function POST(request: NextRequest) { try { const body = await request.json(); - const { status, data } = await apiClient(request, "/api/v1/projects/", { + const { status, data } = await apiClient(request, "/api/v1/projects", { method: "POST", body: JSON.stringify(body), }); diff --git a/app/api/user-projects/route.ts b/app/api/user-projects/route.ts index cfff7e03..937ee1ae 100644 --- a/app/api/user-projects/route.ts +++ b/app/api/user-projects/route.ts @@ -8,7 +8,7 @@ export async function GET(request: NextRequest) { const { status, data } = await apiClient( request, - `/api/v1/user-projects/${queryString ? `?${queryString}` : ""}`, + `/api/v1/user-projects${queryString ? `?${queryString}` : ""}`, ); return NextResponse.json(data, { status }); } catch { @@ -22,14 +22,10 @@ export async function GET(request: NextRequest) { export async function POST(request: NextRequest) { try { const body = await request.json(); - const { status, data } = await apiClient( - request, - "/api/v1/user-projects/", - { - method: "POST", - body: JSON.stringify(body), - }, - ); + const { status, data } = await apiClient(request, "/api/v1/user-projects", { + method: "POST", + body: JSON.stringify(body), + }); return NextResponse.json(data, { status }); } catch { return NextResponse.json( diff --git a/app/components/Sidebar.tsx b/app/components/Sidebar.tsx index e8efa179..afb30f12 100644 --- a/app/components/Sidebar.tsx +++ b/app/components/Sidebar.tsx @@ -360,7 +360,7 @@ export default function Sidebar({
) : !isAuthenticated ? ( -
+

Get full access diff --git a/app/components/keystore/AddKeyModal.tsx b/app/components/keystore/AddKeyModal.tsx index 6f6da8d2..5d15c850 100644 --- a/app/components/keystore/AddKeyModal.tsx +++ b/app/components/keystore/AddKeyModal.tsx @@ -11,6 +11,7 @@ interface AddKeyModalProps { newKeyLabel: string; newKeyValue: string; newKeyProvider: string; + isValidating?: boolean; onLabelChange: (value: string) => void; onValueChange: (value: string) => void; onProviderChange: (value: string) => void; @@ -23,13 +24,15 @@ export default function AddKeyModal({ newKeyLabel, newKeyValue, newKeyProvider, + isValidating, onLabelChange, onValueChange, onProviderChange, onAddKey, onClose, }: AddKeyModalProps) { - const isDisabled = !newKeyLabel.trim() || !newKeyValue.trim(); + const isDisabled = + !newKeyLabel.trim() || !newKeyValue.trim() || !!isValidating; return (

-
diff --git a/app/components/keystore/KeysCard.tsx b/app/components/keystore/KeysCard.tsx index 9c629bbf..fdb4bccf 100644 --- a/app/components/keystore/KeysCard.tsx +++ b/app/components/keystore/KeysCard.tsx @@ -68,7 +68,7 @@ export default function KeysCard({
- + {apiKey.provider}

diff --git a/app/components/knowledge-base/CollectionDetail.tsx b/app/components/knowledge-base/CollectionDetail.tsx index bb5601e8..05231148 100644 --- a/app/components/knowledge-base/CollectionDetail.tsx +++ b/app/components/knowledge-base/CollectionDetail.tsx @@ -109,8 +109,8 @@ export default function CollectionDetail({

-
-
+
+

Documents Present ({documents.length})

@@ -126,8 +126,8 @@ export default function CollectionDetail({
{documents.length > 0 ? ( -
-
+
+

Name @@ -140,7 +140,11 @@ export default function CollectionDetail({

-
+
{visibleDocs.map((doc) => (
{documents.length > 3 && ( -
+
- ))} + {doc.inserted_at && ( +

+ {formatDate(doc.inserted_at)} +

+ )} + + ); + })}
-
+
{previewDoc?.signed_url ? (