Skip to content

Commit a2ad306

Browse files
authored
Merge branch 'main' into visual-parser-frontend
2 parents 8f353c3 + c6d2e92 commit a2ad306

22 files changed

Lines changed: 1180 additions & 262 deletions

docker-compose.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -243,4 +243,4 @@ services:
243243

244244
volumes:
245245
opensearch-data:
246-
azurite-data:
246+
azurite-data:

frontend/app/api/queries/useGetConnectorsQuery.ts

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -133,14 +133,15 @@ export interface Connector {
133133
name: string;
134134
description: string;
135135
icon: string; // The icon name from the API
136-
status: "not_connected" | "connected" | "error";
136+
status: "not_connected" | "configured" | "connected" | "error";
137137
type: string;
138138
connectionId?: string;
139139
clientId?: string;
140140
baseUrl?: string;
141141
access_token?: string;
142142
selectedFiles?: GoogleDriveFile[] | OneDriveFile[];
143143
available?: boolean;
144+
requiresOAuth?: boolean;
144145
}
145146

146147
interface Connection {
@@ -189,6 +190,11 @@ export const useGetConnectorsQuery = (
189190
let status: Connector["status"] = "not_connected";
190191
let connectionId: string | undefined;
191192

193+
// Determine if this connector requires OAuth based on connector kind
194+
// "oauth" connectors require OAuth flow (Google Drive, OneDrive, SharePoint)
195+
// "bucket" connectors use credential-based auth (Azure Blob, S3, IBM COS)
196+
const requiresOAuth = connectorData.kind === "oauth";
197+
192198
if (statusResponse.ok) {
193199
const statusData = await statusResponse.json();
194200
const connections = statusData.connections || [];
@@ -210,8 +216,14 @@ export const useGetConnectorsQuery = (
210216
clientId: activeConnection.client_id,
211217
baseUrl: activeConnection.base_url,
212218
available: connectorData.available,
219+
requiresOAuth,
213220
} as Connector;
214221
}
222+
223+
// For OAuth connectors: check if credentials are configured in .env
224+
if (requiresOAuth && statusData.has_env_credentials) {
225+
status = "configured";
226+
}
215227
}
216228

217229
return {
@@ -223,6 +235,7 @@ export const useGetConnectorsQuery = (
223235
type,
224236
connectionId,
225237
available: connectorData.available,
238+
requiresOAuth,
226239
} as Connector;
227240
}),
228241
);

frontend/app/settings/_components/connector-card.tsx

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ export interface Connector {
2424
available?: boolean;
2525
status?: string;
2626
connectionId?: string;
27+
requiresOAuth?: boolean;
2728
}
2829

2930
interface ConnectorCardProps {
@@ -56,6 +57,7 @@ export default function ConnectorCard({
5657
const canUpload = can("knowledge:upload");
5758
const isConnected =
5859
connector.status === "connected" && connector.connectionId;
60+
const isConfigured = connector.status === "configured";
5961

6062
return (
6163
<Card
@@ -102,9 +104,13 @@ export default function ConnectorCard({
102104
isCloudBrand && "!text-layer-contextual-foreground",
103105
)}
104106
>
105-
{isConnected || connector?.available
106-
? `${connector.name} is configured.`
107-
: "Allowed for this workspace — OAuth credentials not configured yet."}
107+
{isConnected
108+
? `${connector.name} is connected.`
109+
: isConfigured
110+
? `${connector.name} is configured.`
111+
: connector?.available && !connector.requiresOAuth
112+
? `${connector.name} is available to connect.`
113+
: "Allowed for this workspace — OAuth credentials not configured yet."}
108114
</CardDescription>
109115
</div>
110116
</div>

frontend/app/settings/_components/connector-cards.tsx

Lines changed: 121 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import {
88
type Connector as QueryConnector,
99
useGetConnectorsQuery,
1010
} from "@/app/api/queries/useGetConnectorsQuery";
11+
import { DeleteConfirmationDialog } from "@/components/delete-confirmation-dialog";
1112
import { useAuth } from "@/contexts/auth-context";
1213
import { useBrand } from "@/contexts/brand-context";
1314
import { isSaasPolicyContext } from "@/lib/brand";
@@ -29,6 +30,9 @@ export default function ConnectorCards() {
2930
});
3031
const router = useRouter();
3132
const [openDialog, setOpenDialog] = useState<string | null>(null);
33+
const [disconnectTarget, setDisconnectTarget] = useState<Connector | null>(
34+
null,
35+
);
3236

3337
const { data: queryConnectors = [], isLoading: connectorsLoading } =
3438
useGetConnectorsQuery({
@@ -50,10 +54,17 @@ export default function ConnectorCards() {
5054
return <Icon />;
5155
}, []);
5256

53-
const connectors = queryConnectors.map((c) => ({
54-
...c,
55-
icon: getConnectorIcon(c.type),
56-
})) as Connector[];
57+
const connectors = queryConnectors.reduce<Connector[]>((acc, c) => {
58+
// Keep OAuth connectors regardless of availability
59+
// Only hide credential-based connectors when unavailable
60+
if (c.requiresOAuth || c.available !== false) {
61+
acc.push({
62+
...c,
63+
icon: getConnectorIcon(c.type),
64+
} as Connector);
65+
}
66+
return acc;
67+
}, []);
5768

5869
const handleConnect = async (connector: Connector) => {
5970
connectMutation.mutate({
@@ -62,8 +73,19 @@ export default function ConnectorCards() {
6273
});
6374
};
6475

65-
const handleDisconnect = async (connector: Connector) => {
66-
disconnectMutation.mutate(connector as unknown as QueryConnector);
76+
const handleDisconnect = (connector: Connector) => {
77+
setDisconnectTarget(connector);
78+
};
79+
80+
const confirmDisconnect = async () => {
81+
if (!disconnectTarget) return;
82+
try {
83+
await disconnectMutation.mutateAsync(
84+
disconnectTarget as unknown as QueryConnector,
85+
);
86+
} finally {
87+
setDisconnectTarget(null);
88+
}
6789
};
6890

6991
const navigateToKnowledgePage = (connector: Connector) => {
@@ -94,36 +116,82 @@ export default function ConnectorCards() {
94116
(d) => d.SettingsDialog,
95117
);
96118

119+
// Split connectors into OAuth and credential-based
120+
const oauthConnectors = connectors.filter((c) => c.requiresOAuth);
121+
const credentialConnectors = connectors.filter((c) => !c.requiresOAuth);
122+
97123
return (
98124
<>
99-
<div className="grid gap-6 md:grid-cols-2 lg:grid-cols-3">
100-
{connectorsLoading ? (
101-
<>
102-
<ConnectorsSkeleton />
103-
<ConnectorsSkeleton />
104-
<ConnectorsSkeleton />
105-
</>
106-
) : (
107-
connectors.map((connector) => (
108-
<ConnectorCard
109-
key={connector.id}
110-
connector={connector}
111-
isConnecting={
112-
connectMutation.isPending &&
113-
connectMutation.variables?.connector.id === connector.id
114-
}
115-
isDisconnecting={
116-
disconnectMutation.isPending &&
117-
(disconnectMutation.variables as any)?.type === connector.type
118-
}
119-
onConnect={handleConnect}
120-
onDisconnect={handleDisconnect}
121-
onNavigateToKnowledge={navigateToKnowledgePage}
122-
onConfigure={getConfigureHandler(connector)}
123-
/>
124-
))
125-
)}
126-
</div>
125+
{/* OAuth Connectors Section */}
126+
{(connectorsLoading || oauthConnectors.length > 0) && (
127+
<div className="space-y-4">
128+
<h3 className="text-lg font-semibold">OAuth Connectors</h3>
129+
<div className="grid gap-6 md:grid-cols-2 lg:grid-cols-3">
130+
{connectorsLoading ? (
131+
<>
132+
<ConnectorsSkeleton />
133+
<ConnectorsSkeleton />
134+
<ConnectorsSkeleton />
135+
</>
136+
) : (
137+
oauthConnectors.map((connector) => (
138+
<ConnectorCard
139+
key={connector.id}
140+
connector={connector}
141+
isConnecting={
142+
connectMutation.isPending &&
143+
connectMutation.variables?.connector.id === connector.id
144+
}
145+
isDisconnecting={
146+
disconnectMutation.isPending &&
147+
(disconnectMutation.variables as any)?.type ===
148+
connector.type
149+
}
150+
onConnect={handleConnect}
151+
onDisconnect={handleDisconnect}
152+
onNavigateToKnowledge={navigateToKnowledgePage}
153+
onConfigure={getConfigureHandler(connector)}
154+
/>
155+
))
156+
)}
157+
</div>
158+
</div>
159+
)}
160+
161+
{/* Credential-Based Connectors Section */}
162+
{(connectorsLoading || credentialConnectors.length > 0) && (
163+
<div className="space-y-4">
164+
<h3 className="text-lg font-semibold">Credential-Based Connectors</h3>
165+
<div className="grid gap-6 md:grid-cols-2 lg:grid-cols-3">
166+
{connectorsLoading ? (
167+
<>
168+
<ConnectorsSkeleton />
169+
<ConnectorsSkeleton />
170+
</>
171+
) : (
172+
credentialConnectors.map((connector) => (
173+
<ConnectorCard
174+
key={connector.id}
175+
connector={connector}
176+
isConnecting={
177+
connectMutation.isPending &&
178+
connectMutation.variables?.connector.id === connector.id
179+
}
180+
isDisconnecting={
181+
disconnectMutation.isPending &&
182+
(disconnectMutation.variables as any)?.type ===
183+
connector.type
184+
}
185+
onConnect={handleConnect}
186+
onDisconnect={handleDisconnect}
187+
onNavigateToKnowledge={navigateToKnowledgePage}
188+
onConfigure={getConfigureHandler(connector)}
189+
/>
190+
))
191+
)}
192+
</div>
193+
</div>
194+
)}
127195

128196
{dialogDescriptors.map((descriptor) => {
129197
// Render only while open so the component unmounts on close, which
@@ -145,6 +213,25 @@ export default function ConnectorCards() {
145213
/>
146214
);
147215
})}
216+
217+
<DeleteConfirmationDialog
218+
open={disconnectTarget !== null}
219+
onOpenChange={(open) => {
220+
if (!open) setDisconnectTarget(null);
221+
}}
222+
title={`Disconnect ${disconnectTarget?.name ?? "connector"}?`}
223+
description={`This will remove the ${disconnectTarget?.name ?? "connector"} connection from OpenRAG.`}
224+
confirmText="Disconnect"
225+
onConfirm={confirmDisconnect}
226+
isLoading={disconnectMutation.isPending}
227+
>
228+
<p>
229+
Any documents previously ingested from this connector will remain in
230+
your knowledge base, but new syncs will no longer be possible until
231+
you reconnect. Re-authenticating later will allow you to resume
232+
syncing.
233+
</p>
234+
</DeleteConfirmationDialog>
148235
</>
149236
);
150237
}

frontend/components/chat-renderer.tsx

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,8 +53,6 @@ export function ChatRenderer({
5353
setOnboardingComplete,
5454
} = useChat();
5555

56-
//TODO: comment to test CI
57-
5856
// Initialize onboarding state from backend settings
5957
const [currentStep, setCurrentStep] = useState<number>(
6058
settings?.onboarding?.current_step ?? 0,

0 commit comments

Comments
 (0)