Skip to content

Commit 4824728

Browse files
Merge branch 'main' into main
2 parents e569b20 + ea0e1b5 commit 4824728

File tree

7,253 files changed

+671143
-269834
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

7,253 files changed

+671143
-269834
lines changed

.agents/skills/component-refactoring/SKILL.md

Lines changed: 483 additions & 0 deletions
Large diffs are not rendered by default.

.agents/skills/component-refactoring/references/complexity-patterns.md

Lines changed: 493 additions & 0 deletions
Large diffs are not rendered by default.

.agents/skills/component-refactoring/references/component-splitting.md

Lines changed: 477 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 317 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,317 @@
1+
# Hook Extraction Patterns
2+
3+
This document provides detailed guidance on extracting custom hooks from complex components in Dify.
4+
5+
## When to Extract Hooks
6+
7+
Extract a custom hook when you identify:
8+
9+
1. **Coupled state groups** - Multiple `useState` hooks that are always used together
10+
1. **Complex effects** - `useEffect` with multiple dependencies or cleanup logic
11+
1. **Business logic** - Data transformations, validations, or calculations
12+
1. **Reusable patterns** - Logic that appears in multiple components
13+
14+
## Extraction Process
15+
16+
### Step 1: Identify State Groups
17+
18+
Look for state variables that are logically related:
19+
20+
```typescript
21+
// ❌ These belong together - extract to hook
22+
const [modelConfig, setModelConfig] = useState<ModelConfig>(...)
23+
const [completionParams, setCompletionParams] = useState<FormValue>({})
24+
const [modelModeType, setModelModeType] = useState<ModelModeType>(...)
25+
26+
// These are model-related state that should be in useModelConfig()
27+
```
28+
29+
### Step 2: Identify Related Effects
30+
31+
Find effects that modify the grouped state:
32+
33+
```typescript
34+
// ❌ These effects belong with the state above
35+
useEffect(() => {
36+
if (hasFetchedDetail && !modelModeType) {
37+
const mode = currModel?.model_properties.mode
38+
if (mode) {
39+
const newModelConfig = produce(modelConfig, (draft) => {
40+
draft.mode = mode
41+
})
42+
setModelConfig(newModelConfig)
43+
}
44+
}
45+
}, [textGenerationModelList, hasFetchedDetail, modelModeType, currModel])
46+
```
47+
48+
### Step 3: Create the Hook
49+
50+
```typescript
51+
// hooks/use-model-config.ts
52+
import type { FormValue } from '@/app/components/header/account-setting/model-provider-page/declarations'
53+
import type { ModelConfig } from '@/models/debug'
54+
import { produce } from 'immer'
55+
import { useEffect, useState } from 'react'
56+
import { ModelModeType } from '@/types/app'
57+
58+
interface UseModelConfigParams {
59+
initialConfig?: Partial<ModelConfig>
60+
currModel?: { model_properties?: { mode?: ModelModeType } }
61+
hasFetchedDetail: boolean
62+
}
63+
64+
interface UseModelConfigReturn {
65+
modelConfig: ModelConfig
66+
setModelConfig: (config: ModelConfig) => void
67+
completionParams: FormValue
68+
setCompletionParams: (params: FormValue) => void
69+
modelModeType: ModelModeType
70+
}
71+
72+
export const useModelConfig = ({
73+
initialConfig,
74+
currModel,
75+
hasFetchedDetail,
76+
}: UseModelConfigParams): UseModelConfigReturn => {
77+
const [modelConfig, setModelConfig] = useState<ModelConfig>({
78+
provider: 'langgenius/openai/openai',
79+
model_id: 'gpt-3.5-turbo',
80+
mode: ModelModeType.unset,
81+
// ... default values
82+
...initialConfig,
83+
})
84+
85+
const [completionParams, setCompletionParams] = useState<FormValue>({})
86+
87+
const modelModeType = modelConfig.mode
88+
89+
// Fill old app data missing model mode
90+
useEffect(() => {
91+
if (hasFetchedDetail && !modelModeType) {
92+
const mode = currModel?.model_properties?.mode
93+
if (mode) {
94+
setModelConfig(produce(modelConfig, (draft) => {
95+
draft.mode = mode
96+
}))
97+
}
98+
}
99+
}, [hasFetchedDetail, modelModeType, currModel])
100+
101+
return {
102+
modelConfig,
103+
setModelConfig,
104+
completionParams,
105+
setCompletionParams,
106+
modelModeType,
107+
}
108+
}
109+
```
110+
111+
### Step 4: Update Component
112+
113+
```typescript
114+
// Before: 50+ lines of state management
115+
const Configuration: FC = () => {
116+
const [modelConfig, setModelConfig] = useState<ModelConfig>(...)
117+
// ... lots of related state and effects
118+
}
119+
120+
// After: Clean component
121+
const Configuration: FC = () => {
122+
const {
123+
modelConfig,
124+
setModelConfig,
125+
completionParams,
126+
setCompletionParams,
127+
modelModeType,
128+
} = useModelConfig({
129+
currModel,
130+
hasFetchedDetail,
131+
})
132+
133+
// Component now focuses on UI
134+
}
135+
```
136+
137+
## Naming Conventions
138+
139+
### Hook Names
140+
141+
- Use `use` prefix: `useModelConfig`, `useDatasetConfig`
142+
- Be specific: `useAdvancedPromptConfig` not `usePrompt`
143+
- Include domain: `useWorkflowVariables`, `useMCPServer`
144+
145+
### File Names
146+
147+
- Kebab-case: `use-model-config.ts`
148+
- Place in `hooks/` subdirectory when multiple hooks exist
149+
- Place alongside component for single-use hooks
150+
151+
### Return Type Names
152+
153+
- Suffix with `Return`: `UseModelConfigReturn`
154+
- Suffix params with `Params`: `UseModelConfigParams`
155+
156+
## Common Hook Patterns in Dify
157+
158+
### 1. Data Fetching Hook (React Query)
159+
160+
```typescript
161+
// Pattern: Use @tanstack/react-query for data fetching
162+
import { useQuery, useQueryClient } from '@tanstack/react-query'
163+
import { get } from '@/service/base'
164+
import { useInvalid } from '@/service/use-base'
165+
166+
const NAME_SPACE = 'appConfig'
167+
168+
// Query keys for cache management
169+
export const appConfigQueryKeys = {
170+
detail: (appId: string) => [NAME_SPACE, 'detail', appId] as const,
171+
}
172+
173+
// Main data hook
174+
export const useAppConfig = (appId: string) => {
175+
return useQuery({
176+
enabled: !!appId,
177+
queryKey: appConfigQueryKeys.detail(appId),
178+
queryFn: () => get<AppDetailResponse>(`/apps/${appId}`),
179+
select: data => data?.model_config || null,
180+
})
181+
}
182+
183+
// Invalidation hook for refreshing data
184+
export const useInvalidAppConfig = () => {
185+
return useInvalid([NAME_SPACE])
186+
}
187+
188+
// Usage in component
189+
const Component = () => {
190+
const { data: config, isLoading, error, refetch } = useAppConfig(appId)
191+
const invalidAppConfig = useInvalidAppConfig()
192+
193+
const handleRefresh = () => {
194+
invalidAppConfig() // Invalidates cache and triggers refetch
195+
}
196+
197+
return <div>...</div>
198+
}
199+
```
200+
201+
### 2. Form State Hook
202+
203+
```typescript
204+
// Pattern: Form state + validation + submission
205+
export const useConfigForm = (initialValues: ConfigFormValues) => {
206+
const [values, setValues] = useState(initialValues)
207+
const [errors, setErrors] = useState<Record<string, string>>({})
208+
const [isSubmitting, setIsSubmitting] = useState(false)
209+
210+
const validate = useCallback(() => {
211+
const newErrors: Record<string, string> = {}
212+
if (!values.name) newErrors.name = 'Name is required'
213+
setErrors(newErrors)
214+
return Object.keys(newErrors).length === 0
215+
}, [values])
216+
217+
const handleChange = useCallback((field: string, value: any) => {
218+
setValues(prev => ({ ...prev, [field]: value }))
219+
}, [])
220+
221+
const handleSubmit = useCallback(async (onSubmit: (values: ConfigFormValues) => Promise<void>) => {
222+
if (!validate()) return
223+
setIsSubmitting(true)
224+
try {
225+
await onSubmit(values)
226+
} finally {
227+
setIsSubmitting(false)
228+
}
229+
}, [values, validate])
230+
231+
return { values, errors, isSubmitting, handleChange, handleSubmit }
232+
}
233+
```
234+
235+
### 3. Modal State Hook
236+
237+
```typescript
238+
// Pattern: Multiple modal management
239+
type ModalType = 'edit' | 'delete' | 'duplicate' | null
240+
241+
export const useModalState = () => {
242+
const [activeModal, setActiveModal] = useState<ModalType>(null)
243+
const [modalData, setModalData] = useState<any>(null)
244+
245+
const openModal = useCallback((type: ModalType, data?: any) => {
246+
setActiveModal(type)
247+
setModalData(data)
248+
}, [])
249+
250+
const closeModal = useCallback(() => {
251+
setActiveModal(null)
252+
setModalData(null)
253+
}, [])
254+
255+
return {
256+
activeModal,
257+
modalData,
258+
openModal,
259+
closeModal,
260+
isOpen: useCallback((type: ModalType) => activeModal === type, [activeModal]),
261+
}
262+
}
263+
```
264+
265+
### 4. Toggle/Boolean Hook
266+
267+
```typescript
268+
// Pattern: Boolean state with convenience methods
269+
export const useToggle = (initialValue = false) => {
270+
const [value, setValue] = useState(initialValue)
271+
272+
const toggle = useCallback(() => setValue(v => !v), [])
273+
const setTrue = useCallback(() => setValue(true), [])
274+
const setFalse = useCallback(() => setValue(false), [])
275+
276+
return [value, { toggle, setTrue, setFalse, set: setValue }] as const
277+
}
278+
279+
// Usage
280+
const [isExpanded, { toggle, setTrue: expand, setFalse: collapse }] = useToggle()
281+
```
282+
283+
## Testing Extracted Hooks
284+
285+
After extraction, test hooks in isolation:
286+
287+
```typescript
288+
// use-model-config.spec.ts
289+
import { renderHook, act } from '@testing-library/react'
290+
import { useModelConfig } from './use-model-config'
291+
292+
describe('useModelConfig', () => {
293+
it('should initialize with default values', () => {
294+
const { result } = renderHook(() => useModelConfig({
295+
hasFetchedDetail: false,
296+
}))
297+
298+
expect(result.current.modelConfig.provider).toBe('langgenius/openai/openai')
299+
expect(result.current.modelModeType).toBe(ModelModeType.unset)
300+
})
301+
302+
it('should update model config', () => {
303+
const { result } = renderHook(() => useModelConfig({
304+
hasFetchedDetail: true,
305+
}))
306+
307+
act(() => {
308+
result.current.setModelConfig({
309+
...result.current.modelConfig,
310+
model_id: 'gpt-4',
311+
})
312+
})
313+
314+
expect(result.current.modelConfig.model_id).toBe('gpt-4')
315+
})
316+
})
317+
```
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
---
2+
name: frontend-code-review
3+
description: "Trigger when the user requests a review of frontend files (e.g., `.tsx`, `.ts`, `.js`). Support both pending-change reviews and focused file reviews while applying the checklist rules."
4+
---
5+
6+
# Frontend Code Review
7+
8+
## Intent
9+
Use this skill whenever the user asks to review frontend code (especially `.tsx`, `.ts`, or `.js` files). Support two review modes:
10+
11+
1. **Pending-change review** – inspect staged/working-tree files slated for commit and flag checklist violations before submission.
12+
2. **File-targeted review** – review the specific file(s) the user names and report the relevant checklist findings.
13+
14+
Stick to the checklist below for every applicable file and mode.
15+
16+
## Checklist
17+
See [references/code-quality.md](references/code-quality.md), [references/performance.md](references/performance.md), [references/business-logic.md](references/business-logic.md) for the living checklist split by category—treat it as the canonical set of rules to follow.
18+
19+
Flag each rule violation with urgency metadata so future reviewers can prioritize fixes.
20+
21+
## Review Process
22+
1. Open the relevant component/module. Gather lines that relate to class names, React Flow hooks, prop memoization, and styling.
23+
2. For each rule in the review point, note where the code deviates and capture a representative snippet.
24+
3. Compose the review section per the template below. Group violations first by **Urgent** flag, then by category order (Code Quality, Performance, Business Logic).
25+
26+
## Required output
27+
When invoked, the response must exactly follow one of the two templates:
28+
29+
### Template A (any findings)
30+
```
31+
# Code review
32+
Found <N> urgent issues need to be fixed:
33+
34+
## 1 <brief description of bug>
35+
FilePath: <path> line <line>
36+
<relevant code snippet or pointer>
37+
38+
39+
### Suggested fix
40+
<brief description of suggested fix>
41+
42+
---
43+
... (repeat for each urgent issue) ...
44+
45+
Found <M> suggestions for improvement:
46+
47+
## 1 <brief description of suggestion>
48+
FilePath: <path> line <line>
49+
<relevant code snippet or pointer>
50+
51+
52+
### Suggested fix
53+
<brief description of suggested fix>
54+
55+
---
56+
57+
... (repeat for each suggestion) ...
58+
```
59+
60+
If there are no urgent issues, omit that section. If there are no suggestions, omit that section.
61+
62+
If the issue number is more than 10, summarize as "10+ urgent issues" or "10+ suggestions" and just output the first 10 issues.
63+
64+
Don't compress the blank lines between sections; keep them as-is for readability.
65+
66+
If you use Template A (i.e., there are issues to fix) and at least one issue requires code changes, append a brief follow-up question after the structured output asking whether the user wants you to apply the suggested fix(es). For example: "Would you like me to use the Suggested fix section to address these issues?"
67+
68+
### Template B (no issues)
69+
```
70+
## Code review
71+
No issues found.
72+
```
73+

0 commit comments

Comments
 (0)