mirror of
https://github.com/infiniflow/ragflow.git
synced 2026-07-11 14:15:40 +08:00
Feat: Rewrite wiki template with reui (#16797)
This commit is contained in:
@@ -11,7 +11,7 @@
|
||||
"format": "prettier --write \"src/**/*.{ts,tsx}\"",
|
||||
"format:check": "prettier --check \"src/**/*.{ts,tsx}\"",
|
||||
"lint": "eslint src --ext .ts,.tsx --report-unused-disable-directives",
|
||||
"prepare": "cd .. && git rev-parse --git-dir >/dev/null 2>&1 && lefthook install || true",
|
||||
"prepare": "node scripts/prepare.js",
|
||||
"preview": "vite preview",
|
||||
"storybook": "storybook dev -p 6006",
|
||||
"test": "jest --no-cache --coverage",
|
||||
|
||||
33
web/scripts/prepare.js
Normal file
33
web/scripts/prepare.js
Normal file
@@ -0,0 +1,33 @@
|
||||
import { execSync } from 'node:child_process';
|
||||
import { dirname, join } from 'node:path';
|
||||
import process from 'node:process';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = dirname(__filename);
|
||||
const repoRoot = dirname(__dirname);
|
||||
const isWindows = process.platform === 'win32';
|
||||
|
||||
// Use the locally installed lefthook binary to avoid relying on a global install or PATH.
|
||||
const lefthookBin = join(
|
||||
repoRoot,
|
||||
'node_modules',
|
||||
'.bin',
|
||||
`lefthook${isWindows ? '.cmd' : ''}`,
|
||||
);
|
||||
|
||||
try {
|
||||
// Verify we are inside a Git repository first.
|
||||
execSync('git rev-parse --git-dir', {
|
||||
cwd: repoRoot,
|
||||
stdio: 'ignore',
|
||||
});
|
||||
|
||||
// Install lefthook hooks from the repository root.
|
||||
execSync(`"${lefthookBin}" install`, {
|
||||
cwd: repoRoot,
|
||||
stdio: 'inherit',
|
||||
});
|
||||
} catch {
|
||||
// Silently ignore failures (not a Git repo or lefthook install failed) so npm install is not interrupted.
|
||||
}
|
||||
488
web/src/components/reui/stepper.tsx
Normal file
488
web/src/components/reui/stepper.tsx
Normal file
@@ -0,0 +1,488 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
|
||||
'use client';
|
||||
|
||||
import {
|
||||
Children,
|
||||
createContext,
|
||||
HTMLAttributes,
|
||||
isValidElement,
|
||||
ReactElement,
|
||||
useCallback,
|
||||
useContext,
|
||||
useEffect,
|
||||
useMemo,
|
||||
useRef,
|
||||
useState,
|
||||
} from 'react';
|
||||
|
||||
import { cn } from '@/lib/utils';
|
||||
|
||||
// Types
|
||||
type StepperOrientation = 'horizontal' | 'vertical';
|
||||
type StepState = 'active' | 'completed' | 'inactive' | 'loading';
|
||||
type StepIndicators = {
|
||||
active?: React.ReactNode;
|
||||
completed?: React.ReactNode;
|
||||
inactive?: React.ReactNode;
|
||||
loading?: React.ReactNode;
|
||||
};
|
||||
|
||||
interface StepperContextValue {
|
||||
activeStep: number;
|
||||
setActiveStep: (step: number) => void;
|
||||
stepsCount: number;
|
||||
orientation: StepperOrientation;
|
||||
registerTrigger: (node: HTMLButtonElement | null) => void;
|
||||
triggerNodes: HTMLButtonElement[];
|
||||
focusNext: (currentIdx: number) => void;
|
||||
focusPrev: (currentIdx: number) => void;
|
||||
focusFirst: () => void;
|
||||
focusLast: () => void;
|
||||
indicators: StepIndicators;
|
||||
}
|
||||
|
||||
interface StepItemContextValue {
|
||||
step: number;
|
||||
state: StepState;
|
||||
isDisabled: boolean;
|
||||
isLoading: boolean;
|
||||
}
|
||||
|
||||
const StepperContext = createContext<StepperContextValue | undefined>(
|
||||
undefined,
|
||||
);
|
||||
const StepItemContext = createContext<StepItemContextValue | undefined>(
|
||||
undefined,
|
||||
);
|
||||
|
||||
function useStepper() {
|
||||
const ctx = useContext(StepperContext);
|
||||
if (!ctx) throw new Error('useStepper must be used within a Stepper');
|
||||
return ctx;
|
||||
}
|
||||
|
||||
function useStepItem() {
|
||||
const ctx = useContext(StepItemContext);
|
||||
if (!ctx) throw new Error('useStepItem must be used within a StepperItem');
|
||||
return ctx;
|
||||
}
|
||||
|
||||
interface StepperProps extends HTMLAttributes<HTMLDivElement> {
|
||||
defaultValue?: number;
|
||||
value?: number;
|
||||
onValueChange?: (value: number) => void;
|
||||
orientation?: StepperOrientation;
|
||||
indicators?: StepIndicators;
|
||||
}
|
||||
|
||||
function Stepper({
|
||||
defaultValue = 1,
|
||||
value,
|
||||
onValueChange,
|
||||
orientation = 'horizontal',
|
||||
className,
|
||||
children,
|
||||
indicators = {},
|
||||
...props
|
||||
}: StepperProps) {
|
||||
const [activeStep, setActiveStep] = useState(defaultValue);
|
||||
const [triggerNodes, setTriggerNodes] = useState<HTMLButtonElement[]>([]);
|
||||
|
||||
// Register/unregister triggers
|
||||
const registerTrigger = useCallback((node: HTMLButtonElement | null) => {
|
||||
setTriggerNodes((prev) => {
|
||||
if (node && !prev.includes(node)) {
|
||||
return [...prev, node];
|
||||
} else if (!node && prev.includes(node!)) {
|
||||
return prev.filter((n) => n !== node);
|
||||
} else {
|
||||
return prev;
|
||||
}
|
||||
});
|
||||
}, []);
|
||||
|
||||
const handleSetActiveStep = useCallback(
|
||||
(step: number) => {
|
||||
if (value === undefined) {
|
||||
setActiveStep(step);
|
||||
}
|
||||
onValueChange?.(step);
|
||||
},
|
||||
[value, onValueChange],
|
||||
);
|
||||
|
||||
const currentStep = value ?? activeStep;
|
||||
|
||||
// Keyboard navigation logic
|
||||
const focusTrigger = (idx: number) => {
|
||||
if (triggerNodes[idx]) triggerNodes[idx].focus();
|
||||
};
|
||||
const focusNext = (currentIdx: number) =>
|
||||
focusTrigger((currentIdx + 1) % triggerNodes.length);
|
||||
const focusPrev = (currentIdx: number) =>
|
||||
focusTrigger((currentIdx - 1 + triggerNodes.length) % triggerNodes.length);
|
||||
const focusFirst = () => focusTrigger(0);
|
||||
const focusLast = () => focusTrigger(triggerNodes.length - 1);
|
||||
|
||||
// Context value
|
||||
const contextValue = useMemo<StepperContextValue>(
|
||||
() => ({
|
||||
activeStep: currentStep,
|
||||
setActiveStep: handleSetActiveStep,
|
||||
stepsCount: Children.toArray(children).filter(
|
||||
(child): child is ReactElement =>
|
||||
isValidElement(child) &&
|
||||
(child.type as { displayName?: string }).displayName ===
|
||||
'StepperItem',
|
||||
).length,
|
||||
orientation,
|
||||
registerTrigger,
|
||||
focusNext,
|
||||
focusPrev,
|
||||
focusFirst,
|
||||
focusLast,
|
||||
triggerNodes,
|
||||
indicators,
|
||||
}),
|
||||
[
|
||||
currentStep,
|
||||
handleSetActiveStep,
|
||||
children,
|
||||
orientation,
|
||||
registerTrigger,
|
||||
triggerNodes,
|
||||
],
|
||||
);
|
||||
|
||||
return (
|
||||
<StepperContext.Provider value={contextValue}>
|
||||
<div
|
||||
role="tablist"
|
||||
aria-orientation={orientation}
|
||||
data-slot="stepper"
|
||||
className={cn('w-full', className)}
|
||||
data-orientation={orientation}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
</StepperContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
interface StepperItemProps extends React.HTMLAttributes<HTMLDivElement> {
|
||||
step: number;
|
||||
completed?: boolean;
|
||||
disabled?: boolean;
|
||||
loading?: boolean;
|
||||
}
|
||||
|
||||
function StepperItem({
|
||||
step,
|
||||
completed = false,
|
||||
disabled = false,
|
||||
loading = false,
|
||||
className,
|
||||
children,
|
||||
...props
|
||||
}: StepperItemProps) {
|
||||
const { activeStep } = useStepper();
|
||||
|
||||
const state: StepState =
|
||||
completed || step < activeStep
|
||||
? 'completed'
|
||||
: activeStep === step
|
||||
? 'active'
|
||||
: 'inactive';
|
||||
|
||||
const isLoading = loading && step === activeStep;
|
||||
|
||||
return (
|
||||
<StepItemContext.Provider
|
||||
value={{ step, state, isDisabled: disabled, isLoading }}
|
||||
>
|
||||
<div
|
||||
data-slot="stepper-item"
|
||||
className={cn(
|
||||
'group/step flex items-center justify-center not-last:flex-1 group-data-[orientation=horizontal]/stepper-nav:flex-row group-data-[orientation=vertical]/stepper-nav:flex-col',
|
||||
className,
|
||||
)}
|
||||
data-state={state}
|
||||
{...(isLoading ? { 'data-loading': true } : {})}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
</StepItemContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
interface StepperTriggerProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
|
||||
asChild?: boolean;
|
||||
}
|
||||
|
||||
function StepperTrigger({
|
||||
asChild = false,
|
||||
className,
|
||||
children,
|
||||
tabIndex,
|
||||
...props
|
||||
}: StepperTriggerProps) {
|
||||
const { state, isLoading } = useStepItem();
|
||||
const stepperCtx = useStepper();
|
||||
const {
|
||||
setActiveStep,
|
||||
activeStep,
|
||||
registerTrigger,
|
||||
triggerNodes,
|
||||
focusNext,
|
||||
focusPrev,
|
||||
focusFirst,
|
||||
focusLast,
|
||||
} = stepperCtx;
|
||||
const { step, isDisabled } = useStepItem();
|
||||
const isSelected = activeStep === step;
|
||||
const id = `stepper-tab-${step}`;
|
||||
const panelId = `stepper-panel-${step}`;
|
||||
|
||||
// Register this trigger for keyboard navigation
|
||||
const btnRef = useRef<HTMLButtonElement>(null);
|
||||
useEffect(() => {
|
||||
if (btnRef.current) {
|
||||
registerTrigger(btnRef.current);
|
||||
}
|
||||
}, [btnRef.current]);
|
||||
|
||||
// Find our index among triggers for navigation
|
||||
const myIdx = useMemo(
|
||||
() =>
|
||||
triggerNodes.findIndex((n: HTMLButtonElement) => n === btnRef.current),
|
||||
[triggerNodes, btnRef.current],
|
||||
);
|
||||
|
||||
const handleKeyDown = (e: React.KeyboardEvent<HTMLButtonElement>) => {
|
||||
switch (e.key) {
|
||||
case 'ArrowRight':
|
||||
case 'ArrowDown':
|
||||
e.preventDefault();
|
||||
if (myIdx !== -1 && focusNext) focusNext(myIdx);
|
||||
break;
|
||||
case 'ArrowLeft':
|
||||
case 'ArrowUp':
|
||||
e.preventDefault();
|
||||
if (myIdx !== -1 && focusPrev) focusPrev(myIdx);
|
||||
break;
|
||||
case 'Home':
|
||||
e.preventDefault();
|
||||
if (focusFirst) focusFirst();
|
||||
break;
|
||||
case 'End':
|
||||
e.preventDefault();
|
||||
if (focusLast) focusLast();
|
||||
break;
|
||||
case 'Enter':
|
||||
case ' ':
|
||||
e.preventDefault();
|
||||
setActiveStep(step);
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
if (asChild) {
|
||||
return (
|
||||
<span
|
||||
data-slot="stepper-trigger"
|
||||
data-state={state}
|
||||
className={className}
|
||||
>
|
||||
{children}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<button
|
||||
ref={btnRef}
|
||||
role="tab"
|
||||
id={id}
|
||||
aria-selected={isSelected}
|
||||
aria-controls={panelId}
|
||||
tabIndex={typeof tabIndex === 'number' ? tabIndex : isSelected ? 0 : -1}
|
||||
data-slot="stepper-trigger"
|
||||
data-state={state}
|
||||
data-loading={isLoading}
|
||||
className={cn(
|
||||
'focus-visible:border-ring focus-visible:ring-ring/50 inline-flex cursor-pointer items-center outline-none focus-visible:z-10 focus-visible:ring-3 disabled:pointer-events-none disabled:opacity-60',
|
||||
'gap-2.5 rounded-full',
|
||||
className,
|
||||
)}
|
||||
onClick={() => setActiveStep(step)}
|
||||
onKeyDown={handleKeyDown}
|
||||
disabled={isDisabled}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
function StepperIndicator({
|
||||
children,
|
||||
className,
|
||||
}: React.ComponentProps<'div'>) {
|
||||
const { state, isLoading } = useStepItem();
|
||||
const { indicators } = useStepper();
|
||||
|
||||
return (
|
||||
<div
|
||||
data-slot="stepper-indicator"
|
||||
data-state={state}
|
||||
className={cn(
|
||||
'border-background bg-accent text-accent-foreground data-[state=completed]:bg-primary data-[state=completed]:text-primary-foreground data-[state=active]:bg-primary data-[state=active]:text-primary-foreground relative flex size-6 shrink-0 items-center justify-center overflow-hidden',
|
||||
'rounded-full text-xs',
|
||||
className,
|
||||
)}
|
||||
>
|
||||
<div className="absolute">
|
||||
{indicators &&
|
||||
((isLoading && indicators.loading) ||
|
||||
(state === 'completed' && indicators.completed) ||
|
||||
(state === 'active' && indicators.active) ||
|
||||
(state === 'inactive' && indicators.inactive))
|
||||
? (isLoading && indicators.loading) ||
|
||||
(state === 'completed' && indicators.completed) ||
|
||||
(state === 'active' && indicators.active) ||
|
||||
(state === 'inactive' && indicators.inactive)
|
||||
: children}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function StepperSeparator({ className }: React.ComponentProps<'div'>) {
|
||||
const { state } = useStepItem();
|
||||
|
||||
return (
|
||||
<div
|
||||
data-slot="stepper-separator"
|
||||
data-state={state}
|
||||
className={cn(
|
||||
'bg-muted rounded-sm group-data-[orientation=horizontal]/stepper-nav:h-0.5 group-data-[orientation=vertical]/stepper-nav:h-12 group-data-[orientation=vertical]/stepper-nav:w-0.5 m-0.5 group-data-[orientation=horizontal]/stepper-nav:flex-1',
|
||||
className,
|
||||
)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function StepperTitle({ children, className }: React.ComponentProps<'h3'>) {
|
||||
const { state } = useStepItem();
|
||||
|
||||
return (
|
||||
<h3
|
||||
data-slot="stepper-title"
|
||||
data-state={state}
|
||||
className={cn('text-sm leading-none font-medium', className)}
|
||||
>
|
||||
{children}
|
||||
</h3>
|
||||
);
|
||||
}
|
||||
|
||||
function StepperDescription({
|
||||
children,
|
||||
className,
|
||||
}: React.ComponentProps<'div'>) {
|
||||
const { state } = useStepItem();
|
||||
|
||||
return (
|
||||
<div
|
||||
data-slot="stepper-description"
|
||||
data-state={state}
|
||||
className={cn('text-muted-foreground text-sm', className)}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function StepperNav({ children, className }: React.ComponentProps<'nav'>) {
|
||||
const { activeStep, orientation } = useStepper();
|
||||
|
||||
return (
|
||||
<nav
|
||||
data-slot="stepper-nav"
|
||||
data-state={activeStep}
|
||||
data-orientation={orientation}
|
||||
className={cn(
|
||||
'group/stepper-nav inline-flex data-[orientation=horizontal]:w-full data-[orientation=horizontal]:flex-row data-[orientation=vertical]:flex-col',
|
||||
className,
|
||||
)}
|
||||
>
|
||||
{children}
|
||||
</nav>
|
||||
);
|
||||
}
|
||||
|
||||
function StepperPanel({ children, className }: React.ComponentProps<'div'>) {
|
||||
const { activeStep } = useStepper();
|
||||
|
||||
return (
|
||||
<div
|
||||
data-slot="stepper-panel"
|
||||
data-state={activeStep}
|
||||
className={cn('w-full', className)}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
interface StepperContentProps extends React.ComponentProps<'div'> {
|
||||
value: number;
|
||||
forceMount?: boolean;
|
||||
}
|
||||
|
||||
function StepperContent({
|
||||
value,
|
||||
forceMount,
|
||||
children,
|
||||
className,
|
||||
}: StepperContentProps) {
|
||||
const { activeStep } = useStepper();
|
||||
const isActive = value === activeStep;
|
||||
|
||||
if (!forceMount && !isActive) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
data-slot="stepper-content"
|
||||
data-state={activeStep}
|
||||
className={cn('w-full', className, !isActive && forceMount && 'hidden')}
|
||||
hidden={!isActive && forceMount}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export {
|
||||
Stepper,
|
||||
StepperContent,
|
||||
StepperDescription,
|
||||
StepperIndicator,
|
||||
StepperItem,
|
||||
StepperNav,
|
||||
StepperPanel,
|
||||
StepperSeparator,
|
||||
StepperTitle,
|
||||
StepperTrigger,
|
||||
useStepItem,
|
||||
useStepper,
|
||||
type StepperContentProps,
|
||||
type StepperItemProps,
|
||||
type StepperProps,
|
||||
type StepperTriggerProps,
|
||||
};
|
||||
@@ -526,7 +526,6 @@ Example: A 1 KB message with 1024-dim embedding uses ~9 KB. The 5 MB default lim
|
||||
directoryNamePlaceholder: 'Instance name',
|
||||
directoryRule: 'Rule',
|
||||
directoryRulePlaceholder: 'Input',
|
||||
entity: 'Entity',
|
||||
selectArtifact: 'Select an item from the contents to view details',
|
||||
sourceDocuments: 'Source documents',
|
||||
compilationTitleSuffix: "' dataset",
|
||||
|
||||
@@ -0,0 +1,109 @@
|
||||
import {
|
||||
Stepper,
|
||||
StepperDescription,
|
||||
StepperIndicator,
|
||||
StepperItem,
|
||||
StepperNav,
|
||||
StepperSeparator,
|
||||
StepperTitle,
|
||||
StepperTrigger,
|
||||
} from '@/components/reui/stepper';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { Cog, FileText, Palette } from 'lucide-react';
|
||||
import { useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
interface TemplateStepperProps {
|
||||
activeStep: number;
|
||||
isArtifacts: boolean;
|
||||
onStepChange: (step: number) => void;
|
||||
}
|
||||
|
||||
export function TemplateStepper({
|
||||
activeStep,
|
||||
isArtifacts,
|
||||
onStepChange,
|
||||
}: TemplateStepperProps) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const steps = useMemo(
|
||||
() => [
|
||||
{
|
||||
id: 'basic-info',
|
||||
title: t('setting.basicInfo'),
|
||||
description: t('setting.basicInfoDescription'),
|
||||
icon: FileText,
|
||||
},
|
||||
{
|
||||
id: 'configuration',
|
||||
title: t('setting.templateWizardConfiguration'),
|
||||
description: t('setting.templateWizardConfigurationDescription'),
|
||||
icon: Cog,
|
||||
},
|
||||
...(isArtifacts
|
||||
? [
|
||||
{
|
||||
id: 'blueprints',
|
||||
title: t('setting.blueprints'),
|
||||
description: t('setting.blueprintsDescription'),
|
||||
icon: Palette,
|
||||
},
|
||||
]
|
||||
: []),
|
||||
],
|
||||
[isArtifacts, t],
|
||||
);
|
||||
|
||||
return (
|
||||
<Stepper
|
||||
value={activeStep}
|
||||
onValueChange={onStepChange}
|
||||
className="flex justify-center"
|
||||
>
|
||||
<StepperNav className="justify-center max-w-4xl">
|
||||
{steps.map((step, index) => {
|
||||
const stepNumber = index + 1;
|
||||
const isActive = stepNumber === activeStep;
|
||||
const Icon = step.icon;
|
||||
const isLast = index === steps.length - 1;
|
||||
|
||||
return (
|
||||
<StepperItem
|
||||
key={step.id}
|
||||
step={stepNumber}
|
||||
className="items-start flex-1 relative"
|
||||
disabled={stepNumber > activeStep}
|
||||
>
|
||||
<StepperTrigger type="button" className="flex flex-col gap-1">
|
||||
<StepperIndicator
|
||||
className={cn(
|
||||
'size-8 border !bg-bg-card !text-text-primary !border-border-button',
|
||||
isActive && '!border-accent-primary !text-accent-primary',
|
||||
)}
|
||||
>
|
||||
<Icon className="size-4" />
|
||||
</StepperIndicator>
|
||||
<StepperTitle
|
||||
className={cn(
|
||||
'mt-2 text-center text-sm font-medium leading-none',
|
||||
isActive && 'text-accent-primary',
|
||||
)}
|
||||
>
|
||||
{step.title}
|
||||
</StepperTitle>
|
||||
<StepperDescription
|
||||
className={'text-center text-text-secondary'}
|
||||
>
|
||||
{step.description}
|
||||
</StepperDescription>
|
||||
</StepperTrigger>
|
||||
{!isLast && (
|
||||
<StepperSeparator className="group-data-[state=completed]/step:bg-primary absolute inset-x-0 top-3.5 left-[calc(50%+1.4rem)] m-0 group-data-[orientation=horizontal]/stepper-nav:w-[calc(100%-3rem+0.225rem)] group-data-[orientation=horizontal]/stepper-nav:flex-none" />
|
||||
)}
|
||||
</StepperItem>
|
||||
);
|
||||
})}
|
||||
</StepperNav>
|
||||
</Stepper>
|
||||
);
|
||||
}
|
||||
@@ -1,5 +1,4 @@
|
||||
import BackButton from '@/components/back-button';
|
||||
import { CustomTimeline } from '@/components/originui/timeline';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Form } from '@/components/ui/form';
|
||||
import {
|
||||
@@ -9,7 +8,7 @@ import {
|
||||
} from '@/components/ui/resizable';
|
||||
import { CompilationTemplateKind } from '@/constants/compilation';
|
||||
import { Routes } from '@/routes';
|
||||
import { useCallback, useMemo, useState } from 'react';
|
||||
import { useCallback, useState } from 'react';
|
||||
import { useFieldArray, useWatch } from 'react-hook-form';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
@@ -17,6 +16,7 @@ import { BasicInfoStep } from './components/basic-info-step';
|
||||
import { BlueprintsStep } from './components/blueprints-step';
|
||||
import { TemplateConfiguration } from './components/template-configuration';
|
||||
import { TemplateSidebar } from './components/template-sidebar';
|
||||
import { TemplateStepper } from './components/template-stepper';
|
||||
import { useCreateNextCompilationTemplateGroup } from './hooks/use-create-next-compilation-template-group';
|
||||
|
||||
export default function CreateNextCompilationTemplate() {
|
||||
@@ -39,29 +39,14 @@ export default function CreateNextCompilationTemplate() {
|
||||
|
||||
const isArtifacts = selectedKind === CompilationTemplateKind.Artifacts;
|
||||
|
||||
const timelineNodes = useMemo(
|
||||
() => [
|
||||
{
|
||||
id: 'basic-info',
|
||||
title: t('setting.basicInfo'),
|
||||
content: t('setting.basicInfoDescription'),
|
||||
},
|
||||
{
|
||||
id: 'configuration',
|
||||
title: t('setting.templateWizardConfiguration'),
|
||||
content: t('setting.templateWizardConfigurationDescription'),
|
||||
},
|
||||
...(isArtifacts
|
||||
? [
|
||||
{
|
||||
id: 'blueprints',
|
||||
title: t('setting.blueprints'),
|
||||
content: t('setting.blueprintsDescription'),
|
||||
},
|
||||
]
|
||||
: []),
|
||||
],
|
||||
[isArtifacts, t],
|
||||
const handleStepChange = useCallback(
|
||||
(step: number) => {
|
||||
// Allow clicking back to completed steps only.
|
||||
if (step < activeStep) {
|
||||
setActiveStep(step);
|
||||
}
|
||||
},
|
||||
[activeStep],
|
||||
);
|
||||
|
||||
const handleNext = useCallback(async () => {
|
||||
@@ -85,7 +70,7 @@ export default function CreateNextCompilationTemplate() {
|
||||
|
||||
return (
|
||||
<section className="h-full flex flex-col bg-bg-base">
|
||||
<header className="shrink-0 px-5 py-4 border-b border-border-button flex gap-3">
|
||||
<header className="shrink-0 px-5 py-4 border-b border-border-button flex gap-3 items-center">
|
||||
<BackButton
|
||||
to={`${Routes.UserSetting}${Routes.CompilationTemplates}`}
|
||||
/>
|
||||
@@ -97,16 +82,10 @@ export default function CreateNextCompilationTemplate() {
|
||||
</header>
|
||||
|
||||
<div className="shrink-0 px-5 py-4 border-b border-border-button">
|
||||
<CustomTimeline
|
||||
nodes={timelineNodes}
|
||||
<TemplateStepper
|
||||
activeStep={activeStep}
|
||||
onStepChange={(step) => {
|
||||
// Allow clicking back to completed steps only.
|
||||
if (step < activeStep) {
|
||||
setActiveStep(step);
|
||||
}
|
||||
}}
|
||||
orientation="horizontal"
|
||||
isArtifacts={isArtifacts}
|
||||
onStepChange={handleStepChange}
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -37,6 +37,7 @@ export const useCompilationTemplateGroupForm = ({
|
||||
const form = useForm<FormSchemaType>({
|
||||
resolver: zodResolver(buildFormSchema(t)),
|
||||
defaultValues,
|
||||
mode: 'onChange',
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
@@ -64,12 +64,12 @@ export default defineConfig(({ mode }) => {
|
||||
ws: true,
|
||||
},
|
||||
'/api': {
|
||||
target: 'http://127.0.0.1:9386/',
|
||||
target: 'http://127.0.0.1:9380/',
|
||||
changeOrigin: true,
|
||||
ws: true,
|
||||
},
|
||||
'/v1': {
|
||||
target: 'http://127.0.0.1:9386/',
|
||||
target: 'http://127.0.0.1:9380/',
|
||||
changeOrigin: true,
|
||||
ws: true,
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user