Merge pull request #423 from SuanmoSuanyangTechnology/release/v0.2.5
Release/v0.2.5
This commit is contained in:
@@ -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
|
||||
|
||||
|
||||
@@ -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;
|
||||
export default BasicAuthLayout;
|
||||
@@ -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<RadioCardProps> = ({
|
||||
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<RadioCardProps> = ({
|
||||
})}>
|
||||
{/* Render each option as a selectable card */}
|
||||
{options.map(option => (
|
||||
<div key={String(option.value)} className={clsx("rb:border rb:rounded-lg rb:w-full rb:p-[20px_12px] rb:text-center rb:cursor-pointer", {
|
||||
<div key={String(option.value)} className={clsx("rb:relative rb:border rb:rounded-lg rb:w-full rb:p-[20px_12px] rb:text-center rb:cursor-pointer", {
|
||||
'rb:bg-[rgba(21,94,239,0.06)] rb:border-[#155EEF]': option.value === value,
|
||||
'rb:border-[#EBEBEB] rb:bg-[#ffffff]': option.value !== value,
|
||||
'rb:opacity-[0.75]': option.disabled,
|
||||
'rb:flex rb:items-center rb:text-left rb:gap-4': block,
|
||||
})} onClick={() => handleChange(option)}>
|
||||
{option.recommend && <div className="rb:absolute rb:right-0 rb:top-0 rb:bg-[#FF5D34] rb:rounded-[0px_7px_0px_8px] rb:text-[12px] rb:text-white rb:font-regular rb:leading-4 rb:p-[4px_8px]">{t('common.recommend')}</div>}
|
||||
{/* Use custom render or default card layout */}
|
||||
{itemRender ? itemRender(option) : (
|
||||
<>
|
||||
|
||||
@@ -452,6 +452,7 @@ export const en = {
|
||||
nextStep: 'Next Step',
|
||||
prevStep: 'Previous Step',
|
||||
exportSuccess: 'Export successful',
|
||||
recommend: 'Recommend',
|
||||
},
|
||||
model: {
|
||||
searchPlaceholder: 'search model…',
|
||||
|
||||
@@ -1028,6 +1028,7 @@ export const zh = {
|
||||
nextStep: '下一步',
|
||||
prevStep: '上一步',
|
||||
exportSuccess: '导出成功',
|
||||
recommend: '推荐',
|
||||
},
|
||||
model: {
|
||||
searchPlaceholder: '搜索模型…',
|
||||
|
||||
@@ -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<UserState>((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
|
||||
}
|
||||
|
||||
@@ -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<BaseRef, { query: any; handleEdit: (vo?: ModelListI
|
||||
<RbCard
|
||||
key={item.provider}
|
||||
title={t(`modelNew.${item.provider}`)}
|
||||
avatarUrl={getLogoUrl(item.logo)}
|
||||
avatarUrl={getListLogoUrl(item.provider, item.logo)}
|
||||
avatar={
|
||||
<div className="rb:w-12 rb:h-12 rb:rounded-lg rb:mr-3.25 rb:bg-[#155eef] rb:flex rb:items-center rb:justify-center rb:text-[28px] rb:text-[#ffffff]">
|
||||
{item.provider[0].toUpperCase()}
|
||||
|
||||
@@ -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<CustomModelModalRef, CustomModelModalProps>(
|
||||
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);
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -34,8 +34,8 @@ interface SpaceModalProps {
|
||||
}
|
||||
/** Storage types */
|
||||
const types: StorageType[] = [
|
||||
'rag',
|
||||
'neo4j',
|
||||
'rag',
|
||||
]
|
||||
/** Type icons mapping */
|
||||
const typeIcons: Record<StorageType, string> = {
|
||||
@@ -154,6 +154,9 @@ const SpaceModal = forwardRef<SpaceModalRef, SpaceModalProps>(({
|
||||
<Form
|
||||
form={form}
|
||||
layout="vertical"
|
||||
initialValues={{
|
||||
storage_type: types[0],
|
||||
}}
|
||||
>
|
||||
<Form.Item
|
||||
name="icon"
|
||||
@@ -183,7 +186,8 @@ const SpaceModal = forwardRef<SpaceModalRef, SpaceModalProps>(({
|
||||
value: type,
|
||||
label: t(`space.${type}`),
|
||||
labelDesc: t(`space.${type}Desc`),
|
||||
icon: typeIcons[type]
|
||||
icon: typeIcons[type],
|
||||
recommend: type === 'neo4j',
|
||||
}))}
|
||||
block={true}
|
||||
/>
|
||||
|
||||
@@ -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<HTMLDivElement>(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<string, any[]>, suggestion) => {
|
||||
const groupedSuggestions = options.reduce((groups: Record<string, Suggestion[]>, 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 (
|
||||
<div
|
||||
ref={popupRef}
|
||||
data-autocomplete-popup="true"
|
||||
onMouseDown={(e) => e.preventDefault()}
|
||||
style={{
|
||||
@@ -248,6 +273,7 @@ const AutocompletePlugin: FC<{ options: Suggestion[], enableJinja2?: boolean }>
|
||||
return (
|
||||
<div
|
||||
key={option.key}
|
||||
data-selected={selectedIndex === globalIndex}
|
||||
style={{
|
||||
padding: '8px 12px',
|
||||
cursor: option.disabled ? 'not-allowed' : 'pointer',
|
||||
|
||||
Reference in New Issue
Block a user