diff --git a/api/app/controllers/user_controller.py b/api/app/controllers/user_controller.py index 3c574c81..2806da1a 100644 --- a/api/app/controllers/user_controller.py +++ b/api/app/controllers/user_controller.py @@ -100,7 +100,7 @@ def get_current_user_info( result_schema.current_workspace_name = current_workspace.name for ws in result.workspaces: - if ws.workspace_id == current_user.current_workspace_id: + if ws.workspace_id == current_user.current_workspace_id and ws.is_active: result_schema.role = ws.role break diff --git a/web/src/components/Layout/BasicAuthLayout.tsx b/web/src/components/Layout/BasicAuthLayout.tsx index a73f6c69..f279a48b 100644 --- a/web/src/components/Layout/BasicAuthLayout.tsx +++ b/web/src/components/Layout/BasicAuthLayout.tsx @@ -2,10 +2,10 @@ * @Author: ZhaoYing * @Date: 2026-02-02 15:12:42 * @Last Modified by: ZhaoYing - * @Last Modified time: 2026-02-04 14:06:28 + * @Last Modified time: 2026-02-28 17:28:41 */ /** - * BasicLayout Component + * BasicAuthLayout Component * * A minimal layout wrapper that provides: * - User information initialization @@ -26,12 +26,12 @@ import { useUser } from '@/store/user'; * Basic layout component for pages without navigation UI. * Fetches user info and storage type on mount, then renders child routes. */ -const BasicLayout: FC = () => { +const BasicAuthLayout: FC = () => { const { getUserInfo } = useUser(); // Fetch user information and storage type on component mount useEffect(() => { - getUserInfo(); + getUserInfo(undefined, true); // Pass true to skip navigation jump }, [getUserInfo]); return ( @@ -42,4 +42,4 @@ const BasicLayout: FC = () => { ) }; -export default BasicLayout; \ No newline at end of file +export default BasicAuthLayout; \ No newline at end of file diff --git a/web/src/components/RadioGroupCard/index.tsx b/web/src/components/RadioGroupCard/index.tsx index 41924c61..e09466cd 100644 --- a/web/src/components/RadioGroupCard/index.tsx +++ b/web/src/components/RadioGroupCard/index.tsx @@ -20,6 +20,7 @@ import { type FC, type Key, type ReactNode, useEffect } from 'react'; import { type RadioGroupProps } from 'antd'; import clsx from 'clsx' +import { useTranslation } from 'react-i18next'; /** Radio card option interface */ interface RadioCardOption { @@ -33,6 +34,8 @@ interface RadioCardOption { icon?: string; /** Whether the option is disabled */ disabled?: boolean; + /** Whether the option is recommended */ + recommend?: boolean; /** Additional properties */ [key: string]: string | number | boolean | undefined | null | Key; } @@ -63,6 +66,7 @@ const RadioGroupCard: FC = ({ allowClear = true, block = false, }) => { + const { t } = useTranslation(); /** Listen to value changes and trigger side effects via onValueChange callback */ useEffect(() => { if (onValueChange) { @@ -91,12 +95,13 @@ const RadioGroupCard: FC = ({ })}> {/* Render each option as a selectable card */} {options.map(option => ( -
handleChange(option)}> + {option.recommend &&
{t('common.recommend')}
} {/* Use custom render or default card layout */} {itemRender ? itemRender(option) : ( <> diff --git a/web/src/i18n/en.ts b/web/src/i18n/en.ts index 9df6d018..fdbde290 100644 --- a/web/src/i18n/en.ts +++ b/web/src/i18n/en.ts @@ -452,6 +452,7 @@ export const en = { nextStep: 'Next Step', prevStep: 'Previous Step', exportSuccess: 'Export successful', + recommend: 'Recommend', }, model: { searchPlaceholder: 'search model…', diff --git a/web/src/i18n/zh.ts b/web/src/i18n/zh.ts index 3fe37ea8..c855667f 100644 --- a/web/src/i18n/zh.ts +++ b/web/src/i18n/zh.ts @@ -1028,6 +1028,7 @@ export const zh = { nextStep: '下一步', prevStep: '上一步', exportSuccess: '导出成功', + recommend: '推荐', }, model: { searchPlaceholder: '搜索模型…', diff --git a/web/src/store/user.ts b/web/src/store/user.ts index c9231d9c..f5e0cb28 100644 --- a/web/src/store/user.ts +++ b/web/src/store/user.ts @@ -2,7 +2,7 @@ * @Author: ZhaoYing * @Date: 2026-02-02 16:33:54 * @Last Modified by: ZhaoYing - * @Last Modified time: 2026-02-04 18:30:10 + * @Last Modified time: 2026-02-28 17:21:20 */ /** * User Store @@ -44,7 +44,7 @@ export interface UserState { /** Update login information */ updateLoginInfo: (values: LoginInfo) => void; /** Get user information */ - getUserInfo: (flag?: boolean) => void; + getUserInfo: (flag?: boolean, notNeedJump?: boolean) => void; /** Clear user information */ clearUserInfo: () => void; /** Logout user */ @@ -73,13 +73,13 @@ export const useUser = create((set, get) => ({ cookieUtils.set('refreshToken', values.refresh_token); set({ loginInfo: values }); }, - getUserInfo: async (flag?: boolean) => { + getUserInfo: async (flag?: boolean, notNeedJump?: boolean) => { if (!cookieUtils.get('authToken')) { return } const { checkJump } = get() const localUser = JSON.parse(localStorage.getItem('user') || '{}') as User; - if (localUser.id) { + if (localUser.id && !notNeedJump) { checkJump() return } diff --git a/web/src/views/ModelManagement/List.tsx b/web/src/views/ModelManagement/List.tsx index ffa89fb4..ce4d61aa 100644 --- a/web/src/views/ModelManagement/List.tsx +++ b/web/src/views/ModelManagement/List.tsx @@ -1,8 +1,8 @@ /* * @Author: ZhaoYing * @Date: 2026-02-03 16:50:10 - * @Last Modified by: ZhaoYing - * @Last Modified time: 2026-02-03 16:50:10 + * @Last Modified by: ZhaoYing + * @Last Modified time: 2026-02-27 10:20:51 */ /** * Model List View @@ -21,7 +21,7 @@ import PageEmpty from '@/components/Empty/PageEmpty'; import Tag from '@/components/Tag'; import KeyConfigModal from './components/KeyConfigModal' import ModelListDetail from './components/ModelListDetail' -import { getLogoUrl } from './utils' +import { getListLogoUrl } from './utils' /** * Model list component @@ -70,7 +70,7 @@ const ModelList = forwardRef {item.provider[0].toUpperCase()} diff --git a/web/src/views/ModelManagement/components/CustomModelModal.tsx b/web/src/views/ModelManagement/components/CustomModelModal.tsx index fb0db96e..17373a02 100644 --- a/web/src/views/ModelManagement/components/CustomModelModal.tsx +++ b/web/src/views/ModelManagement/components/CustomModelModal.tsx @@ -1,8 +1,8 @@ /* * @Author: ZhaoYing * @Date: 2026-02-03 16:49:28 - * @Last Modified by: ZhaoYing - * @Last Modified time: 2026-02-03 16:49:28 + * @Last Modified by: ZhaoYing + * @Last Modified time: 2026-02-28 17:24:05 */ /** * Custom Model Modal @@ -50,7 +50,7 @@ const CustomModelModal = forwardRef( setModel(model); form.setFieldsValue({ ...model, - logo: model.logo ? { url: model.logo, uid: model.logo, status: 'done', name: 'logo' } : undefined + logo: model.logo && model.logo.startsWith('http') ? { url: model.logo, uid: model.logo, status: 'done', name: 'logo' } : undefined }); } else { setIsEdit(false); diff --git a/web/src/views/ModelManagement/utils.ts b/web/src/views/ModelManagement/utils.ts index fe36e137..bf44367f 100644 --- a/web/src/views/ModelManagement/utils.ts +++ b/web/src/views/ModelManagement/utils.ts @@ -1,8 +1,8 @@ /* * @Author: ZhaoYing * @Date: 2026-02-03 16:50:22 - * @Last Modified by: ZhaoYing - * @Last Modified time: 2026-02-03 16:50:22 + * @Last Modified by: ZhaoYing + * @Last Modified time: 2026-02-27 10:22:46 */ /** * Utility functions for Model Management @@ -40,5 +40,26 @@ export const getLogoUrl = (logo?: string) => { return logo } + return ICONS[logo as keyof typeof ICONS] || undefined +} + +/** + * Get logo URL from provider name or URL + * @param provider - Provider name + * @param logo - Provider name or logo URL + * @returns Logo URL or undefined + */ +export const getListLogoUrl = (provider?: string, logo?: string) => { + let url = ICONS[provider as keyof typeof ICONS] + + if (url) return url + + if (!logo) { + return undefined + } + if (logo.startsWith('http')) { + return logo + } + return ICONS[logo as keyof typeof ICONS] || undefined } \ No newline at end of file diff --git a/web/src/views/SpaceManagement/components/SpaceModal.tsx b/web/src/views/SpaceManagement/components/SpaceModal.tsx index a0703d81..4f37b246 100644 --- a/web/src/views/SpaceManagement/components/SpaceModal.tsx +++ b/web/src/views/SpaceManagement/components/SpaceModal.tsx @@ -34,8 +34,8 @@ interface SpaceModalProps { } /** Storage types */ const types: StorageType[] = [ - 'rag', 'neo4j', + 'rag', ] /** Type icons mapping */ const typeIcons: Record = { @@ -154,6 +154,9 @@ const SpaceModal = forwardRef(({
(({ value: type, label: t(`space.${type}`), labelDesc: t(`space.${type}Desc`), - icon: typeIcons[type] + icon: typeIcons[type], + recommend: type === 'neo4j', }))} block={true} /> diff --git a/web/src/views/Workflow/components/Editor/plugin/AutocompletePlugin.tsx b/web/src/views/Workflow/components/Editor/plugin/AutocompletePlugin.tsx index 8e2687f1..f9fe097e 100644 --- a/web/src/views/Workflow/components/Editor/plugin/AutocompletePlugin.tsx +++ b/web/src/views/Workflow/components/Editor/plugin/AutocompletePlugin.tsx @@ -1,4 +1,4 @@ -import { useEffect, useState, type FC } from 'react'; +import { useEffect, useState, useRef, type FC } from 'react'; import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext'; import { $getSelection, $isRangeSelection, $isTextNode, COMMAND_PRIORITY_HIGH, KEY_ENTER_COMMAND, KEY_ARROW_DOWN_COMMAND, KEY_ARROW_UP_COMMAND, KEY_ESCAPE_COMMAND } from 'lexical'; @@ -22,6 +22,26 @@ const AutocompletePlugin: FC<{ options: Suggestion[], enableJinja2?: boolean }> const [showSuggestions, setShowSuggestions] = useState(false); const [selectedIndex, setSelectedIndex] = useState(0); const [popupPosition, setPopupPosition] = useState({ top: 0, left: 0 }); + const popupRef = useRef(null); + + const scrollSelectedIntoView = () => { + if (!popupRef.current) return; + + const selectedElement = popupRef.current.querySelector('[data-selected="true"]'); + if (!selectedElement) return; + + const container = popupRef.current; + const element = selectedElement as HTMLElement; + + const containerRect = container.getBoundingClientRect(); + const elementRect = element.getBoundingClientRect(); + + if (elementRect.bottom > containerRect.bottom) { + container.scrollTop += elementRect.bottom - containerRect.bottom; + } else if (elementRect.top < containerRect.top) { + container.scrollTop -= containerRect.top - elementRect.top; + } + }; useEffect(() => { return editor.registerUpdateListener(({ editorState }) => { @@ -116,7 +136,7 @@ const AutocompletePlugin: FC<{ options: Suggestion[], enableJinja2?: boolean }> setShowSuggestions(false); }; - const groupedSuggestions = options.reduce((groups: Record, suggestion) => { + const groupedSuggestions = options.reduce((groups: Record, suggestion) => { const { nodeData } = suggestion const nodeId = nodeData.id as string; if (!groups[nodeId]) { @@ -163,7 +183,9 @@ const AutocompletePlugin: FC<{ options: Suggestion[], enableJinja2?: boolean }> while (nextIndex < allOptions.length && allOptions[nextIndex].disabled) { nextIndex++; } - return nextIndex >= allOptions.length ? prev : nextIndex; + const newIndex = nextIndex >= allOptions.length ? prev : nextIndex; + setTimeout(() => scrollSelectedIntoView(), 0); + return newIndex; }); return true; } @@ -182,7 +204,9 @@ const AutocompletePlugin: FC<{ options: Suggestion[], enableJinja2?: boolean }> while (prevIndex >= 0 && allOptions[prevIndex].disabled) { prevIndex--; } - return prevIndex < 0 ? prev : prevIndex; + const newIndex = prevIndex < 0 ? prev : prevIndex; + setTimeout(() => scrollSelectedIntoView(), 0); + return newIndex; }); return true; } @@ -218,6 +242,7 @@ const AutocompletePlugin: FC<{ options: Suggestion[], enableJinja2?: boolean }> } return (
e.preventDefault()} style={{ @@ -248,6 +273,7 @@ const AutocompletePlugin: FC<{ options: Suggestion[], enableJinja2?: boolean }> return (