您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243
  1. 'use client'
  2. import { useEffect, useMemo, useState } from 'react'
  3. import { useContext } from 'use-context-selector'
  4. import { useTranslation } from 'react-i18next'
  5. import { RiCloseLine, RiListUnordered } from '@remixicon/react'
  6. import TemplateEn from './template/template.en.mdx'
  7. import TemplateZh from './template/template.zh.mdx'
  8. import TemplateJa from './template/template.ja.mdx'
  9. import TemplateAdvancedChatEn from './template/template_advanced_chat.en.mdx'
  10. import TemplateAdvancedChatZh from './template/template_advanced_chat.zh.mdx'
  11. import TemplateAdvancedChatJa from './template/template_advanced_chat.ja.mdx'
  12. import TemplateWorkflowEn from './template/template_workflow.en.mdx'
  13. import TemplateWorkflowZh from './template/template_workflow.zh.mdx'
  14. import TemplateWorkflowJa from './template/template_workflow.ja.mdx'
  15. import TemplateChatEn from './template/template_chat.en.mdx'
  16. import TemplateChatZh from './template/template_chat.zh.mdx'
  17. import TemplateChatJa from './template/template_chat.ja.mdx'
  18. import I18n from '@/context/i18n'
  19. import { LanguagesSupported } from '@/i18n-config/language'
  20. import useTheme from '@/hooks/use-theme'
  21. import { Theme } from '@/types/app'
  22. import cn from '@/utils/classnames'
  23. type IDocProps = {
  24. appDetail: any
  25. }
  26. const Doc = ({ appDetail }: IDocProps) => {
  27. const { locale } = useContext(I18n)
  28. const { t } = useTranslation()
  29. const [toc, setToc] = useState<Array<{ href: string; text: string }>>([])
  30. const [isTocExpanded, setIsTocExpanded] = useState(false)
  31. const [activeSection, setActiveSection] = useState<string>('')
  32. const { theme } = useTheme()
  33. const variables = appDetail?.model_config?.configs?.prompt_variables || []
  34. const inputs = variables.reduce((res: any, variable: any) => {
  35. res[variable.key] = variable.name || ''
  36. return res
  37. }, {})
  38. useEffect(() => {
  39. const mediaQuery = window.matchMedia('(min-width: 1280px)')
  40. setIsTocExpanded(mediaQuery.matches)
  41. }, [])
  42. useEffect(() => {
  43. const extractTOC = () => {
  44. const article = document.querySelector('article')
  45. if (article) {
  46. const headings = article.querySelectorAll('h2')
  47. const tocItems = Array.from(headings).map((heading) => {
  48. const anchor = heading.querySelector('a')
  49. if (anchor) {
  50. return {
  51. href: anchor.getAttribute('href') || '',
  52. text: anchor.textContent || '',
  53. }
  54. }
  55. return null
  56. }).filter((item): item is { href: string; text: string } => item !== null)
  57. setToc(tocItems)
  58. if (tocItems.length > 0)
  59. setActiveSection(tocItems[0].href.replace('#', ''))
  60. }
  61. }
  62. setTimeout(extractTOC, 0)
  63. }, [appDetail, locale])
  64. useEffect(() => {
  65. const handleScroll = () => {
  66. const scrollContainer = document.querySelector('.overflow-auto')
  67. if (!scrollContainer || toc.length === 0)
  68. return
  69. let currentSection = ''
  70. toc.forEach((item) => {
  71. const targetId = item.href.replace('#', '')
  72. const element = document.getElementById(targetId)
  73. if (element) {
  74. const rect = element.getBoundingClientRect()
  75. if (rect.top <= window.innerHeight / 2)
  76. currentSection = targetId
  77. }
  78. })
  79. if (currentSection && currentSection !== activeSection)
  80. setActiveSection(currentSection)
  81. }
  82. const scrollContainer = document.querySelector('.overflow-auto')
  83. if (scrollContainer) {
  84. scrollContainer.addEventListener('scroll', handleScroll)
  85. handleScroll()
  86. return () => scrollContainer.removeEventListener('scroll', handleScroll)
  87. }
  88. }, [toc, activeSection])
  89. const handleTocClick = (e: React.MouseEvent<HTMLAnchorElement>, item: { href: string; text: string }) => {
  90. e.preventDefault()
  91. const targetId = item.href.replace('#', '')
  92. const element = document.getElementById(targetId)
  93. if (element) {
  94. const scrollContainer = document.querySelector('.overflow-auto')
  95. if (scrollContainer) {
  96. const headerOffset = 80
  97. const elementTop = element.offsetTop - headerOffset
  98. scrollContainer.scrollTo({
  99. top: elementTop,
  100. behavior: 'smooth',
  101. })
  102. }
  103. }
  104. }
  105. const Template = useMemo(() => {
  106. if (appDetail?.mode === 'chat' || appDetail?.mode === 'agent-chat') {
  107. switch (locale) {
  108. case LanguagesSupported[1]:
  109. return <TemplateChatZh appDetail={appDetail} variables={variables} inputs={inputs} />
  110. case LanguagesSupported[7]:
  111. return <TemplateChatJa appDetail={appDetail} variables={variables} inputs={inputs} />
  112. default:
  113. return <TemplateChatEn appDetail={appDetail} variables={variables} inputs={inputs} />
  114. }
  115. }
  116. if (appDetail?.mode === 'advanced-chat') {
  117. switch (locale) {
  118. case LanguagesSupported[1]:
  119. return <TemplateAdvancedChatZh appDetail={appDetail} variables={variables} inputs={inputs} />
  120. case LanguagesSupported[7]:
  121. return <TemplateAdvancedChatJa appDetail={appDetail} variables={variables} inputs={inputs} />
  122. default:
  123. return <TemplateAdvancedChatEn appDetail={appDetail} variables={variables} inputs={inputs} />
  124. }
  125. }
  126. if (appDetail?.mode === 'workflow') {
  127. switch (locale) {
  128. case LanguagesSupported[1]:
  129. return <TemplateWorkflowZh appDetail={appDetail} variables={variables} inputs={inputs} />
  130. case LanguagesSupported[7]:
  131. return <TemplateWorkflowJa appDetail={appDetail} variables={variables} inputs={inputs} />
  132. default:
  133. return <TemplateWorkflowEn appDetail={appDetail} variables={variables} inputs={inputs} />
  134. }
  135. }
  136. if (appDetail?.mode === 'completion') {
  137. switch (locale) {
  138. case LanguagesSupported[1]:
  139. return <TemplateZh appDetail={appDetail} variables={variables} inputs={inputs} />
  140. case LanguagesSupported[7]:
  141. return <TemplateJa appDetail={appDetail} variables={variables} inputs={inputs} />
  142. default:
  143. return <TemplateEn appDetail={appDetail} variables={variables} inputs={inputs} />
  144. }
  145. }
  146. return null
  147. }, [appDetail, locale, variables, inputs])
  148. return (
  149. <div className="flex">
  150. <div className={`fixed right-20 top-32 z-10 transition-all duration-150 ease-out ${isTocExpanded ? 'w-[280px]' : 'w-11'}`}>
  151. {isTocExpanded
  152. ? (
  153. <nav className="toc flex max-h-[calc(100vh-150px)] w-full flex-col overflow-hidden rounded-xl border-[0.5px] border-components-panel-border bg-background-default-hover shadow-xl">
  154. <div className="relative z-10 flex items-center justify-between border-b border-components-panel-border-subtle bg-background-default-hover px-4 py-2.5">
  155. <span className="text-xs font-medium uppercase tracking-wide text-text-tertiary">
  156. {t('appApi.develop.toc')}
  157. </span>
  158. <button type="button"
  159. onClick={() => setIsTocExpanded(false)}
  160. className="group flex h-6 w-6 items-center justify-center rounded-md transition-colors hover:bg-state-base-hover"
  161. aria-label="Close"
  162. >
  163. <RiCloseLine className="h-3 w-3 text-text-quaternary transition-colors group-hover:text-text-secondary" />
  164. </button>
  165. </div>
  166. <div className="from-components-panel-border-subtle/20 pointer-events-none absolute left-0 right-0 top-[41px] z-10 h-2 bg-gradient-to-b to-transparent"></div>
  167. <div className="pointer-events-none absolute left-0 right-0 top-[43px] z-10 h-3 bg-gradient-to-b from-background-default-hover to-transparent"></div>
  168. <div className="relative flex-1 overflow-y-auto px-3 py-3 pt-1">
  169. {toc.length === 0 ? (
  170. <div className="px-2 py-8 text-center text-xs text-text-quaternary">
  171. {t('appApi.develop.noContent')}
  172. </div>
  173. ) : (
  174. <ul className="space-y-0.5">
  175. {toc.map((item, index) => {
  176. const isActive = activeSection === item.href.replace('#', '')
  177. return (
  178. <li key={index}>
  179. <a
  180. href={item.href}
  181. onClick={e => handleTocClick(e, item)}
  182. className={cn(
  183. 'group relative flex items-center rounded-md px-3 py-2 text-[13px] transition-all duration-200',
  184. isActive
  185. ? 'bg-state-base-hover font-medium text-text-primary'
  186. : 'text-text-tertiary hover:bg-state-base-hover hover:text-text-secondary',
  187. )}
  188. >
  189. <span
  190. className={cn(
  191. 'mr-2 h-1.5 w-1.5 rounded-full transition-all duration-200',
  192. isActive
  193. ? 'scale-100 bg-text-accent'
  194. : 'scale-75 bg-components-panel-border',
  195. )}
  196. />
  197. <span className="flex-1 truncate">
  198. {item.text}
  199. </span>
  200. </a>
  201. </li>
  202. )
  203. })}
  204. </ul>
  205. )}
  206. </div>
  207. <div className="pointer-events-none absolute bottom-0 left-0 right-0 z-10 h-4 rounded-b-xl bg-gradient-to-t from-background-default-hover to-transparent"></div>
  208. </nav>
  209. )
  210. : (
  211. <button type="button"
  212. onClick={() => setIsTocExpanded(true)}
  213. className="group flex h-11 w-11 items-center justify-center rounded-full border-[0.5px] border-components-panel-border bg-components-panel-bg shadow-lg transition-all duration-150 hover:bg-background-default-hover hover:shadow-xl"
  214. aria-label="Open table of contents"
  215. >
  216. <RiListUnordered className="h-5 w-5 text-text-tertiary transition-colors group-hover:text-text-secondary" />
  217. </button>
  218. )}
  219. </div>
  220. <article className={cn('prose-xl prose', theme === Theme.dark && 'prose-invert')}>
  221. {Template}
  222. </article>
  223. </div>
  224. )
  225. }
  226. export default Doc