mirror of
https://github.com/Syngnat/GoNavi.git
synced 2026-06-21 14:04:01 +08:00
✨ feat(chroma): 新增 Chroma 向量库连接支持
- 后端新增 Chroma REST 连接、元数据浏览、JSON/SELECT 查询与 upsert/delete 写入 - 前端新增 Chroma 类型、连接配置、图标、方言和能力矩阵 - 测试覆盖 v1/v2 兼容、真实服务 smoke 和前端配置 Refs #560
This commit is contained in:
@@ -12,6 +12,8 @@ describe('connectionDriverType', () => {
|
||||
expect(normalizeDriverType('postgresql')).toBe('postgres');
|
||||
expect(normalizeDriverType('pgx')).toBe('postgres');
|
||||
expect(normalizeDriverType('elastic')).toBe('elasticsearch');
|
||||
expect(normalizeDriverType('chromadb')).toBe('chroma');
|
||||
expect(normalizeDriverType('chroma-db')).toBe('chroma');
|
||||
expect(normalizeDriverType('doris')).toBe('diros');
|
||||
expect(normalizeDriverType('open-gauss')).toBe('opengauss');
|
||||
expect(normalizeDriverType('InterSystemsIRIS')).toBe('iris');
|
||||
|
||||
@@ -15,6 +15,7 @@ export const normalizeDriverType = (value: string): string => {
|
||||
const normalized = String(value || '').trim().toLowerCase();
|
||||
if (normalized === 'postgresql' || normalized === 'pg' || normalized === 'pq' || normalized === 'pgx') return 'postgres';
|
||||
if (normalized === 'elastic') return 'elasticsearch';
|
||||
if (normalized === 'chromadb' || normalized === 'chroma-db') return 'chroma';
|
||||
if (normalized === 'doris') return 'diros';
|
||||
if (
|
||||
normalized === 'open_gauss' ||
|
||||
|
||||
@@ -87,6 +87,7 @@ describe('connectionModalPresentation', () => {
|
||||
'iris',
|
||||
'mongodb',
|
||||
'elasticsearch',
|
||||
'chroma',
|
||||
'redis',
|
||||
'tdengine',
|
||||
'custom',
|
||||
@@ -156,11 +157,20 @@ describe('connectionModalPresentation', () => {
|
||||
'credentials',
|
||||
'databaseScope',
|
||||
]);
|
||||
expect(resolveConnectionConfigLayout('chroma').sections).toEqual([
|
||||
'identity',
|
||||
'uri',
|
||||
'target',
|
||||
'service',
|
||||
'credentials',
|
||||
'databaseScope',
|
||||
]);
|
||||
});
|
||||
|
||||
it('uses localized labels for layout kinds shown in the modal', () => {
|
||||
expect(getConnectionConfigLayoutKindLabel('mysql-compatible')).toBe('MySQL 兼容');
|
||||
expect(getConnectionConfigLayoutKindLabel('file')).toBe('文件型数据库');
|
||||
expect(getConnectionConfigLayoutKindLabel('search')).toBe('搜索引擎');
|
||||
expect(getConnectionConfigLayoutKindLabel('vector')).toBe('向量数据库');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -40,6 +40,7 @@ export type ConnectionConfigLayoutKind =
|
||||
| 'oracle'
|
||||
| 'file'
|
||||
| 'search'
|
||||
| 'vector'
|
||||
| 'custom'
|
||||
| 'jvm'
|
||||
| 'generic-sql';
|
||||
@@ -160,6 +161,8 @@ export const getConnectionConfigLayoutKindLabel = (
|
||||
return '文件型数据库';
|
||||
case 'search':
|
||||
return '搜索引擎';
|
||||
case 'vector':
|
||||
return '向量数据库';
|
||||
case 'custom':
|
||||
return '自定义连接';
|
||||
case 'jvm':
|
||||
@@ -249,6 +252,19 @@ export const resolveConnectionConfigLayout = (
|
||||
],
|
||||
};
|
||||
}
|
||||
if (type === 'chroma') {
|
||||
return {
|
||||
kind: 'vector',
|
||||
sections: [
|
||||
'identity',
|
||||
'uri',
|
||||
'target',
|
||||
'service',
|
||||
'credentials',
|
||||
'databaseScope',
|
||||
],
|
||||
};
|
||||
}
|
||||
if (postgresCompatibleTypes.has(type)) {
|
||||
return {
|
||||
kind: 'postgres-compatible',
|
||||
|
||||
@@ -17,6 +17,7 @@ describe('connectionTypeCapabilities', () => {
|
||||
expect(singleHostUriSchemesByType.opengauss).toContain('jdbc:opengauss');
|
||||
expect(singleHostUriSchemesByType.dameng).toEqual(['dameng', 'dm']);
|
||||
expect(singleHostUriSchemesByType.elasticsearch).toEqual(['http', 'https']);
|
||||
expect(singleHostUriSchemesByType.chroma).toEqual(['http', 'https', 'chroma']);
|
||||
expect(singleHostUriSchemesByType.redis).toEqual(['redis']);
|
||||
});
|
||||
|
||||
@@ -24,6 +25,7 @@ describe('connectionTypeCapabilities', () => {
|
||||
expect(supportsSSLForType('redis')).toBe(true);
|
||||
expect(supportsSSLForType('MongoDB')).toBe(true);
|
||||
expect(supportsSSLForType('elasticsearch')).toBe(true);
|
||||
expect(supportsSSLForType('chroma')).toBe(true);
|
||||
expect(supportsSSLForType('tdengine')).toBe(true);
|
||||
expect(supportsSSLForType('dameng')).toBe(true);
|
||||
expect(supportsSSLForType('sqlite')).toBe(false);
|
||||
@@ -36,6 +38,8 @@ describe('connectionTypeCapabilities', () => {
|
||||
expect(supportsSSLClientCertificateForType('sqlserver')).toBe(false);
|
||||
expect(supportsSSLCAPathForType('redis')).toBe(true);
|
||||
expect(supportsSSLClientCertificateForType('redis')).toBe(true);
|
||||
expect(supportsSSLCAPathForType('chroma')).toBe(true);
|
||||
expect(supportsSSLClientCertificateForType('chroma')).toBe(false);
|
||||
});
|
||||
|
||||
it('detects postgres-compatible SSL parameter dialects', () => {
|
||||
@@ -63,6 +67,7 @@ describe('connectionTypeCapabilities', () => {
|
||||
expect(supportsConnectionParamsForType('dameng')).toBe(true);
|
||||
expect(supportsConnectionParamsForType('tdengine')).toBe(true);
|
||||
expect(supportsConnectionParamsForType('elasticsearch')).toBe(true);
|
||||
expect(supportsConnectionParamsForType('chroma')).toBe(true);
|
||||
expect(supportsConnectionParamsForType('redis')).toBe(false);
|
||||
expect(supportsConnectionParamsForType('sqlite')).toBe(false);
|
||||
expect(supportsConnectionParamsForType('jvm')).toBe(false);
|
||||
|
||||
@@ -12,6 +12,7 @@ export const singleHostUriSchemesByType: Record<string, string[]> = {
|
||||
highgo: ["highgo"],
|
||||
vastbase: ["vastbase"],
|
||||
elasticsearch: ["http", "https"],
|
||||
chroma: ["http", "https", "chroma"],
|
||||
};
|
||||
|
||||
const normalizeConnectionType = (type: string) =>
|
||||
@@ -40,6 +41,7 @@ const sslSupportedTypes = new Set([
|
||||
"redis",
|
||||
"tdengine",
|
||||
"elasticsearch",
|
||||
"chroma",
|
||||
]);
|
||||
|
||||
export const supportsSSLForType = (type: string) =>
|
||||
@@ -62,6 +64,7 @@ const sslCAPathSupportedTypes = new Set([
|
||||
"mongodb",
|
||||
"redis",
|
||||
"elasticsearch",
|
||||
"chroma",
|
||||
]);
|
||||
|
||||
const sslClientCertificateSupportedTypes = new Set([
|
||||
@@ -123,4 +126,5 @@ export const supportsConnectionParamsForType = (type: string) =>
|
||||
type === "mongodb" ||
|
||||
type === "dameng" ||
|
||||
type === "tdengine" ||
|
||||
type === "elasticsearch";
|
||||
type === "elasticsearch" ||
|
||||
type === "chroma";
|
||||
|
||||
@@ -13,6 +13,7 @@ describe('connectionTypeCatalog', () => {
|
||||
'关系型数据库',
|
||||
'国产数据库',
|
||||
'NoSQL',
|
||||
'向量数据库',
|
||||
'时序数据库',
|
||||
'其他',
|
||||
]);
|
||||
@@ -23,6 +24,7 @@ describe('connectionTypeCatalog', () => {
|
||||
expect(keys).toContain('mongodb');
|
||||
expect(keys).toContain('redis');
|
||||
expect(keys).toContain('elasticsearch');
|
||||
expect(keys).toContain('chroma');
|
||||
expect(keys).toContain('jvm');
|
||||
expect(keys).toContain('custom');
|
||||
expect(new Set(keys).size).toBe(keys.length);
|
||||
@@ -37,6 +39,7 @@ describe('connectionTypeCatalog', () => {
|
||||
expect(getConnectionTypeDefaultPort('oracle')).toBe(1521);
|
||||
expect(getConnectionTypeDefaultPort('mongodb')).toBe(27017);
|
||||
expect(getConnectionTypeDefaultPort('elasticsearch')).toBe(9200);
|
||||
expect(getConnectionTypeDefaultPort('chroma')).toBe(8000);
|
||||
expect(getConnectionTypeDefaultPort('sqlite')).toBe(0);
|
||||
expect(getConnectionTypeDefaultPort('duckdb')).toBe(0);
|
||||
expect(getConnectionTypeDefaultPort('unknown')).toBe(3306);
|
||||
@@ -46,6 +49,7 @@ describe('connectionTypeCatalog', () => {
|
||||
expect(getConnectionTypeHint('redis')).toBe('单机 / 哨兵 / 集群');
|
||||
expect(getConnectionTypeHint('mongodb')).toBe('单机 / 副本集');
|
||||
expect(getConnectionTypeHint('elasticsearch')).toContain('Mapping');
|
||||
expect(getConnectionTypeHint('chroma')).toContain('向量');
|
||||
expect(getConnectionTypeHint('oceanbase')).toBe('MySQL / Oracle 租户');
|
||||
expect(getConnectionTypeHint('duckdb')).toBe('本地文件连接');
|
||||
expect(getConnectionTypeHint('mysql')).toBe('标准连接配置');
|
||||
|
||||
@@ -45,6 +45,12 @@ export const CONNECTION_TYPE_GROUPS: ConnectionTypeCatalogGroup[] = [
|
||||
{ key: 'elasticsearch', name: 'Elasticsearch' },
|
||||
],
|
||||
},
|
||||
{
|
||||
label: '向量数据库',
|
||||
items: [
|
||||
{ key: 'chroma', name: 'Chroma' },
|
||||
],
|
||||
},
|
||||
{
|
||||
label: '时序数据库',
|
||||
items: [
|
||||
@@ -97,6 +103,8 @@ export const getConnectionTypeDefaultPort = (type: string): number => {
|
||||
return 27017;
|
||||
case 'elasticsearch':
|
||||
return 9200;
|
||||
case 'chroma':
|
||||
return 8000;
|
||||
case 'highgo':
|
||||
return 5866;
|
||||
case 'mariadb':
|
||||
@@ -123,6 +131,8 @@ export const getConnectionTypeHint = (type: string): string => {
|
||||
return '单机 / 副本集';
|
||||
case 'elasticsearch':
|
||||
return '支持索引浏览、Mapping 检查、JSON DSL 和 query_string 查询';
|
||||
case 'chroma':
|
||||
return 'Collection 浏览、向量检索和元数据过滤';
|
||||
case 'oceanbase':
|
||||
return 'MySQL / Oracle 租户';
|
||||
case 'sqlite':
|
||||
|
||||
@@ -72,6 +72,24 @@ describe('dataSourceCapabilities', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('treats Chroma as a queryable vector datasource without SQL export actions', () => {
|
||||
expect(getDataSourceCapabilities({ type: 'chroma' })).toMatchObject({
|
||||
type: 'chroma',
|
||||
supportsQueryEditor: true,
|
||||
supportsSqlQueryExport: false,
|
||||
supportsCopyInsert: false,
|
||||
supportsCreateDatabase: false,
|
||||
supportsRenameDatabase: false,
|
||||
supportsDropDatabase: false,
|
||||
forceReadOnlyQueryResult: false,
|
||||
});
|
||||
expect(getDataSourceCapabilities({ type: 'custom', driver: 'chromadb' })).toMatchObject({
|
||||
type: 'chroma',
|
||||
supportsQueryEditor: true,
|
||||
supportsCopyInsert: false,
|
||||
});
|
||||
});
|
||||
|
||||
it('treats OceanBase Oracle protocol as Oracle capabilities', () => {
|
||||
expect(getDataSourceCapabilities({
|
||||
type: 'oceanbase',
|
||||
|
||||
@@ -21,6 +21,9 @@ const normalizeDataSourceToken = (raw: string): string => {
|
||||
case 'elastic':
|
||||
case 'elasticsearch':
|
||||
return 'elasticsearch';
|
||||
case 'chromadb':
|
||||
case 'chroma-db':
|
||||
return 'chroma';
|
||||
case 'intersystems':
|
||||
case 'intersystemsiris':
|
||||
case 'inter-systems':
|
||||
|
||||
@@ -30,6 +30,8 @@ describe('sqlDialect', () => {
|
||||
expect(resolveSqlDialect('custom', 'open_gauss')).toBe('opengauss');
|
||||
expect(resolveSqlDialect('Elasticsearch')).toBe('elasticsearch');
|
||||
expect(resolveSqlDialect('custom', 'elastic')).toBe('elasticsearch');
|
||||
expect(resolveSqlDialect('ChromaDB')).toBe('chroma');
|
||||
expect(resolveSqlDialect('custom', 'chroma-db')).toBe('chroma');
|
||||
expect(resolveSqlDialect('OceanBase', '', { oceanBaseProtocol: 'oracle' })).toBe('oracle');
|
||||
expect(resolveSqlDialect('custom', 'oceanbase', { oceanBaseProtocol: 'oracle' })).toBe('oracle');
|
||||
expect(isMysqlFamilyDialect('mariadb')).toBe(true);
|
||||
|
||||
@@ -30,6 +30,7 @@ export type SqlDialect =
|
||||
| 'mongodb'
|
||||
| 'redis'
|
||||
| 'elasticsearch'
|
||||
| 'chroma'
|
||||
| 'unknown'
|
||||
| string;
|
||||
|
||||
@@ -115,6 +116,10 @@ export const resolveSqlDialect = (
|
||||
return source;
|
||||
case 'elastic':
|
||||
return 'elasticsearch';
|
||||
case 'chromadb':
|
||||
case 'chroma-db':
|
||||
case 'chroma':
|
||||
return 'chroma';
|
||||
default:
|
||||
break;
|
||||
}
|
||||
@@ -140,6 +145,7 @@ export const resolveSqlDialect = (
|
||||
if (source.includes('sqlserver') || source.includes('mssql')) return 'sqlserver';
|
||||
if (source.includes('iris') || source.includes('intersystems')) return 'iris';
|
||||
if (source.includes('elastic')) return 'elasticsearch';
|
||||
if (source.includes('chroma')) return 'chroma';
|
||||
|
||||
return source;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user