mirror of
https://github.com/csunny/DB-GPT.git
synced 2026-07-16 17:15:22 +00:00
fix(connector): route order + frontend list URL trailing slash
Two backend integration bugs surfaced when /construct/connectors loaded:
1. GET /pending-confirms returned {Connector 'pending-confirms' not found}
because the route was defined AFTER GET /{connector_id}, so FastAPI
matched the catch-all first and treated 'pending-confirms' as a
connector_id. HITL polling never reached the queue endpoint.
Move /pending-confirms, /confirm, and ConfirmRequest BEFORE the
/{connector_id} routes so FastAPI matches the fixed paths first.
2. GET /api/v2/serve/connectors (no trailing slash) hit DB-GPT's static
Next-export fallback (404 HTML), because FastAPI registers the list
route as GET / under api_prefix and the auto-redirect was intercepted.
Frontend useConnectors() used the bare base URL.
Add API_BASE_LIST = `${API_BASE}/` constant and use it for the list
(GET /) and create (POST /) calls. Single-resource calls keep the
bare base + "/{id}" form.
This commit is contained in:
@@ -132,6 +132,42 @@ async def list_connectors(
|
||||
return Result.failed(msg=str(e))
|
||||
|
||||
|
||||
class ConfirmRequest(BaseModel):
|
||||
confirm_id: str
|
||||
approved: bool
|
||||
|
||||
|
||||
# IMPORTANT: keep these fixed-path routes BEFORE any "/{connector_id}" routes,
|
||||
# otherwise FastAPI matches /{connector_id} first and treats "pending-confirms" /
|
||||
# "confirm" as a connector_id value.
|
||||
@router.get("/pending-confirms")
|
||||
async def list_pending_confirms() -> List[dict]:
|
||||
from dbgpt.agent.resource.connector.confirmation import _PENDING_CONFIRMATIONS
|
||||
|
||||
return list(_PENDING_CONFIRMATIONS.values())
|
||||
|
||||
|
||||
@router.post("/confirm")
|
||||
async def confirm_action(request: ConfirmRequest) -> Dict[str, str]:
|
||||
from dbgpt.agent.resource.connector.manager import ConnectorManager as _CM
|
||||
|
||||
if global_system_app is None:
|
||||
raise HTTPException(status_code=503, detail="SystemApp not initialised")
|
||||
cm = global_system_app.get_component(
|
||||
"connector_manager", _CM, default_component=None
|
||||
)
|
||||
if cm is None:
|
||||
raise HTTPException(status_code=503, detail="ConnectorManager not available")
|
||||
registry = cm.get_confirmation_registry()
|
||||
resolved = registry.resolve(request.confirm_id, request.approved)
|
||||
if not resolved:
|
||||
raise HTTPException(status_code=404, detail="confirm_id not found or expired")
|
||||
from dbgpt.agent.resource.connector.confirmation import _PENDING_CONFIRMATIONS
|
||||
|
||||
_PENDING_CONFIRMATIONS.pop(request.confirm_id, None)
|
||||
return {"status": "ok"}
|
||||
|
||||
|
||||
@router.get("/{connector_id}", response_model=Result[ConnectorResponse])
|
||||
async def get_connector(
|
||||
connector_id: str,
|
||||
@@ -195,39 +231,6 @@ def init_endpoints(system_app: SystemApp, config: ServeConfig) -> None:
|
||||
global_system_app = system_app
|
||||
|
||||
|
||||
class ConfirmRequest(BaseModel):
|
||||
confirm_id: str
|
||||
approved: bool
|
||||
|
||||
|
||||
@router.get("/pending-confirms")
|
||||
async def list_pending_confirms() -> List[dict]:
|
||||
from dbgpt.agent.resource.connector.confirmation import _PENDING_CONFIRMATIONS
|
||||
|
||||
return list(_PENDING_CONFIRMATIONS.values())
|
||||
|
||||
|
||||
@router.post("/confirm")
|
||||
async def confirm_action(request: ConfirmRequest) -> Dict[str, str]:
|
||||
from dbgpt.agent.resource.connector.manager import ConnectorManager as _CM
|
||||
|
||||
if global_system_app is None:
|
||||
raise HTTPException(status_code=503, detail="SystemApp not initialised")
|
||||
cm = global_system_app.get_component(
|
||||
"connector_manager", _CM, default_component=None
|
||||
)
|
||||
if cm is None:
|
||||
raise HTTPException(status_code=503, detail="ConnectorManager not available")
|
||||
registry = cm.get_confirmation_registry()
|
||||
resolved = registry.resolve(request.confirm_id, request.approved)
|
||||
if not resolved:
|
||||
raise HTTPException(status_code=404, detail="confirm_id not found or expired")
|
||||
from dbgpt.agent.resource.connector.confirmation import _PENDING_CONFIRMATIONS
|
||||
|
||||
_PENDING_CONFIRMATIONS.pop(request.confirm_id, None)
|
||||
return {"status": "ok"}
|
||||
|
||||
|
||||
def _get_task_service():
|
||||
from ..service.scheduled_task_service import ScheduledTaskService
|
||||
|
||||
|
||||
@@ -3,6 +3,9 @@ import { ConnectorCatalogEntry, ConnectorInstance, CreateConnectorRequest } from
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
|
||||
const API_BASE = '/api/v2/serve/connectors';
|
||||
// FastAPI registers GET / and POST / under api_prefix, so list/create require
|
||||
// a trailing slash. Single-resource routes (`/{id}`, `/{id}/test`, etc.) do not.
|
||||
const API_BASE_LIST = `${API_BASE}/`;
|
||||
|
||||
// Backend ConnectorResponse uses `connector_id`; frontend code uses `id`.
|
||||
// Normalize at the boundary so the rest of the app can stay on `id`.
|
||||
@@ -43,7 +46,7 @@ export function useConnectors() {
|
||||
useEffect(() => {
|
||||
setLoading(true);
|
||||
axios
|
||||
.get(API_BASE)
|
||||
.get(API_BASE_LIST)
|
||||
.then(res => {
|
||||
const raw = unwrap<BackendConnector[]>(res) ?? [];
|
||||
setConnectors(raw.map(normalizeConnector));
|
||||
@@ -65,7 +68,7 @@ export function useCreateConnector() {
|
||||
const create = useCallback(async (data: CreateConnectorRequest): Promise<ConnectorInstance> => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const res = await axios.post(API_BASE, data);
|
||||
const res = await axios.post(API_BASE_LIST, data);
|
||||
return normalizeConnector(unwrap<BackendConnector>(res));
|
||||
} catch (err) {
|
||||
console.error('Failed to create connector:', err);
|
||||
|
||||
Reference in New Issue
Block a user