mirror of
https://github.com/langgenius/dify.git
synced 2026-05-04 01:18:05 +08:00
Merge branch 'main' into feat/hitl-frontend
This commit is contained in:
@ -1,27 +1,15 @@
|
||||
import type { StorybookConfig } from '@storybook/nextjs'
|
||||
import path from 'node:path'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
|
||||
const storybookDir = path.dirname(fileURLToPath(import.meta.url))
|
||||
import type { StorybookConfig } from '@storybook/nextjs-vite'
|
||||
|
||||
const config: StorybookConfig = {
|
||||
stories: ['../app/components/**/*.stories.@(js|jsx|mjs|ts|tsx)'],
|
||||
addons: [
|
||||
'@storybook/addon-onboarding',
|
||||
// Not working with Storybook Vite framework
|
||||
// '@storybook/addon-onboarding',
|
||||
'@storybook/addon-links',
|
||||
'@storybook/addon-docs',
|
||||
'@chromatic-com/storybook',
|
||||
],
|
||||
framework: {
|
||||
name: '@storybook/nextjs',
|
||||
options: {
|
||||
builder: {
|
||||
useSWC: true,
|
||||
lazyCompilation: false,
|
||||
},
|
||||
nextConfigPath: undefined,
|
||||
},
|
||||
},
|
||||
framework: '@storybook/nextjs-vite',
|
||||
staticDirs: ['../public'],
|
||||
core: {
|
||||
disableWhatsNewNotifications: true,
|
||||
@ -29,17 +17,5 @@ const config: StorybookConfig = {
|
||||
docs: {
|
||||
defaultName: 'Documentation',
|
||||
},
|
||||
webpackFinal: async (config) => {
|
||||
// Add alias to mock problematic modules with circular dependencies
|
||||
config.resolve = config.resolve || {}
|
||||
config.resolve.alias = {
|
||||
...config.resolve.alias,
|
||||
// Mock the plugin index files to avoid circular dependencies
|
||||
[path.resolve(storybookDir, '../app/components/base/prompt-editor/plugins/context-block/index.tsx')]: path.resolve(storybookDir, '__mocks__/context-block.tsx'),
|
||||
[path.resolve(storybookDir, '../app/components/base/prompt-editor/plugins/history-block/index.tsx')]: path.resolve(storybookDir, '__mocks__/history-block.tsx'),
|
||||
[path.resolve(storybookDir, '../app/components/base/prompt-editor/plugins/query-block/index.tsx')]: path.resolve(storybookDir, '__mocks__/query-block.tsx'),
|
||||
}
|
||||
return config
|
||||
},
|
||||
}
|
||||
export default config
|
||||
|
||||
@ -1,3 +1,6 @@
|
||||
/**
|
||||
* @vitest-environment jsdom
|
||||
*/
|
||||
import type { ReactNode } from 'react'
|
||||
import type { ModalContextState } from '@/context/modal-context'
|
||||
import type { ProviderContextState } from '@/context/provider-context'
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import type { Meta, StoryObj } from '@storybook/nextjs'
|
||||
import type { Meta, StoryObj } from '@storybook/nextjs-vite'
|
||||
import { RiAddLine, RiDeleteBinLine, RiEditLine, RiMore2Fill, RiSaveLine, RiShareLine } from '@remixicon/react'
|
||||
import ActionButton, { ActionButtonState } from '.'
|
||||
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import type { Meta, StoryObj } from '@storybook/nextjs'
|
||||
import type { Meta, StoryObj } from '@storybook/nextjs-vite'
|
||||
import type { IChatItem } from '@/app/components/base/chat/chat/type'
|
||||
import type { AgentLogDetailResponse } from '@/models/log'
|
||||
import { useEffect, useRef } from 'react'
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import type { Meta, StoryObj } from '@storybook/nextjs'
|
||||
import type { Meta, StoryObj } from '@storybook/nextjs-vite'
|
||||
import type { ReactNode } from 'react'
|
||||
import AnswerIcon from '.'
|
||||
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import type { Meta, StoryObj } from '@storybook/nextjs'
|
||||
import type { Meta, StoryObj } from '@storybook/nextjs-vite'
|
||||
import type { AppIconSelection } from '.'
|
||||
import { useState } from 'react'
|
||||
import AppIconPicker from '.'
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import type { Meta, StoryObj } from '@storybook/nextjs'
|
||||
import type { Meta, StoryObj } from '@storybook/nextjs-vite'
|
||||
import type { ComponentProps } from 'react'
|
||||
import AppIcon from '.'
|
||||
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import type { Meta, StoryObj } from '@storybook/nextjs'
|
||||
import type { Meta, StoryObj } from '@storybook/nextjs-vite'
|
||||
import type { ComponentProps } from 'react'
|
||||
import { useEffect } from 'react'
|
||||
import AudioBtn from '.'
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import type { Meta, StoryObj } from '@storybook/nextjs'
|
||||
import type { Meta, StoryObj } from '@storybook/nextjs-vite'
|
||||
import AudioGallery from '.'
|
||||
|
||||
const AUDIO_SOURCES = [
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import type { Meta, StoryObj } from '@storybook/nextjs'
|
||||
import type { Meta, StoryObj } from '@storybook/nextjs-vite'
|
||||
import { useState } from 'react'
|
||||
import AutoHeightTextarea from '.'
|
||||
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import type { Meta, StoryObj } from '@storybook/nextjs'
|
||||
import type { Meta, StoryObj } from '@storybook/nextjs-vite'
|
||||
import Avatar from '.'
|
||||
|
||||
const meta = {
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import type { Meta, StoryObj } from '@storybook/nextjs'
|
||||
import type { Meta, StoryObj } from '@storybook/nextjs-vite'
|
||||
import Badge from '../badge'
|
||||
|
||||
const meta = {
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import type { Meta, StoryObj } from '@storybook/nextjs'
|
||||
import type { Meta, StoryObj } from '@storybook/nextjs-vite'
|
||||
import { useState } from 'react'
|
||||
import BlockInput from '.'
|
||||
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import type { Meta, StoryObj } from '@storybook/nextjs'
|
||||
import type { Meta, StoryObj } from '@storybook/nextjs-vite'
|
||||
import AddButton from './add-button'
|
||||
|
||||
const meta = {
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import type { Meta, StoryObj } from '@storybook/nextjs'
|
||||
import type { Meta, StoryObj } from '@storybook/nextjs-vite'
|
||||
|
||||
import { RocketLaunchIcon } from '@heroicons/react/20/solid'
|
||||
import { Button } from '.'
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import type { Meta, StoryObj } from '@storybook/nextjs'
|
||||
import type { Meta, StoryObj } from '@storybook/nextjs-vite'
|
||||
import SyncButton from './sync-button'
|
||||
|
||||
const meta = {
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import type { Meta, StoryObj } from '@storybook/nextjs'
|
||||
import type { Meta, StoryObj } from '@storybook/nextjs-vite'
|
||||
import type { ChatItem } from '../../types'
|
||||
import { WorkflowRunningStatus } from '@/app/components/workflow/types'
|
||||
import Answer from '.'
|
||||
|
||||
178
web/app/components/base/chat/chat/hooks.multimodal.spec.ts
Normal file
178
web/app/components/base/chat/chat/hooks.multimodal.spec.ts
Normal file
@ -0,0 +1,178 @@
|
||||
/**
|
||||
* Tests for multimodal image file handling in chat hooks.
|
||||
* Tests the file object conversion logic without full hook integration.
|
||||
*/
|
||||
|
||||
describe('Multimodal File Handling', () => {
|
||||
describe('File type to MIME type mapping', () => {
|
||||
it('should map image to image/png', () => {
|
||||
const fileType: string = 'image'
|
||||
const expectedMime = 'image/png'
|
||||
const mimeType = fileType === 'image' ? 'image/png' : 'application/octet-stream'
|
||||
expect(mimeType).toBe(expectedMime)
|
||||
})
|
||||
|
||||
it('should map video to video/mp4', () => {
|
||||
const fileType: string = 'video'
|
||||
const expectedMime = 'video/mp4'
|
||||
const mimeType = fileType === 'video' ? 'video/mp4' : 'application/octet-stream'
|
||||
expect(mimeType).toBe(expectedMime)
|
||||
})
|
||||
|
||||
it('should map audio to audio/mpeg', () => {
|
||||
const fileType: string = 'audio'
|
||||
const expectedMime = 'audio/mpeg'
|
||||
const mimeType = fileType === 'audio' ? 'audio/mpeg' : 'application/octet-stream'
|
||||
expect(mimeType).toBe(expectedMime)
|
||||
})
|
||||
|
||||
it('should map unknown to application/octet-stream', () => {
|
||||
const fileType: string = 'unknown'
|
||||
const expectedMime = 'application/octet-stream'
|
||||
const mimeType = ['image', 'video', 'audio'].includes(fileType) ? 'image/png' : 'application/octet-stream'
|
||||
expect(mimeType).toBe(expectedMime)
|
||||
})
|
||||
})
|
||||
|
||||
describe('TransferMethod selection', () => {
|
||||
it('should select remote_url for images', () => {
|
||||
const fileType: string = 'image'
|
||||
const transferMethod = fileType === 'image' ? 'remote_url' : 'local_file'
|
||||
expect(transferMethod).toBe('remote_url')
|
||||
})
|
||||
|
||||
it('should select local_file for non-images', () => {
|
||||
const fileType: string = 'video'
|
||||
const transferMethod = fileType === 'image' ? 'remote_url' : 'local_file'
|
||||
expect(transferMethod).toBe('local_file')
|
||||
})
|
||||
})
|
||||
|
||||
describe('File extension mapping', () => {
|
||||
it('should use .png extension for images', () => {
|
||||
const fileType: string = 'image'
|
||||
const expectedExtension = '.png'
|
||||
const extension = fileType === 'image' ? 'png' : 'bin'
|
||||
expect(extension).toBe(expectedExtension.replace('.', ''))
|
||||
})
|
||||
|
||||
it('should use .mp4 extension for videos', () => {
|
||||
const fileType: string = 'video'
|
||||
const expectedExtension = '.mp4'
|
||||
const extension = fileType === 'video' ? 'mp4' : 'bin'
|
||||
expect(extension).toBe(expectedExtension.replace('.', ''))
|
||||
})
|
||||
|
||||
it('should use .mp3 extension for audio', () => {
|
||||
const fileType: string = 'audio'
|
||||
const expectedExtension = '.mp3'
|
||||
const extension = fileType === 'audio' ? 'mp3' : 'bin'
|
||||
expect(extension).toBe(expectedExtension.replace('.', ''))
|
||||
})
|
||||
})
|
||||
|
||||
describe('File name generation', () => {
|
||||
it('should generate correct file name for images', () => {
|
||||
const fileType: string = 'image'
|
||||
const expectedName = 'generated_image.png'
|
||||
const fileName = `generated_${fileType}.${fileType === 'image' ? 'png' : 'bin'}`
|
||||
expect(fileName).toBe(expectedName)
|
||||
})
|
||||
|
||||
it('should generate correct file name for videos', () => {
|
||||
const fileType: string = 'video'
|
||||
const expectedName = 'generated_video.mp4'
|
||||
const fileName = `generated_${fileType}.${fileType === 'video' ? 'mp4' : 'bin'}`
|
||||
expect(fileName).toBe(expectedName)
|
||||
})
|
||||
|
||||
it('should generate correct file name for audio', () => {
|
||||
const fileType: string = 'audio'
|
||||
const expectedName = 'generated_audio.mp3'
|
||||
const fileName = `generated_${fileType}.${fileType === 'audio' ? 'mp3' : 'bin'}`
|
||||
expect(fileName).toBe(expectedName)
|
||||
})
|
||||
})
|
||||
|
||||
describe('SupportFileType mapping', () => {
|
||||
it('should map image type to image supportFileType', () => {
|
||||
const fileType: string = 'image'
|
||||
const supportFileType = fileType === 'image' ? 'image' : fileType === 'video' ? 'video' : fileType === 'audio' ? 'audio' : 'document'
|
||||
expect(supportFileType).toBe('image')
|
||||
})
|
||||
|
||||
it('should map video type to video supportFileType', () => {
|
||||
const fileType: string = 'video'
|
||||
const supportFileType = fileType === 'image' ? 'image' : fileType === 'video' ? 'video' : fileType === 'audio' ? 'audio' : 'document'
|
||||
expect(supportFileType).toBe('video')
|
||||
})
|
||||
|
||||
it('should map audio type to audio supportFileType', () => {
|
||||
const fileType: string = 'audio'
|
||||
const supportFileType = fileType === 'image' ? 'image' : fileType === 'video' ? 'video' : fileType === 'audio' ? 'audio' : 'document'
|
||||
expect(supportFileType).toBe('audio')
|
||||
})
|
||||
|
||||
it('should map unknown type to document supportFileType', () => {
|
||||
const fileType: string = 'unknown'
|
||||
const supportFileType = fileType === 'image' ? 'image' : fileType === 'video' ? 'video' : fileType === 'audio' ? 'audio' : 'document'
|
||||
expect(supportFileType).toBe('document')
|
||||
})
|
||||
})
|
||||
|
||||
describe('File conversion logic', () => {
|
||||
it('should detect existing transferMethod', () => {
|
||||
const fileWithTransferMethod = {
|
||||
id: 'file-123',
|
||||
transferMethod: 'remote_url' as const,
|
||||
type: 'image/png',
|
||||
name: 'test.png',
|
||||
size: 1024,
|
||||
supportFileType: 'image',
|
||||
progress: 100,
|
||||
}
|
||||
const hasTransferMethod = 'transferMethod' in fileWithTransferMethod
|
||||
expect(hasTransferMethod).toBe(true)
|
||||
})
|
||||
|
||||
it('should detect missing transferMethod', () => {
|
||||
const fileWithoutTransferMethod = {
|
||||
id: 'file-456',
|
||||
type: 'image',
|
||||
url: 'http://example.com/image.png',
|
||||
belongs_to: 'assistant',
|
||||
}
|
||||
const hasTransferMethod = 'transferMethod' in fileWithoutTransferMethod
|
||||
expect(hasTransferMethod).toBe(false)
|
||||
})
|
||||
|
||||
it('should create file with size 0 for generated files', () => {
|
||||
const expectedSize = 0
|
||||
expect(expectedSize).toBe(0)
|
||||
})
|
||||
})
|
||||
|
||||
describe('Agent vs Non-Agent mode logic', () => {
|
||||
it('should check for agent_thoughts to determine mode', () => {
|
||||
const agentResponse: { agent_thoughts?: Array<Record<string, unknown>> } = {
|
||||
agent_thoughts: [{}],
|
||||
}
|
||||
const isAgentMode = agentResponse.agent_thoughts && agentResponse.agent_thoughts.length > 0
|
||||
expect(isAgentMode).toBe(true)
|
||||
})
|
||||
|
||||
it('should detect non-agent mode when agent_thoughts is empty', () => {
|
||||
const nonAgentResponse: { agent_thoughts?: Array<Record<string, unknown>> } = {
|
||||
agent_thoughts: [],
|
||||
}
|
||||
const isAgentMode = nonAgentResponse.agent_thoughts && nonAgentResponse.agent_thoughts.length > 0
|
||||
expect(isAgentMode).toBe(false)
|
||||
})
|
||||
|
||||
it('should detect non-agent mode when agent_thoughts is undefined', () => {
|
||||
const nonAgentResponse: { agent_thoughts?: Array<Record<string, unknown>> } = {}
|
||||
const isAgentMode = nonAgentResponse.agent_thoughts && nonAgentResponse.agent_thoughts.length > 0
|
||||
expect(isAgentMode).toBeFalsy()
|
||||
})
|
||||
})
|
||||
})
|
||||
@ -745,9 +745,40 @@ export const useChat = (
|
||||
}
|
||||
},
|
||||
onFile(file) {
|
||||
// Convert simple file type to MIME type for non-agent mode
|
||||
// Backend sends: { id, type: "image", belongs_to, url }
|
||||
// Frontend expects: { id, type: "image/png", transferMethod, url, uploadedId, supportFileType, name, size }
|
||||
|
||||
// Determine file type for MIME conversion
|
||||
const fileType = (file as { type?: string }).type || 'image'
|
||||
|
||||
// If file already has transferMethod, use it as base and ensure all required fields exist
|
||||
// Otherwise, create a new complete file object
|
||||
const baseFile = ('transferMethod' in file) ? (file as Partial<FileEntity>) : null
|
||||
|
||||
const convertedFile: FileEntity = {
|
||||
id: baseFile?.id || (file as { id: string }).id,
|
||||
type: baseFile?.type || (fileType === 'image' ? 'image/png' : fileType === 'video' ? 'video/mp4' : fileType === 'audio' ? 'audio/mpeg' : 'application/octet-stream'),
|
||||
transferMethod: (baseFile?.transferMethod as FileEntity['transferMethod']) || (fileType === 'image' ? 'remote_url' : 'local_file'),
|
||||
uploadedId: baseFile?.uploadedId || (file as { id: string }).id,
|
||||
supportFileType: baseFile?.supportFileType || (fileType === 'image' ? 'image' : fileType === 'video' ? 'video' : fileType === 'audio' ? 'audio' : 'document'),
|
||||
progress: baseFile?.progress ?? 100,
|
||||
name: baseFile?.name || `generated_${fileType}.${fileType === 'image' ? 'png' : fileType === 'video' ? 'mp4' : fileType === 'audio' ? 'mp3' : 'bin'}`,
|
||||
url: baseFile?.url || (file as { url?: string }).url,
|
||||
size: baseFile?.size ?? 0, // Generated files don't have a known size
|
||||
}
|
||||
|
||||
// For agent mode, add files to the last thought
|
||||
const lastThought = responseItem.agent_thoughts?.[responseItem.agent_thoughts?.length - 1]
|
||||
if (lastThought)
|
||||
responseItem.agent_thoughts![responseItem.agent_thoughts!.length - 1].message_files = [...(lastThought as any).message_files, file]
|
||||
if (lastThought) {
|
||||
const thought = lastThought as { message_files?: FileEntity[] }
|
||||
responseItem.agent_thoughts![responseItem.agent_thoughts!.length - 1].message_files = [...(thought.message_files ?? []), convertedFile]
|
||||
}
|
||||
// For non-agent mode, add files directly to responseItem.message_files
|
||||
else {
|
||||
const currentFiles = (responseItem.message_files as FileEntity[] | undefined) ?? []
|
||||
responseItem.message_files = [...currentFiles, convertedFile]
|
||||
}
|
||||
|
||||
updateCurrentQAOnTree({
|
||||
placeholderQuestionId,
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import type { Meta, StoryObj } from '@storybook/nextjs'
|
||||
import type { Meta, StoryObj } from '@storybook/nextjs-vite'
|
||||
|
||||
import type { ChatItem } from '../types'
|
||||
import { User } from '@/app/components/base/icons/src/public/avatar'
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import type { Meta, StoryObj } from '@storybook/nextjs'
|
||||
import type { Meta, StoryObj } from '@storybook/nextjs-vite'
|
||||
import { useState } from 'react'
|
||||
import Checkbox from '.'
|
||||
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import type { Meta, StoryObj } from '@storybook/nextjs'
|
||||
import type { Meta, StoryObj } from '@storybook/nextjs-vite'
|
||||
import type { Item } from '.'
|
||||
import { useState } from 'react'
|
||||
import Chip from '.'
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import type { Meta, StoryObj } from '@storybook/nextjs'
|
||||
import type { Meta, StoryObj } from '@storybook/nextjs-vite'
|
||||
import { useState } from 'react'
|
||||
import Confirm from '.'
|
||||
import Button from '../button'
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import type { Meta, StoryObj } from '@storybook/nextjs'
|
||||
import type { Meta, StoryObj } from '@storybook/nextjs-vite'
|
||||
import { useEffect, useState } from 'react'
|
||||
import ContentDialog from '.'
|
||||
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import type { Meta, StoryObj } from '@storybook/nextjs'
|
||||
import type { Meta, StoryObj } from '@storybook/nextjs-vite'
|
||||
import { useState } from 'react'
|
||||
import CopyFeedback, { CopyFeedbackNew } from '.'
|
||||
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import type { Meta, StoryObj } from '@storybook/nextjs'
|
||||
import type { Meta, StoryObj } from '@storybook/nextjs-vite'
|
||||
import CopyIcon from '.'
|
||||
|
||||
const meta = {
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import type { Meta, StoryObj } from '@storybook/nextjs'
|
||||
import type { Meta, StoryObj } from '@storybook/nextjs-vite'
|
||||
import CornerLabel from '.'
|
||||
|
||||
const meta = {
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import type { Meta, StoryObj } from '@storybook/nextjs'
|
||||
import type { Meta, StoryObj } from '@storybook/nextjs-vite'
|
||||
import type { DatePickerProps } from './types'
|
||||
import { useState } from 'react'
|
||||
import { fn } from 'storybook/test'
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import type { Meta, StoryObj } from '@storybook/nextjs'
|
||||
import type { Meta, StoryObj } from '@storybook/nextjs-vite'
|
||||
import { useEffect, useState } from 'react'
|
||||
import Dialog from '.'
|
||||
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import type { Meta, StoryObj } from '@storybook/nextjs'
|
||||
import type { Meta, StoryObj } from '@storybook/nextjs-vite'
|
||||
import Divider from '.'
|
||||
|
||||
const meta = {
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import type { Meta, StoryObj } from '@storybook/nextjs'
|
||||
import type { Meta, StoryObj } from '@storybook/nextjs-vite'
|
||||
import { useState } from 'react'
|
||||
import { fn } from 'storybook/test'
|
||||
import DrawerPlus from '.'
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import type { Meta, StoryObj } from '@storybook/nextjs'
|
||||
import type { Meta, StoryObj } from '@storybook/nextjs-vite'
|
||||
import { useState } from 'react'
|
||||
import { fn } from 'storybook/test'
|
||||
import Drawer from '.'
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import type { Meta, StoryObj } from '@storybook/nextjs'
|
||||
import type { Meta, StoryObj } from '@storybook/nextjs-vite'
|
||||
import type { Item } from '.'
|
||||
import { useState } from 'react'
|
||||
import { fn } from 'storybook/test'
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
/* eslint-disable tailwindcss/classnames-order */
|
||||
import type { Meta, StoryObj } from '@storybook/nextjs'
|
||||
import type { Meta, StoryObj } from '@storybook/nextjs-vite'
|
||||
import Effect from '.'
|
||||
|
||||
const meta = {
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import type { Meta, StoryObj } from '@storybook/nextjs'
|
||||
import type { Meta, StoryObj } from '@storybook/nextjs-vite'
|
||||
import { useState } from 'react'
|
||||
import EmojiPickerInner from './Inner'
|
||||
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import type { Meta, StoryObj } from '@storybook/nextjs'
|
||||
import type { Meta, StoryObj } from '@storybook/nextjs-vite'
|
||||
import { useState } from 'react'
|
||||
import EmojiPicker from '.'
|
||||
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import type { Meta, StoryObj } from '@storybook/nextjs'
|
||||
import type { Meta, StoryObj } from '@storybook/nextjs-vite'
|
||||
import type { Features } from './types'
|
||||
import { useState } from 'react'
|
||||
import { FeaturesProvider } from '.'
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import type { Meta, StoryObj } from '@storybook/nextjs'
|
||||
import type { Meta, StoryObj } from '@storybook/nextjs-vite'
|
||||
import FileIcon from '.'
|
||||
|
||||
const meta = {
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import type { Meta, StoryObj } from '@storybook/nextjs'
|
||||
import type { Meta, StoryObj } from '@storybook/nextjs-vite'
|
||||
import FileImageRender from './file-image-render'
|
||||
|
||||
const SAMPLE_IMAGE = 'data:image/svg+xml;utf8,<svg xmlns=\'http://www.w3.org/2000/svg\' width=\'320\' height=\'180\'><defs><linearGradient id=\'grad\' x1=\'0%\' y1=\'0%\' x2=\'100%\' y2=\'100%\'><stop offset=\'0%\' stop-color=\'#FEE2FF\'/><stop offset=\'100%\' stop-color=\'#E0EAFF\'/></linearGradient></defs><rect width=\'320\' height=\'180\' rx=\'18\' fill=\'url(#grad)\'/><text x=\'50%\' y=\'50%\' dominant-baseline=\'middle\' text-anchor=\'middle\' font-family=\'sans-serif\' font-size=\'24\' fill=\'#1F2937\'>Preview</text></svg>'
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import type { Meta, StoryObj } from '@storybook/nextjs'
|
||||
import type { Meta, StoryObj } from '@storybook/nextjs-vite'
|
||||
import type { FileEntity } from './types'
|
||||
import { useState } from 'react'
|
||||
import { SupportUploadFileTypes } from '@/app/components/workflow/types'
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import type { Meta, StoryObj } from '@storybook/nextjs'
|
||||
import type { Meta, StoryObj } from '@storybook/nextjs-vite'
|
||||
import FileTypeIcon from './file-type-icon'
|
||||
import { FileAppearanceTypeEnum } from './types'
|
||||
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import type { Meta, StoryObj } from '@storybook/nextjs'
|
||||
import type { Meta, StoryObj } from '@storybook/nextjs-vite'
|
||||
import type { FileEntity } from '../types'
|
||||
import type { FileUpload } from '@/app/components/base/features/types'
|
||||
import { useState } from 'react'
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import type { Meta, StoryObj } from '@storybook/nextjs'
|
||||
import type { Meta, StoryObj } from '@storybook/nextjs-vite'
|
||||
import type { FileEntity } from '../types'
|
||||
import type { FileUpload } from '@/app/components/base/features/types'
|
||||
import { useState } from 'react'
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import type { Meta, StoryObj } from '@storybook/nextjs'
|
||||
import type { Meta, StoryObj } from '@storybook/nextjs-vite'
|
||||
import { useState } from 'react'
|
||||
import { fn } from 'storybook/test'
|
||||
import FloatRightContainer from '.'
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import type { Meta, StoryObj } from '@storybook/nextjs'
|
||||
import type { Meta, StoryObj } from '@storybook/nextjs-vite'
|
||||
import type { FormStoryRender } from '../../../../.storybook/utils/form-story-wrapper'
|
||||
import type { FormSchema } from './types'
|
||||
import { useStore } from '@tanstack/react-form'
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import type { Meta, StoryObj } from '@storybook/nextjs'
|
||||
import type { Meta, StoryObj } from '@storybook/nextjs-vite'
|
||||
import { useState } from 'react'
|
||||
import FullScreenModal from '.'
|
||||
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import type { Meta, StoryObj } from '@storybook/nextjs'
|
||||
import type { Meta, StoryObj } from '@storybook/nextjs-vite'
|
||||
import GridMask from '.'
|
||||
|
||||
const meta = {
|
||||
|
||||
@ -1,9 +1,9 @@
|
||||
import type { Meta, StoryObj } from '@storybook/nextjs'
|
||||
/// <reference types="vite/client" />
|
||||
import type { Meta, StoryObj } from '@storybook/nextjs-vite'
|
||||
import * as React from 'react'
|
||||
|
||||
declare const require: any
|
||||
|
||||
type IconComponent = React.ComponentType<Record<string, unknown>>
|
||||
type IconModule = { default: IconComponent }
|
||||
|
||||
type IconEntry = {
|
||||
name: string
|
||||
@ -12,18 +12,16 @@ type IconEntry = {
|
||||
Component: IconComponent
|
||||
}
|
||||
|
||||
const iconContext = require.context('./src', true, /\.tsx$/)
|
||||
const iconModules: Record<string, IconModule> = import.meta.glob('./src/**/*.tsx', { eager: true })
|
||||
|
||||
const iconEntries: IconEntry[] = iconContext
|
||||
.keys()
|
||||
.filter((key: string) => !key.endsWith('.stories.tsx') && !key.endsWith('.spec.tsx'))
|
||||
.map((key: string) => {
|
||||
const mod = iconContext(key)
|
||||
const Component = mod.default as IconComponent | undefined
|
||||
const iconEntries: IconEntry[] = Object.entries(iconModules)
|
||||
.filter(([key]) => !key.endsWith('.stories.tsx') && !key.endsWith('.spec.tsx'))
|
||||
.map(([key, mod]) => {
|
||||
const Component = mod.default
|
||||
if (!Component)
|
||||
return null
|
||||
|
||||
const relativePath = key.replace(/^\.\//, '')
|
||||
const relativePath = key.replace(/^\.\/src\//, '')
|
||||
const path = `app/components/base/icons/src/${relativePath}`
|
||||
const parts = relativePath.split('/')
|
||||
const fileName = parts.pop() || ''
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import type { Meta, StoryObj } from '@storybook/nextjs'
|
||||
import type { Meta, StoryObj } from '@storybook/nextjs-vite'
|
||||
import ImageGallery from '.'
|
||||
|
||||
const IMAGE_SOURCES = [
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import type { Meta, StoryObj } from '@storybook/nextjs'
|
||||
import type { Meta, StoryObj } from '@storybook/nextjs-vite'
|
||||
import type { ImageFile } from '@/types/app'
|
||||
import { useMemo, useState } from 'react'
|
||||
import { TransferMethod } from '@/types/app'
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import type { Meta, StoryObj } from '@storybook/nextjs'
|
||||
import type { Meta, StoryObj } from '@storybook/nextjs-vite'
|
||||
import { useState } from 'react'
|
||||
import { fn } from 'storybook/test'
|
||||
import InlineDeleteConfirm from '.'
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import type { Meta, StoryObj } from '@storybook/nextjs'
|
||||
import type { Meta, StoryObj } from '@storybook/nextjs-vite'
|
||||
import { useState } from 'react'
|
||||
import { InputNumber } from '.'
|
||||
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import type { Meta, StoryObj } from '@storybook/nextjs'
|
||||
import type { Meta, StoryObj } from '@storybook/nextjs-vite'
|
||||
import { useState } from 'react'
|
||||
import Input from '.'
|
||||
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import type { Meta, StoryObj } from '@storybook/nextjs'
|
||||
import type { Meta, StoryObj } from '@storybook/nextjs-vite'
|
||||
import type { RelatedApp } from '@/models/datasets'
|
||||
import { AppModeEnum } from '@/types/app'
|
||||
import LinkedAppsPanel from '.'
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import type { Meta, StoryObj } from '@storybook/nextjs'
|
||||
import type { Meta, StoryObj } from '@storybook/nextjs-vite'
|
||||
import ListEmpty from '.'
|
||||
|
||||
const meta = {
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import type { Meta, StoryObj } from '@storybook/nextjs'
|
||||
import type { Meta, StoryObj } from '@storybook/nextjs-vite'
|
||||
import Loading from '.'
|
||||
|
||||
const meta = {
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import type { Meta, StoryObj } from '@storybook/nextjs'
|
||||
import type { Meta, StoryObj } from '@storybook/nextjs-vite'
|
||||
import type { ReactNode } from 'react'
|
||||
import { ThemeProvider } from 'next-themes'
|
||||
import DifyLogo from './dify-logo'
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import type { Meta, StoryObj } from '@storybook/nextjs'
|
||||
import type { Meta, StoryObj } from '@storybook/nextjs-vite'
|
||||
import CodeBlock from './code-block'
|
||||
|
||||
const SAMPLE_CODE = `const greet = (name: string) => {
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import type { Meta, StoryObj } from '@storybook/nextjs'
|
||||
import type { Meta, StoryObj } from '@storybook/nextjs-vite'
|
||||
import { useState } from 'react'
|
||||
import { ChatContextProvider } from '@/app/components/base/chat/chat/context'
|
||||
import ThinkBlock from './think-block'
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import type { Meta, StoryObj } from '@storybook/nextjs'
|
||||
import type { Meta, StoryObj } from '@storybook/nextjs-vite'
|
||||
import { useState } from 'react'
|
||||
import { Markdown } from '.'
|
||||
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import type { Meta, StoryObj } from '@storybook/nextjs'
|
||||
import type { Meta, StoryObj } from '@storybook/nextjs-vite'
|
||||
import { useState } from 'react'
|
||||
import Flowchart from '.'
|
||||
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import type { Meta, StoryObj } from '@storybook/nextjs'
|
||||
import type { Meta, StoryObj } from '@storybook/nextjs-vite'
|
||||
import type { IChatItem } from '@/app/components/base/chat/chat/type'
|
||||
import type { WorkflowRunDetailResponse } from '@/models/log'
|
||||
import type { NodeTracing, NodeTracingListResponse } from '@/types/workflow'
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import type { Meta, StoryObj } from '@storybook/nextjs'
|
||||
import type { Meta, StoryObj } from '@storybook/nextjs-vite'
|
||||
import ModalLikeWrap from '.'
|
||||
|
||||
const meta = {
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import type { Meta, StoryObj } from '@storybook/nextjs'
|
||||
import type { Meta, StoryObj } from '@storybook/nextjs-vite'
|
||||
import { useEffect, useState } from 'react'
|
||||
import Modal from '.'
|
||||
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import type { Meta, StoryObj } from '@storybook/nextjs'
|
||||
import type { Meta, StoryObj } from '@storybook/nextjs-vite'
|
||||
import { useEffect, useState } from 'react'
|
||||
import Modal from './modal'
|
||||
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import type { Meta, StoryObj } from '@storybook/nextjs'
|
||||
import type { Meta, StoryObj } from '@storybook/nextjs-vite'
|
||||
import type { ComponentProps } from 'react'
|
||||
import { useEffect } from 'react'
|
||||
import AudioBtn from '.'
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import type { Meta, StoryObj } from '@storybook/nextjs'
|
||||
import type { Meta, StoryObj } from '@storybook/nextjs-vite'
|
||||
import NotionConnector from '.'
|
||||
|
||||
const meta = {
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import type { Meta, StoryObj } from '@storybook/nextjs'
|
||||
import type { Meta, StoryObj } from '@storybook/nextjs-vite'
|
||||
import NotionIcon from '.'
|
||||
|
||||
const meta = {
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import type { Meta, StoryObj } from '@storybook/nextjs'
|
||||
import type { Meta, StoryObj } from '@storybook/nextjs-vite'
|
||||
import type { DataSourceCredential } from '@/app/components/header/account-setting/data-source-page-new/types'
|
||||
import type { NotionPage } from '@/models/common'
|
||||
import { useEffect, useMemo, useState } from 'react'
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import type { Meta, StoryObj } from '@storybook/nextjs'
|
||||
import type { Meta, StoryObj } from '@storybook/nextjs-vite'
|
||||
import { useMemo, useState } from 'react'
|
||||
import Pagination from '.'
|
||||
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import type { Meta, StoryObj } from '@storybook/nextjs'
|
||||
import type { Meta, StoryObj } from '@storybook/nextjs-vite'
|
||||
import { useState } from 'react'
|
||||
import ParamItem from '.'
|
||||
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import type { Meta, StoryObj } from '@storybook/nextjs'
|
||||
import type { Meta, StoryObj } from '@storybook/nextjs-vite'
|
||||
import { useState } from 'react'
|
||||
import CustomPopover from '.'
|
||||
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import type { Meta, StoryObj } from '@storybook/nextjs'
|
||||
import type { Meta, StoryObj } from '@storybook/nextjs-vite'
|
||||
import { useState } from 'react'
|
||||
import {
|
||||
PortalToFollowElem,
|
||||
|
||||
@ -61,9 +61,12 @@ export function usePortalToFollowElem({
|
||||
}),
|
||||
shift({ padding: 5 }),
|
||||
size({
|
||||
apply({ rects, elements }) {
|
||||
if (triggerPopupSameWidth)
|
||||
elements.floating.style.width = `${rects.reference.width}px`
|
||||
apply({ rects, elements, availableHeight }) {
|
||||
Object.assign(elements.floating.style, {
|
||||
maxHeight: `${Math.max(0, availableHeight)}px`,
|
||||
overflowY: 'auto',
|
||||
...(triggerPopupSameWidth && { width: `${rects.reference.width}px` }),
|
||||
})
|
||||
},
|
||||
}),
|
||||
],
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import type { Meta, StoryObj } from '@storybook/nextjs'
|
||||
import type { Meta, StoryObj } from '@storybook/nextjs-vite'
|
||||
import PremiumBadge from '.'
|
||||
|
||||
const colors: Array<NonNullable<React.ComponentProps<typeof PremiumBadge>['color']>> = ['blue', 'indigo', 'gray', 'orange']
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import type { Meta, StoryObj } from '@storybook/nextjs'
|
||||
import type { Meta, StoryObj } from '@storybook/nextjs-vite'
|
||||
import { useState } from 'react'
|
||||
import ProgressCircle from './progress-circle'
|
||||
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import type { Meta, StoryObj } from '@storybook/nextjs'
|
||||
import type { Meta, StoryObj } from '@storybook/nextjs-vite'
|
||||
import { useState } from 'react'
|
||||
|
||||
// Mock component to avoid complex initialization issues
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import type { Meta, StoryObj } from '@storybook/nextjs'
|
||||
import type { Meta, StoryObj } from '@storybook/nextjs-vite'
|
||||
import type { IChatItem } from '@/app/components/base/chat/chat/type'
|
||||
import { useEffect } from 'react'
|
||||
import { useStore } from '@/app/components/app/store'
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import type { Meta, StoryObj } from '@storybook/nextjs'
|
||||
import type { Meta, StoryObj } from '@storybook/nextjs-vite'
|
||||
import ShareQRCode from '.'
|
||||
|
||||
const QRDemo = ({
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import type { Meta, StoryObj } from '@storybook/nextjs'
|
||||
import type { Meta, StoryObj } from '@storybook/nextjs-vite'
|
||||
import { RiCloudLine, RiCpuLine, RiDatabase2Line, RiLightbulbLine, RiRocketLine, RiShieldLine } from '@remixicon/react'
|
||||
import { useState } from 'react'
|
||||
import RadioCard from '.'
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import type { Meta, StoryObj } from '@storybook/nextjs'
|
||||
import type { Meta, StoryObj } from '@storybook/nextjs-vite'
|
||||
import { useState } from 'react'
|
||||
import Radio from '.'
|
||||
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import type { Meta, StoryObj } from '@storybook/nextjs'
|
||||
import type { Meta, StoryObj } from '@storybook/nextjs-vite'
|
||||
import { useState } from 'react'
|
||||
import SearchInput from '.'
|
||||
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import type { Meta, StoryObj } from '@storybook/nextjs'
|
||||
import type { Meta, StoryObj } from '@storybook/nextjs-vite'
|
||||
import { RiLineChartLine, RiListCheck2, RiRobot2Line } from '@remixicon/react'
|
||||
import { useState } from 'react'
|
||||
import { SegmentedControl } from '.'
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import type { Meta, StoryObj } from '@storybook/nextjs'
|
||||
import type { Meta, StoryObj } from '@storybook/nextjs-vite'
|
||||
import type { Item } from '.'
|
||||
import { useState } from 'react'
|
||||
import Select, { PortalSelect, SimpleSelect } from '.'
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import type { Meta, StoryObj } from '@storybook/nextjs'
|
||||
import type { Meta, StoryObj } from '@storybook/nextjs-vite'
|
||||
import { useMemo, useState } from 'react'
|
||||
import SimplePieChart from '.'
|
||||
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import type { Meta, StoryObj } from '@storybook/nextjs'
|
||||
import type { Meta, StoryObj } from '@storybook/nextjs-vite'
|
||||
import {
|
||||
SkeletonContainer,
|
||||
SkeletonPoint,
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import type { Meta, StoryObj } from '@storybook/nextjs'
|
||||
import type { Meta, StoryObj } from '@storybook/nextjs-vite'
|
||||
import { useState } from 'react'
|
||||
import Slider from '.'
|
||||
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import type { Meta, StoryObj } from '@storybook/nextjs'
|
||||
import type { Meta, StoryObj } from '@storybook/nextjs-vite'
|
||||
import { useMemo, useState } from 'react'
|
||||
import Sort from '.'
|
||||
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import type { Meta, StoryObj } from '@storybook/nextjs'
|
||||
import type { Meta, StoryObj } from '@storybook/nextjs-vite'
|
||||
import { useState } from 'react'
|
||||
import Spinner from '.'
|
||||
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import type { Meta, StoryObj } from '@storybook/nextjs'
|
||||
import type { Meta, StoryObj } from '@storybook/nextjs-vite'
|
||||
import SVGRenderer from '.'
|
||||
|
||||
const SAMPLE_SVG = `
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import type { Meta, StoryObj } from '@storybook/nextjs'
|
||||
import type { Meta, StoryObj } from '@storybook/nextjs-vite'
|
||||
import { useState } from 'react'
|
||||
import SVGBtn from '.'
|
||||
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import type { Meta, StoryObj } from '@storybook/nextjs'
|
||||
import type { Meta, StoryObj } from '@storybook/nextjs-vite'
|
||||
import { useState } from 'react'
|
||||
import Switch from '.'
|
||||
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import type { Meta, StoryObj } from '@storybook/nextjs'
|
||||
import type { Meta, StoryObj } from '@storybook/nextjs-vite'
|
||||
import type { ITabHeaderProps } from '.'
|
||||
import { useState } from 'react'
|
||||
import TabHeader from '.'
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import type { Meta, StoryObj } from '@storybook/nextjs'
|
||||
import type { Meta, StoryObj } from '@storybook/nextjs-vite'
|
||||
import { RiSparklingFill, RiTerminalBoxLine } from '@remixicon/react'
|
||||
import { useState } from 'react'
|
||||
import TabSliderNew from '.'
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import type { Meta, StoryObj } from '@storybook/nextjs'
|
||||
import type { Meta, StoryObj } from '@storybook/nextjs-vite'
|
||||
import { useState } from 'react'
|
||||
import TabSliderPlain from '.'
|
||||
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import type { Meta, StoryObj } from '@storybook/nextjs'
|
||||
import type { Meta, StoryObj } from '@storybook/nextjs-vite'
|
||||
import { useEffect, useState } from 'react'
|
||||
import TabSlider from '.'
|
||||
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import type { Meta, StoryObj } from '@storybook/nextjs'
|
||||
import type { Meta, StoryObj } from '@storybook/nextjs-vite'
|
||||
import { useState } from 'react'
|
||||
import TagInput from '.'
|
||||
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import type { Meta, StoryObj } from '@storybook/nextjs'
|
||||
import type { Meta, StoryObj } from '@storybook/nextjs-vite'
|
||||
import type { Tag } from './constant'
|
||||
import { useEffect, useRef } from 'react'
|
||||
import { ToastProvider } from '@/app/components/base/toast'
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import type { Meta, StoryObj } from '@storybook/nextjs'
|
||||
import type { Meta, StoryObj } from '@storybook/nextjs-vite'
|
||||
import Tag from '.'
|
||||
|
||||
const COLORS: Array<NonNullable<React.ComponentProps<typeof Tag>['color']>> = ['green', 'yellow', 'red', 'gray']
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import type { Meta, StoryObj } from '@storybook/nextjs'
|
||||
import type { Meta, StoryObj } from '@storybook/nextjs-vite'
|
||||
import { useState } from 'react'
|
||||
import Textarea from '.'
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user