Compare commits

...

1 Commits

Author SHA1 Message Date
Innei 565dfcf2e5 fix: inline style
Signed-off-by: Innei <tukon479@gmail.com>
2026-01-20 23:44:08 +08:00
513 changed files with 7356 additions and 3516 deletions
@@ -5,6 +5,14 @@ import { Result } from 'antd';
import { useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { StyleSheet } from '@/utils/styles';
const styles = StyleSheet.create({
style: {
minWidth: 240,
},
});
type CallbackStatus = 'loading' | 'success' | 'error';
/**
@@ -146,7 +154,7 @@ const MarketAuthCallbackPage = () => {
const getExtra = () => {
if (status === 'error') {
return (
<Button block onClick={() => window.close()} size={'large'} style={{ minWidth: 240 }}>
<Button block onClick={() => window.close()} size={'large'} style={styles.style}>
{t('callback.buttons.close')}
</Button>
);
@@ -2,11 +2,19 @@
import { Button, Flexbox, FluentEmoji, Highlighter, Text } from '@lobehub/ui';
import { Result } from 'antd';
import Link from '@/libs/next/Link';
import { parseAsString, useQueryState } from 'nuqs';
import React, { memo } from 'react';
import { useTranslation } from 'react-i18next';
import Link from '@/libs/next/Link';
import { StyleSheet } from '@/utils/styles';
const styles = StyleSheet.create({
style: {
minWidth: 240,
},
});
const FailedPage = memo(() => {
const { t } = useTranslation('oauth');
const [reason] = useQueryState('reason');
@@ -16,7 +24,7 @@ const FailedPage = memo(() => {
<Result
extra={
<Link href="/public">
<Button block size={'large'} style={{ minWidth: 240 }}>
<Button block size={'large'} style={styles.style}>
{t('error.backToHome')}
</Button>
</Link>
@@ -6,6 +6,13 @@ import { memo, useEffect, useRef } from 'react';
import { useTranslation } from 'react-i18next';
import BrandLoading from '@/components/Loading/BrandTextLoading';
import { StyleSheet } from '@/utils/styles';
const styles = StyleSheet.create({
display: {
display: 'none',
},
});
interface BuiltinConsentProps {
uid: string;
@@ -27,7 +34,7 @@ const BuiltinConsent = memo<BuiltinConsentProps>(({ uid }) => {
status="success"
title={<Text fontSize={14}>{t('consent.redirecting')}</Text>}
/>
<form action="/oidc/consent" method="post" ref={formRef} style={{ display: 'none' }}>
<form action="/oidc/consent" method="post" ref={formRef} style={styles.display}>
<input name="uid" type="hidden" value={uid} />
<input name="consent" type="hidden" value="accept" />
</form>
@@ -5,10 +5,20 @@ import React, { memo, useState } from 'react';
import { useTranslation } from 'react-i18next';
import AuthCard from '@/features/AuthCard';
import { StyleSheet } from '@/utils/styles';
import OAuthApplicationLogo from '../components/OAuthApplicationLogo';
import BuiltinConsent from './BuiltinConsent';
const styles = StyleSheet.create({
fullWidth: {
width: '100%',
},
spacing: {
marginTop: 8,
},
});
interface ClientProps {
clientId: string;
clientMetadata: {
@@ -51,7 +61,7 @@ const ConsentClient = memo<ClientProps>(({ uid, clientId, scopes, clientMetadata
/>
<AuthCard
footer={
<form action="/oidc/consent" method="post" style={{ width: '100%' }}>
<form action="/oidc/consent" method="post" style={styles.fullWidth}>
<input name="uid" type="hidden" value={uid} />
<Flexbox gap={12}>
<Button
@@ -79,7 +89,7 @@ const ConsentClient = memo<ClientProps>(({ uid, clientId, scopes, clientMetadata
<Text fontSize={16} type={'secondary'}>
{t('consent.permissionsTitle')}
</Text>
<Flexbox gap={4} style={{ marginTop: 8 }} width={'100%'}>
<Flexbox gap={4} style={styles.spacing} width={'100%'}>
{scopes.map((scope) => (
<Block key={scope} padding={16} variant={'filled'}>
<Text>{getScopeDescription(scope, t)}</Text>
@@ -7,9 +7,16 @@ import { useTranslation } from 'react-i18next';
import AuthCard from '@/features/AuthCard';
import { useUserStore } from '@/store/user';
import { userProfileSelectors } from '@/store/user/selectors';
import { StyleSheet } from '@/utils/styles';
import OAuthApplicationLogo from './components/OAuthApplicationLogo';
const styles = StyleSheet.create({
fullWidth: {
width: '100%',
},
});
interface LoginConfirmProps {
clientMetadata: {
clientName?: string;
@@ -47,7 +54,7 @@ const LoginConfirmClient = memo<LoginConfirmProps>(({ uid, clientMetadata }) =>
action="/oidc/consent"
method="post"
onSubmit={() => setIsLoading(true)}
style={{ width: '100%' }}
style={styles.fullWidth}
>
{/* Adjust action URL */}
<input name="uid" type="hidden" value={uid} />
@@ -3,8 +3,19 @@ import { Form } from 'antd';
import { Lock } from 'lucide-react';
import { useTranslation } from 'react-i18next';
import { StyleSheet } from '@/utils/styles';
import { useResetPassword } from './useResetPassword';
const styles = StyleSheet.create({
spacing: {
marginInline: 6,
},
spacing1: {
marginBottom: 0,
},
});
interface ResetPasswordContentProps {
email: string | null;
onSuccessRedirect: (url: string) => void;
@@ -45,14 +56,7 @@ export const ResetPasswordContent = ({
>
<InputPassword
placeholder={t('betterAuth.resetPassword.newPasswordPlaceholder')}
prefix={
<Icon
icon={Lock}
style={{
marginInline: 6,
}}
/>
}
prefix={<Icon icon={Lock} style={styles.spacing} />}
size="large"
/>
</Form.Item>
@@ -71,18 +75,11 @@ export const ResetPasswordContent = ({
>
<InputPassword
placeholder={t('betterAuth.resetPassword.confirmPasswordPlaceholder')}
prefix={
<Icon
icon={Lock}
style={{
marginInline: 6,
}}
/>
}
prefix={<Icon icon={Lock} style={styles.spacing} />}
size="large"
/>
</Form.Item>
<Form.Item style={{ marginBottom: 0 }}>
<Form.Item style={styles.spacing1}>
<Button block htmlType="submit" loading={loading} size="large" type="primary">
{t('betterAuth.resetPassword.submit')}
</Button>
@@ -8,9 +8,32 @@ import { Trans, useTranslation } from 'react-i18next';
import AuthIcons from '@/components/NextAuth/AuthIcons';
import { PRIVACY_URL, TERMS_URL } from '@/const/url';
import { StyleSheet } from '@/utils/styles';
import AuthCard from '../../../../features/AuthCard';
const styles = StyleSheet.create({
colored: {
color: 'inherit',
cursor: 'pointer',
textDecoration: 'underline',
},
spacing: {
marginBottom: 0,
},
spacing1: {
marginInline: 6,
},
spacing2: {
padding: 6,
},
style: {
left: 12,
position: 'absolute',
top: 13,
},
});
export const EMAIL_REGEX = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
export const USERNAME_REGEX = /^\w+$/;
@@ -62,18 +85,12 @@ export const SignInEmailStep = ({
<Trans
components={{
privacy: (
<a
href={PRIVACY_URL}
style={{ color: 'inherit', cursor: 'pointer', textDecoration: 'underline' }}
>
<a href={PRIVACY_URL} style={styles.colored}>
{t('footer.terms')}
</a>
),
terms: (
<a
href={TERMS_URL}
style={{ color: 'inherit', cursor: 'pointer', textDecoration: 'underline' }}
>
<a href={TERMS_URL} style={styles.colored}>
{t('footer.privacy')}
</a>
),
@@ -102,16 +119,7 @@ export const SignInEmailStep = ({
{oAuthSSOProviders.map((provider) => (
<Button
block
icon={
<Icon
icon={AuthIcons(provider, 18)}
style={{
left: 12,
position: 'absolute',
top: 13,
}}
/>
}
icon={<Icon icon={AuthIcons(provider, 18)} style={styles.style} />}
key={provider}
loading={socialLoading === provider}
onClick={() => onSocialSignIn(provider)}
@@ -143,23 +151,14 @@ export const SignInEmailStep = ({
},
},
]}
style={{ marginBottom: 0 }}
style={styles.spacing}
>
<Input
placeholder={t('betterAuth.signin.emailPlaceholder')}
prefix={
<Icon
icon={Mail}
style={{
marginInline: 6,
}}
/>
}
prefix={<Icon icon={Mail} style={styles.spacing1} />}
ref={emailInputRef}
size="large"
style={{
padding: 6,
}}
style={styles.spacing2}
suffix={
<Button
icon={ChevronRight}
@@ -6,8 +6,36 @@ import { ChevronLeft, ChevronRight, Lock } from 'lucide-react';
import { useEffect, useRef } from 'react';
import { useTranslation } from 'react-i18next';
import { StyleSheet } from '@/utils/styles';
import AuthCard from '../../../../features/AuthCard';
const styles = StyleSheet.create({
colored: {
color: 'inherit',
cursor: 'pointer',
textDecoration: 'underline',
},
colored1: {
color: cssVar.colorPrimary,
},
spacing: {
marginTop: 16,
},
spacing1: {
marginTop: 12,
},
spacing2: {
marginBottom: 0,
},
spacing3: {
marginInline: 6,
},
spacing4: {
padding: 6,
},
});
export interface SignInPasswordStepProps {
email: string;
form: FormInstance<{ password: string }>;
@@ -37,19 +65,11 @@ export const SignInPasswordStep = ({
footer={
<>
<Text fontSize={13} type={'secondary'}>
<a
onClick={onForgotPassword}
style={{ color: 'inherit', cursor: 'pointer', textDecoration: 'underline' }}
>
<a onClick={onForgotPassword} style={styles.colored}>
{t('betterAuth.signin.forgotPassword')}
</a>
</Text>
<Button
icon={ChevronLeft}
onClick={onBackToEmail}
size={'large'}
style={{ marginTop: 16 }}
>
<Button icon={ChevronLeft} onClick={onBackToEmail} size={'large'} style={styles.spacing}>
{t('betterAuth.signin.backToEmail')}
</Button>
</>
@@ -62,34 +82,25 @@ export const SignInPasswordStep = ({
form={form}
layout="vertical"
onFinish={(values) => onSubmit(values as { password: string })}
style={{ marginTop: 12 }}
style={styles.spacing1}
>
<Form.Item
name="password"
rules={[{ message: t('betterAuth.errors.passwordRequired'), required: true }]}
style={{ marginBottom: 0 }}
style={styles.spacing2}
>
<InputPassword
placeholder={t('betterAuth.signin.passwordPlaceholder')}
prefix={
<Icon
icon={Lock}
style={{
marginInline: 6,
}}
/>
}
prefix={<Icon icon={Lock} style={styles.spacing3} />}
ref={passwordInputRef}
size="large"
style={{
padding: 6,
}}
style={styles.spacing4}
suffix={
<Button
icon={ChevronRight}
loading={loading}
onClick={() => form.submit()}
style={{ color: cssVar.colorPrimary }}
style={styles.colored1}
title={t('betterAuth.signin.submit')}
variant={'filled'}
/>
@@ -3,14 +3,22 @@
import { Button, Icon, Text } from '@lobehub/ui';
import { Form, Input } from 'antd';
import { Lock, Mail } from 'lucide-react';
import Link from '@/libs/next/Link';
import { useSearchParams } from '@/libs/next/navigation';
import { useEffect } from 'react';
import { useTranslation } from 'react-i18next';
import Link from '@/libs/next/Link';
import { useSearchParams } from '@/libs/next/navigation';
import { StyleSheet } from '@/utils/styles';
import { AuthCard } from '../../../../../features/AuthCard';
import { type SignUpFormValues, useSignUp } from './useSignUp';
const styles = StyleSheet.create({
spacing: {
marginInline: 6,
},
});
const BetterAuthSignUpForm = () => {
const [form] = Form.useForm<SignUpFormValues>();
const { loading, onSubmit, businessElement } = useSignUp();
@@ -46,14 +54,7 @@ const BetterAuthSignUpForm = () => {
>
<Input
placeholder={t('betterAuth.signup.emailPlaceholder')}
prefix={
<Icon
icon={Mail}
style={{
marginInline: 6,
}}
/>
}
prefix={<Icon icon={Mail} style={styles.spacing} />}
size="large"
/>
</Form.Item>
@@ -76,14 +77,7 @@ const BetterAuthSignUpForm = () => {
>
<Input.Password
placeholder={t('betterAuth.signup.passwordPlaceholder')}
prefix={
<Icon
icon={Lock}
style={{
marginInline: 6,
}}
/>
}
prefix={<Icon icon={Lock} style={styles.spacing} />}
size="large"
/>
</Form.Item>
@@ -104,14 +98,7 @@ const BetterAuthSignUpForm = () => {
>
<Input.Password
placeholder={t('betterAuth.signup.confirmPasswordPlaceholder')}
prefix={
<Icon
icon={Lock}
style={{
marginInline: 6,
}}
/>
}
prefix={<Icon icon={Lock} style={styles.spacing} />}
size="large"
/>
</Form.Item>
@@ -2,6 +2,20 @@ import { Flexbox, type FlexboxProps } from '@lobehub/ui';
import { cssVar } from 'antd-style';
import { type ReactNode, memo } from 'react';
import { StyleSheet } from '@/utils/styles';
const styles = StyleSheet.create({
fullWidth: {
background: cssVar.colorBgContainer,
bottom: 0,
marginTop: 'auto',
paddingTop: 16,
position: 'sticky',
width: '100%',
zIndex: 10,
},
});
interface OnboardingFooterActionsProps extends Omit<FlexboxProps, 'children'> {
left?: ReactNode;
right?: ReactNode;
@@ -14,16 +28,7 @@ const OnboardingFooterActions = memo<OnboardingFooterActionsProps>(
align={'center'}
horizontal
justify={'space-between'}
style={{
background: cssVar.colorBgContainer,
bottom: 0,
marginTop: 'auto',
paddingTop: 16,
position: 'sticky',
width: '100%',
zIndex: 10,
...style,
}}
style={StyleSheet.compose(styles.fullWidth, style)}
{...rest}
>
<div>{left}</div>
@@ -8,10 +8,36 @@ import { useTranslation } from 'react-i18next';
import { useUserStore } from '@/store/user';
import { userGeneralSettingsSelectors } from '@/store/user/selectors';
import { StyleSheet } from '@/utils/styles';
import LobeMessage from '../components/LobeMessage';
import OnboardingFooterActions from '../components/OnboardingFooterActions';
const styles = StyleSheet.create({
colored2: {
color: cssVar.colorTextDescription,
},
fullWidth: {
width: '100%',
},
spacing: {
listStyle: 'none',
padding: 0,
},
spacing1: {
marginTop: 16,
},
style: {
position: 'absolute',
right: 12,
top: 12,
},
style1: {
height: '100%',
minHeight: '100%',
},
});
type DataMode = 'share' | 'privacy';
interface DataModeStepProps {
@@ -38,23 +64,36 @@ const DataModeStep = memo<DataModeStepProps>(({ onBack, onNext }) => {
[telemetryEnabled, updateGeneralConfig],
);
// Dynamic styles based on selectedMode
const dynamicShareStyle = StyleSheet.create({
colored: {
borderColor: selectedMode === 'share' ? cssVar.colorSuccess : undefined,
},
});
const dynamicPrivacyStyle = StyleSheet.create({
colored1: {
borderColor: selectedMode === 'privacy' ? cssVar.colorSuccess : undefined,
},
});
const checkIcon = (
<Checkbox
backgroundColor={cssVar.colorSuccess}
checked
shape={'circle'}
size={20}
style={{ position: 'absolute', right: 12, top: 12 }}
style={styles.style}
/>
);
return (
<Flexbox gap={16} style={{ height: '100%', minHeight: '100%' }}>
<Flexbox gap={16} style={styles.style1}>
<Flexbox>
<LobeMessage sentences={[t('screen4.title'), t('screen4.title2'), t('screen4.title3')]} />
<Text as={'p'}>{t('screen4.description')}</Text>
</Flexbox>
<Flexbox gap={16} style={{ width: '100%' }}>
<Flexbox gap={16} style={styles.fullWidth}>
{/* 共享数据选项 */}
<Block
clickable
@@ -62,7 +101,7 @@ const DataModeStep = memo<DataModeStepProps>(({ onBack, onNext }) => {
gap={16}
onClick={() => setMode('share')}
padding={16}
style={{ borderColor: selectedMode === 'share' ? cssVar.colorSuccess : undefined }}
style={StyleSheet.compose(styles.fullWidth, dynamicShareStyle.colored)}
variant={'outlined'}
>
{selectedMode === 'share' && checkIcon}
@@ -79,7 +118,7 @@ const DataModeStep = memo<DataModeStepProps>(({ onBack, onNext }) => {
}}
type={'page'}
/>
<Flexbox as={'ul'} gap={4} style={{ listStyle: 'none', padding: 0 }}>
<Flexbox as={'ul'} gap={4} style={styles.spacing}>
<li>
<Text> {t('screen4.share.items.1')}</Text>
</li>
@@ -99,7 +138,7 @@ const DataModeStep = memo<DataModeStepProps>(({ onBack, onNext }) => {
gap={6}
onClick={() => setMode('privacy')}
padding={16}
style={{ borderColor: selectedMode === 'privacy' ? cssVar.colorSuccess : undefined }}
style={StyleSheet.compose(styles.fullWidth, dynamicPrivacyStyle.colored1)}
variant={'outlined'}
>
{selectedMode === 'privacy' && checkIcon}
@@ -111,17 +150,12 @@ const DataModeStep = memo<DataModeStepProps>(({ onBack, onNext }) => {
</Text>
</Block>
</Flexbox>
<Text color={cssVar.colorTextSecondary} fontSize={12} style={{ marginTop: 16 }}>
<Text color={cssVar.colorTextSecondary} fontSize={12} style={styles.spacing1}>
{t('screen4.footerNote')}
</Text>
<OnboardingFooterActions
left={
<Button
icon={Undo2Icon}
onClick={onBack}
style={{ color: cssVar.colorTextDescription }}
type={'text'}
>
<Button icon={Undo2Icon} onClick={onBack} style={styles.colored2} type={'text'}>
{t('back')}
</Button>
}
@@ -17,9 +17,36 @@ import UserInfo from '@/features/User/UserInfo';
import { remoteServerService } from '@/services/electron/remoteServer';
import { useElectronStore } from '@/store/electron';
import { setDesktopAutoOidcFirstOpenHandled } from '@/utils/electron/autoOidc';
import { StyleSheet } from '@/utils/styles';
import LobeMessage from '../components/LobeMessage';
const styles = StyleSheet.create({
colored: {
background: cssVar.colorFillSecondary,
borderRadius: 8,
},
colored1: {
color: cssVar.colorTextDescription,
},
colored2: {
color: cssVar.colorTextSecondary,
},
fullWidth: {
width: '100%',
},
spacing: {
marginRight: 4,
},
spacing1: {
marginTop: 32,
},
style: {
height: '100%',
minHeight: '100%',
},
});
// 登录方式类型
type LoginMethod = 'cloud' | 'selfhost';
@@ -212,19 +239,14 @@ const LoginStep = memo<LoginStepProps>(({ onBack, onNext }) => {
const renderCloudContent = () => {
if (cloudLoginStatus === 'success') {
return (
<Flexbox gap={16} style={{ width: '100%' }}>
<Flexbox gap={16} style={styles.fullWidth}>
<Alert
description={t('authResult.success.desc')}
style={{ width: '100%' }}
style={styles.fullWidth}
title={t('authResult.success.title')}
type={'success'}
/>
<UserInfo
style={{
background: cssVar.colorFillSecondary,
borderRadius: 8,
}}
/>
<UserInfo style={styles.colored} />
<Button
block
disabled={isSigningOut || isConnectingServer}
@@ -241,7 +263,7 @@ const LoginStep = memo<LoginStepProps>(({ onBack, onNext }) => {
if (cloudLoginStatus === 'error') {
return (
<Flexbox style={{ width: '100%' }}>
<Flexbox style={styles.fullWidth}>
<Alert
description={remoteError || t('authResult.failed.desc')}
title={t('authResult.failed.title')}
@@ -269,16 +291,16 @@ const LoginStep = memo<LoginStepProps>(({ onBack, onNext }) => {
: null;
return (
<Flexbox gap={8} style={{ width: '100%' }}>
<Flexbox gap={8} style={styles.fullWidth}>
<Button block disabled={true} icon={Cloud} loading={true} size={'large'} type={'primary'}>
{t('screen5.actions.signingIn')}
</Button>
<Text style={{ color: cssVar.colorTextDescription }} type={'secondary'}>
<Text style={styles.colored1} type={'secondary'}>
{phaseText}
</Text>
<Flexbox align={'center'} horizontal justify={'space-between'}>
{remainingSeconds !== null ? (
<Text style={{ color: cssVar.colorTextDescription }} type={'secondary'}>
<Text style={styles.colored1} type={'secondary'}>
{t('screen5.auth.remaining', {
time: remainingSeconds,
})}
@@ -313,19 +335,14 @@ const LoginStep = memo<LoginStepProps>(({ onBack, onNext }) => {
const renderSelfhostContent = () => {
if (selfhostLoginStatus === 'success') {
return (
<Flexbox gap={16} style={{ width: '100%' }}>
<Flexbox gap={16} style={styles.fullWidth}>
<Alert
description={t('authResult.success.desc')}
style={{ width: '100%' }}
style={styles.fullWidth}
title={t('authResult.success.title')}
type={'success'}
/>
<UserInfo
style={{
background: cssVar.colorFillSecondary,
borderRadius: 8,
}}
/>
<UserInfo style={styles.colored} />
<Button
block
disabled={isSigningOut || isConnectingServer}
@@ -342,7 +359,7 @@ const LoginStep = memo<LoginStepProps>(({ onBack, onNext }) => {
if (selfhostLoginStatus === 'error') {
return (
<Flexbox gap={16} style={{ width: '100%' }}>
<Flexbox gap={16} style={styles.fullWidth}>
<Alert
description={remoteError || t('authResult.failed.desc')}
title={t('authResult.failed.title')}
@@ -364,7 +381,7 @@ const LoginStep = memo<LoginStepProps>(({ onBack, onNext }) => {
: null;
return (
<Flexbox gap={8} style={{ width: '100%' }}>
<Flexbox gap={8} style={styles.fullWidth}>
<Button
block
disabled={true}
@@ -375,12 +392,12 @@ const LoginStep = memo<LoginStepProps>(({ onBack, onNext }) => {
>
{t('screen5.actions.connecting')}
</Button>
<Text style={{ color: cssVar.colorTextDescription }} type={'secondary'}>
<Text style={styles.colored1} type={'secondary'}>
{phaseText}
</Text>
<Flexbox align={'center'} horizontal justify={'space-between'}>
{remainingSeconds !== null ? (
<Text style={{ color: cssVar.colorTextDescription }} type={'secondary'}>
<Text style={styles.colored1} type={'secondary'}>
{t('screen5.auth.remaining', {
time: remainingSeconds,
})}
@@ -397,7 +414,7 @@ const LoginStep = memo<LoginStepProps>(({ onBack, onNext }) => {
}
return (
<Flexbox gap={16} style={{ width: '100%' }}>
<Flexbox gap={16} style={styles.fullWidth}>
<Text color={cssVar.colorTextSecondary}>{t(loginMethodMetas.selfhost.descriptionKey)}</Text>
<Input
onChange={(e) => setEndpoint(e.target.value)}
@@ -413,9 +430,9 @@ const LoginStep = memo<LoginStepProps>(({ onBack, onNext }) => {
}
}}
placeholder={t('screen5.selfhost.endpointPlaceholder')}
prefix={<Icon icon={Server} style={{ marginRight: 4 }} />}
prefix={<Icon icon={Server} style={styles.spacing} />}
size={'large'}
style={{ width: '100%' }}
style={styles.fullWidth}
value={endpoint}
/>
<Button
@@ -423,7 +440,7 @@ const LoginStep = memo<LoginStepProps>(({ onBack, onNext }) => {
loading={false}
onClick={handleSelfhostConnect}
size={'large'}
style={{ width: '100%' }}
style={styles.fullWidth}
type={'primary'}
>
{t('screen5.actions.connectToServer')}
@@ -433,23 +450,17 @@ const LoginStep = memo<LoginStepProps>(({ onBack, onNext }) => {
};
return (
<Center gap={32} style={{ height: '100%', minHeight: '100%' }}>
<Flexbox align={'flex-start'} justify={'flex-start'} style={{ width: '100%' }}>
<Center gap={32} style={styles.style}>
<Flexbox align={'flex-start'} justify={'flex-start'} style={styles.fullWidth}>
<LobeMessage sentences={[t('screen5.title'), t('screen5.title2'), t('screen5.title3')]} />
<Text as={'p'}>{t('screen5.description')}</Text>
</Flexbox>
<Flexbox align={'flex-start'} gap={16} style={{ width: '100%' }} width={'100%'}>
<Flexbox align={'flex-start'} gap={16} style={styles.fullWidth} width={'100%'}>
{renderCloudContent()}
{!showEndpoint ? (
<Center width={'100%'}>
<Button
onClick={() => setShowEndpoint(true)}
style={{
color: cssVar.colorTextSecondary,
}}
type={'text'}
>
<Button onClick={() => setShowEndpoint(true)} style={styles.colored2} type={'text'}>
{t(loginMethodMetas.selfhost.descriptionKey)}
</Button>
</Center>
@@ -466,13 +477,8 @@ const LoginStep = memo<LoginStepProps>(({ onBack, onNext }) => {
)}
</Flexbox>
{canStart() && (
<Flexbox horizontal justify={'space-between'} style={{ marginTop: 32 }}>
<Button
icon={Undo2Icon}
onClick={onBack}
style={{ color: cssVar.colorTextDescription }}
type={'text'}
>
<Flexbox horizontal justify={'space-between'} style={styles.spacing1}>
<Button icon={Undo2Icon} onClick={onBack} style={styles.colored1} type={'text'}>
{t('back')}
</Button>
<Button onClick={onNext} type={'primary'}>
@@ -16,10 +16,30 @@ import { memo, useCallback, useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { ensureElectronIpc } from '@/utils/electron/ipc';
import { StyleSheet } from '@/utils/styles';
import LobeMessage from '../components/LobeMessage';
import OnboardingFooterActions from '../components/OnboardingFooterActions';
const styles = StyleSheet.create({
colored1: {
color: cssVar.colorTextSecondary,
},
colored2: {
color: cssVar.colorTextDescription,
},
flexContainer: {
flex: 1,
},
fullWidth: {
width: '100%',
},
style: {
height: '100%',
minHeight: '100%',
},
});
type PermissionMeta = {
descriptionKey: string;
icon: typeof Bell;
@@ -155,67 +175,69 @@ const PermissionsStep = memo<PermissionsStepProps>(({ onBack, onNext }) => {
};
return (
<Flexbox gap={16} style={{ height: '100%', minHeight: '100%' }}>
<Flexbox gap={16} style={styles.style}>
<Flexbox>
<LobeMessage sentences={[t('screen3.title'), t('screen3.title2'), t('screen3.title3')]} />
<Text as={'p'}>{t('screen3.description')}</Text>
</Flexbox>
<Block gap={12} padding={4} style={{ width: '100%' }} variant={'outlined'}>
{permissions.map((permission) => (
<Block
align={'center'}
clickable={!permission.granted}
gap={16}
horizontal
key={permission.id}
onClick={() => !permission.granted && handlePermissionRequest(permission.id)}
paddingBlock={8}
paddingInline={'12px 12px'}
style={{
background: permission.granted ? cssVar.colorFillSecondary : undefined,
borderColor: permission.granted ? cssVar.colorSuccess : undefined,
}}
variant={'borderless'}
>
<Block align={'center'} height={40} justify={'center'} variant={'outlined'} width={40}>
<Icon color={cssVar.colorTextDescription} icon={permission.icon} size={20} />
</Block>
<Flexbox gap={2} style={{ flex: 1 }}>
<Text weight={500}>{t(permission.titleKey as any)}</Text>
<Text color={cssVar.colorTextSecondary} fontSize={12}>
{t(permission.descriptionKey as any)}
</Text>
</Flexbox>
{permission.granted ? (
<Icon color={cssVar.colorSuccess} icon={Check} size={20} />
) : (
<Button
icon={SquareArrowOutUpRight}
iconPosition={'end'}
onClick={(e) => {
e.stopPropagation();
handlePermissionRequest(permission.id);
}}
size={'small'}
style={{
color: cssVar.colorTextSecondary,
}}
type={'text'}
<Block gap={12} padding={4} style={styles.fullWidth} variant={'outlined'}>
{permissions.map((permission) => {
const dynamicColoredStyle = {
background: permission.granted ? cssVar.colorFillSecondary : undefined,
borderColor: permission.granted ? cssVar.colorSuccess : undefined,
};
return (
<Block
align={'center'}
clickable={!permission.granted}
gap={16}
horizontal
key={permission.id}
onClick={() => !permission.granted && handlePermissionRequest(permission.id)}
paddingBlock={8}
paddingInline={'12px 12px'}
style={dynamicColoredStyle}
variant={'borderless'}
>
<Block
align={'center'}
height={40}
justify={'center'}
variant={'outlined'}
width={40}
>
{t(permission.buttonKey)}
</Button>
)}
</Block>
))}
<Icon color={cssVar.colorTextDescription} icon={permission.icon} size={20} />
</Block>
<Flexbox gap={2} style={styles.flexContainer}>
<Text weight={500}>{t(permission.titleKey as any)}</Text>
<Text color={cssVar.colorTextSecondary} fontSize={12}>
{t(permission.descriptionKey as any)}
</Text>
</Flexbox>
{permission.granted ? (
<Icon color={cssVar.colorSuccess} icon={Check} size={20} />
) : (
<Button
icon={SquareArrowOutUpRight}
iconPosition={'end'}
onClick={(e) => {
e.stopPropagation();
handlePermissionRequest(permission.id);
}}
size={'small'}
style={styles.colored1}
type={'text'}
>
{t(permission.buttonKey)}
</Button>
)}
</Block>
);
})}
</Block>
<OnboardingFooterActions
left={
<Button
icon={Undo2Icon}
onClick={onBack}
style={{ color: cssVar.colorTextDescription }}
type={'text'}
>
<Button icon={Undo2Icon} onClick={onBack} style={styles.colored2} type={'text'}>
{t('back')}
</Button>
}
@@ -11,6 +11,17 @@ import { useTranslation } from 'react-i18next';
import { ProductLogo } from '@/components/Branding';
import { useUserStore } from '@/store/user';
import { StyleSheet } from '@/utils/styles';
const styles = StyleSheet.create({
spacing: {
marginBottom: 16,
},
spacing1: {
marginBlock: 8,
maxWidth: 240,
},
});
interface WelcomeStepProps {
onNext: () => void;
@@ -46,7 +57,7 @@ const WelcomeStep = memo<WelcomeStepProps>(({ onNext }) => {
return (
<Flexbox gap={16}>
<ProductLogo size={64} />
<Flexbox style={{ marginBottom: 16 }}>
<Flexbox style={styles.spacing}>
<Text as={'h1'} fontSize={28} weight={'bold'}>
<TypewriterEffect
cursorCharacter={<LoadingDots size={28} variant={'pulse'} />}
@@ -72,7 +83,7 @@ const WelcomeStep = memo<WelcomeStepProps>(({ onNext }) => {
items={[
{
description: (
<Text as={'p'} color={cssVar.colorTextSecondary} style={{ marginBottom: 16 }}>
<Text as={'p'} color={cssVar.colorTextSecondary} style={styles.spacing}>
{t('telemetry.rows.create.desc')}
</Text>
),
@@ -85,7 +96,7 @@ const WelcomeStep = memo<WelcomeStepProps>(({ onNext }) => {
},
{
description: (
<Text as={'p'} color={cssVar.colorTextSecondary} style={{ marginBottom: 16 }}>
<Text as={'p'} color={cssVar.colorTextSecondary} style={styles.spacing}>
{t('telemetry.rows.collaborate.desc')}
</Text>
),
@@ -111,15 +122,7 @@ const WelcomeStep = memo<WelcomeStepProps>(({ onNext }) => {
},
]}
/>
<Button
onClick={handleNext}
size={'large'}
style={{
marginBlock: 8,
maxWidth: 240,
}}
type="primary"
>
<Button onClick={handleNext} size={'large'} style={styles.spacing1} type="primary">
{t('telemetry.next')}
</Button>
</Flexbox>
@@ -7,6 +7,7 @@ import { useSearchParams } from 'react-router-dom';
import Loading from '@/components/Loading/BrandTextLoading';
import { electronSystemService } from '@/services/electron/system';
import { StyleSheet } from '@/utils/styles';
import OnboardingContainer from './_layout';
import DataModeStep from './features/DataModeStep';
@@ -21,6 +22,14 @@ import {
} from './storage';
import { DesktopOnboardingScreen, isDesktopOnboardingScreen } from './types';
const styles = StyleSheet.create({
fullWidth: {
maxWidth: 560,
minHeight: '100%',
width: '100%',
},
});
const DesktopOnboardingPage = memo(() => {
const [searchParams, setSearchParams] = useSearchParams();
const [isMac, setIsMac] = useState(true);
@@ -206,7 +215,7 @@ const DesktopOnboardingPage = memo(() => {
return (
<OnboardingContainer>
<Flexbox gap={24} style={{ maxWidth: 560, minHeight: '100%', width: '100%' }}>
<Flexbox gap={24} style={styles.fullWidth}>
<Suspense
fallback={
<Flexbox gap={8}>
@@ -8,10 +8,26 @@ import { useParams } from 'react-router-dom';
import type { AgentCronJob } from '@/database/schemas/agentCronJob';
import { useRouter } from '@/libs/router/navigation';
import { StyleSheet } from '@/utils/styles';
import Actions from './Actions';
import CronTopicItem from './CronTopicItem';
const styles = StyleSheet.create({
flexContainer: {
flex: 1,
},
spacing: {
padding: '8px 12px',
},
style: {
overflow: 'hidden',
},
style1: {
opacity: 0.5,
},
});
interface CronTopicGroupProps {
cronJob: AgentCronJob | null;
cronJobId: string;
@@ -58,9 +74,9 @@ const CronTopicGroup = memo<CronTopicGroupProps>(({ cronJob, cronJobId, topics }
paddingBlock={4}
paddingInline={'8px 4px'}
title={
<Flexbox align="center" gap={6} height={24} horizontal style={{ overflow: 'hidden' }}>
<Icon icon={isEnabled ? TimerIcon : TimerOffIcon} style={{ opacity: 0.5 }} />
<Text ellipsis style={{ flex: 1 }} type={isActive ? undefined : 'secondary'}>
<Flexbox align="center" gap={6} height={24} horizontal style={styles.style}>
<Icon icon={isEnabled ? TimerIcon : TimerOffIcon} style={styles.style1} />
<Text ellipsis style={styles.flexContainer} type={isActive ? undefined : 'secondary'}>
{cronJobName}
</Text>
{topics.length > 0 && (
@@ -76,7 +92,7 @@ const CronTopicGroup = memo<CronTopicGroupProps>(({ cronJob, cronJobId, topics }
{topics.length > 0 ? (
topics.map((topic) => <CronTopicItem key={topic.id} topic={topic} />)
) : (
<Text fontSize={12} style={{ padding: '8px 12px' }} type="secondary">
<Text fontSize={12} style={styles.spacing} type="secondary">
{t('agentCronJobs.noExecutionResults')}
</Text>
)}
@@ -9,9 +9,20 @@ import { DEFAULT_AVATAR, DEFAULT_INBOX_AVATAR } from '@/const/meta';
import { SkeletonItem } from '@/features/NavPanel/components/SkeletonList';
import { useAgentStore } from '@/store/agent';
import { agentSelectors, builtinAgentSelectors } from '@/store/agent/selectors';
import { StyleSheet } from '@/utils/styles';
import SwitchPanel from './SwitchPanel';
const styles = StyleSheet.create({
style: {
minWidth: 32,
overflow: 'hidden',
},
style1: {
width: 24,
},
});
const Agent = memo<PropsWithChildren>(() => {
const { t } = useTranslation(['chat', 'common']);
@@ -35,10 +46,7 @@ const Agent = memo<PropsWithChildren>(() => {
gap={8}
horizontal
padding={2}
style={{
minWidth: 32,
overflow: 'hidden',
}}
style={styles.style}
variant={'borderless'}
>
<Avatar
@@ -56,9 +64,7 @@ const Agent = memo<PropsWithChildren>(() => {
blockSize: 28,
size: 16,
}}
style={{
width: 24,
}}
style={styles.style1}
/>
</Block>
</SwitchPanel>
@@ -9,9 +9,16 @@ import SkeletonList from '@/features/NavPanel/components/SkeletonList';
import TopicEmpty from '@/features/TopicEmpty';
import { useChatStore } from '@/store/chat';
import { topicSelectors } from '@/store/chat/selectors';
import { StyleSheet } from '@/utils/styles';
import TopicItem from '../List/Item';
const styles = StyleSheet.create({
style: {
height: '100%',
},
});
const ITEM_HEIGHT = 44; // Each topic item height
interface ContentProps {
@@ -155,7 +162,7 @@ const Content = memo<ContentProps>(({ open, searchKeyword }) => {
bufferSize={typeof window !== 'undefined' ? window.innerHeight : 0}
onScroll={handleScroll}
ref={virtuaRef}
style={{ height: '100%' }}
style={styles.style}
>
{activeTopicList?.map((topic) => (
<Flexbox gap={1} key={topic.id} padding={'4px 8px'}>
@@ -9,6 +9,7 @@ import NavItem from '@/features/NavPanel/components/NavItem';
import { useAgentStore } from '@/store/agent';
import { useChatStore } from '@/store/chat';
import { useGlobalStore } from '@/store/global';
import { StyleSheet } from '@/utils/styles';
import ThreadList from '../../TopicListContent/ThreadList';
import { useTopicNavigation } from '../../hooks/useTopicNavigation';
@@ -16,6 +17,20 @@ import Actions from './Actions';
import Editing from './Editing';
import { useTopicItemDropdownMenu } from './useDropdownMenu';
const styles = StyleSheet.create({
colored: {
color: cssVar.colorTextDescription,
fontSize: 10,
},
fullWidth: {
height: 18,
width: '100%',
},
style: {
position: 'relative',
},
});
interface TopicItemProps {
active?: boolean;
fav?: boolean;
@@ -81,13 +96,7 @@ const TopicItem = memo<TopicItemProps>(({ id, title, fav, active, threadId }) =>
title={
<Flexbox align={'center'} flex={1} gap={6} horizontal>
{t('defaultTitle')}
<Tag
size={'small'}
style={{
color: cssVar.colorTextDescription,
fontSize: 10,
}}
>
<Tag size={'small'} style={styles.colored}>
{t('temp')}
</Tag>
</Flexbox>
@@ -97,7 +106,7 @@ const TopicItem = memo<TopicItemProps>(({ id, title, fav, active, threadId }) =>
}
return (
<Flexbox style={{ position: 'relative' }}>
<Flexbox style={styles.style}>
<NavItem
actions={<Actions dropdownMenu={dropdownMenu} />}
active={active && !threadId && !isInAgentSubRoute}
@@ -126,8 +135,8 @@ const TopicItem = memo<TopicItemProps>(({ id, title, fav, active, threadId }) =>
<Suspense
fallback={
<Flexbox gap={8} paddingBlock={8} paddingInline={24} width={'100%'}>
<Skeleton.Button active size={'small'} style={{ height: 18, width: '100%' }} />
<Skeleton.Button active size={'small'} style={{ height: 18, width: '100%' }} />
<Skeleton.Button active size={'small'} style={styles.fullWidth} />
<Skeleton.Button active size={'small'} style={styles.fullWidth} />
</Flexbox>
}
>
@@ -5,9 +5,22 @@ import React, { memo, useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import { type GroupedTopic } from '@/types/topic';
import { StyleSheet } from '@/utils/styles';
import TopicItem from '../../List/Item';
const styles = StyleSheet.create({
flexContainer: {
flex: 1,
},
style: {
overflow: 'hidden',
},
style1: {
opacity: 0.5,
},
});
const preformat = (id: string) =>
id.startsWith('20') ? (id.includes('-') ? dayjs(id).format('MMMM') : id) : undefined;
@@ -29,9 +42,9 @@ const GroupItem = memo<GroupItemProps>(({ group, activeTopicId, activeThreadId }
paddingBlock={4}
paddingInline={'8px 4px'}
title={
<Flexbox align="center" gap={6} height={24} horizontal style={{ overflow: 'hidden' }}>
<Icon icon={HashIcon} style={{ opacity: 0.5 }} />
<Text ellipsis fontSize={12} style={{ flex: 1 }} type={'secondary'} weight={500}>
<Flexbox align="center" gap={6} height={24} horizontal style={styles.style}>
<Icon icon={HashIcon} style={styles.style1} />
<Text ellipsis fontSize={12} style={styles.flexContainer} type={'secondary'} weight={500}>
{title || timeTitle}
</Text>
</Flexbox>
@@ -14,6 +14,24 @@ import { Clock } from 'lucide-react';
import { memo, useCallback, useEffect, useRef } from 'react';
import { useTranslation } from 'react-i18next';
import { StyleSheet } from '@/utils/styles';
const styles = StyleSheet.create({
spacing: {
paddingBottom: 48,
},
style: {
fontWeight: 600,
},
style1: {
borderRadius: 12,
overflow: 'hidden',
},
style2: {
minHeight: 220,
},
});
interface CronJobContentEditorProps {
enableRichRender: boolean;
initialValue: string;
@@ -70,14 +88,10 @@ const CronJobContentEditor = memo<CronJobContentEditorProps>(
<Flexbox gap={12}>
<Flexbox align="center" gap={6} horizontal>
<Icon icon={Clock} size={16} />
<Text style={{ fontWeight: 600 }}>{t('agentCronJobs.content')}</Text>
<Text style={styles.style}>{t('agentCronJobs.content')}</Text>
</Flexbox>
<Card
size="small"
style={{ borderRadius: 12, overflow: 'hidden' }}
styles={{ body: { padding: 0 } }}
>
<Flexbox padding={16} style={{ minHeight: 220 }}>
<Card size="small" style={styles.style1} styles={{ body: { padding: 0 } }}>
<Flexbox padding={16} style={styles.style2}>
<Editor
content={''}
editor={editor}
@@ -97,7 +111,7 @@ const CronJobContentEditor = memo<CronJobContentEditorProps>(
]
: undefined
}
style={{ paddingBottom: 48 }}
style={styles.spacing}
type={'text'}
variant={'chat'}
/>
@@ -3,6 +3,16 @@ import { Switch } from 'antd';
import { memo } from 'react';
import { useTranslation } from 'react-i18next';
import { StyleSheet } from '@/utils/styles';
const styles = StyleSheet.create({
spacing: {
fontSize: 28,
fontWeight: 600,
padding: 0,
},
});
interface CronJobHeaderProps {
enabled?: boolean;
isNewJob?: boolean;
@@ -21,11 +31,7 @@ const CronJobHeader = memo<CronJobHeaderProps>(
<Input
onChange={(e) => onNameChange(e.target.value)}
placeholder={t('agentCronJobs.form.name.placeholder')}
style={{
fontSize: 28,
fontWeight: 600,
padding: 0,
}}
style={styles.spacing}
value={name}
variant={'borderless'}
/>
@@ -3,6 +3,14 @@ import { Save } from 'lucide-react';
import { memo } from 'react';
import { useTranslation } from 'react-i18next';
import { StyleSheet } from '@/utils/styles';
const styles = StyleSheet.create({
style: {
width: 200,
},
});
interface CronJobSaveButtonProps {
disabled?: boolean;
loading?: boolean;
@@ -19,7 +27,7 @@ const CronJobSaveButton = memo<CronJobSaveButtonProps>(({ disabled, loading, onS
icon={Save}
loading={loading}
onClick={onSave}
style={{ width: 200 }}
style={styles.style}
type="primary"
>
{t('agentCronJobs.saveAsNew', { defaultValue: 'Save as New Scheduled Task' })}
@@ -5,6 +5,8 @@ import dayjs from 'dayjs';
import { memo, useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import { StyleSheet } from '@/utils/styles';
import {
SCHEDULE_TYPE_OPTIONS,
type ScheduleType,
@@ -13,6 +15,31 @@ import {
WEEKDAY_OPTIONS,
} from '../CronConfig';
const styles = StyleSheet.create({
flexContainer: {
flexWrap: 'wrap',
},
style: {
borderRadius: 12,
},
style1: {
minWidth: 120,
},
style2: {
minWidth: 150,
},
style3: {
width: 80,
},
style4: {
maxWidth: 300,
minWidth: 200,
},
style5: {
width: 160,
},
});
interface CronJobScheduleConfigProps {
hourlyInterval?: number;
maxExecutions?: number | null;
@@ -89,11 +116,11 @@ const CronJobScheduleConfig = memo<CronJobScheduleConfigProps>(
}, [scheduleType, triggerTime, timezone, weekdays, hourlyInterval, t]);
return (
<Card size="small" style={{ borderRadius: 12 }} styles={{ body: { padding: 12 } }}>
<Card size="small" style={styles.style} styles={{ body: { padding: 12 } }}>
<Flexbox gap={12}>
{/* Summary Tags */}
{summaryTags.length > 0 && (
<Flexbox align="center" gap={8} horizontal style={{ flexWrap: 'wrap' }}>
<Flexbox align="center" gap={8} horizontal style={styles.flexContainer}>
{summaryTags.map((tag) => (
<Tag key={tag.key} variant={'filled'}>
{tag.label}
@@ -102,7 +129,7 @@ const CronJobScheduleConfig = memo<CronJobScheduleConfigProps>(
</Flexbox>
)}
{/* Schedule Configuration - All in one row */}
<Flexbox align="center" gap={8} horizontal style={{ flexWrap: 'wrap' }}>
<Flexbox align="center" gap={8} horizontal style={styles.flexContainer}>
<Tag variant={'borderless'}>{t('agentCronJobs.schedule')}</Tag>
<Select
onChange={(value: ScheduleType) =>
@@ -116,7 +143,7 @@ const CronJobScheduleConfig = memo<CronJobScheduleConfigProps>(
value: opt.value,
}))}
size="small"
style={{ minWidth: 120 }}
style={styles.style1}
value={scheduleType}
/>
@@ -129,7 +156,7 @@ const CronJobScheduleConfig = memo<CronJobScheduleConfigProps>(
options={WEEKDAY_OPTIONS}
placeholder="Select days"
size="small"
style={{ minWidth: 150 }}
style={styles.style2}
value={weekdays}
/>
)}
@@ -143,7 +170,7 @@ const CronJobScheduleConfig = memo<CronJobScheduleConfigProps>(
if (value) onScheduleChange({ triggerTime: value });
}}
size="small"
style={{ minWidth: 120 }}
style={styles.style1}
value={triggerTime ?? dayjs().hour(0).minute(0)}
/>
)}
@@ -159,7 +186,7 @@ const CronJobScheduleConfig = memo<CronJobScheduleConfigProps>(
onScheduleChange({ hourlyInterval: value ?? 1 })
}
size="small"
style={{ width: 80 }}
style={styles.style3}
value={hourlyInterval ?? 1}
/>
<Text type="secondary">hour(s) at</Text>
@@ -172,7 +199,7 @@ const CronJobScheduleConfig = memo<CronJobScheduleConfigProps>(
{ label: ':30', value: 30 },
]}
size="small"
style={{ width: 80 }}
style={styles.style3}
value={triggerTime?.minute() ?? 0}
/>
</>
@@ -184,13 +211,13 @@ const CronJobScheduleConfig = memo<CronJobScheduleConfigProps>(
options={TIMEZONE_OPTIONS}
showSearch
size="small"
style={{ maxWidth: 300, minWidth: 200 }}
style={styles.style4}
value={timezone}
/>
</Flexbox>
{/* Max Executions */}
<Flexbox align="center" gap={8} horizontal style={{ flexWrap: 'wrap' }}>
<Flexbox align="center" gap={8} horizontal style={styles.flexContainer}>
<Tag variant={'borderless'}>{t('agentCronJobs.maxExecutions')}</Tag>
<InputNumber
min={1}
@@ -199,7 +226,7 @@ const CronJobScheduleConfig = memo<CronJobScheduleConfigProps>(
}
placeholder={t('agentCronJobs.form.maxExecutions.placeholder')}
size="small"
style={{ width: 160 }}
style={styles.style5}
value={maxExecutions ?? null}
/>
</Flexbox>
@@ -26,6 +26,7 @@ import { useChatStore } from '@/store/chat';
import { serverConfigSelectors, useServerConfigStore } from '@/store/serverConfig';
import { useUserStore } from '@/store/user';
import { labPreferSelectors } from '@/store/user/selectors';
import { StyleSheet } from '@/utils/styles';
import { type ScheduleType, buildCronPattern, parseCronPattern } from './CronConfig';
import CronJobContentEditor from './features/CronJobContentEditor';
@@ -33,6 +34,12 @@ import CronJobHeader from './features/CronJobHeader';
import CronJobSaveButton from './features/CronJobSaveButton';
import CronJobScheduleConfig from './features/CronJobScheduleConfig';
const styles = StyleSheet.create({
style: {
overflowY: 'auto',
},
});
interface CronJobDraft {
content: string;
cronPattern: string;
@@ -458,7 +465,7 @@ const CronJobDetailPage = memo(() => {
) : undefined
}
/>
<Flexbox flex={1} style={{ overflowY: 'auto' }}>
<Flexbox flex={1} style={styles.style}>
<WideScreenContainer paddingBlock={16}>
{isLoading && <Loading debugId="CronJobDetailPage" />}
{!isLoading && !cronJob && !isNewJob && (
@@ -5,6 +5,18 @@ import { useNavigate } from 'react-router-dom';
import { useActionSWR } from '@/libs/swr';
import { useAgentStore } from '@/store/agent';
import { StyleSheet } from '@/utils/styles';
const styles = StyleSheet.create({
spacing: {
alignItems: 'center',
borderRadius: 4,
height: '20px',
justifyContent: 'center',
padding: '0 1px',
width: '20px',
},
});
const AddButton = memo(() => {
const navigate = useNavigate();
@@ -21,14 +33,7 @@ const AddButton = memo(() => {
icon={<Icon icon={Plus} size={'small'} />}
loading={isValidating}
onClick={() => mutate()}
style={{
alignItems: 'center',
borderRadius: 4,
height: '20px',
justifyContent: 'center',
padding: '0 1px',
width: '20px',
}}
style={styles.spacing}
variant={'filled'}
/>
);
@@ -20,6 +20,25 @@ import {
} from '@/store/tool/slices/klavisStore';
import { useUserStore } from '@/store/user';
import { userProfileSelectors } from '@/store/user/selectors';
import { StyleSheet } from '@/utils/styles';
const styles = StyleSheet.create({
flexContainer: {
flex: 'none',
},
fullWidth: {
width: '100%',
},
spacing: {
marginBlock: 12,
},
spacing1: {
marginTop: 8,
},
style: {
cursor: 'pointer',
},
});
// Tools that require Market authentication
const MARKET_AUTH_TOOLS = [
@@ -192,7 +211,7 @@ const KlavisToolAuthItem = memo<KlavisToolAuthItemProps>(({ tool, onAuthComplete
const renderIcon = () => {
if (typeof tool.icon === 'string') {
return <Avatar alt={tool.label} avatar={tool.icon} size={20} style={{ flex: 'none' }} />;
return <Avatar alt={tool.label} avatar={tool.icon} size={20} style={styles.flexContainer} />;
}
return <Icon fill={cssVar.colorText} icon={tool.icon} size={20} />;
};
@@ -206,9 +225,7 @@ const KlavisToolAuthItem = memo<KlavisToolAuthItemProps>(({ tool, onAuthComplete
horizontal
justify="space-between"
onClick={handleAuthorize}
style={{
cursor: 'pointer',
}}
style={styles.style}
>
<Flexbox align="center" gap={8} horizontal>
{renderIcon()}
@@ -253,12 +270,10 @@ const MarketToolAuthItem = memo<MarketToolAuthItemProps>(({ tool }) => {
horizontal
justify="space-between"
onClick={handleSignIn}
style={{
cursor: 'pointer',
}}
style={styles.style}
>
<Flexbox align="center" gap={8} horizontal>
<Avatar alt={tool.label} avatar={tool.avatar} size={20} style={{ flex: 'none' }} />
<Avatar alt={tool.label} avatar={tool.avatar} size={20} style={styles.flexContainer} />
<Text>{tool.label}</Text>
</Flexbox>
<Button
@@ -320,8 +335,8 @@ const ToolAuthAlert = memo(() => {
description={
<>
{t('toolAuth.hint')}
<Divider dashed style={{ marginBlock: 12 }} />
<Flexbox gap={12} style={{ marginTop: 8 }}>
<Divider dashed style={styles.spacing} />
<Flexbox gap={12} style={styles.spacing1}>
{pendingAuthTools.map((tool) =>
tool.authType === 'klavis' ? (
<KlavisToolAuthItem
@@ -339,7 +354,7 @@ const ToolAuthAlert = memo(() => {
</>
}
showIcon={false}
style={{ width: '100%' }}
style={styles.fullWidth}
title={
<Flexbox align="center" gap={6} horizontal>
{t('toolAuth.title')}
@@ -11,11 +11,18 @@ import { useAgentStore } from '@/store/agent';
import { agentSelectors, builtinAgentSelectors } from '@/store/agent/selectors';
import { useUserStore } from '@/store/user';
import { userGeneralSettingsSelectors } from '@/store/user/selectors';
import { StyleSheet } from '@/utils/styles';
import AddButton from './AddButton';
import OpeningQuestions from './OpeningQuestions';
import ToolAuthAlert from './ToolAuthAlert';
const styles = StyleSheet.create({
spacing: {
paddingBottom: 'max(10vh, 32px)',
},
});
const InboxWelcome = memo(() => {
const { t } = useTranslation(['welcome', 'chat']);
const mobile = useIsMobile();
@@ -40,13 +47,7 @@ const InboxWelcome = memo(() => {
return (
<>
<Flexbox flex={1} />
<Flexbox
gap={12}
style={{
paddingBottom: 'max(10vh, 32px)',
}}
width={'100%'}
>
<Flexbox gap={12} style={styles.spacing} width={'100%'}>
<Avatar
avatar={isInbox ? DEFAULT_INBOX_AVATAR : meta.avatar || DEFAULT_AVATAR}
background={meta.backgroundColor}
@@ -9,6 +9,7 @@ import ZenModeToast from '@/features/ZenModeToast';
import { useOperationState } from '@/hooks/useOperationState';
import { useChatStore } from '@/store/chat';
import { messageMapKey } from '@/store/chat/utils/messageMapKey';
import { StyleSheet } from '@/utils/styles';
import WelcomeChatItem from './AgentWelcome';
import ChatHydration from './ChatHydration';
@@ -18,6 +19,14 @@ import ThreadHydration from './ThreadHydration';
import { useActionsBarConfig } from './useActionsBarConfig';
import { useAgentContext } from './useAgentContext';
const styles = StyleSheet.create({
style: {
overflowX: 'hidden',
overflowY: 'auto',
position: 'relative',
},
});
/**
* ConversationArea
*
@@ -54,15 +63,7 @@ const Conversation = memo(() => {
operationState={operationState}
>
<ZenModeToast />
<Flexbox
flex={1}
style={{
overflowX: 'hidden',
overflowY: 'auto',
position: 'relative',
}}
width={'100%'}
>
<Flexbox flex={1} style={styles.style} width={'100%'}>
<ChatList welcome={<WelcomeChatItem />} />
</Flexbox>
<TodoProgress />
@@ -13,11 +13,18 @@ import { useSessionStore } from '@/store/session';
import { sessionSelectors } from '@/store/session/selectors';
import { useUserStore } from '@/store/user';
import { authSelectors } from '@/store/user/selectors';
import { StyleSheet } from '@/utils/styles';
import KnowledgeTag from './KnowledgeTag';
import MemberCountTag from './MemberCountTag';
import SearchTags from './SearchTags';
const styles = StyleSheet.create({
style: {
height: 20,
},
});
const TitleTags = memo(() => {
const [model, provider, hasKnowledge, isLoading] = useAgentStore((s) => [
agentSelectors.currentAgentModel(s),
@@ -44,7 +51,7 @@ const TitleTags = memo(() => {
}
return isLoading && isLogin ? (
<Skeleton.Button active size={'small'} style={{ height: 20 }} />
<Skeleton.Button active size={'small'} style={styles.style} />
) : (
<Flexbox align={'center'} gap={4} horizontal>
<ModelSwitchPanel>
@@ -10,6 +10,27 @@ import { useAgentStore } from '@/store/agent';
import { agentByIdSelectors } from '@/store/agent/selectors';
import { useChatStore } from '@/store/chat';
import { topicSelectors } from '@/store/chat/selectors';
import { StyleSheet } from '@/utils/styles';
const styles = StyleSheet.create({
flexContainer: {
flex: 1,
fontSize: 12,
},
spacing: {
margin: 0,
},
style: {
maxWidth: 600,
minWidth: 320,
},
style1: {
fontSize: 12,
},
style2: {
cursor: 'pointer',
},
});
interface WorkingDirectoryContentProps {
agentId: string;
@@ -91,9 +112,9 @@ const WorkingDirectoryContent = memo<WorkingDirectoryContentProps>(({ agentId, o
]);
return (
<Flexbox gap={12} style={{ maxWidth: 600, minWidth: 320 }}>
<Flexbox gap={12} style={styles.style}>
<Flexbox gap={4}>
<Text style={{ fontSize: 12 }} type="secondary">
<Text style={styles.style1} type="secondary">
{t('localSystem.workingDirectory.agentLevel')}
</Text>
<Flexbox gap={8} horizontal>
@@ -101,7 +122,7 @@ const WorkingDirectoryContent = memo<WorkingDirectoryContentProps>(({ agentId, o
onChange={(e) => setAgentDir(e.target.value)}
placeholder={t('localSystem.workingDirectory.placeholder')}
size="small"
style={{ flex: 1, fontSize: 12 }}
style={styles.flexContainer}
value={agentDir}
variant={'filled'}
/>
@@ -115,7 +136,7 @@ const WorkingDirectoryContent = memo<WorkingDirectoryContentProps>(({ agentId, o
{activeTopicId && (
<>
<Divider style={{ margin: 0 }} />
<Divider style={styles.spacing} />
<Flexbox
align="center"
@@ -124,15 +145,15 @@ const WorkingDirectoryContent = memo<WorkingDirectoryContentProps>(({ agentId, o
onClick={() => {
setUseTopicOverride(!useTopicOverride);
}}
style={{ cursor: 'pointer' }}
style={styles.style2}
>
<Switch checked={useTopicOverride} onChange={setUseTopicOverride} size="small" />
<Text style={{ fontSize: 12 }}>{t('localSystem.workingDirectory.topicOverride')}</Text>
<Text style={styles.style1}>{t('localSystem.workingDirectory.topicOverride')}</Text>
</Flexbox>
{useTopicOverride && (
<Flexbox gap={4}>
<Text style={{ fontSize: 12 }} type="secondary">
<Text style={styles.style1} type="secondary">
{t('localSystem.workingDirectory.topicLevel')}
</Text>
<Flexbox gap={8} horizontal>
@@ -140,7 +161,7 @@ const WorkingDirectoryContent = memo<WorkingDirectoryContentProps>(({ agentId, o
onChange={(e) => setTopicDir(e.target.value)}
placeholder={t('localSystem.workingDirectory.placeholder')}
size="small"
style={{ flex: 1, fontSize: 12 }}
style={styles.flexContainer}
value={topicDir}
variant={'filled'}
/>
@@ -6,6 +6,7 @@ import { cssVar } from 'antd-style';
import { memo } from 'react';
import NavHeader from '@/features/NavHeader';
import { StyleSheet } from '@/utils/styles';
import HeaderActions from './HeaderActions';
import NotebookButton from './NotebookButton';
@@ -13,16 +14,22 @@ import ShareButton from './ShareButton';
import Tags from './Tags';
import WorkingDirectory from './WorkingDirectory';
const styles = StyleSheet.create({
colored: {
backgroundColor: cssVar.colorBgContainer,
},
});
const Header = memo(() => {
return (
<NavHeader
left={
<Flexbox style={{ backgroundColor: cssVar.colorBgContainer }}>
<Flexbox style={styles.colored}>
<Tags />
</Flexbox>
}
right={
<Flexbox align={'center'} horizontal style={{ backgroundColor: cssVar.colorBgContainer }}>
<Flexbox align={'center'} horizontal style={styles.colored}>
{isDesktop && <WorkingDirectory />}
<NotebookButton />
<ShareButton />
@@ -7,10 +7,18 @@ import { useAgentStore } from '@/store/agent';
import { agentSelectors } from '@/store/agent/selectors';
import { useGlobalStore } from '@/store/global';
import { systemStatusSelectors } from '@/store/global/selectors';
import { StyleSheet } from '@/utils/styles';
import ConversationArea from './ConversationArea';
import ChatHeader from './Header';
const styles = StyleSheet.create({
style: {
overflow: 'hidden',
position: 'relative',
},
});
const wrapperStyle: React.CSSProperties = {
height: '100%',
minWidth: 300,
@@ -28,11 +36,7 @@ const ChatConversation = memo(() => {
return (
<Suspense fallback={<Loading debugId="Agent > ChatConversation" />}>
<DragUploadZone onUploadFiles={handleUploadFiles} style={wrapperStyle}>
<Flexbox
height={'100%'}
style={{ overflow: 'hidden', position: 'relative' }}
width={'100%'}
>
<Flexbox height={'100%'} style={styles.style} width={'100%'}>
{showHeader && <ChatHeader />}
<TooltipGroup>
<ConversationArea />
@@ -4,6 +4,16 @@ import { Flexbox } from '@lobehub/ui';
import { css, cx } from 'antd-style';
import { type PropsWithChildren } from 'react';
import { StyleSheet } from '@/utils/styles';
const styles = StyleSheet.create({
flexContainer: {
flex: 1,
height: 0,
position: 'relative',
},
});
const body = css`
:has(.portal-artifact) {
overflow: hidden;
@@ -16,7 +26,7 @@ const Body = ({ children }: PropsWithChildren) => {
<Flexbox
className={cx(body, 'portal-body')}
height={'100%'}
style={{ flex: 1, height: 0, position: 'relative' }}
style={styles.flexContainer}
width={'100%'}
>
{children}
+9 -6
View File
@@ -4,22 +4,25 @@ import { Flexbox } from '@lobehub/ui';
import { memo } from 'react';
import MainInterfaceTracker from '@/components/Analytics/MainInterfaceTracker';
import { StyleSheet } from '@/utils/styles';
import Conversation from './features/Conversation';
import PageTitle from './features/PageTitle';
import Portal from './features/Portal';
import TelemetryNotification from './features/TelemetryNotification';
const styles = StyleSheet.create({
style: {
overflow: 'hidden',
position: 'relative',
},
});
const ChatPage = memo(() => {
return (
<>
<PageTitle />
<Flexbox
height={'100%'}
horizontal
style={{ overflow: 'hidden', position: 'relative' }}
width={'100%'}
>
<Flexbox height={'100%'} horizontal style={styles.style} width={'100%'}>
<Conversation />
<Portal />
</Flexbox>
@@ -8,9 +8,37 @@ import { memo } from 'react';
import { useTranslation } from 'react-i18next';
import type { AgentCronJob } from '@/database/schemas/agentCronJob';
import { StyleSheet } from '@/utils/styles';
import { useAgentCronJobs } from './hooks/useAgentCronJobs';
const styles = StyleSheet.create({
display: {
WebkitBoxOrient: 'vertical',
WebkitLineClamp: 2,
color: '#666',
display: '-webkit-box',
fontSize: '12px',
overflow: 'hidden',
},
flexContainer: {
flex: 1,
},
style: {
height: '100%',
},
style1: {
fontSize: '13px',
fontWeight: 500,
overflow: 'hidden',
textOverflow: 'ellipsis',
whiteSpace: 'nowrap',
},
style2: {
fontSize: '11px',
},
});
const { Text } = Typography;
interface CronJobCardsProps {
@@ -81,7 +109,7 @@ const CronJobCards = memo<CronJobCardsProps>(({ cronJobs, loading, onDelete, onE
}
loading={loading}
size="small"
style={{ height: '100%' }}
style={styles.style}
styles={{
actions: { marginTop: 0 },
body: { paddingBottom: 12, paddingTop: 8 },
@@ -89,18 +117,8 @@ const CronJobCards = memo<CronJobCardsProps>(({ cronJobs, loading, onDelete, onE
}}
title={
<Flexbox align="center" horizontal justify="space-between">
<Flexbox align="center" gap={8} horizontal style={{ flex: 1 }}>
<span
style={{
fontSize: '13px',
fontWeight: 500,
overflow: 'hidden',
textOverflow: 'ellipsis',
whiteSpace: 'nowrap',
}}
>
{job.name || t('agentCronJobs.unnamedTask')}
</span>
<Flexbox align="center" gap={8} horizontal style={styles.flexContainer}>
<span style={styles.style1}>{job.name || t('agentCronJobs.unnamedTask')}</span>
<Badge status={statusInfo.status} />
</Flexbox>
<Switch
@@ -112,29 +130,19 @@ const CronJobCards = memo<CronJobCardsProps>(({ cronJobs, loading, onDelete, onE
}
>
<Flexbox gap={8}>
<Text
ellipsis={{ tooltip: job.content }}
style={{
WebkitBoxOrient: 'vertical',
WebkitLineClamp: 2,
color: '#666',
display: '-webkit-box',
fontSize: '12px',
overflow: 'hidden',
}}
>
<Text ellipsis={{ tooltip: job.content }} style={styles.display}>
{job.content}
</Text>
<Flexbox gap={8}>
<Flexbox align="center" gap={6} horizontal>
<Icon icon={Clock} size={12} />
<Text style={{ fontSize: '11px' }}>{t(intervalText as any)}</Text>
<Text style={styles.style2}>{t(intervalText as any)}</Text>
</Flexbox>
{job.remainingExecutions !== null && (
<Flexbox align="center" gap={6} horizontal>
<Text style={{ fontSize: '11px' }}>
<Text style={styles.style2}>
{t('agentCronJobs.remainingExecutions', { count: job.remainingExecutions })}
</Text>
</Flexbox>
@@ -143,7 +151,7 @@ const CronJobCards = memo<CronJobCardsProps>(({ cronJobs, loading, onDelete, onE
{job.lastExecutedAt && (
<Flexbox align="center" gap={6} horizontal>
<Icon icon={Calendar} size={12} />
<Text style={{ fontSize: '11px' }}>
<Text style={styles.style2}>
{dayjs(job.lastExecutedAt).format('MM/DD HH:mm')}
</Text>
</Flexbox>
@@ -6,6 +6,13 @@ import { memo, useEffect } from 'react';
import { useTranslation } from 'react-i18next';
import type { AgentCronJob } from '@/database/schemas/agentCronJob';
import { StyleSheet } from '@/utils/styles';
const styles = StyleSheet.create({
fullWidth: {
width: '100%',
},
});
// Form data interface - excludes server-managed fields
interface CronJobFormData {
@@ -167,7 +174,7 @@ const CronJobForm = memo<CronJobFormProps>(({ editingJob, formRef, onSubmit }) =
<InputNumber
min={1}
placeholder={t('agentCronJobs.form.maxExecutions.placeholder')}
style={{ width: '100%' }}
style={styles.fullWidth}
/>
</Form.Item>
@@ -182,7 +189,7 @@ const CronJobForm = memo<CronJobFormProps>(({ editingJob, formRef, onSubmit }) =
t('agentCronJobs.form.timeRange.start'),
t('agentCronJobs.form.timeRange.end'),
]}
style={{ width: '100%' }}
style={styles.fullWidth}
/>
</Form.Item>
@@ -194,7 +201,7 @@ const CronJobForm = memo<CronJobFormProps>(({ editingJob, formRef, onSubmit }) =
<InputNumber
min={1}
placeholder="Leave empty for no daily limit"
style={{ width: '100%' }}
style={styles.fullWidth}
/>
</Form.Item>
</Form>
@@ -8,9 +8,24 @@ import { memo } from 'react';
import { useTranslation } from 'react-i18next';
import type { AgentCronJob } from '@/database/schemas/agentCronJob';
import { StyleSheet } from '@/utils/styles';
import { useAgentCronJobs } from './hooks/useAgentCronJobs';
const styles = StyleSheet.create({
spacing: {
color: '#666',
fontSize: '12px',
margin: 0,
},
spacing1: {
marginTop: 4,
},
style: {
fontSize: '11px',
},
});
const { Text, Paragraph } = Typography;
interface CronJobListProps {
@@ -91,21 +106,18 @@ const CronJobList = memo<CronJobListProps>(({ cronJobs, loading, onEdit, onDelet
}
description={
<Flexbox gap={4}>
<Paragraph
ellipsis={{ rows: 2, tooltip: job.content }}
style={{ color: '#666', fontSize: '12px', margin: 0 }}
>
<Paragraph ellipsis={{ rows: 2, tooltip: job.content }} style={styles.spacing}>
{job.content}
</Paragraph>
<Flexbox gap={8} horizontal style={{ marginTop: 4 }}>
<Flexbox gap={8} horizontal style={styles.spacing1}>
<Flexbox align="center" gap={4} horizontal>
<Icon icon={Clock} size={12} />
<Text style={{ fontSize: '11px' }}>{t(intervalText as any)}</Text>
<Text style={styles.style}>{t(intervalText as any)}</Text>
</Flexbox>
{job.remainingExecutions !== null && (
<Text style={{ fontSize: '11px' }}>
<Text style={styles.style}>
{t('agentCronJobs.remainingExecutions', { count: job.remainingExecutions })}
</Text>
)}
@@ -113,7 +125,7 @@ const CronJobList = memo<CronJobListProps>(({ cronJobs, loading, onEdit, onDelet
{job.lastExecutedAt && (
<Flexbox align="center" gap={4} horizontal>
<Icon icon={Calendar} size={12} />
<Text style={{ fontSize: '11px' }}>
<Text style={styles.style}>
{dayjs(job.lastExecutedAt).format('MM/DD HH:mm')}
</Text>
</Flexbox>
@@ -10,10 +10,21 @@ import urlJoin from 'url-join';
import { useQueryRoute } from '@/hooks/useQueryRoute';
import { useAgentStore } from '@/store/agent';
import { StyleSheet } from '@/utils/styles';
import CronJobCards from './CronJobCards';
import { useAgentCronJobs } from './hooks/useAgentCronJobs';
const styles = StyleSheet.create({
spacing: {
marginBottom: 16,
marginTop: 16,
},
spacing1: {
margin: 0,
},
});
const { Title } = Typography;
const AgentCronJobs = memo(() => {
@@ -54,8 +65,8 @@ const AgentCronJobs = memo(() => {
}
return (
<Flexbox gap={12} style={{ marginBottom: 16, marginTop: 16 }}>
<Title level={5} style={{ margin: 0 }}>
<Flexbox gap={12} style={styles.spacing}>
<Title level={5} style={styles.spacing1}>
<Flexbox align="center" gap={8} horizontal>
<Clock size={16} />
{t('agentCronJobs.title')}
@@ -18,6 +18,7 @@ import { useTranslation } from 'react-i18next';
import { useAgentStore } from '@/store/agent';
import { agentSelectors } from '@/store/agent/selectors';
import { StyleSheet } from '@/utils/styles';
import { useMentionOptions } from '../ProfileEditor/MentionList';
import { EMPTY_EDITOR_STATE } from '../constants';
@@ -25,6 +26,12 @@ import { useProfileStore } from '../store';
import TypoBar from './TypoBar';
import { useSlashItems } from './useSlashItems';
const styles = StyleSheet.create({
spacing: {
paddingBottom: 64,
},
});
const EditorCanvas = memo(() => {
const { t } = useTranslation('setting');
const [editorInit, setEditorInit] = useState(false);
@@ -133,9 +140,7 @@ const EditorCanvas = memo(() => {
slashOption={{
items: slashItems,
}}
style={{
paddingBottom: 64,
}}
style={styles.spacing}
/>
</div>
);
@@ -4,6 +4,28 @@ import { Avatar, Flexbox, Modal } from '@lobehub/ui';
import { memo } from 'react';
import { useTranslation } from 'react-i18next';
import { StyleSheet } from '@/utils/styles';
const styles = StyleSheet.create({
flexContainer: {
flex: 'none',
},
spacing: {
marginTop: 16,
},
spacing1: {
lineHeight: 1.6,
margin: 0,
},
style: {
fontWeight: 500,
},
style1: {
fontSize: 12,
opacity: 0.6,
},
});
interface OriginalAgentInfo {
author?: {
avatar?: string;
@@ -44,18 +66,18 @@ const ForkConfirmModal = memo<ForkConfirmModalProps>(
title={t('marketPublish.forkConfirm.title')}
width={480}
>
<Flexbox gap={16} style={{ marginTop: 16 }}>
<Flexbox gap={16} style={styles.spacing}>
<Flexbox align="center" gap={12} horizontal>
<Avatar avatar={originalAgent.avatar} size={48} style={{ flex: 'none' }} />
<Avatar avatar={originalAgent.avatar} size={48} style={styles.flexContainer} />
<Flexbox gap={4}>
<div style={{ fontWeight: 500 }}>{originalAgent.name}</div>
<div style={{ fontSize: 12, opacity: 0.6 }}>
<div style={styles.style}>{originalAgent.name}</div>
<div style={styles.style1}>
{t('marketPublish.forkConfirm.by', { author: authorName })}
</div>
</Flexbox>
</Flexbox>
<p style={{ lineHeight: 1.6, margin: 0 }}>{t('marketPublish.forkConfirm.description')}</p>
<p style={styles.spacing1}>{t('marketPublish.forkConfirm.description')}</p>
</Flexbox>
</Modal>
);
@@ -6,6 +6,16 @@ import { memo } from 'react';
import { useTranslation } from 'react-i18next';
import { useNavigate } from 'react-router-dom';
import { StyleSheet } from '@/utils/styles';
const styles = StyleSheet.create({
fullWidth: {
paddingBottom: 32,
paddingTop: 48,
width: '100%',
},
});
interface PublishResultModalProps {
identifier?: string;
onCancel: () => void;
@@ -37,11 +47,7 @@ const PublishResultModal = memo<PublishResultModalProps>(({ identifier, onCancel
>
<Result
icon={<FluentEmoji emoji={'🎉'} size={96} type={'anim'} />}
style={{
paddingBottom: 32,
paddingTop: 48,
width: '100%',
}}
style={styles.fullWidth}
subTitle={
<Text fontSize={14} type={'secondary'}>
{t('marketPublish.resultModal.message')}
@@ -16,6 +16,22 @@ import { agentSelectors } from '@/store/agent/selectors';
import { useFileStore } from '@/store/file';
import { useGlobalStore } from '@/store/global';
import { globalGeneralSelectors } from '@/store/global/selectors';
import { StyleSheet } from '@/utils/styles';
const styles = StyleSheet.create({
fullWidth: {
fontSize: 36,
fontWeight: 600,
padding: 0,
width: '100%',
},
style: {
cursor: 'default',
},
style1: {
height: 38,
},
});
const MAX_AVATAR_SIZE = 1024 * 1024; // 1MB limit for server actions
@@ -94,9 +110,7 @@ const AgentHeader = memo(() => {
e.preventDefault();
}}
paddingBlock={16}
style={{
cursor: 'default',
}}
style={styles.style}
>
<EmojiPicker
allowDelete={!!meta.avatar}
@@ -118,8 +132,8 @@ const AgentHeader = memo(() => {
<Suspense
fallback={
<Flexbox gap={8}>
<Skeleton.Button block style={{ height: 38 }} />
<Skeleton.Button block style={{ height: 38 }} />
<Skeleton.Button block style={styles.style1} />
<Skeleton.Button block style={styles.style1} />
</Flexbox>
}
>
@@ -154,12 +168,7 @@ const AgentHeader = memo(() => {
debouncedSaveTitle(e.target.value);
}}
placeholder={t('settingAgent.name.placeholder', { ns: 'setting' })}
style={{
fontSize: 36,
fontWeight: 600,
padding: 0,
width: '100%',
}}
style={styles.fullWidth}
value={localTitle}
variant={'borderless'}
/>
@@ -3,8 +3,41 @@ import { Flexbox } from '@lobehub/ui';
import { cssVar } from 'antd-style';
import { type ReactNode, memo, useEffect, useLayoutEffect, useRef, useState } from 'react';
import { StyleSheet } from '@/utils/styles';
import { type MentionListOption } from './types';
const styles = StyleSheet.create({
colored1: {
borderTop: `1px solid ${cssVar.colorBorderSecondary}`,
},
colored3: {
color: cssVar.colorText,
fontSize: 14,
overflow: 'hidden',
textOverflow: 'ellipsis',
whiteSpace: 'nowrap',
},
coloredBase: {
background: cssVar.colorBgElevated,
border: `1px solid ${cssVar.colorBorderSecondary}`,
borderRadius: 12,
boxShadow: cssVar.boxShadowSecondary,
maxHeight: 260,
maxWidth: 400,
minWidth: 150,
overflow: 'hidden auto',
position: 'fixed',
zIndex: 9999,
},
coloredBaseActive: {
cursor: 'pointer',
},
flexContainer: {
flex: 'none',
},
});
/**
* Get cursor position from browser selection API
*/
@@ -47,35 +80,27 @@ const MentionDropdown = memo<MenuRenderProps>(
if (!open || !options.length || !position) return null;
const dropdownStyle = {
...styles.coloredBase,
left: position.x,
top: position.y,
};
return (
<Flexbox
style={{
background: cssVar.colorBgElevated,
border: `1px solid ${cssVar.colorBorderSecondary}`,
borderRadius: 12,
boxShadow: cssVar.boxShadowSecondary,
left: position.x,
maxHeight: 260,
maxWidth: 400,
minWidth: 150,
overflow: 'hidden auto',
position: 'fixed',
top: position.y,
zIndex: 9999,
}}
>
<Flexbox style={dropdownStyle}>
{options.map((option) => {
if ((option as any)?.type === 'divider') {
return (
<div
key={`divider-${(option as any)?.key ?? 'divider'}`}
style={{ borderTop: `1px solid ${cssVar.colorBorderSecondary}` }}
/>
<div key={`divider-${(option as any)?.key ?? 'divider'}`} style={styles.colored1} />
);
}
const item = option as MentionListOption;
const isActive = activeKey === item.key;
const itemStyle = {
...styles.coloredBaseActive,
background: isActive ? cssVar.colorFillSecondary : undefined,
};
return (
<Flexbox
@@ -91,23 +116,12 @@ const MentionDropdown = memo<MenuRenderProps>(
paddingBlock={8}
paddingInline={12}
ref={isActive ? activeItemRef : null}
style={{
background: isActive ? cssVar.colorFillSecondary : undefined,
cursor: 'pointer',
}}
style={itemStyle}
>
{item.icon && <Flexbox style={{ flex: 'none' }}>{item?.icon as ReactNode}</Flexbox>}
<div
style={{
color: cssVar.colorText,
fontSize: 14,
overflow: 'hidden',
textOverflow: 'ellipsis',
whiteSpace: 'nowrap',
}}
>
{item.label}
</div>
{item.icon && (
<Flexbox style={styles.flexContainer}>{item?.icon as ReactNode}</Flexbox>
)}
<div style={styles.colored3}>{item.label}</div>
</Flexbox>
);
})}
@@ -14,10 +14,17 @@ import { useAgentStore } from '@/store/agent';
import { pluginHelpers, useToolStore } from '@/store/tool';
import { toolSelectors } from '@/store/tool/selectors';
import { hydrationPrompt } from '@/utils/promptTemplate';
import { StyleSheet } from '@/utils/styles';
import MentionDropdown from './MentionDropdown';
import { type MentionListOption, type MentionMetadata } from './types';
const styles = StyleSheet.create({
flexContainer: {
flex: 'none',
},
});
// 根据 identifier 获取 Klavis 服务器类型配置
const getKlavisServerType = (identifier: string) =>
KLAVIS_SERVER_TYPES.find((type) => type.identifier === identifier);
@@ -29,7 +36,7 @@ const getKlavisServerType = (identifier: string) =>
*/
const KlavisIcon = memo<Pick<KlavisServerType, 'icon' | 'label'>>(({ icon, label }) => {
if (typeof icon === 'string') {
return <Image alt={label} height={20} src={icon} style={{ flex: 'none' }} width={20} />;
return <Image alt={label} height={20} src={icon} style={styles.flexContainer} width={20} />;
}
// 使用主题色填充,在深色模式下自动适应
@@ -14,6 +14,7 @@ import { useQueryRoute } from '@/hooks/useQueryRoute';
import { useAgentStore } from '@/store/agent';
import { agentSelectors } from '@/store/agent/selectors';
import { useChatStore } from '@/store/chat';
import { StyleSheet } from '@/utils/styles';
import AgentCronJobs from '../AgentCronJobs';
import EditorCanvas from '../EditorCanvas';
@@ -21,6 +22,19 @@ import AgentPublishButton from '../Header/AgentPublishButton';
import AgentHeader from './AgentHeader';
import AgentTool from './AgentTool';
const styles = StyleSheet.create({
spacing: {
cursor: 'default',
marginBottom: 12,
},
spacing1: {
marginBottom: 12,
},
spacing2: {
marginTop: 16,
},
});
const ProfileEditor = memo(() => {
const { t } = useTranslation('setting');
const config = useAgentStore(agentSelectors.currentAgentConfig, isEqual);
@@ -40,18 +54,12 @@ const ProfileEditor = memo(() => {
onClick={(e) => {
e.stopPropagation();
}}
style={{ cursor: 'default', marginBottom: 12 }}
style={styles.spacing}
>
{/* Header: Avatar + Name + Description */}
<AgentHeader />
{/* Config Bar: Model Selector */}
<Flexbox
align={'center'}
gap={8}
horizontal
justify={'flex-start'}
style={{ marginBottom: 12 }}
>
<Flexbox align={'center'} gap={8} horizontal justify={'flex-start'} style={styles.spacing1}>
<ModelSelect
onChange={updateConfig}
value={{
@@ -61,13 +69,7 @@ const ProfileEditor = memo(() => {
/>
</Flexbox>
<AgentTool />
<Flexbox
align={'center'}
gap={8}
horizontal
justify={'flex-start'}
style={{ marginTop: 16 }}
>
<Flexbox align={'center'} gap={8} horizontal justify={'flex-start'} style={styles.spacing2}>
<Button
icon={PlayIcon}
onClick={() => {
@@ -1,8 +1,16 @@
import { Flexbox, Tag } from '@lobehub/ui';
import { type ReactNode, memo } from 'react';
import { StyleSheet } from '@/utils/styles';
import Title from '../../../../../features/Title';
const styles = StyleSheet.create({
spacing: {
marginBottom: 24,
},
});
interface BlockProps {
children?: ReactNode;
count: number;
@@ -17,7 +25,7 @@ const Block = memo<BlockProps>(({ title, count, desc, children, id }) => {
<Title id={id} tag={<Tag>{count}</Tag>}>
{title}
</Title>
<p style={{ marginBottom: 24 }}>{desc}</p>
<p style={styles.spacing}>{desc}</p>
{children}
</Flexbox>
);
@@ -8,10 +8,17 @@ import { useTranslation } from 'react-i18next';
import { DEFAULT_USER_AVATAR_URL } from '@/const/meta';
import { useUserStore } from '@/store/user';
import { authSelectors, userProfileSelectors } from '@/store/user/selectors';
import { StyleSheet } from '@/utils/styles';
import Title from '../../../../../features/Title';
import { useDetailContext } from '../../DetailProvider';
const styles = StyleSheet.create({
fullWidth: {
width: '100%',
},
});
const Overview = memo(() => {
const [userAvatar, username] = useUserStore((s) => [
userProfileSelectors.userAvatar(s),
@@ -61,6 +68,10 @@ const Overview = memo(() => {
};
});
const coloredStyle = {
background: theme.colorBgContainerSecondary,
};
return (
<Flexbox gap={16}>
<Collapse
@@ -76,18 +87,13 @@ const Overview = memo(() => {
variant={'outlined'}
/>
<Title>{t('assistants.details.overview.example')}</Title>
<Block
style={{
background: theme.colorBgContainerSecondary,
}}
variant={'outlined'}
>
<Block style={coloredStyle} variant={'outlined'}>
<ChatList
data={data}
renderMessages={{
default: ({ id, editableContent }) => <div id={id}>{editableContent}</div>,
}}
style={{ width: '100%' }}
style={styles.fullWidth}
/>
</Block>
</Flexbox>
@@ -4,12 +4,20 @@ import { MessageCircleHeartIcon, MessageCircleQuestionIcon } from 'lucide-react'
import { memo } from 'react';
import { useTranslation } from 'react-i18next';
import { StyleSheet } from '@/utils/styles';
import TokenTag from '../../../../../(list)/agent/features/List/TokenTag';
import Title from '../../../../../features/Title';
import MarkdownRender from '../../../../features/MakedownRender';
import { useDetailContext } from '../../DetailProvider';
import TagList from './TagList';
const styles = StyleSheet.create({
spacing: {
marginTop: 4,
},
});
const Overview = memo(() => {
const { t } = useTranslation('discover');
const { tokenUsage, tags = [], config } = useDetailContext();
@@ -36,9 +44,7 @@ const Overview = memo(() => {
color={cssVar.colorError}
icon={MessageCircleHeartIcon}
size={20}
style={{
marginTop: 4,
}}
style={styles.spacing}
/>
<MarkdownRender>{openingMessage?.trimEnd()}</MarkdownRender>
</Block>
@@ -10,10 +10,20 @@ import PublishedTime from '@/components/PublishedTime';
import Link from '@/libs/router/Link';
import { usePathname, useQuery } from '@/libs/router/navigation';
import { type AssistantMarketSource, AssistantNavKey } from '@/types/discover';
import { StyleSheet } from '@/utils/styles';
import Title from '../../../../../features/Title';
import { useDetailContext } from '../../DetailProvider';
const styles = StyleSheet.create({
colored: {
color: 'inherit',
},
style: {
fontSize: 14,
},
});
const Versions = memo(() => {
const { t } = useTranslation('discover');
const pathname = usePathname();
@@ -69,7 +79,7 @@ const Versions = memo(() => {
const statusMeta = statusKey ? statusTagMap[statusKey] : undefined;
const content = (
<Flexbox align={'center'} gap={8} horizontal>
<code style={{ fontSize: 14 }}>{record.version}</code>
<code style={styles.style}>{record.version}</code>
{(record.isLatest || record.version === currentVersion) && (
<Tag color={'info'}>{t('assistants.details.version.table.isLatest')}</Tag>
)}
@@ -92,7 +102,7 @@ const Versions = memo(() => {
},
{ skipNull: true },
)}
style={{ color: 'inherit' }}
style={styles.colored}
>
{content}
</Link>
@@ -6,6 +6,7 @@ import { memo } from 'react';
import { useQueryState } from '@/hooks/useQueryParam';
import { AssistantNavKey } from '@/types/discover';
import { StyleSheet } from '@/utils/styles';
import Sidebar from '../Sidebar';
import Capabilities from './Capabilities';
@@ -15,6 +16,12 @@ import Related from './Related';
import SystemRole from './SystemRole';
import Versions from './Versions';
const styles = StyleSheet.create({
style: {
overflow: 'hidden',
},
});
const Details = memo<{ mobile?: boolean }>(({ mobile: isMobile }) => {
const { mobile = isMobile } = useResponsive();
const [activeTab, setActiveTab] = useQueryState('activeTab', {
@@ -30,12 +37,7 @@ const Details = memo<{ mobile?: boolean }>(({ mobile: isMobile }) => {
horizontal={!mobile}
style={mobile ? { flexDirection: 'column-reverse' } : undefined}
>
<Flexbox
style={{
overflow: 'hidden',
}}
width={'100%'}
>
<Flexbox style={styles.style} width={'100%'}>
{activeTab === AssistantNavKey.Overview && <Overview />}
{activeTab === AssistantNavKey.SystemRole && <SystemRole />}
{activeTab === AssistantNavKey.Capabilities && <Capabilities />}
@@ -7,11 +7,19 @@ import urlJoin from 'url-join';
import { useQuery } from '@/hooks/useQuery';
import { type AssistantMarketSource } from '@/types/discover';
import { StyleSheet } from '@/utils/styles';
import Title from '../../../../../features/Title';
import { useDetailContext } from '../../DetailProvider';
import Item from './Item';
const styles = StyleSheet.create({
colored: {
color: 'inherit',
overflow: 'hidden',
},
});
const Related = memo(() => {
const { t } = useTranslation('discover');
const { related, category } = useDetailContext();
@@ -45,7 +53,7 @@ const Related = memo(() => {
{ skipNull: true },
);
return (
<Link key={index} style={{ color: 'inherit', overflow: 'hidden' }} to={link}>
<Link key={index} style={styles.colored} to={link}>
<Item {...item} />
</Link>
);
@@ -3,8 +3,17 @@ import { cssVar } from 'antd-style';
import { memo } from 'react';
import { useTranslation } from 'react-i18next';
import { StyleSheet } from '@/utils/styles';
import { useDetailContext } from '../../DetailProvider';
const styles = StyleSheet.create({
spacing: {
color: cssVar.colorTextSecondary,
margin: 0,
},
});
const Summary = memo(() => {
const { description, summary } = useDetailContext();
const { t } = useTranslation('discover');
@@ -14,16 +23,7 @@ const Summary = memo(() => {
expandIconPlacement={'end'}
items={[
{
children: (
<p
style={{
color: cssVar.colorTextSecondary,
margin: 0,
}}
>
{summary || description}
</p>
),
children: <p style={styles.spacing}>{summary || description}</p>,
key: 'summary',
label: t('assistants.details.summary.title'),
},
@@ -3,12 +3,22 @@ import { memo } from 'react';
import { useQuery } from '@/hooks/useQuery';
import { AssistantNavKey } from '@/types/discover';
import { StyleSheet } from '@/utils/styles';
import ActionButton from './ActionButton';
import Related from './Related';
import Summary from './Summary';
import TocList from './TocList';
const styles = StyleSheet.create({
spacing: {
maxHeight: 'calc(100vh - 76px)',
paddingBottom: 24,
position: 'sticky',
top: 16,
},
});
const Sidebar = memo<{ mobile?: boolean }>(({ mobile }) => {
const { activeTab = AssistantNavKey.Overview } = useQuery() as { activeTab: AssistantNavKey };
@@ -21,19 +31,7 @@ const Sidebar = memo<{ mobile?: boolean }>(({ mobile }) => {
}
return (
<ScrollShadow
flex={'none'}
gap={32}
hideScrollBar
size={4}
style={{
maxHeight: 'calc(100vh - 76px)',
paddingBottom: 24,
position: 'sticky',
top: 16,
}}
width={360}
>
<ScrollShadow flex={'none'} gap={32} hideScrollBar size={4} style={styles.spacing} width={360}>
<ActionButton />
{activeTab !== AssistantNavKey.Overview && <Summary />}
{activeTab === AssistantNavKey.SystemRole && <TocList />}
@@ -7,6 +7,42 @@ import { memo } from 'react';
import { Trans, useTranslation } from 'react-i18next';
import { useNavigate } from 'react-router-dom';
import { StyleSheet } from '@/utils/styles';
const styles = StyleSheet.create({
colored: {
color: '#8c8c8c',
},
colored1: {
color: '#ff4d4f',
},
colored2: {
color: '#666',
lineHeight: 1.6,
},
flexContainer: {
alignItems: 'center',
display: 'flex',
flex: 1,
justifyContent: 'center',
minHeight: '60vh',
padding: '20px',
},
flexContainer1: {
alignItems: 'center',
display: 'flex',
flex: 1,
justifyContent: 'center',
minHeight: '60vh',
padding: '20px',
},
spacing: {
margin: '16px 0',
paddingLeft: '20px',
textAlign: 'left',
},
});
interface StatusPageProps {
status: 'unpublished' | 'archived' | 'deprecated';
}
@@ -22,16 +58,7 @@ const StatusPage = memo<StatusPageProps>(({ status }) => {
// 审核中状态
if (status === 'unpublished') {
return (
<div
style={{
alignItems: 'center',
display: 'flex',
flex: 1,
justifyContent: 'center',
minHeight: '60vh',
padding: '20px',
}}
>
<div style={styles.flexContainer}>
<Result
extra={
<Button onClick={handleBackToMarket} size={'large'} type="primary">
@@ -64,22 +91,13 @@ const StatusPage = memo<StatusPageProps>(({ status }) => {
const isArchived = status === 'archived';
const statusKey = isArchived ? 'archived' : 'deprecated';
const statusIcon = isArchived ? (
<FolderOpenOutlined style={{ color: '#8c8c8c' }} />
<FolderOpenOutlined style={styles.colored} />
) : (
<ExclamationCircleOutlined style={{ color: '#ff4d4f' }} />
<ExclamationCircleOutlined style={styles.colored1} />
);
return (
<div
style={{
alignItems: 'center',
display: 'flex',
flex: 1,
justifyContent: 'center',
minHeight: '60vh',
padding: '20px',
}}
>
<div style={styles.flexContainer1}>
<Result
extra={
<Button onClick={handleBackToMarket} type="primary">
@@ -88,9 +106,9 @@ const StatusPage = memo<StatusPageProps>(({ status }) => {
}
icon={statusIcon}
subTitle={
<div style={{ color: '#666', lineHeight: 1.6 }}>
<div style={styles.colored2}>
<p>{t(`assistants.status.${statusKey}.subtitle`)}</p>
<ul style={{ margin: '16px 0', paddingLeft: '20px', textAlign: 'left' }}>
<ul style={styles.spacing}>
<li>{t(`assistants.status.${statusKey}.reasons.owner`)}</li>
<li>{t(`assistants.status.${statusKey}.reasons.official`)}</li>
</ul>
@@ -4,8 +4,23 @@ import { Flexbox, Skeleton } from '@lobehub/ui';
import { useResponsive } from 'antd-style';
import { memo } from 'react';
import { StyleSheet } from '@/utils/styles';
import Nav from './features/Details/Nav';
const styles = StyleSheet.create({
style: {
height: 36,
width: 200,
},
style1: {
width: 200,
},
style2: {
overflow: 'hidden',
},
});
const Loading = memo(() => {
const { mobile } = useResponsive();
return (
@@ -13,9 +28,9 @@ const Loading = memo(() => {
<Flexbox gap={12}>
<Flexbox align={'center'} gap={16} horizontal width={'100%'}>
<Skeleton.Avatar active shape={'square'} size={mobile ? 48 : 64} />
<Skeleton.Button active style={{ height: 36, width: 200 }} />
<Skeleton.Button active style={styles.style} />
</Flexbox>
<Skeleton.Button size={'small'} style={{ width: 200 }} />
<Skeleton.Button size={'small'} style={styles.style1} />
</Flexbox>
<Nav />
<Flexbox
@@ -23,14 +38,7 @@ const Loading = memo(() => {
horizontal={!mobile}
style={mobile ? { flexDirection: 'column-reverse' } : undefined}
>
<Flexbox
flex={1}
gap={16}
style={{
overflow: 'hidden',
}}
width={'100%'}
>
<Flexbox flex={1} gap={16} style={styles.style2} width={'100%'}>
<Skeleton paragraph={{ rows: 3 }} title={false} />
<Skeleton paragraph={{ rows: 8 }} title={false} />
<Skeleton paragraph={{ rows: 8 }} title={false} />
@@ -4,11 +4,19 @@ import { Flexbox } from '@lobehub/ui';
import { memo } from 'react';
import { useTranslation } from 'react-i18next';
import { StyleSheet } from '@/utils/styles';
const styles = StyleSheet.create({
style: {
minHeight: 400,
},
});
const NotFound = memo(() => {
const { t } = useTranslation('error');
return (
<Flexbox align="center" height="100%" justify="center" style={{ minHeight: 400 }} width="100%">
<Flexbox align="center" height="100%" justify="center" style={styles.style} width="100%">
<h2>{t('notFound.title')}</h2>
</Flexbox>
);
@@ -8,6 +8,13 @@ import { useTranslation } from 'react-i18next';
import { Link } from 'react-router-dom';
import { DiscoverTab } from '@/types/discover';
import { StyleSheet } from '@/utils/styles';
const styles = StyleSheet.create({
colored: {
color: cssVar.colorTextSecondary,
},
});
const Breadcrumb = memo<{ identifier: string; tab: DiscoverTab }>(({ tab, identifier }) => {
const { t } = useTranslation('discover');
@@ -27,14 +34,7 @@ const Breadcrumb = memo<{ identifier: string; tab: DiscoverTab }>(({ tab, identi
},
{
title: (
<Flexbox
align="center"
gap={4}
horizontal
style={{
color: cssVar.colorTextSecondary,
}}
>
<Flexbox align="center" gap={4} horizontal style={styles.colored}>
@{identifier}
</Flexbox>
),
@@ -51,14 +51,7 @@ const Breadcrumb = memo<{ identifier: string; tab: DiscoverTab }>(({ tab, identi
},
{
title: (
<Flexbox
align="center"
gap={4}
horizontal
style={{
color: cssVar.colorTextSecondary,
}}
>
<Flexbox align="center" gap={4} horizontal style={styles.colored}>
{identifier}
<CopyButton
content={identifier}
@@ -5,9 +5,17 @@ import { useResponsive } from 'antd-style';
import { type ReactNode, memo } from 'react';
import Footer from '@/features/Setting/Footer';
import { StyleSheet } from '@/utils/styles';
import SidebarContainer from './SidebarContainer';
const styles = StyleSheet.create({
style: {
overflow: 'hidden',
position: 'relative',
},
});
interface DetailLayoutProps {
actions?: ReactNode;
children?: ReactNode;
@@ -39,7 +47,7 @@ const DetailLayout = memo<DetailLayoutProps>(
<>
{header}
<Flexbox gap={32} horizontal width={'100%'}>
<Flexbox flex={1} gap={48} style={{ overflow: 'hidden', position: 'relative' }}>
<Flexbox flex={1} gap={48} style={styles.style}>
{children}
</Flexbox>
<SidebarContainer>
@@ -5,8 +5,16 @@ import { FileText } from 'lucide-react';
import { type ReactNode, memo } from 'react';
import { useTranslation } from 'react-i18next';
import { StyleSheet } from '@/utils/styles';
import { H1, H2, H3, H4, H5 } from './Toc/Heading';
const styles = StyleSheet.create({
style: {
maxWidth: 400,
},
});
const MarkdownRender = memo<{ children?: string }>(({ children }) => {
const { t } = useTranslation('common');
if (!children)
@@ -16,7 +24,7 @@ const MarkdownRender = memo<{ children?: string }>(({ children }) => {
description={t('noContent')}
descriptionProps={{ fontSize: 14 }}
icon={FileText}
style={{ maxWidth: 400 }}
style={styles.style}
/>
</Center>
);
@@ -1,13 +1,22 @@
import { Flexbox, type FlexboxProps } from '@lobehub/ui';
import { type FC } from 'react';
import { StyleSheet } from '@/utils/styles';
const styles = StyleSheet.create({
style: {
position: 'relative',
width: 320,
},
});
const SidebarContainer: FC<FlexboxProps> = ({ children, style, ...rest }) => {
return (
<Flexbox
flex={'none'}
gap={48}
height={'100%'}
style={{ position: 'relative', width: 320, ...style }}
style={StyleSheet.compose(styles.style, style)}
width={'100%'}
{...rest}
>
@@ -4,94 +4,97 @@ import { Crown, User } from 'lucide-react';
import { memo } from 'react';
import { useTranslation } from 'react-i18next';
import { StyleSheet } from '@/utils/styles';
import { useDetailContext } from '../../DetailProvider';
const styles = StyleSheet.create({
spacing: {
margin: 0,
},
style: {
fontSize: 12,
},
});
const { Title, Text, Paragraph } = Typography;
const MemberCard = memo(
({
agent,
currentVersion,
}: {
agent: any;
currentVersion: any;
}) => {
const { t } = useTranslation('discover');
const isSupervisor = agent.role === 'supervisor';
const MemberCard = memo(({ agent, currentVersion }: { agent: any; currentVersion: any }) => {
const { t } = useTranslation('discover');
const isSupervisor = agent.role === 'supervisor';
return (
<Card hoverable>
<Flexbox gap={12}>
{/* Avatar and Basic Info */}
<Flexbox align="center" gap={12} horizontal>
<Avatar avatar={currentVersion.avatar || agent.name[0]} size={48} />
<Flexbox flex={1} gap={4}>
<Flexbox align="center" gap={8} horizontal>
<Title level={5} style={{ margin: 0 }}>
{currentVersion.name || agent.name}
</Title>
{isSupervisor ? (
<Tag color="gold" icon={<Crown size={12} />}>
{t('members.supervisor', { defaultValue: 'Supervisor' })}
</Tag>
) : (
<Tag color="blue" icon={<User size={12} />}>
{t('members.participant', { defaultValue: 'Participant' })}
</Tag>
)}
</Flexbox>
<Text type="secondary">{agent.identifier}</Text>
return (
<Card hoverable>
<Flexbox gap={12}>
{/* Avatar and Basic Info */}
<Flexbox align="center" gap={12} horizontal>
<Avatar avatar={currentVersion.avatar || agent.name[0]} size={48} />
<Flexbox flex={1} gap={4}>
<Flexbox align="center" gap={8} horizontal>
<Title level={5} style={styles.spacing}>
{currentVersion.name || agent.name}
</Title>
{isSupervisor ? (
<Tag color="gold" icon={<Crown size={12} />}>
{t('members.supervisor', { defaultValue: 'Supervisor' })}
</Tag>
) : (
<Tag color="blue" icon={<User size={12} />}>
{t('members.participant', { defaultValue: 'Participant' })}
</Tag>
)}
</Flexbox>
<Text type="secondary">{agent.identifier}</Text>
</Flexbox>
</Flexbox>
{/* Description */}
{currentVersion.description && (
<Paragraph ellipsis={{ rows: 2 }} style={{ margin: 0 }} type="secondary">
{currentVersion.description}
{/* Description */}
{currentVersion.description && (
<Paragraph ellipsis={{ rows: 2 }} style={styles.spacing} type="secondary">
{currentVersion.description}
</Paragraph>
)}
{/* System Role (if available) */}
{currentVersion.config?.systemRole && (
<Flexbox gap={4}>
<Text strong>{t('members.systemRole', { defaultValue: 'System Role' })}:</Text>
<Paragraph ellipsis={{ rows: 3 }} style={styles.spacing} type="secondary">
{currentVersion.config.systemRole}
</Paragraph>
)}
{/* System Role (if available) */}
{currentVersion.config?.systemRole && (
<Flexbox gap={4}>
<Text strong>{t('members.systemRole', { defaultValue: 'System Role' })}:</Text>
<Paragraph ellipsis={{ rows: 3 }} style={{ margin: 0 }} type="secondary">
{currentVersion.config.systemRole}
</Paragraph>
</Flexbox>
)}
{/* Metadata */}
<Flexbox gap={8} horizontal wrap="wrap">
{currentVersion.version && (
<Text type="secondary">
{t('members.version', { defaultValue: 'Version' })}: {currentVersion.version}
</Text>
)}
{currentVersion.tokenUsage !== undefined && (
<Text type="secondary">
{t('members.tokenUsage', { defaultValue: 'Token Usage' })}:{' '}
{currentVersion.tokenUsage}
</Text>
)}
</Flexbox>
)}
{/* URL */}
{currentVersion.url && (
<Text
copyable={{ text: currentVersion.url }}
ellipsis
style={{ fontSize: 12 }}
type="secondary"
>
{currentVersion.url}
{/* Metadata */}
<Flexbox gap={8} horizontal wrap="wrap">
{currentVersion.version && (
<Text type="secondary">
{t('members.version', { defaultValue: 'Version' })}: {currentVersion.version}
</Text>
)}
{currentVersion.tokenUsage !== undefined && (
<Text type="secondary">
{t('members.tokenUsage', { defaultValue: 'Token Usage' })}:{' '}
{currentVersion.tokenUsage}
</Text>
)}
</Flexbox>
</Card>
);
},
);
{/* URL */}
{currentVersion.url && (
<Text
copyable={{ text: currentVersion.url }}
ellipsis
style={styles.style}
type="secondary"
>
{currentVersion.url}
</Text>
)}
</Flexbox>
</Card>
);
});
MemberCard.displayName = 'MemberCard';
@@ -6,10 +6,30 @@ import { useNavigate } from 'react-router-dom';
import urlJoin from 'url-join';
import { type DiscoverGroupAgentItem } from '@/types/discover';
import { StyleSheet } from '@/utils/styles';
import Title from '../../../../../features/Title';
import { useDetailContext } from '../../DetailProvider';
const styles = StyleSheet.create({
colored: {
border: '1px solid var(--lobe-border-color)',
borderRadius: 8,
cursor: 'pointer',
transition: 'all 0.2s',
},
colored1: {
color: '#999',
},
style: {
fontWeight: 500,
},
style1: {
fontSize: 12,
opacity: 0.65,
},
});
const GroupAgentCard = memo<DiscoverGroupAgentItem>((item) => {
const navigate = useNavigate();
@@ -23,24 +43,14 @@ const GroupAgentCard = memo<DiscoverGroupAgentItem>((item) => {
};
return (
<Flexbox
gap={12}
onClick={handleClick}
padding={16}
style={{
border: '1px solid var(--lobe-border-color)',
borderRadius: 8,
cursor: 'pointer',
transition: 'all 0.2s',
}}
>
<Flexbox gap={12} onClick={handleClick} padding={16} style={styles.colored}>
<Flexbox align="center" gap={12} horizontal>
<Avatar avatar={item.avatar || item.title[0]} shape="square" size={48} />
<Flexbox flex={1} gap={4}>
<Text ellipsis style={{ fontWeight: 500 }}>
<Text ellipsis style={styles.style}>
{item.title}
</Text>
<Text ellipsis style={{ fontSize: 12, opacity: 0.65 }} type="secondary">
<Text ellipsis style={styles.style1} type="secondary">
{item.description}
</Text>
</Flexbox>
@@ -76,7 +86,7 @@ const Related = memo(() => {
))}
</Grid>
) : (
<Flexbox align="center" padding={32} style={{ color: '#999' }}>
<Flexbox align="center" padding={32} style={styles.colored1}>
{t('groupAgents.details.related.empty', {
defaultValue: 'No related group agents found',
})}
@@ -4,11 +4,19 @@ import { MessageCircleHeartIcon, MessageCircleQuestionIcon } from 'lucide-react'
import { memo } from 'react';
import { useTranslation } from 'react-i18next';
import { StyleSheet } from '@/utils/styles';
import Title from '../../../../../features/Title';
import MarkdownRender from '../../../../features/MakedownRender';
import { useDetailContext } from '../../DetailProvider';
import TagList from './TagList';
const styles = StyleSheet.create({
spacing: {
marginTop: 4,
},
});
const SystemRole = memo(() => {
const { t } = useTranslation('discover');
const { tokenUsage, tags = [], config } = useDetailContext();
@@ -18,7 +26,15 @@ const SystemRole = memo(() => {
<Flexbox gap={16}>
{systemRole && (
<>
<Title tag={tokenUsage && <Tag>{t('groupAgents.details.tokenUsage', { defaultValue: `${tokenUsage} tokens` })}</Tag>}>
<Title
tag={
tokenUsage && (
<Tag>
{t('groupAgents.details.tokenUsage', { defaultValue: `${tokenUsage} tokens` })}
</Tag>
)
}
>
{t('groupAgents.details.systemRole.title', { defaultValue: 'System Role' })}
</Title>
<Block gap={16} padding={16} variant={'outlined'}>
@@ -39,9 +55,7 @@ const SystemRole = memo(() => {
color={cssVar.colorError}
icon={MessageCircleHeartIcon}
size={20}
style={{
marginTop: 4,
}}
style={styles.spacing}
/>
<MarkdownRender>{openingMessage?.trimEnd()}</MarkdownRender>
</Block>
@@ -6,10 +6,17 @@ import { useTranslation } from 'react-i18next';
import InlineTable from '@/components/InlineTable';
import PublishedTime from '@/components/PublishedTime';
import { StyleSheet } from '@/utils/styles';
import Title from '../../../../../features/Title';
import { useDetailContext } from '../../DetailProvider';
const styles = StyleSheet.create({
style: {
fontSize: 14,
},
});
const Versions = memo(() => {
const { t } = useTranslation('discover');
const { versions = [], currentVersion } = useDetailContext();
@@ -39,9 +46,7 @@ const Versions = memo(() => {
if (!versions.length) {
return (
<Flexbox gap={16}>
<Title>
{t('groupAgents.details.version.title', { defaultValue: 'Version History' })}
</Title>
<Title>{t('groupAgents.details.version.title', { defaultValue: 'Version History' })}</Title>
<Block padding={24} variant={'outlined'}>
{t('groupAgents.details.version.empty', { defaultValue: 'No version history available' })}
</Block>
@@ -51,9 +56,7 @@ const Versions = memo(() => {
return (
<Flexbox gap={16}>
<Title>
{t('groupAgents.details.version.title', { defaultValue: 'Version History' })}
</Title>
<Title>{t('groupAgents.details.version.title', { defaultValue: 'Version History' })}</Title>
<Block variant={'outlined'}>
<InlineTable
columns={[
@@ -61,18 +64,19 @@ const Versions = memo(() => {
dataIndex: 'version',
render: (_: any, record: any) => {
const statusKey =
record.status &&
Object.prototype.hasOwnProperty.call(statusTagMap, record.status)
record.status && Object.prototype.hasOwnProperty.call(statusTagMap, record.status)
? (record.status as keyof typeof statusTagMap)
: undefined;
const statusMeta = statusKey ? statusTagMap[statusKey] : undefined;
return (
<Flexbox align={'center'} gap={8} horizontal>
<code style={{ fontSize: 14 }}>{record.version}</code>
<code style={styles.style}>{record.version}</code>
{(record.isLatest || record.version === currentVersion) && (
<Tag color={'info'}>
{t('groupAgents.details.version.table.isLatest', { defaultValue: 'Latest' })}
{t('groupAgents.details.version.table.isLatest', {
defaultValue: 'Latest',
})}
</Tag>
)}
{statusMeta && <Tag color={statusMeta.color}>{statusMeta.label}</Tag>}
@@ -3,6 +3,7 @@ import { useResponsive } from 'antd-style';
import { memo } from 'react';
import { useQueryState } from '@/hooks/useQueryParam';
import { StyleSheet } from '@/utils/styles';
import Sidebar from '../Sidebar';
import Nav, { GroupAgentNavKey } from './Nav';
@@ -10,6 +11,12 @@ import Overview from './Overview';
import SystemRole from './SystemRole';
import Versions from './Versions';
const styles = StyleSheet.create({
style: {
overflow: 'hidden',
},
});
const Details = memo<{ mobile?: boolean }>(({ mobile: isMobile }) => {
const { mobile = isMobile } = useResponsive();
const [activeTab, setActiveTab] = useQueryState('activeTab', {
@@ -29,12 +36,9 @@ const Details = memo<{ mobile?: boolean }>(({ mobile: isMobile }) => {
horizontal={!mobile}
style={mobile ? { flexDirection: 'column-reverse' } : undefined}
>
<Flexbox
style={{
overflow: 'hidden',
}}
width={'100%'}
>
{/* Main Content */}
<Flexbox style={styles.style} width={'100%'}>
{/* Tab Content */}
{activeTab === GroupAgentNavKey.Overview && <Overview />}
{activeTab === GroupAgentNavKey.SystemRole && <SystemRole />}
{activeTab === GroupAgentNavKey.Versions && <Versions />}
@@ -3,8 +3,17 @@ import { cssVar } from 'antd-style';
import { memo } from 'react';
import { useTranslation } from 'react-i18next';
import { StyleSheet } from '@/utils/styles';
import { useDetailContext } from '../../DetailProvider';
const styles = StyleSheet.create({
spacing: {
color: cssVar.colorTextSecondary,
margin: 0,
},
});
const Summary = memo(() => {
const { description, summary } = useDetailContext();
const { t } = useTranslation('discover');
@@ -17,16 +26,7 @@ const Summary = memo(() => {
expandIconPlacement={'end'}
items={[
{
children: (
<p
style={{
color: cssVar.colorTextSecondary,
margin: 0,
}}
>
{displayDescription}
</p>
),
children: <p style={styles.spacing}>{displayDescription}</p>,
key: 'summary',
label: t('groupAgents.details.summary.title', {
defaultValue: 'What can you use this group for?',
@@ -2,11 +2,21 @@ import { Flexbox, ScrollShadow } from '@lobehub/ui';
import { memo } from 'react';
import { useQuery } from '@/hooks/useQuery';
import { StyleSheet } from '@/utils/styles';
import { GroupAgentNavKey } from '../Details/Nav';
import ActionButton from './ActionButton';
import Summary from './Summary';
const styles = StyleSheet.create({
spacing: {
maxHeight: 'calc(100vh - 76px)',
paddingBottom: 24,
position: 'sticky',
top: 16,
},
});
const Sidebar = memo<{ mobile?: boolean }>(({ mobile }) => {
const { activeTab = GroupAgentNavKey.Overview } = useQuery() as { activeTab: GroupAgentNavKey };
@@ -19,19 +29,7 @@ const Sidebar = memo<{ mobile?: boolean }>(({ mobile }) => {
}
return (
<ScrollShadow
flex={'none'}
gap={32}
hideScrollBar
size={4}
style={{
maxHeight: 'calc(100vh - 76px)',
paddingBottom: 24,
position: 'sticky',
top: 16,
}}
width={360}
>
<ScrollShadow flex={'none'} gap={32} hideScrollBar size={4} style={styles.spacing} width={360}>
<ActionButton />
{activeTab !== GroupAgentNavKey.Overview && <Summary />}
</ScrollShadow>
@@ -7,6 +7,37 @@ import { memo } from 'react';
import { useTranslation } from 'react-i18next';
import { useNavigate } from 'react-router-dom';
import { StyleSheet } from '@/utils/styles';
const styles = StyleSheet.create({
colored: {
color: '#8c8c8c',
},
colored1: {
color: '#ff4d4f',
},
colored2: {
color: '#666',
lineHeight: 1.6,
},
flexContainer: {
alignItems: 'center',
display: 'flex',
flex: 1,
justifyContent: 'center',
minHeight: '60vh',
padding: '20px',
},
flexContainer1: {
alignItems: 'center',
display: 'flex',
flex: 1,
justifyContent: 'center',
minHeight: '60vh',
padding: '20px',
},
});
interface StatusPageProps {
status: 'unpublished' | 'archived' | 'deprecated';
}
@@ -22,16 +53,7 @@ const StatusPage = memo<StatusPageProps>(({ status }) => {
// Unpublished status
if (status === 'unpublished') {
return (
<div
style={{
alignItems: 'center',
display: 'flex',
flex: 1,
justifyContent: 'center',
minHeight: '60vh',
padding: '20px',
}}
>
<div style={styles.flexContainer}>
<Result
extra={
<Button onClick={handleBackToMarket} size={'large'} type="primary">
@@ -61,22 +83,13 @@ const StatusPage = memo<StatusPageProps>(({ status }) => {
const isArchived = status === 'archived';
const statusKey = isArchived ? 'archived' : 'deprecated';
const statusIcon = isArchived ? (
<FolderOpenOutlined style={{ color: '#8c8c8c' }} />
<FolderOpenOutlined style={styles.colored} />
) : (
<ExclamationCircleOutlined style={{ color: '#ff4d4f' }} />
<ExclamationCircleOutlined style={styles.colored1} />
);
return (
<div
style={{
alignItems: 'center',
display: 'flex',
flex: 1,
justifyContent: 'center',
minHeight: '60vh',
padding: '20px',
}}
>
<div style={styles.flexContainer1}>
<Result
extra={
<Button onClick={handleBackToMarket} type="primary">
@@ -85,7 +98,7 @@ const StatusPage = memo<StatusPageProps>(({ status }) => {
}
icon={statusIcon}
subTitle={
<div style={{ color: '#666', lineHeight: 1.6 }}>
<div style={styles.colored2}>
<p>
{t(`groupAgents.status.${statusKey}.subtitle`, {
defaultValue: `This group agent has been ${statusKey}.`,
@@ -5,14 +5,24 @@ import qs from 'query-string';
import { memo } from 'react';
import { useTranslation } from 'react-i18next';
import InlineTable from '@/components/InlineTable';
import Link from '@/libs/router/Link';
import { usePathname } from '@/libs/router/navigation';
import InlineTable from '@/components/InlineTable';
import { StyleSheet } from '@/utils/styles';
import PublishedTime from '../../../../../../../../../components/PublishedTime';
import { useDetailContext } from '../../../../../../../../../features/MCPPluginDetail/DetailProvider';
import Title from '../../../../../features/Title';
const styles = StyleSheet.create({
colored: {
color: 'inherit',
},
style: {
fontSize: 14,
},
});
const Versions = memo(() => {
const { t } = useTranslation('discover');
const { versions } = useDetailContext();
@@ -33,10 +43,10 @@ const Versions = memo(() => {
},
url: pathname,
})}
style={{ color: 'inherit' }}
style={styles.colored}
>
<Flexbox align={'center'} gap={8} horizontal>
<code style={{ fontSize: 14 }}>{record.version}</code>
<code style={styles.style}>{record.version}</code>
{record.isLatest && (
<Tag color={'info'}>{t('mcp.details.versions.table.isLatest')}</Tag>
)}
@@ -11,11 +11,18 @@ import Schema from '@/features/MCPPluginDetail/Schema';
import Score from '@/features/MCPPluginDetail/Score';
import { useQueryState } from '@/hooks/useQueryParam';
import { McpNavKey } from '@/types/discover';
import { StyleSheet } from '@/utils/styles';
import Sidebar from '../Sidebar';
import Related from './Related';
import Versions from './Versions';
const styles = StyleSheet.create({
style: {
overflow: 'hidden',
},
});
const Details = memo<{ mobile?: boolean }>(({ mobile: isMobile }) => {
const { mobile = isMobile } = useResponsive();
const [activeTab, setActiveTab] = useQueryState('activeTab', {
@@ -36,13 +43,7 @@ const Details = memo<{ mobile?: boolean }>(({ mobile: isMobile }) => {
horizontal={!mobile}
style={mobile ? { flexDirection: 'column-reverse' } : undefined}
>
<Flexbox
flex={1}
style={{
overflow: 'hidden',
}}
width={'100%'}
>
<Flexbox flex={1} style={styles.style} width={'100%'}>
{activeTab === McpNavKey.Overview && <Overview />}
{activeTab === McpNavKey.Deployment && <Deployment mobile={mobile} />}
{activeTab === McpNavKey.Schema && <Schema />}
@@ -6,10 +6,18 @@ import { Link } from 'react-router-dom';
import urlJoin from 'url-join';
import { useDetailContext } from '@/features/MCPPluginDetail/DetailProvider';
import { StyleSheet } from '@/utils/styles';
import Title from '../../../../../features/Title';
import Item from './Item';
const styles = StyleSheet.create({
colored: {
color: 'inherit',
overflow: 'hidden',
},
});
const Related = memo(() => {
const { t } = useTranslation('discover');
const { related, category } = useDetailContext();
@@ -31,7 +39,7 @@ const Related = memo(() => {
{related?.map((item, index) => {
const link = urlJoin('/community/mcp', item.identifier);
return (
<Link key={index} style={{ color: 'inherit', overflow: 'hidden' }} to={link}>
<Link key={index} style={styles.colored} to={link}>
<Item {...item} />
</Link>
);
@@ -4,6 +4,7 @@ import { memo } from 'react';
import { isDesktop } from '@/const/version';
import { useQuery } from '@/hooks/useQuery';
import { McpNavKey } from '@/types/discover';
import { StyleSheet } from '@/utils/styles';
import ActionButton from './ActionButton';
import ConnectionTypeAlert from './ConnectionTypeAlert';
@@ -11,6 +12,15 @@ import Related from './Related';
import ServerConfig from './ServerConfig';
import TocList from './TocList';
const styles = StyleSheet.create({
spacing: {
maxHeight: 'calc(100vh - 76px)',
paddingBottom: 24,
position: 'sticky',
top: 16,
},
});
const Sidebar = memo<{ mobile?: boolean }>(({ mobile }) => {
const { activeTab = McpNavKey.Overview } = useQuery() as { activeTab: McpNavKey };
@@ -25,19 +35,7 @@ const Sidebar = memo<{ mobile?: boolean }>(({ mobile }) => {
}
return (
<ScrollShadow
flex={'none'}
gap={32}
hideScrollBar
size={4}
style={{
maxHeight: 'calc(100vh - 76px)',
paddingBottom: 24,
position: 'sticky',
top: 16,
}}
width={360}
>
<ScrollShadow flex={'none'} gap={32} hideScrollBar size={4} style={styles.spacing} width={360}>
{isDesktop ? (
<Flexbox>
<ActionButton />
@@ -14,9 +14,19 @@ import { ModelInfoTags } from '@/components/ModelSelect';
import { BASE_PROVIDER_DOC_URL } from '@/const/url';
import { formatPriceByCurrency, formatTokenNumber } from '@/utils/format';
import { getTextInputUnitRate, getTextOutputUnitRate } from '@/utils/pricing';
import { StyleSheet } from '@/utils/styles';
import { useDetailContext } from '../../../DetailProvider';
const styles = StyleSheet.create({
colored: {
color: 'inherit',
},
style: {
fontWeight: 500,
},
});
const ProviderList = memo(() => {
const { providers = [] } = useDetailContext();
const { t } = useTranslation('discover');
@@ -31,10 +41,10 @@ const ProviderList = memo(() => {
key: 'provider',
render: (_, record) => {
return (
<Link style={{ color: 'inherit' }} to={urlJoin('/community/provider', record.id)}>
<Link style={styles.colored} to={urlJoin('/community/provider', record.id)}>
<Flexbox align="center" gap={8} horizontal>
<ProviderIcon provider={record.id} size={24} type={'avatar'} />
<div style={{ fontWeight: 500 }}>{record.name}</div>
<div style={styles.style}>{record.name}</div>
</Flexbox>
</Link>
);
@@ -167,10 +177,7 @@ const ProviderList = memo(() => {
<ActionIcon icon={BookIcon} size={'small'} variant={'filled'} />
</a>
</Tooltip>
<Link
style={{ color: 'inherit' }}
to={urlJoin('/community/provider', record.id)}
>
<Link style={styles.colored} to={urlJoin('/community/provider', record.id)}>
<ActionIcon
color={cssVar.colorTextDescription}
icon={ChevronRightIcon}
@@ -4,8 +4,23 @@ import { cssVar } from 'antd-style';
import { memo } from 'react';
import { useTranslation } from 'react-i18next';
import { StyleSheet } from '@/utils/styles';
import Statistic from '../../../../../components/Statistic';
const styles = StyleSheet.create({
spacing: {
color: cssVar.colorTextSecondary,
margin: 0,
},
spacing1: {
margin: 0,
},
spacing2: {
paddingBottom: 8,
},
});
const DEFAULT_DOC_URL = 'https://lobehub.com/docs/usage/agents/model';
export interface ParameterItemProps {
@@ -26,14 +41,14 @@ const ParameterItem = memo<ParameterItemProps>(
return (
<Flexbox align={'flex-start'} gap={16}>
<p style={{ color: cssVar.colorTextSecondary, margin: 0 }}>
<p style={styles.spacing}>
{desc}{' '}
<a href={docUrl} rel="noreferrer" target="_blank">
{t('models.parameterList.docs')}
</a>
</p>
<Divider dashed style={{ margin: 0 }} />
<Flexbox align={'center'} gap={16} horizontal style={{ paddingBottom: 8 }} wrap={'wrap'}>
<Divider dashed style={styles.spacing1} />
<Flexbox align={'center'} gap={16} horizontal style={styles.spacing2} wrap={'wrap'}>
<Statistic
gap={4}
title={t('models.parameterList.type')}
@@ -6,6 +6,7 @@ import { memo } from 'react';
import { useQueryState } from '@/hooks/useQueryParam';
import { ModelNavKey } from '@/types/discover';
import { StyleSheet } from '@/utils/styles';
import Sidebar from '../Sidebar';
import Nav from './Nav';
@@ -13,6 +14,12 @@ import Overview from './Overview';
import Parameter from './Parameter';
import Related from './Related';
const styles = StyleSheet.create({
style: {
overflow: 'hidden',
},
});
const Details = memo<{ mobile?: boolean }>(({ mobile: isMobile }) => {
const { mobile = isMobile } = useResponsive();
const [activeTab, setActiveTab] = useQueryState('activeTab', {
@@ -28,12 +35,7 @@ const Details = memo<{ mobile?: boolean }>(({ mobile: isMobile }) => {
horizontal={!mobile}
style={mobile ? { flexDirection: 'column-reverse' } : undefined}
>
<Flexbox
style={{
overflow: 'hidden',
}}
width={'100%'}
>
<Flexbox style={styles.style} width={'100%'}>
{activeTab === ModelNavKey.Overview && <Overview />}
{activeTab === ModelNavKey.Parameter && <Parameter />}
{activeTab === ModelNavKey.Related && <Related />}
@@ -5,10 +5,19 @@ import { useTranslation } from 'react-i18next';
import { Link } from 'react-router-dom';
import urlJoin from 'url-join';
import { StyleSheet } from '@/utils/styles';
import Title from '../../../../../features/Title';
import { useDetailContext } from '../../DetailProvider';
import Item from './Item';
const styles = StyleSheet.create({
colored: {
color: 'inherit',
overflow: 'hidden',
},
});
const Related = memo(() => {
const { t } = useTranslation('discover');
const { related, category } = useDetailContext();
@@ -30,7 +39,7 @@ const Related = memo(() => {
{related?.map((item, index) => {
const link = urlJoin('/community/model', item.identifier);
return (
<Link key={index} style={{ color: 'inherit', overflow: 'hidden' }} to={link}>
<Link key={index} style={styles.colored} to={link}>
<Item {...item} />
</Link>
);
@@ -4,10 +4,19 @@ import { useTranslation } from 'react-i18next';
import { Link } from 'react-router-dom';
import urlJoin from 'url-join';
import { StyleSheet } from '@/utils/styles';
import Title from '../../../../../features/Title';
import { useDetailContext } from '../../DetailProvider';
import Item from './Item';
const styles = StyleSheet.create({
colored: {
color: 'inherit',
overflow: 'hidden',
},
});
const Related = memo(() => {
const { t } = useTranslation('discover');
const { providers = [] } = useDetailContext();
@@ -21,7 +30,7 @@ const Related = memo(() => {
{providers.slice(0, 6).map((item, index) => {
const link = urlJoin('/community/provider', item.id);
return (
<Link key={index} style={{ color: 'inherit', overflow: 'hidden' }} to={link}>
<Link key={index} style={styles.colored} to={link}>
<Item {...item} />
</Link>
);
@@ -3,11 +3,21 @@ import { memo } from 'react';
import { useQuery } from '@/hooks/useQuery';
import { ModelNavKey } from '@/types/discover';
import { StyleSheet } from '@/utils/styles';
import ActionButton from './ActionButton';
import Related from './Related';
import RelatedProviders from './RelatedProviders';
const styles = StyleSheet.create({
spacing: {
maxHeight: 'calc(100vh - 76px)',
paddingBottom: 24,
position: 'sticky',
top: 16,
},
});
const Sidebar = memo<{ mobile?: boolean }>(({ mobile }) => {
const { activeTab = ModelNavKey.Overview } = useQuery() as { activeTab: ModelNavKey };
@@ -20,19 +30,7 @@ const Sidebar = memo<{ mobile?: boolean }>(({ mobile }) => {
}
return (
<ScrollShadow
flex={'none'}
gap={32}
hideScrollBar
size={4}
style={{
maxHeight: 'calc(100vh - 76px)',
paddingBottom: 24,
position: 'sticky',
top: 16,
}}
width={360}
>
<ScrollShadow flex={'none'} gap={32} hideScrollBar size={4} style={styles.spacing} width={360}>
<ActionButton />
{activeTab !== ModelNavKey.Related && <Related />}
{activeTab !== ModelNavKey.Overview && <RelatedProviders />}
@@ -4,8 +4,16 @@ import { BookOpen } from 'lucide-react';
import { memo } from 'react';
import { useTranslation } from 'react-i18next';
import { StyleSheet } from '@/utils/styles';
import { useDetailContext } from '../../DetailProvider';
const styles = StyleSheet.create({
style: {
maxWidth: 400,
},
});
const Guide = memo(() => {
const { t } = useTranslation('discover');
const { readme = '' } = useDetailContext();
@@ -17,7 +25,7 @@ const Guide = memo(() => {
description={t('providers.details.guide.title')}
descriptionProps={{ fontSize: 14 }}
icon={BookOpen}
style={{ maxWidth: 400 }}
style={styles.style}
/>
</Block>
);
@@ -13,9 +13,26 @@ import InlineTable from '@/components/InlineTable';
import { ModelInfoTags } from '@/components/ModelSelect';
import { formatPriceByCurrency, formatTokenNumber } from '@/utils/format';
import { getTextInputUnitRate, getTextOutputUnitRate } from '@/utils/pricing';
import { StyleSheet } from '@/utils/styles';
import { useDetailContext } from '../../../DetailProvider';
const styles = StyleSheet.create({
colored: {
color: 'inherit',
},
colored1: {
color: cssVar.colorTextSecondary,
fontSize: 12,
},
style: {
overflow: 'hidden',
},
style1: {
fontWeight: 500,
},
});
const ModelList = memo(() => {
const { models = [] } = useDetailContext();
const { t } = useTranslation('discover');
@@ -30,14 +47,12 @@ const ModelList = memo(() => {
key: 'model',
render: (_, record) => {
return (
<Link style={{ color: 'inherit' }} to={urlJoin('/community/model', record.id)}>
<Link style={styles.colored} to={urlJoin('/community/model', record.id)}>
<Flexbox align="center" gap={8} horizontal>
<ModelIcon model={record.id} size={24} type={'avatar'} />
<Flexbox style={{ overflow: 'hidden' }}>
<div style={{ fontWeight: 500 }}>{record.displayName}</div>
<div style={{ color: cssVar.colorTextSecondary, fontSize: 12 }}>
{record.id}
</div>
<Flexbox style={styles.style}>
<div style={styles.style1}>{record.displayName}</div>
<div style={styles.colored1}>{record.id}</div>
</Flexbox>
</Flexbox>
</Link>
@@ -132,7 +147,7 @@ const ModelList = memo(() => {
render: (_, record) => {
return (
<Flexbox align="center" gap={4} horizontal justify={'flex-end'}>
<Link style={{ color: 'inherit' }} to={urlJoin('/community/model', record.id)}>
<Link style={styles.colored} to={urlJoin('/community/model', record.id)}>
<ActionIcon
color={cssVar.colorTextDescription}
icon={ChevronRightIcon}
@@ -6,6 +6,7 @@ import { memo } from 'react';
import { useQueryState } from '@/hooks/useQueryParam';
import { ProviderNavKey } from '@/types/discover';
import { StyleSheet } from '@/utils/styles';
import Sidebar from '../Sidebar';
import Guide from './Guide';
@@ -13,6 +14,12 @@ import Nav from './Nav';
import Overview from './Overview';
import Related from './Related';
const styles = StyleSheet.create({
style: {
overflow: 'hidden',
},
});
const Details = memo<{ mobile?: boolean }>(({ mobile: isMobile }) => {
const { mobile = isMobile } = useResponsive();
const [activeTab, setActiveTab] = useQueryState('activeTab', {
@@ -28,12 +35,7 @@ const Details = memo<{ mobile?: boolean }>(({ mobile: isMobile }) => {
horizontal={!mobile}
style={mobile ? { flexDirection: 'column-reverse' } : undefined}
>
<Flexbox
style={{
overflow: 'hidden',
}}
width={'100%'}
>
<Flexbox style={styles.style} width={'100%'}>
{activeTab === ProviderNavKey.Overview && <Overview />}
{activeTab === ProviderNavKey.Guide && <Guide />}
{activeTab === ProviderNavKey.Related && <Related />}
@@ -8,8 +8,20 @@ import { memo } from 'react';
import { useTranslation } from 'react-i18next';
import urlJoin from 'url-join';
import { StyleSheet } from '@/utils/styles';
import { useDetailContext } from './DetailProvider';
const styles = StyleSheet.create({
colored: {
color: cssVar.colorTextSecondary,
},
style: {
overflow: 'hidden',
position: 'relative',
},
});
const Header = memo<{ mobile?: boolean }>(({ mobile: isMobile }) => {
const { t } = useTranslation('providers');
const { identifier, url, modelsUrl, name } = useDetailContext();
@@ -22,10 +34,7 @@ const Header = memo<{ mobile?: boolean }>(({ mobile: isMobile }) => {
gap={8}
horizontal
justify={'space-between'}
style={{
overflow: 'hidden',
position: 'relative',
}}
style={styles.style}
>
<Flexbox align={'flex-start'} width={'100%'}>
<ProviderCombine provider={identifier} size={mobile ? 32 : 48} />
@@ -65,14 +74,7 @@ const Header = memo<{ mobile?: boolean }>(({ mobile: isMobile }) => {
</Flexbox>
</Flexbox>
<Flexbox
align={'center'}
gap={mobile ? 12 : 24}
horizontal
style={{
color: cssVar.colorTextSecondary,
}}
>
<Flexbox align={'center'} gap={mobile ? 12 : 24} horizontal style={styles.colored}>
{t(`${identifier}.description`)}
</Flexbox>
</Flexbox>
@@ -7,11 +7,18 @@ import { useTranslation } from 'react-i18next';
import urlJoin from 'url-join';
import { OFFICIAL_URL } from '@/const/url';
import { StyleSheet } from '@/utils/styles';
import ShareButton from '../../../../features/ShareButton';
import { useDetailContext } from '../../DetailProvider';
import ProviderConfig from './ProviderConfig';
const styles = StyleSheet.create({
spacing: {
margin: 0,
},
});
const ActionButton = memo(() => {
const { models = [], identifier, name } = useDetailContext();
const { t } = useTranslation('providers');
@@ -28,7 +35,7 @@ const ActionButton = memo(() => {
.slice(0, 4)
.filter(Boolean)
.map((item) => (
<ModelTag key={item.id} model={item.id} style={{ margin: 0 }} />
<ModelTag key={item.id} model={item.id} style={styles.spacing} />
))}
{models.length > 3 && <Tag>+{models.length - 3}</Tag>}
</Flexbox>
@@ -4,10 +4,19 @@ import { useTranslation } from 'react-i18next';
import { Link } from 'react-router-dom';
import urlJoin from 'url-join';
import { StyleSheet } from '@/utils/styles';
import Title from '../../../../../features/Title';
import { useDetailContext } from '../../DetailProvider';
import Item from './Item';
const styles = StyleSheet.create({
colored: {
color: 'inherit',
overflow: 'hidden',
},
});
const Related = memo(() => {
const { t } = useTranslation('discover');
const { related } = useDetailContext();
@@ -21,7 +30,7 @@ const Related = memo(() => {
{related?.map((item, index) => {
const link = urlJoin('/community/provider', item.identifier);
return (
<Link key={index} style={{ color: 'inherit', overflow: 'hidden' }} to={link}>
<Link key={index} style={styles.colored} to={link}>
<Item {...item} />
</Link>
);
@@ -5,10 +5,19 @@ import { useTranslation } from 'react-i18next';
import { Link } from 'react-router-dom';
import urlJoin from 'url-join';
import { StyleSheet } from '@/utils/styles';
import Title from '../../../../../features/Title';
import { useDetailContext } from '../../DetailProvider';
import Item from './Item';
const styles = StyleSheet.create({
colored: {
color: 'inherit',
overflow: 'hidden',
},
});
const Related = memo(() => {
const { t } = useTranslation('discover');
const { models = [], identifier } = useDetailContext();
@@ -30,7 +39,7 @@ const Related = memo(() => {
{models?.slice(0, 6)?.map((item, index) => {
const link = urlJoin('/community/model', item.id);
return (
<Link key={index} style={{ color: 'inherit', overflow: 'hidden' }} to={link}>
<Link key={index} style={styles.colored} to={link}>
<Item {...item} />
</Link>
);
@@ -3,11 +3,21 @@ import { memo } from 'react';
import { useQuery } from '@/hooks/useQuery';
import { ProviderNavKey } from '@/types/discover';
import { StyleSheet } from '@/utils/styles';
import ActionButton from './ActionButton';
import Related from './Related';
import RelatedModels from './RelatedModels';
const styles = StyleSheet.create({
spacing: {
maxHeight: 'calc(100vh - 76px)',
paddingBottom: 24,
position: 'sticky',
top: 16,
},
});
const Sidebar = memo<{ mobile?: boolean }>(({ mobile }) => {
const { activeTab = ProviderNavKey.Overview } = useQuery() as { activeTab: ProviderNavKey };
@@ -20,19 +30,7 @@ const Sidebar = memo<{ mobile?: boolean }>(({ mobile }) => {
}
return (
<ScrollShadow
flex={'none'}
gap={32}
hideScrollBar
size={4}
style={{
maxHeight: 'calc(100vh - 76px)',
paddingBottom: 24,
position: 'sticky',
top: 16,
}}
width={360}
>
<ScrollShadow flex={'none'} gap={32} hideScrollBar size={4} style={styles.spacing} width={360}>
<ActionButton />
{activeTab !== ProviderNavKey.Related && <Related />}
{activeTab !== ProviderNavKey.Overview && <RelatedModels />}
@@ -7,6 +7,14 @@ import { useTranslation } from 'react-i18next';
import { useMarketAuth } from '@/layout/AuthProvider/MarketAuth';
import { socialService } from '@/services/social';
import { useDiscoverStore } from '@/store/discover';
import { StyleSheet } from '@/utils/styles';
const styles = StyleSheet.create({
style: {
fontWeight: 500,
minWidth: 120,
},
});
interface FollowButtonProps {
userId: number;
@@ -56,10 +64,7 @@ const FollowButton = memo<FollowButtonProps>(({ userId }) => {
onClick={handleClick}
shape={'round'}
size={'large'}
style={{
fontWeight: 500,
minWidth: 120,
}}
style={styles.style}
type={isFollowing ? 'default' : 'primary'}
>
{isFollowing ? t('user.unfollow') : t('user.follow')}
@@ -4,8 +4,16 @@ import { Flexbox, Text } from '@lobehub/ui';
import { memo } from 'react';
import { useTranslation } from 'react-i18next';
import { StyleSheet } from '@/utils/styles';
import { useUserDetailContext } from './DetailProvider';
const styles = StyleSheet.create({
style: {
fontWeight: 600,
},
});
const FollowStats = memo(() => {
const { t } = useTranslation('discover');
const { user } = useUserDetailContext();
@@ -18,11 +26,11 @@ const FollowStats = memo(() => {
return (
<Flexbox align={'center'} gap={16} horizontal>
<Flexbox align={'center'} gap={8} horizontal>
<Text style={{ fontWeight: 600 }}>{followingCount}</Text>
<Text style={styles.style}>{followingCount}</Text>
<Text type={'secondary'}>{t('user.following')}</Text>
</Flexbox>
<Flexbox align={'center'} gap={8} horizontal>
<Text style={{ fontWeight: 600 }}>{followersCount}</Text>
<Text style={styles.style}>{followersCount}</Text>
<Text type={'secondary'}>{t('user.followers')}</Text>
</Flexbox>
</Flexbox>
@@ -7,11 +7,26 @@ import { Globe } from 'lucide-react';
import { memo, useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import { StyleSheet } from '@/utils/styles';
import { useUserDetailContext } from '../DetailProvider';
import FollowButton from '../FollowButton';
import FollowStats from '../FollowStats';
import Banner from './Banner';
const styles = StyleSheet.create({
flexContainer: {
boxShadow: `0 0 0 4px ${cssVar.colorBgContainer}`,
flexShrink: 0,
},
spacing: {
margin: 0,
},
style: {
overflow: 'hidden',
},
});
const UserHeader = memo(() => {
const { t } = useTranslation('discover');
const { user, isOwner, onEditProfile } = useUserDetailContext();
@@ -42,20 +57,10 @@ const UserHeader = memo(() => {
<>
<Banner avatar={avatarUrl} bannerUrl={bannerUrl} />
<Flexbox gap={16}>
<Avatar
avatar={avatarUrl}
shape={'square'}
size={64}
style={{ boxShadow: `0 0 0 4px ${cssVar.colorBgContainer}`, flexShrink: 0 }}
/>
<Avatar avatar={avatarUrl} shape={'square'} size={64} style={styles.flexContainer} />
<Flexbox align={'flex-start'} gap={16} horizontal justify={'space-between'}>
<Flexbox
gap={4}
style={{
overflow: 'hidden',
}}
>
<Text as={'h1'} ellipsis fontSize={24} style={{ margin: 0 }} weight={'bold'}>
<Flexbox gap={4} style={styles.style}>
<Text as={'h1'} ellipsis fontSize={24} style={styles.spacing} weight={'bold'}>
{displayName}
</Text>
<Text ellipsis fontSize={12} type={'secondary'}>
@@ -5,20 +5,24 @@ import { cssVar } from 'antd-style';
import { memo } from 'react';
import ListLoading from '@/app/[variants]/(main)/community/components/ListLoading';
import { StyleSheet } from '@/utils/styles';
import Banner from './features/Header/Banner';
const styles = StyleSheet.create({
flexContainer: {
boxShadow: `0 0 0 4px ${cssVar.colorBgContainer}`,
flexShrink: 0,
},
});
const Loading = memo(() => {
return (
<Flexbox gap={24} width={'100%'}>
{/* User Header Skeleton */}
<Banner />
<Flexbox gap={16}>
<Skeleton.Avatar
shape={'square'}
size={64}
style={{ boxShadow: `0 0 0 4px ${cssVar.colorBgContainer}`, flexShrink: 0 }}
/>
<Skeleton.Avatar shape={'square'} size={64} style={styles.flexContainer} />
<Skeleton paragraph={{ rows: 1 }} />
</Flexbox>
@@ -10,10 +10,18 @@ import { withSuspense } from '@/components/withSuspense';
import { useQuery } from '@/hooks/useQuery';
import { useDiscoverStore } from '@/store/discover';
import { AssistantCategory } from '@/types/discover';
import { StyleSheet } from '@/utils/styles';
import CategoryMenu from '../../../../components/CategoryMenu';
import { useCategory } from './useCategory';
const styles = StyleSheet.create({
spacing: {
borderRadius: 12,
paddingInline: 6,
},
});
const Category = memo(() => {
const useAssistantCategories = useDiscoverStore((s) => s.useAssistantCategories);
const {
@@ -51,24 +59,12 @@ const Category = memo(() => {
extra:
item.key === 'all'
? total > 0 && (
<Tag
size={'small'}
style={{
borderRadius: 12,
paddingInline: 6,
}}
>
<Tag size={'small'} style={styles.spacing}>
{total}
</Tag>
)
: itemData && (
<Tag
size={'small'}
style={{
borderRadius: 12,
paddingInline: 6,
}}
>
<Tag size={'small'} style={styles.spacing}>
{itemData.count}
</Tag>
),
@@ -11,12 +11,23 @@ import { useCategory } from '@/hooks/useMCPCategory';
import { useQuery } from '@/hooks/useQuery';
import { useDiscoverStore } from '@/store/discover';
import { McpCategory, McpSorts } from '@/types/discover';
import { StyleSheet } from '@/utils/styles';
import CategoryMenu from '../../../../components/CategoryMenu';
const styles = StyleSheet.create({
spacing: {
borderRadius: 12,
paddingInline: 6,
},
});
const Category = memo(() => {
const useMcpCategories = useDiscoverStore((s) => s.useMcpCategories);
const { category = McpCategory.Discover, q } = useQuery() as { category?: McpCategory; q?: string };
const { category = McpCategory.Discover, q } = useQuery() as {
category?: McpCategory;
q?: string;
};
const { data: items = [] } = useMcpCategories({ q });
const navigate = useNavigate();
const cates = useCategory();
@@ -50,24 +61,12 @@ const Category = memo(() => {
extra:
item.key === 'all'
? total > 0 && (
<Tag
size={'small'}
style={{
borderRadius: 12,
paddingInline: 6,
}}
>
<Tag size={'small'} style={styles.spacing}>
{total}
</Tag>
)
: itemData && (
<Tag
size={'small'}
style={{
borderRadius: 12,
paddingInline: 6,
}}
>
<Tag size={'small'} style={styles.spacing}>
{itemData.count}
</Tag>
),
@@ -4,6 +4,15 @@ import { Blend, Cloud, LaptopMinimalIcon } from 'lucide-react';
import { memo } from 'react';
import { useTranslation } from 'react-i18next';
import { StyleSheet } from '@/utils/styles';
const styles = StyleSheet.create({
colored: {
color: cssVar.colorTextSecondary,
fontSize: 12,
},
});
interface ConnectionTypeTagProps {
type?: 'hybrid' | 'local' | 'remote';
}
@@ -30,15 +39,7 @@ const ConnectionTypeTag = memo<ConnectionTypeTagProps>(({ type }) => {
return (
<Tooltip title={t(`mcp.details.connectionType.${type}.desc`)}>
<Flexbox
align={'center'}
gap={6}
horizontal
style={{
color: cssVar.colorTextSecondary,
fontSize: 12,
}}
>
<Flexbox align={'center'} gap={6} horizontal style={styles.colored}>
<Icon color={icons[type].color} icon={icons[type].icon} size={14} />
{t(`mcp.details.connectionType.${type}.title`)}
</Flexbox>
@@ -9,10 +9,18 @@ import { SCROLL_PARENT_ID } from '@/app/[variants]/(main)/community/features/con
import { withSuspense } from '@/components/withSuspense';
import { useQuery } from '@/hooks/useQuery';
import { useDiscoverStore } from '@/store/discover';
import { StyleSheet } from '@/utils/styles';
import CategoryMenu from '../../../../components/CategoryMenu';
import { useCategory } from './useCategory';
const styles = StyleSheet.create({
spacing: {
borderRadius: 12,
paddingInline: 6,
},
});
const Category = memo(() => {
const useModelCategories = useDiscoverStore((s) => s.useModelCategories);
const { category = 'all', q } = useQuery() as { category?: string; q?: string };
@@ -45,24 +53,12 @@ const Category = memo(() => {
extra:
item.key === 'all'
? total > 0 && (
<Tag
size={'small'}
style={{
borderRadius: 12,
paddingInline: 6,
}}
>
<Tag size={'small'} style={styles.spacing}>
{total}
</Tag>
)
: itemData && (
<Tag
size={'small'}
style={{
borderRadius: 12,
paddingInline: 6,
}}
>
<Tag size={'small'} style={styles.spacing}>
{itemData.count}
</Tag>
),
@@ -1,7 +1,24 @@
import { ScrollShadow } from '@lobehub/ui';
import { type FC, type PropsWithChildren } from 'react';
import { type FC, type PropsWithChildren, useMemo } from 'react';
import { StyleSheet } from '@/utils/styles';
const styles = StyleSheet.create({
spacing: {
paddingBottom: 16,
position: 'sticky',
},
});
const CategoryContainer: FC<PropsWithChildren<{ top?: number }>> = ({ children, top = 16 }) => {
const dynamicStyles = useMemo(
() => ({
...styles.spacing,
top,
}),
[top],
);
return (
<ScrollShadow
as={'aside'}
@@ -10,7 +27,7 @@ const CategoryContainer: FC<PropsWithChildren<{ top?: number }>> = ({ children,
hideScrollBar
offset={16}
size={4}
style={{ paddingBottom: 16, position: 'sticky', top }}
style={dynamicStyles}
width={280}
>
{children}
@@ -3,6 +3,17 @@ import { Bot } from 'lucide-react';
import { memo } from 'react';
import { useTranslation } from 'react-i18next';
import { StyleSheet } from '@/utils/styles';
const styles = StyleSheet.create({
style: {
minHeight: '50vh',
},
style1: {
maxWidth: 400,
},
});
interface AssistantEmptyProps extends Omit<EmptyProps, 'icon'> {
search?: boolean;
}
@@ -11,16 +22,14 @@ const AssistantEmpty = memo<AssistantEmptyProps>(({ search, ...rest }) => {
const { t } = useTranslation('discover');
return (
<Center height="100%" style={{ minHeight: '50vh' }} width="100%">
<Center height="100%" style={styles.style} width="100%">
<Empty
description={search ? t('assistants.empty.search') : t('assistants.empty.description')}
descriptionProps={{
fontSize: 14,
}}
icon={Bot}
style={{
maxWidth: 400,
}}
style={styles.style1}
title={search ? undefined : t('assistants.empty.title')}
type={search ? 'default' : 'page'}
{...rest}
@@ -1,11 +1,20 @@
import { Button, Icon, Tag, Typography } from '@lobehub/ui';
import { Divider } from 'antd';
import { Github, Settings, Share2 } from 'lucide-react';
import Image from '@/libs/next/Image';
import { memo } from 'react';
import { useTranslation } from 'react-i18next';
import { AGENTS_INDEX_GITHUB, imageUrl } from '@/const/url';
import Image from '@/libs/next/Image';
import { StyleSheet } from '@/utils/styles';
const styles = StyleSheet.create({
fullWidth: {
height: 'auto',
marginBottom: 24,
width: '100%',
},
});
const Inner = memo(() => {
const { t } = useTranslation('discover');
@@ -15,7 +24,7 @@ const Inner = memo(() => {
alt={'banner'}
height={602}
src={imageUrl('banner_market_modal.webp')}
style={{ height: 'auto', marginBottom: 24, width: '100%' }}
style={styles.fullWidth}
width={1602}
/>
<h3>
@@ -3,6 +3,17 @@ import { Boxes } from 'lucide-react';
import { memo } from 'react';
import { useTranslation } from 'react-i18next';
import { StyleSheet } from '@/utils/styles';
const styles = StyleSheet.create({
style: {
minHeight: '50vh',
},
style1: {
maxWidth: 400,
},
});
interface McpEmptyProps extends Omit<EmptyProps, 'icon'> {
search?: boolean;
}
@@ -11,16 +22,14 @@ const McpEmpty = memo<McpEmptyProps>(({ search, ...rest }) => {
const { t } = useTranslation('discover');
return (
<Center height="100%" style={{ minHeight: '50vh' }} width="100%">
<Center height="100%" style={styles.style} width="100%">
<Empty
description={search ? t('mcpEmpty.search') : t('mcpEmpty.description')}
descriptionProps={{
fontSize: 14,
}}
icon={Boxes}
style={{
maxWidth: 400,
}}
style={styles.style1}
title={search ? undefined : t('mcpEmpty.title')}
type={search ? 'default' : 'page'}
{...rest}
@@ -3,6 +3,17 @@ import { Cpu } from 'lucide-react';
import { memo } from 'react';
import { useTranslation } from 'react-i18next';
import { StyleSheet } from '@/utils/styles';
const styles = StyleSheet.create({
style: {
minHeight: '50vh',
},
style1: {
maxWidth: 400,
},
});
interface ModelEmptyProps extends Omit<EmptyProps, 'icon'> {
search?: boolean;
}
@@ -11,16 +22,14 @@ const ModelEmpty = memo<ModelEmptyProps>(({ search, ...rest }) => {
const { t } = useTranslation('discover');
return (
<Center height="100%" style={{ minHeight: '50vh' }} width="100%">
<Center height="100%" style={styles.style} width="100%">
<Empty
description={search ? t('models.empty.search') : t('models.empty.description')}
descriptionProps={{
fontSize: 14,
}}
icon={Cpu}
style={{
maxWidth: 400,
}}
style={styles.style1}
title={search ? undefined : t('models.empty.title')}
type={search ? 'default' : 'page'}
{...rest}

Some files were not shown because too many files have changed in this diff Show More