mirror of
https://github.com/Syngnat/GoNavi.git
synced 2026-07-03 01:01:23 +08:00
✨ feat(chroma): 新增 Chroma 向量库连接支持
- 后端新增 Chroma REST 连接、元数据浏览、JSON/SELECT 查询与 upsert/delete 写入 - 前端新增 Chroma 类型、连接配置、图标、方言和能力矩阵 - 测试覆盖 v1/v2 兼容、真实服务 smoke 和前端配置 Refs #560
This commit is contained in:
@@ -33,13 +33,25 @@ describe('ConnectionModal data source registry', () => {
|
||||
expect(source).toContain('type === "elasticsearch"');
|
||||
expect(source).toContain("return '支持索引浏览、Mapping 检查、JSON DSL 和 query_string 查询';");
|
||||
expect(source).toContain(
|
||||
'type === "clickhouse" ? "default" : (type === "redis" || type === "elasticsearch") ? "" : "root";',
|
||||
'type === "clickhouse" ? "default" : (type === "redis" || type === "elasticsearch" || type === "chroma") ? "" : "root";',
|
||||
);
|
||||
expect(source).toContain(
|
||||
'placeholder={dbType === "elasticsearch" ? "未开启认证可留空" : undefined}',
|
||||
'placeholder={(dbType === "elasticsearch" || dbType === "chroma") ? "未开启认证可留空" : undefined}',
|
||||
);
|
||||
expect(source).toContain('label="显示数据库 (留空显示全部)"');
|
||||
});
|
||||
|
||||
it('exposes Chroma in the create-connection picker with vector defaults', () => {
|
||||
expect(source).toContain("case 'chroma':");
|
||||
expect(source).toContain('return 8000;');
|
||||
expect(source).toContain('chroma: ["http", "https", "chroma"]');
|
||||
expect(source).toContain("key: 'chroma'");
|
||||
expect(source).toContain("name: 'Chroma'");
|
||||
expect(source).toContain('type === "chroma"');
|
||||
expect(source).toContain("return 'Collection 浏览、向量检索和元数据过滤';");
|
||||
expect(source).toContain('return "http://127.0.0.1:8000/default_database?tenant=default_tenant";');
|
||||
expect(source).toContain('return "tenant=default_tenant&apiKey=...";');
|
||||
});
|
||||
});
|
||||
|
||||
describe('ConnectionModal Redis Sentinel configuration', () => {
|
||||
|
||||
@@ -1789,6 +1789,22 @@ const ConnectionModal: React.FC<{
|
||||
parsedValues.useSSL = false;
|
||||
parsedValues.sslMode = "disable";
|
||||
}
|
||||
} else if (type === "chroma") {
|
||||
const tls = String(
|
||||
parsed.params.get("tls") ||
|
||||
parsed.params.get("ssl") ||
|
||||
parsed.params.get("useSSL") ||
|
||||
parsed.params.get("use_ssl") ||
|
||||
"",
|
||||
)
|
||||
.trim()
|
||||
.toLowerCase();
|
||||
const skipVerify = normalizeBool(
|
||||
parsed.params.get("skip_verify") || parsed.params.get("skipVerify"),
|
||||
);
|
||||
const enabled = tls ? normalizeBool(tls) : trimmedUri.toLowerCase().startsWith("https://");
|
||||
parsedValues.useSSL = enabled;
|
||||
parsedValues.sslMode = enabled ? (skipVerify ? "skip-verify" : "required") : "disable";
|
||||
}
|
||||
}
|
||||
return parsedValues;
|
||||
@@ -1851,6 +1867,9 @@ const ConnectionModal: React.FC<{
|
||||
if (dbType === "clickhouse") {
|
||||
return "clickhouse://default:pass@127.0.0.1:9000/default";
|
||||
}
|
||||
if (dbType === "chroma") {
|
||||
return "http://127.0.0.1:8000/default_database?tenant=default_tenant";
|
||||
}
|
||||
if (dbType === "redis") {
|
||||
return "redis://:pass@127.0.0.1:6379,127.0.0.2:6379/0?topology=cluster 或 redis://:pass@10.0.0.1:26379,10.0.0.2:26379/0?topology=sentinel&master=mymaster";
|
||||
}
|
||||
@@ -1892,6 +1911,8 @@ const ConnectionModal: React.FC<{
|
||||
return "max_execution_time=60&compress=lz4";
|
||||
case "mongodb":
|
||||
return "retryWrites=true&readPreference=secondaryPreferred";
|
||||
case "chroma":
|
||||
return "tenant=default_tenant&apiKey=...";
|
||||
case "dameng":
|
||||
return "schema=SYSDBA";
|
||||
case "tdengine":
|
||||
@@ -2033,6 +2054,10 @@ const ConnectionModal: React.FC<{
|
||||
const scheme =
|
||||
type === "postgres"
|
||||
? "postgresql"
|
||||
: type === "chroma"
|
||||
? values.useSSL
|
||||
? "https"
|
||||
: "http"
|
||||
: type === "clickhouse" && clickHouseProtocol === "http"
|
||||
? values.useSSL
|
||||
? "https"
|
||||
@@ -2083,6 +2108,11 @@ const ConnectionModal: React.FC<{
|
||||
if (mode === "skip-verify" || mode === "preferred") {
|
||||
params.set("skip_verify", "true");
|
||||
}
|
||||
} else if (type === "chroma") {
|
||||
if (mode === "skip-verify" || mode === "preferred") {
|
||||
params.set("skip_verify", "true");
|
||||
}
|
||||
appendSSLPathParamsForUri(params, type, values);
|
||||
}
|
||||
} else if (supportsSSLForType(type)) {
|
||||
if (isPostgresCompatibleSSLType(type)) {
|
||||
@@ -3675,7 +3705,7 @@ const ConnectionModal: React.FC<{
|
||||
});
|
||||
} else if (type !== "custom") {
|
||||
const defaultUser =
|
||||
type === "clickhouse" ? "default" : (type === "redis" || type === "elasticsearch") ? "" : "root";
|
||||
type === "clickhouse" ? "default" : (type === "redis" || type === "elasticsearch" || type === "chroma") ? "" : "root";
|
||||
const sslCapableType = supportsSSLForType(type);
|
||||
setUseSSL(false);
|
||||
setUseHttpTunnel(false);
|
||||
@@ -4971,13 +5001,13 @@ const ConnectionModal: React.FC<{
|
||||
name="user"
|
||||
label="用户名"
|
||||
rules={
|
||||
(dbType === "mongodb" || dbType === "elasticsearch")
|
||||
(dbType === "mongodb" || dbType === "elasticsearch" || dbType === "chroma")
|
||||
? []
|
||||
: [createUriAwareRequiredRule("请输入用户名")]
|
||||
}
|
||||
style={{ marginBottom: 0 }}
|
||||
>
|
||||
<Input {...noAutoCapInputProps} placeholder={dbType === "elasticsearch" ? "未开启认证可留空" : undefined} />
|
||||
<Input {...noAutoCapInputProps} placeholder={(dbType === "elasticsearch" || dbType === "chroma") ? "未开启认证可留空" : undefined} />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
name="password"
|
||||
|
||||
@@ -18,6 +18,13 @@ describe('DatabaseIcons', () => {
|
||||
expect(markup).toContain('alt="elasticsearch"');
|
||||
});
|
||||
|
||||
it('includes Chroma in the selectable database icons', () => {
|
||||
expect(DB_ICON_TYPES).toContain('chroma');
|
||||
expect(getDbIconLabel('chroma')).toBe('Chroma');
|
||||
const markup = renderToStaticMarkup(<>{getDbIcon('chroma', undefined, 22)}</>);
|
||||
expect(markup).toContain('>Ch</text>');
|
||||
});
|
||||
|
||||
it('wraps database icons in a consistent frame for sidebar sizing', () => {
|
||||
const mysqlMarkup = renderToStaticMarkup(<>{getDbIcon('mysql', undefined, 22)}</>);
|
||||
const jvmMarkup = renderToStaticMarkup(<>{getDbIcon('jvm', undefined, 22)}</>);
|
||||
|
||||
@@ -49,6 +49,7 @@ const DB_DEFAULT_COLORS: Record<string, string> = {
|
||||
highgo: '#00A86B',
|
||||
iris: '#1F6FEB',
|
||||
tdengine: '#2962FF',
|
||||
chroma: '#7C3AED',
|
||||
diros: '#0050B3',
|
||||
starrocks: '#00A6A6',
|
||||
sphinx: '#2F5D62',
|
||||
@@ -178,6 +179,9 @@ const IrisIcon: React.FC<DbIconProps> = ({ size = 16, color }) => (
|
||||
const TDengineIcon: React.FC<DbIconProps> = ({ size = 16, color }) => (
|
||||
<ColorBadge size={size} color={color || DB_DEFAULT_COLORS.tdengine} label="TD" />
|
||||
);
|
||||
const ChromaIcon: React.FC<DbIconProps> = ({ size = 16, color }) => (
|
||||
<ColorBadge size={size} color={color || DB_DEFAULT_COLORS.chroma} label="Ch" />
|
||||
);
|
||||
const JVMIcon: React.FC<DbIconProps> = ({ size = 16, color }) => (
|
||||
<ColorBadge size={size} color={color || DB_DEFAULT_COLORS.jvm} label="JVM" />
|
||||
);
|
||||
@@ -231,6 +235,7 @@ const DB_ICON_MAP: Record<string, React.FC<DbIconProps>> = {
|
||||
highgo: HighGoIcon,
|
||||
iris: IrisIcon,
|
||||
tdengine: TDengineIcon,
|
||||
chroma: ChromaIcon,
|
||||
elasticsearch: ElasticsearchIcon,
|
||||
custom: CustomIcon,
|
||||
};
|
||||
@@ -239,7 +244,7 @@ const DB_ICON_MAP: Record<string, React.FC<DbIconProps>> = {
|
||||
export const DB_ICON_TYPES: string[] = [
|
||||
'mysql', 'mariadb', 'oceanbase', 'postgres', 'redis', 'mongodb', 'jvm',
|
||||
'oracle', 'sqlserver', 'sqlite', 'duckdb', 'clickhouse', 'starrocks',
|
||||
'kingbase', 'dameng', 'vastbase', 'opengauss', 'highgo', 'iris', 'tdengine', 'elasticsearch', 'custom',
|
||||
'kingbase', 'dameng', 'vastbase', 'opengauss', 'highgo', 'iris', 'tdengine', 'chroma', 'elasticsearch', 'custom',
|
||||
];
|
||||
|
||||
/** 该类型是否有品牌 SVG 文件 */
|
||||
@@ -262,6 +267,7 @@ export const getDbIconLabel = (type: string): string => {
|
||||
starrocks: 'StarRocks',
|
||||
duckdb: 'DuckDB', kingbase: '金仓', dameng: '达梦',
|
||||
vastbase: 'VastBase', opengauss: 'OpenGauss', highgo: '瀚高', iris: 'InterSystems IRIS', tdengine: 'TDengine',
|
||||
chroma: 'Chroma',
|
||||
elasticsearch: 'Elasticsearch',
|
||||
custom: '自定义',
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user