output-panel.tsx 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112
  1. 'use client'
  2. import type { FC } from 'react'
  3. import { useMemo } from 'react'
  4. import CodeEditor from '@/app/components/workflow/nodes/_base/components/editor/code-editor'
  5. import { CodeLanguage } from '@/app/components/workflow/nodes/code/types'
  6. import { Markdown } from '@/app/components/base/markdown'
  7. import LoadingAnim from '@/app/components/base/chat/chat/loading-anim'
  8. import { FileList } from '@/app/components/base/file-uploader'
  9. import StatusContainer from '@/app/components/workflow/run/status-container'
  10. import { getProcessedFilesFromResponse } from '@/app/components/base/file-uploader/utils'
  11. type OutputPanelProps = {
  12. isRunning?: boolean
  13. outputs?: any
  14. error?: string
  15. height?: number
  16. }
  17. const OutputPanel: FC<OutputPanelProps> = ({
  18. isRunning,
  19. outputs,
  20. error,
  21. height,
  22. }) => {
  23. const isTextOutput = useMemo(() => {
  24. if (!outputs || typeof outputs !== 'object')
  25. return false
  26. const keys = Object.keys(outputs)
  27. const value = outputs[keys[0]]
  28. return keys.length === 1 && (
  29. typeof value === 'string'
  30. || (Array.isArray(value) && value.every(item => typeof item === 'string'))
  31. )
  32. }, [outputs])
  33. const fileList = useMemo(() => {
  34. const fileList: any[] = []
  35. if (!outputs)
  36. return fileList
  37. if (Object.keys(outputs).length > 1)
  38. return fileList
  39. for (const key in outputs) {
  40. if (Array.isArray(outputs[key])) {
  41. outputs[key].map((output: any) => {
  42. if (output?.dify_model_identity === '__dify__file__')
  43. fileList.push(output)
  44. return null
  45. })
  46. }
  47. else if (outputs[key]?.dify_model_identity === '__dify__file__') {
  48. fileList.push(outputs[key])
  49. }
  50. }
  51. return getProcessedFilesFromResponse(fileList)
  52. }, [outputs])
  53. return (
  54. <div className='p-2'>
  55. {isRunning && (
  56. <div className='pt-4 pl-[26px]'>
  57. <LoadingAnim type='text' />
  58. </div>
  59. )}
  60. {!isRunning && error && (
  61. <div className='px-4'>
  62. <StatusContainer status='failed'>{error}</StatusContainer>
  63. </div>
  64. )}
  65. {!isRunning && !outputs && (
  66. <div className='px-4 py-2'>
  67. <Markdown content='No Output' />
  68. </div>
  69. )}
  70. {isTextOutput && (
  71. <div className='px-4 py-2'>
  72. <Markdown
  73. content={
  74. Array.isArray(outputs[Object.keys(outputs)[0]])
  75. ? outputs[Object.keys(outputs)[0]].join('\n')
  76. : (outputs[Object.keys(outputs)[0]] || '')
  77. }
  78. />
  79. </div>
  80. )}
  81. {fileList.length > 0 && (
  82. <div className='px-4 py-2'>
  83. <FileList
  84. files={fileList}
  85. showDeleteAction={false}
  86. showDownloadAction
  87. canPreview
  88. />
  89. </div>
  90. )}
  91. {!isTextOutput && outputs && Object.keys(outputs).length > 0 && height! > 0 && (
  92. <div className='flex flex-col gap-2'>
  93. <CodeEditor
  94. showFileList
  95. readOnly
  96. title={<div tabIndex={0}>Output</div>}
  97. language={CodeLanguage.json}
  98. value={JSON.stringify(outputs, null, 2)}
  99. isJSONStringifyBeauty
  100. height={height ? (height - 16) / 2 : undefined}
  101. />
  102. </div>
  103. )}
  104. </div>
  105. )
  106. }
  107. export default OutputPanel