mirror of
https://github.com/csunny/DB-GPT.git
synced 2026-07-16 17:15:22 +00:00
fix(connector): wire frontend through ctx-axios + add sidebar entry + normalize field names
Pre-existing integration gaps surfaced when running the dev server: 1. use-connector-api.ts used bare fetch() instead of ctx-axios, so all /api/v2/serve/connectors/* requests hit the Next.js dev server (3000) instead of the backend (5670) and returned the Next 404 HTML page. Migrate every helper to axios via @/utils/ctx-axios. 2. useConfirmPolling.ts had the same bare-fetch problem; HITL confirm polling and resolve calls would never reach the backend. 3. Backend ConnectorResponse exposes connector_id while the frontend ConnectorInstance type uses id, leaving every c.id undefined. Add a normalizeConnector boundary in use-connector-api.ts so the rest of the frontend can keep using id. 4. /construct/connectors had no entry in the side-bar settings menu — users had to type the URL by hand. Add an "ApiOutlined / 连接器管理" item next to prompts in the management group. 5. Add the new `connectors` translation key to zh/en common.ts so the sidebar label resolves and TS strict-key check passes.
This commit is contained in:
@@ -7,6 +7,7 @@ import type { IChatDialogueSchema } from '@/types/chat';
|
||||
import { STORAGE_LANG_KEY, STORAGE_THEME_KEY } from '@/utils/constants/index';
|
||||
import Icon, {
|
||||
ApartmentOutlined,
|
||||
ApiOutlined,
|
||||
AppstoreOutlined,
|
||||
DeleteOutlined,
|
||||
EditOutlined,
|
||||
@@ -238,6 +239,21 @@ function SideBar() {
|
||||
<EditOutlined className='text-orange-500' />
|
||||
<span>{t('prompts')}</span>
|
||||
</div>
|
||||
<div
|
||||
onClick={() => {
|
||||
router.push('/construct/connectors');
|
||||
setSettingsOpen(false);
|
||||
}}
|
||||
className={cls(
|
||||
'flex items-center gap-3 px-3 py-2.5 text-sm text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-800 cursor-pointer transition-colors',
|
||||
{
|
||||
'bg-blue-50 text-blue-600 dark:bg-blue-900/20 dark:text-blue-400': pathname.startsWith('/construct/connectors'),
|
||||
},
|
||||
)}
|
||||
>
|
||||
<ApiOutlined className='text-violet-500' />
|
||||
<span>{t('connectors')}</span>
|
||||
</div>
|
||||
<div
|
||||
onClick={() => {
|
||||
router.push('/construct/dbgpts');
|
||||
|
||||
@@ -1,19 +1,22 @@
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import axios from '@/utils/ctx-axios';
|
||||
import { ConnectorCatalogEntry, ConnectorInstance, CreateConnectorRequest } from '@/new-components/connector/types';
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
|
||||
const API_BASE = '/api/v2/serve/connectors';
|
||||
|
||||
async function apiFetch<T>(url: string, options?: RequestInit): Promise<T> {
|
||||
const response = await fetch(url, {
|
||||
headers: { 'Content-Type': 'application/json', ...options?.headers },
|
||||
...options,
|
||||
});
|
||||
if (!response.ok) {
|
||||
const text = await response.text();
|
||||
throw new Error(`HTTP ${response.status}: ${text}`);
|
||||
}
|
||||
const json = await response.json();
|
||||
return json.data as T;
|
||||
// Backend ConnectorResponse uses `connector_id`; frontend code uses `id`.
|
||||
// Normalize at the boundary so the rest of the app can stay on `id`.
|
||||
type BackendConnector = Omit<ConnectorInstance, 'id'> & { connector_id: string };
|
||||
|
||||
function normalizeConnector(raw: BackendConnector): ConnectorInstance {
|
||||
const { connector_id, ...rest } = raw;
|
||||
return { id: connector_id, ...rest } as ConnectorInstance;
|
||||
}
|
||||
|
||||
function unwrap<T>(payload: any): T {
|
||||
// ctx-axios already unwrapped axios.response → response.data, so payload here
|
||||
// is the API envelope { success, err_code, err_msg, data }.
|
||||
return (payload?.data ?? payload) as T;
|
||||
}
|
||||
|
||||
export function useConnectorTypes() {
|
||||
@@ -22,8 +25,9 @@ export function useConnectorTypes() {
|
||||
|
||||
useEffect(() => {
|
||||
setLoading(true);
|
||||
apiFetch<ConnectorCatalogEntry[]>(`${API_BASE}/types`)
|
||||
.then(data => setTypes(data ?? []))
|
||||
axios
|
||||
.get(`${API_BASE}/types`)
|
||||
.then(res => setTypes(unwrap<ConnectorCatalogEntry[]>(res) ?? []))
|
||||
.catch(err => console.error('Failed to fetch connector types:', err))
|
||||
.finally(() => setLoading(false));
|
||||
}, []);
|
||||
@@ -38,8 +42,12 @@ export function useConnectors() {
|
||||
|
||||
useEffect(() => {
|
||||
setLoading(true);
|
||||
apiFetch<ConnectorInstance[]>(API_BASE)
|
||||
.then(data => setConnectors(data ?? []))
|
||||
axios
|
||||
.get(API_BASE)
|
||||
.then(res => {
|
||||
const raw = unwrap<BackendConnector[]>(res) ?? [];
|
||||
setConnectors(raw.map(normalizeConnector));
|
||||
})
|
||||
.catch(err => console.error('Failed to fetch connectors:', err))
|
||||
.finally(() => setLoading(false));
|
||||
}, [version]);
|
||||
@@ -57,10 +65,8 @@ export function useCreateConnector() {
|
||||
const create = useCallback(async (data: CreateConnectorRequest): Promise<ConnectorInstance> => {
|
||||
setLoading(true);
|
||||
try {
|
||||
return await apiFetch<ConnectorInstance>(API_BASE, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(data),
|
||||
});
|
||||
const res = await axios.post(API_BASE, data);
|
||||
return normalizeConnector(unwrap<BackendConnector>(res));
|
||||
} catch (err) {
|
||||
console.error('Failed to create connector:', err);
|
||||
throw err;
|
||||
@@ -79,10 +85,8 @@ export function useUpdateConnector() {
|
||||
async (id: string, data: Partial<CreateConnectorRequest>): Promise<ConnectorInstance> => {
|
||||
setLoading(true);
|
||||
try {
|
||||
return await apiFetch<ConnectorInstance>(`${API_BASE}/${id}`, {
|
||||
method: 'PUT',
|
||||
body: JSON.stringify(data),
|
||||
});
|
||||
const res = await axios.put(`${API_BASE}/${id}`, data);
|
||||
return normalizeConnector(unwrap<BackendConnector>(res));
|
||||
} catch (err) {
|
||||
console.error('Failed to update connector:', err);
|
||||
throw err;
|
||||
@@ -102,11 +106,7 @@ export function useDeleteConnector() {
|
||||
const remove = useCallback(async (id: string): Promise<void> => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const response = await fetch(`${API_BASE}/${id}`, { method: 'DELETE' });
|
||||
if (!response.ok) {
|
||||
const text = await response.text();
|
||||
throw new Error(`HTTP ${response.status}: ${text}`);
|
||||
}
|
||||
await axios.delete(`${API_BASE}/${id}`);
|
||||
} catch (err) {
|
||||
console.error('Failed to delete connector:', err);
|
||||
throw err;
|
||||
@@ -124,9 +124,8 @@ export function useTestConnection() {
|
||||
const test = useCallback(async (id: string): Promise<{ success: boolean; message: string }> => {
|
||||
setLoading(true);
|
||||
try {
|
||||
return await apiFetch<{ success: boolean; message: string }>(`${API_BASE}/${id}/test`, {
|
||||
method: 'POST',
|
||||
});
|
||||
const res = await axios.post(`${API_BASE}/${id}/test`);
|
||||
return unwrap<{ success: boolean; message: string }>(res);
|
||||
} catch (err) {
|
||||
console.error('Failed to test connection:', err);
|
||||
throw err;
|
||||
|
||||
@@ -459,6 +459,7 @@ export const CommonEn = {
|
||||
app_management: 'App Management',
|
||||
awel_workflow: 'AWEL Workflow',
|
||||
prompts: 'Prompts',
|
||||
connectors: 'Connectors',
|
||||
// Skills page
|
||||
skills_page_subtitle: 'Pre-packaged and reusable best practices and tools for your agents',
|
||||
skills_search_placeholder: 'Search skills',
|
||||
|
||||
@@ -462,6 +462,7 @@ export const CommonZh: Resources['translation'] = {
|
||||
app_management: '应用管理',
|
||||
awel_workflow: 'AWEL 工作流',
|
||||
prompts: '提示词',
|
||||
connectors: '连接器管理',
|
||||
// Skills page
|
||||
skills_page_subtitle: '为您的智能体提供预封装且可重复的最佳实践与工具',
|
||||
skills_search_placeholder: '搜索技能',
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import axios from '@/utils/ctx-axios';
|
||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import { ConfirmActionRequest, PendingConfirmation } from './types';
|
||||
|
||||
@@ -23,10 +24,10 @@ export function useConfirmPolling({
|
||||
|
||||
const fetchPending = useCallback(async () => {
|
||||
try {
|
||||
const res = await fetch('/api/v2/serve/connectors/pending-confirms');
|
||||
if (!res.ok) return;
|
||||
const data: PendingConfirmation[] = await res.json();
|
||||
if (data.length > 0) {
|
||||
const data = (await axios.get('/api/v2/serve/connectors/pending-confirms')) as
|
||||
| PendingConfirmation[]
|
||||
| undefined;
|
||||
if (data && data.length > 0) {
|
||||
const first = data[0];
|
||||
if (first.confirm_id !== currentConfirmIdRef.current) {
|
||||
currentConfirmIdRef.current = first.confirm_id;
|
||||
@@ -63,11 +64,7 @@ export function useConfirmPolling({
|
||||
approved,
|
||||
};
|
||||
try {
|
||||
await fetch('/api/v2/serve/connectors/confirm', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(body),
|
||||
});
|
||||
await axios.post('/api/v2/serve/connectors/confirm', body);
|
||||
} catch {}
|
||||
currentConfirmIdRef.current = null;
|
||||
setPendingConfirmation(null);
|
||||
|
||||
Reference in New Issue
Block a user