mirror of
https://github.com/langgenius/dify.git
synced 2026-02-09 23:20:12 -05:00
feat: code/txt editor sync
This commit is contained in:
@@ -0,0 +1,91 @@
|
||||
import { useCallback, useEffect, useRef } from 'react'
|
||||
import { useWorkflowStore } from '@/app/components/workflow/store'
|
||||
import { skillCollaborationManager } from './skill-collaboration-manager'
|
||||
|
||||
type UseSkillCodeCollaborationProps = {
|
||||
appId: string
|
||||
fileId: string | null
|
||||
enabled: boolean
|
||||
initialContent: string
|
||||
baselineContent: string
|
||||
onLocalChange: (value: string) => void
|
||||
onLeaderSync: () => void
|
||||
}
|
||||
|
||||
export const useSkillCodeCollaboration = ({
|
||||
appId,
|
||||
fileId,
|
||||
enabled,
|
||||
initialContent,
|
||||
baselineContent,
|
||||
onLocalChange,
|
||||
onLeaderSync,
|
||||
}: UseSkillCodeCollaborationProps) => {
|
||||
const storeApi = useWorkflowStore()
|
||||
const suppressNextChangeRef = useRef<string | null>(null)
|
||||
// Keep the latest server baseline to avoid marking the editor dirty on initial sync.
|
||||
const baselineContentRef = useRef(baselineContent)
|
||||
|
||||
useEffect(() => {
|
||||
suppressNextChangeRef.current = null
|
||||
}, [fileId])
|
||||
|
||||
useEffect(() => {
|
||||
baselineContentRef.current = baselineContent
|
||||
}, [baselineContent])
|
||||
|
||||
useEffect(() => {
|
||||
if (!enabled || !fileId)
|
||||
return
|
||||
|
||||
skillCollaborationManager.openFile(appId, fileId, initialContent)
|
||||
skillCollaborationManager.setActiveFile(appId, fileId, true)
|
||||
|
||||
const unsubscribe = skillCollaborationManager.subscribe(fileId, (nextText) => {
|
||||
suppressNextChangeRef.current = nextText
|
||||
const state = storeApi.getState()
|
||||
if (nextText === baselineContentRef.current) {
|
||||
state.clearDraftContent(fileId)
|
||||
}
|
||||
else {
|
||||
state.setDraftContent(fileId, nextText)
|
||||
state.pinTab(fileId)
|
||||
}
|
||||
})
|
||||
|
||||
const unsubscribeSync = skillCollaborationManager.onSyncRequest(fileId, onLeaderSync)
|
||||
|
||||
return () => {
|
||||
unsubscribe()
|
||||
unsubscribeSync()
|
||||
skillCollaborationManager.setActiveFile(appId, fileId, false)
|
||||
skillCollaborationManager.closeFile(fileId)
|
||||
}
|
||||
}, [appId, enabled, fileId, initialContent, onLeaderSync, storeApi])
|
||||
|
||||
const handleCollaborativeChange = useCallback((value: string | undefined) => {
|
||||
const nextValue = value ?? ''
|
||||
if (!fileId) {
|
||||
onLocalChange(nextValue)
|
||||
return
|
||||
}
|
||||
|
||||
if (!enabled) {
|
||||
onLocalChange(nextValue)
|
||||
return
|
||||
}
|
||||
|
||||
if (suppressNextChangeRef.current === nextValue) {
|
||||
suppressNextChangeRef.current = null
|
||||
return
|
||||
}
|
||||
|
||||
skillCollaborationManager.updateText(fileId, nextValue)
|
||||
onLocalChange(nextValue)
|
||||
}, [enabled, fileId, onLocalChange])
|
||||
|
||||
return {
|
||||
handleCollaborativeChange,
|
||||
isLeader: fileId ? skillCollaborationManager.isLeader(fileId) : false,
|
||||
}
|
||||
}
|
||||
@@ -12,6 +12,7 @@ import { useStore, useWorkflowStore } from '@/app/components/workflow/store'
|
||||
import useTheme from '@/hooks/use-theme'
|
||||
import { Theme } from '@/types/app'
|
||||
import { basePath } from '@/utils/var'
|
||||
import { useSkillCodeCollaboration } from '../collaboration/skills/use-skill-code-collaboration'
|
||||
import { useSkillMarkdownCollaboration } from '../collaboration/skills/use-skill-markdown-collaboration'
|
||||
import { START_TAB_ID } from './constants'
|
||||
import CodeFileEditor from './editor/code-file-editor'
|
||||
@@ -62,7 +63,7 @@ const FileContentPanel = () => {
|
||||
const originalContent = fileContent?.content ?? ''
|
||||
const currentContent = draftContent !== undefined ? draftContent : originalContent
|
||||
const initialContentRegistryRef = useRef<Map<string, string>>(new Map())
|
||||
const canInitCollaboration = Boolean(appId && fileTabId && isMarkdown && isEditable && !isLoading && !error)
|
||||
const canInitCollaboration = Boolean(appId && fileTabId && isEditable && !isLoading && !error)
|
||||
|
||||
if (canInitCollaboration && fileTabId && !initialContentRegistryRef.current.has(fileTabId))
|
||||
initialContentRegistryRef.current.set(fileTabId, currentContent)
|
||||
@@ -155,10 +156,19 @@ const FileContentPanel = () => {
|
||||
const language = currentFileNode ? getFileLanguage(currentFileNode.name) : 'plaintext'
|
||||
const theme = appTheme === Theme.light ? 'light' : 'vs-dark'
|
||||
|
||||
const { handleCollaborativeChange } = useSkillMarkdownCollaboration({
|
||||
const { handleCollaborativeChange: handleMarkdownCollaborativeChange } = useSkillMarkdownCollaboration({
|
||||
appId,
|
||||
fileId: fileTabId,
|
||||
enabled: canInitCollaboration,
|
||||
enabled: canInitCollaboration && isMarkdown,
|
||||
initialContent: initialCollaborativeContent,
|
||||
baselineContent: originalContent,
|
||||
onLocalChange: handleEditorChange,
|
||||
onLeaderSync: handleLeaderSync,
|
||||
})
|
||||
const { handleCollaborativeChange: handleCodeCollaborativeChange } = useSkillCodeCollaboration({
|
||||
appId,
|
||||
fileId: fileTabId,
|
||||
enabled: canInitCollaboration && isCodeOrText,
|
||||
initialContent: initialCollaborativeContent,
|
||||
baselineContent: originalContent,
|
||||
onLocalChange: handleEditorChange,
|
||||
@@ -210,7 +220,7 @@ const FileContentPanel = () => {
|
||||
key={fileTabId}
|
||||
instanceId={fileTabId || undefined}
|
||||
value={currentContent}
|
||||
onChange={handleCollaborativeChange}
|
||||
onChange={handleMarkdownCollaborativeChange}
|
||||
collaborationEnabled={canInitCollaboration}
|
||||
/>
|
||||
)
|
||||
@@ -222,7 +232,7 @@ const FileContentPanel = () => {
|
||||
language={language}
|
||||
theme={isMounted ? theme : 'default-theme'}
|
||||
value={currentContent}
|
||||
onChange={handleEditorChange}
|
||||
onChange={handleCodeCollaborativeChange}
|
||||
onMount={handleEditorDidMount}
|
||||
/>
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user