Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions backend/consts/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -276,6 +276,7 @@ class VectorDatabaseType(str, Enum):
APP_NAME = "APP_NAME"
APP_DESCRIPTION = "APP_DESCRIPTION"
ICON_TYPE = "ICON_TYPE"
ICON_KEY = "ICON_KEY"
AVATAR_URI = "AVATAR_URI"
CUSTOM_ICON_URL = "CUSTOM_ICON_URL"
TENANT_NAME = "TENANT_NAME"
Expand Down
1 change: 1 addition & 0 deletions backend/consts/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@ class AppConfig(BaseModel):
appName: str
appDescription: str
iconType: str
iconKey: Optional[str] = "search"
customIconUrl: Optional[str] = None
avatarUri: Optional[str] = None
modelEngineEnabled: bool = False
Expand Down
2 changes: 2 additions & 0 deletions backend/services/config_sync_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
DEFAULT_APP_NAME_ZH,
DEFAULT_GROUP_ID,
ICON_TYPE,
ICON_KEY,
LANGUAGE,
MODEL_CONFIG_MAPPING,
LANGUAGE,
Expand Down Expand Up @@ -139,6 +140,7 @@ def build_app_config(language: str, tenant_id: str) -> dict:
"defaultGroupId": tenant_config_manager.get_app_config(DEFAULT_GROUP_ID, tenant_id=tenant_id) or "",
"icon": {
"type": tenant_config_manager.get_app_config(ICON_TYPE, tenant_id=tenant_id) or "preset",
"iconKey": tenant_config_manager.get_app_config(ICON_KEY, tenant_id=tenant_id) or "search",
"avatarUri": tenant_config_manager.get_app_config(AVATAR_URI, tenant_id=tenant_id) or "",
"customUrl": tenant_config_manager.get_app_config(CUSTOM_ICON_URL, tenant_id=tenant_id) or ""
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -604,6 +604,10 @@ function DataConfig({ isActive }: DataConfigProps) {

// Handle new knowledge base creation
const handleCreateNew = () => {
// Clear active knowledge base selection when entering create mode
// This prevents issues with chunk loading from previously selected KB
setActiveKnowledgeBase(null);

// Generate default knowledge base name
const defaultName = generateUniqueKbName(kbState.knowledgeBases);
setNewKbName(defaultName);
Expand Down Expand Up @@ -917,6 +921,7 @@ function DataConfig({ isActive }: DataConfigProps) {
>
{isCreatingMode ? (
<DocumentList
key="create-mode"
documents={[]}
onDelete={() => {}}
knowledgeBaseSource={""}
Expand All @@ -942,6 +947,7 @@ function DataConfig({ isActive }: DataConfigProps) {
/>
) : kbState.activeKnowledgeBase ? (
<DocumentList
key={`kb-${kbState.activeKnowledgeBase.id}`}
documents={viewingDocuments}
onDelete={handleDeleteDocument}
knowledgeBaseSource={kbState.activeKnowledgeBase?.source}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -276,6 +276,16 @@ const DocumentListContainer = forwardRef<DocumentListRef, DocumentListProps>(
}
}, [isCreatingMode, tenantId]);

// Clear group IDs when permission is set to PRIVATE
React.useEffect(() => {
if (ingroupPermission === "PRIVATE" && onSelectedGroupIdsChange) {
onSelectedGroupIdsChange([]);
}
}, [ingroupPermission, onSelectedGroupIdsChange]);

// Check if group select should be disabled (when permission is PRIVATE)
const isGroupSelectDisabled = ingroupPermission === "PRIVATE";

// Load available models when showing detail
useEffect(() => {
const loadModels = async () => {
Expand Down Expand Up @@ -472,13 +482,14 @@ const DocumentListContainer = forwardRef<DocumentListRef, DocumentListProps>(
<Can permission="kb.groups:update">
<Select
mode="multiple"
value={selectedGroupIds}
value={isGroupSelectDisabled ? [] : selectedGroupIds}
onChange={onSelectedGroupIdsChange}
style={{ minWidth: 200, justifyContent: "center", alignItems: "flex-end" }}
placeholder={t("knowledgeBase.create.permission.groupPlaceholder")}
options={groupOptions}
maxTagCount={2}
allowClear
disabled={isGroupSelectDisabled}
/>
</Can>
{/* Group permission dropdown - second position */}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"use client";

import React, { useState, useRef } from "react";
import React, { useState, useRef, useEffect } from "react";
import { useTranslation } from "react-i18next";
import { Modal, Form, Input, Select, message } from "antd";
import { useGroupList } from "@/hooks/group/useGroupList";
Expand Down Expand Up @@ -35,22 +35,28 @@ export function KnowledgeBaseEditModal({
// Store original name for comparison
const originalNameRef = useRef<string>("");

// Track current permission value for conditional logic
const [currentPermission, setCurrentPermission] = useState<string>("READ_ONLY");

// Fetch groups for group selection
const { data: groupData } = useGroupList(tenantId);
const groups = groupData?.groups || [];

// Reset form and states when knowledge base changes
React.useEffect(() => {
if (knowledgeBase && open) {
const permission = knowledgeBase.ingroup_permission || "READ_ONLY";
form.setFieldsValue({
knowledge_name: knowledgeBase.name,
ingroup_permission: knowledgeBase.ingroup_permission || "READ_ONLY",
group_ids: knowledgeBase.group_ids || [],
ingroup_permission: permission,
group_ids: permission === "PRIVATE" ? [] : (knowledgeBase.group_ids || []),
});
// Store original name for comparison
originalNameRef.current = knowledgeBase.name;
// Reset error state
setNameError(null);
// Set current permission
setCurrentPermission(permission);
}
}, [knowledgeBase, open, form]);

Expand Down Expand Up @@ -90,10 +96,13 @@ export function KnowledgeBaseEditModal({
return; // Error message is displayed via Form.Item help
}

// Ensure group_ids is empty when permission is PRIVATE
const groupIds = values.ingroup_permission === "PRIVATE" ? [] : values.group_ids;

await knowledgeBaseService.updateKnowledgeBase(knowledgeBase.id, {
knowledge_name: values.knowledge_name,
ingroup_permission: values.ingroup_permission,
group_ids: values.group_ids,
group_ids: groupIds,
});

message.success(t("tenantResources.knowledgeBase.updated"));
Expand All @@ -103,7 +112,7 @@ export function KnowledgeBaseEditModal({
...knowledgeBase,
name: values.knowledge_name,
ingroup_permission: values.ingroup_permission,
group_ids: values.group_ids,
group_ids: groupIds,
};

// Trigger knowledge base list refresh to seamlessly update UI
Expand All @@ -119,6 +128,17 @@ export function KnowledgeBaseEditModal({
}
};

// Handle permission change - clear group_ids when PRIVATE is selected
const handlePermissionChange = (value: string) => {
setCurrentPermission(value);
if (value === "PRIVATE") {
form.setFieldsValue({ group_ids: [] });
}
};

// Check if group select should be disabled
const isGroupSelectDisabled = currentPermission === "PRIVATE";

return (
<Modal
title={t("tenantResources.knowledgeBase.edit")}
Expand Down Expand Up @@ -152,6 +172,7 @@ export function KnowledgeBaseEditModal({
>
<Select
placeholder={t("tenantResources.knowledgeBase.permission")}
onChange={handlePermissionChange}
options={[
{ value: "EDIT", label: t("tenantResources.knowledgeBase.permission.EDIT") },
{ value: "READ_ONLY", label: t("tenantResources.knowledgeBase.permission.READ_ONLY") },
Expand All @@ -165,11 +186,13 @@ export function KnowledgeBaseEditModal({
<Form.Item name="group_ids" label={t("tenantResources.knowledgeBase.groupNames")}>
<Select
mode="multiple"
placeholder={t("tenantResources.knowledgeBase.groupNames")}
placeholder={isGroupSelectDisabled ? t("knowledgeBase.create.permission.groupPlaceholder") : t("tenantResources.knowledgeBase.groupNames")}
value={isGroupSelectDisabled ? [] : form.getFieldValue("group_ids")}
options={groups.map((group) => ({
label: group.group_name,
value: group.group_id,
}))}
disabled={isGroupSelectDisabled}
/>
</Form.Item>
</Can>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -589,16 +589,17 @@ const KnowledgeBaseList: React.FC<KnowledgeBaseListProps> = ({
</span>
)}

{/* User group tags */}
{/* User group tags - only show when not PRIVATE */}
<Can permission="group:read">
{getGroupNames(kb.group_ids).map((groupName, idx) => (
<span
key={idx}
className={`inline-flex items-center ${KB_LAYOUT.TAG_PADDING} ${KB_LAYOUT.TAG_ROUNDED} ${KB_LAYOUT.TAG_TEXT} ${KB_LAYOUT.SECOND_ROW_TAG_MARGIN} bg-blue-100 text-blue-800 border border-blue-200 mr-1`}
>
{groupName}
</span>
))}
{kb.ingroup_permission !== "PRIVATE" &&
getGroupNames(kb.group_ids).map((groupName, idx) => (
<span
key={idx}
className={`inline-flex items-center ${KB_LAYOUT.TAG_PADDING} ${KB_LAYOUT.TAG_ROUNDED} ${KB_LAYOUT.TAG_TEXT} ${KB_LAYOUT.SECOND_ROW_TAG_MARGIN} bg-blue-100 text-blue-800 border border-blue-200 mr-1`}
>
{groupName}
</span>
))}
</Can>
</>
)}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ export const KnowledgeBaseContext = createContext<{
) => Promise<KnowledgeBase | null>;
deleteKnowledgeBase: (id: string) => Promise<boolean>;
selectKnowledgeBase: (id: string) => void;
setActiveKnowledgeBase: (kb: KnowledgeBase) => void;
setActiveKnowledgeBase: (kb: KnowledgeBase | null) => void;
isKnowledgeBaseSelectable: (kb: KnowledgeBase) => boolean;
hasKnowledgeBaseModelMismatch: (kb: KnowledgeBase) => boolean;
refreshKnowledgeBaseData: (forceRefresh?: boolean) => Promise<void>;
Expand Down Expand Up @@ -304,7 +304,7 @@ export const KnowledgeBaseProvider: React.FC<KnowledgeBaseProviderProps> = ({
);

// Set current active knowledge base - memoized with useCallback
const setActiveKnowledgeBase = useCallback((kb: KnowledgeBase) => {
const setActiveKnowledgeBase = useCallback((kb: KnowledgeBase | null) => {
dispatch({ type: KNOWLEDGE_BASE_ACTION_TYPES.SET_ACTIVE, payload: kb });
}, []);

Expand Down
11 changes: 8 additions & 3 deletions frontend/app/[locale]/models/components/appConfig.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,11 @@ export const AppConfigSection: React.FC = () => {
// Avatar-related state
const [isAvatarModalOpen, setIsAvatarModalOpen] = useState(false);
const [selectedIconKey, setSelectedIconKey] = useState<string>(
presetIcons[0].key
appConfig.iconKey || presetIcons[0].key
);
const [tempIconKey, setTempIconKey] = useState<string>(
appConfig.iconKey || presetIcons[0].key
);
const [tempIconKey, setTempIconKey] = useState<string>(presetIcons[0].key);
const [tempColor, setTempColor] = useState<string>("#2689cb");
const [avatarType, setAvatarType] = useState<
(typeof ICON_TYPES)[keyof typeof ICON_TYPES]
Expand Down Expand Up @@ -114,11 +116,13 @@ export const AppConfigSection: React.FC = () => {
}
setAvatarType(appConfig.iconType);
setCustomAvatarUrl(appConfig.customIconUrl);
setSelectedIconKey(appConfig.iconKey || presetIcons[0].key);
}, [
appConfig.appName,
appConfig.appDescription,
appConfig.iconType,
appConfig.customIconUrl,
appConfig.iconKey,
]);

// Listen for highlight missing field events
Expand Down Expand Up @@ -253,6 +257,7 @@ export const AppConfigSection: React.FC = () => {

updateAppConfig({
iconType: ICON_TYPES.PRESET,
iconKey: tempIconKey,
customIconUrl: null,
avatarUri: avatarUri,
});
Expand Down Expand Up @@ -558,4 +563,4 @@ export const AppConfigSection: React.FC = () => {
)}
</div>
);
};
};
Original file line number Diff line number Diff line change
Expand Up @@ -193,10 +193,12 @@ function TenantList({
});

// Register admin account using the invitation code
// Do not auto-login for tenant admin creation
const signupResult = await authService.signUp(
values.adminEmail,
values.adminPassword,
invitation.invitation_code
invitation.invitation_code,
false
);

if (signupResult.error) {
Expand Down Expand Up @@ -615,7 +617,7 @@ export default function UserManageComp() {
// For super-admin: use paginated tenant list (tenantData)
let currentTenant: Tenant | undefined;
let currentTenantName: string;

if (!isSuperAdmin && directTenantData) {
// Non-super-admin: use directly fetched tenant info
currentTenant = directTenantData;
Expand Down
5 changes: 5 additions & 0 deletions frontend/hooks/useConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ const defaultConfig: GlobalConfig = {
appName: "",
appDescription: "",
iconType: ICON_TYPES.PRESET,
iconKey: "search",
customIconUrl: "",
avatarUri: "",
modelEngineEnabled: false,
Expand Down Expand Up @@ -112,12 +113,16 @@ function transformModelEntry(
* Transform backend config format to frontend format
*/
function transformBackendToFrontend(backendConfig: any): GlobalConfig {
// Get iconKey from backend - if not available, use default "search"
const iconKey = backendConfig.app?.icon?.iconKey || "search";

const app: AppConfig = backendConfig.app
? {
appName: backendConfig.app.name || "",
appDescription: backendConfig.app.description || "",
iconType:
(backendConfig.app.icon?.type as "preset" | "custom") || "preset",
iconKey: iconKey,
customIconUrl: backendConfig.app.icon?.customUrl || null,
avatarUri: backendConfig.app.icon?.avatarUri || null,
modelEngineEnabled: backendConfig.app.modelEngineEnabled ?? false,
Expand Down
2 changes: 1 addition & 1 deletion frontend/public/locales/en/common.json
Original file line number Diff line number Diff line change
Expand Up @@ -545,7 +545,7 @@
"knowledgeBase.summary.notGenerated": "Knowledge base summary was not generated, please change model configuration and retry",
"knowledgeBase.name.new": "new_base",
"knowledgeBase.message.getDocumentsFailed": "Failed to get documents",
"knowledgeBase.create.permission.groupPlaceholder": "User groups of this knowledge base",
"knowledgeBase.create.permission.groupPlaceholder": "No user group",
"knowledgeBase.ingroup.permission.EDIT": "In Group Read/Write",
"knowledgeBase.ingroup.permission.READ_ONLY": "In Group Read Only",
"knowledgeBase.ingroup.permission.PRIVATE": "Personal Private",
Expand Down
2 changes: 1 addition & 1 deletion frontend/public/locales/zh/common.json
Original file line number Diff line number Diff line change
Expand Up @@ -547,7 +547,7 @@
"knowledgeBase.summary.notGenerated": "未生成知识库总结,请更换模型配置重试",
"knowledgeBase.name.new": "新知识库",
"knowledgeBase.message.getDocumentsFailed": "获取文档列表失败",
"knowledgeBase.create.permission.groupPlaceholder": "该知识库所属用户组",
"knowledgeBase.create.permission.groupPlaceholder": "无所属用户组",
"knowledgeBase.ingroup.permission.EDIT": "同组可编辑",
"knowledgeBase.ingroup.permission.READ_ONLY": "同组只读",
"knowledgeBase.ingroup.permission.PRIVATE": "私有",
Expand Down
1 change: 1 addition & 0 deletions frontend/types/modelConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ export interface AppConfig {
appName: string;
appDescription: string;
iconType: "preset" | "custom";
iconKey: string; // Selected preset icon key
customIconUrl: string | null;
avatarUri: string | null;
modelEngineEnabled: boolean;
Expand Down
Loading