chat.stories.jsx 35 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046
  1. import { getUuidv4 } from '@douyinfe/semi-foundation/utils/uuid';
  2. import Chat from '../index';
  3. import React, { useState, useCallback, useRef, useEffect, useMemo } from 'react';
  4. import { Form, Button, Avatar, Dropdown, Radio, RadioGroup, Switch, Collapsible, AvatarGroup, Divider } from '@douyinfe/semi-ui';
  5. import { IconUpload, IconForward, IconMoreStroked, IconArrowRight, IconChevronUp } from '@douyinfe/semi-icons';
  6. import MarkdownRender from '../../markdownRender';
  7. import { initMessage, roleInfo, commonOuterStyle, hintsExample, infoWithAttachment, simpleInitMessage, semiCode, infoWithDivider, infoWithJSX, tableContent } from './constant';
  8. export default {
  9. title: 'Chat',
  10. parameters: {
  11. chromatic: { disableSnapshot: true },
  12. }
  13. }
  14. const uploadProps = { action: 'https://api.semi.design/upload' }
  15. export const _Chat = () => {
  16. const [message, setMessage] = useState(initMessage);
  17. const [hints, setHints] = useState(hintsExample);
  18. const [mode, setMode] = useState('bubble');
  19. const [align, setAlign] = useState('leftRight');
  20. const [sendHotKey, setSendHotKey] = useState('enter');
  21. const [key, setKey] = useState(1);
  22. const [showClearContext, setShowClearContext] = useState(false);
  23. const onClear = useCallback((clearMessage) => {
  24. console.log('onClear');
  25. }, []);
  26. const onMessageSend = useCallback((content, attachment) => {
  27. const newAssistantMessage = {
  28. role: 'assistant',
  29. id: getUuidv4(),
  30. content: "这是一条 mock 回复信息",
  31. }
  32. setMessage((message) => {
  33. return [
  34. ...message,
  35. newAssistantMessage
  36. ]
  37. })
  38. }, []);
  39. const onMessageDelete = useCallback((message) => {
  40. console.log('message delete', message);
  41. }, []);
  42. const onChatsChange = useCallback((chats) => {
  43. console.log('onChatsChange', chats);
  44. setMessage(chats);
  45. }, []);
  46. const onMessageGoodFeedback = useCallback((message) => {
  47. console.log('message good feedback', message);
  48. }, []);
  49. const onMessageBadFeedback = useCallback((message) => {
  50. console.log('message bad feedback', message);
  51. }, []);
  52. const onMessageReset = useCallback((message) => {
  53. console.log('message reset', message);
  54. }, []);
  55. const onInputChange = useCallback((props) => {
  56. console.log('onInputChange', props);
  57. }, []);
  58. const onHintClick = useCallback((hint) => {
  59. setHints([]);
  60. }, []);
  61. const onModeChange = useCallback((e) => {
  62. setMode(e.target.value);
  63. setKey((key) => key + 1);
  64. }, []);
  65. const onAlignChange = useCallback((e) => {
  66. setAlign(e.target.value);
  67. setKey((key) => key + 1);
  68. }, []);
  69. const onSwitchChange = useCallback(() => {
  70. setShowClearContext((showClearContext) => !showClearContext);
  71. }, [])
  72. const onSendHotKeyChange = useCallback((e) => {
  73. setSendHotKey(e.target.value);
  74. }, []);
  75. return (
  76. <>
  77. <div style={{margin: 10, display: 'flex', flexDirection: 'column', rowGap: 5}}>
  78. <span style={{ display: 'flex', alignItems: 'center', columnGap: '10px'}}>
  79. 展示清除上下文按钮:
  80. <Switch checked={showClearContext} onChange={onSwitchChange}/>
  81. </span>
  82. <span style={{ display: 'flex', alignItems: 'center', columnGap: '10px'}}>
  83. 模式:
  84. <RadioGroup onChange={onModeChange} value={mode} type="button">
  85. <Radio value={'bubble'}>气泡</Radio>
  86. <Radio value={'noBubble'}>非气泡</Radio>
  87. <Radio value={'userBubble'}>用户会话气泡</Radio>
  88. </RadioGroup>
  89. </span>
  90. <span style={{ display: 'flex', alignItems: 'center', columnGap: '10px'}}>
  91. 布局:
  92. <RadioGroup onChange={onAlignChange} value={align} type="button">
  93. <Radio value={'leftRight'}>左右分布</Radio>
  94. <Radio value={'leftAlign'}>全左</Radio>
  95. </RadioGroup>
  96. </span>
  97. <span style={{ display: 'flex', alignItems: 'center', columnGap: '10px'}}>
  98. 按键发送策略:
  99. <RadioGroup onChange={onSendHotKeyChange} value={sendHotKey} type="button">
  100. <Radio value={'enter'}>enter</Radio>
  101. <Radio value={'shift+enter'}>shift+enter</Radio>
  102. </RadioGroup>
  103. </span>
  104. </div>
  105. <div style={{ height: 650}}>
  106. <Chat
  107. key={key}
  108. style={commonOuterStyle}
  109. chats={message}
  110. hints={hints}
  111. roleConfig={roleInfo}
  112. onClear={onClear}
  113. onMessageSend={onMessageSend}
  114. onMessageDelete={onMessageDelete}
  115. onMessageGoodFeedback={onMessageGoodFeedback}
  116. onMessageBadFeedback={onMessageBadFeedback}
  117. onChatsChange={onChatsChange}
  118. onMessageReset={onMessageReset}
  119. onInputChange={onInputChange}
  120. onHintClick={onHintClick}
  121. uploadProps={uploadProps}
  122. uploadTipProps={{
  123. content: '自定义输入提示'
  124. }}
  125. mode={mode}
  126. align={align}
  127. sendHotKey={sendHotKey}
  128. showClearContext={showClearContext}
  129. />
  130. </div>
  131. </>
  132. )
  133. }
  134. export const TableContent = () => {
  135. const [message, setMessage] = useState(tableContent);
  136. return (
  137. <div
  138. style={{ height: 600, width: 400}}
  139. >
  140. <Chat
  141. placeholder={'不处理输入信息,仅用于展示'}
  142. style={commonOuterStyle}
  143. chats={message}
  144. roleConfig={roleInfo}
  145. uploadProps={uploadProps}
  146. />
  147. </div>
  148. )
  149. }
  150. export const Attachment = () => {
  151. const [message, setMessage] = useState(infoWithAttachment);
  152. return (
  153. <div
  154. style={{ height: 600}}
  155. >
  156. <Chat
  157. placeholder={'不处理输入信息,仅用于展示附件'}
  158. style={commonOuterStyle}
  159. chats={message}
  160. roleConfig={roleInfo}
  161. uploadProps={uploadProps}
  162. />
  163. </div>
  164. )
  165. }
  166. function CustomInputRender(props) {
  167. const { defaultNode, onClear, onSend } = props;
  168. const api = useRef();
  169. const onSubmit = useCallback(() => {
  170. if (api.current) {
  171. const values = api.current.getValues();
  172. if ((values.name && values.name.length !== 0) || (values.file && values.file.length !== 0)) {
  173. onSend(values.name, values.file);
  174. api.current.reset();
  175. }
  176. }
  177. }, []);
  178. return (<div style={{
  179. display: 'flex',
  180. flexDirection: 'column',
  181. border: '1px solid var(--semi-color-border)',
  182. margin: '8px 16px',
  183. borderRadius: 8,
  184. padding: 8
  185. }}>
  186. <Form
  187. getFormApi={formApi => api.current = formApi}
  188. >
  189. <strong>输入信息</strong>
  190. <Form.Input
  191. field="name"
  192. label="名称(Input)"
  193. style={{ width: 250 }}
  194. trigger='blur'
  195. />
  196. <Form.Upload
  197. field='file'
  198. label='文档'
  199. action='https://api.semi.design/upload'
  200. >
  201. <Button icon={<IconUpload />} theme="light">
  202. 点击上传
  203. </Button>
  204. </Form.Upload>
  205. </Form>
  206. <Button style={{ width: 'fit-content' }} onClick={onSubmit}>提交</Button>
  207. </div>);
  208. }
  209. export const CustomRenderInputArea = () => {
  210. const [message, setMessage] = useState(initMessage.slice(0, 1));
  211. const onChatsChange = useCallback((chats) => {
  212. setMessage(chats);
  213. }, []);
  214. const onMessageSend = useCallback((content, attachment) => {
  215. const newUserMessage = {
  216. role: 'user',
  217. id: getUuidv4(),
  218. content: content,
  219. attachment: attachment
  220. }
  221. const newAssistantMessage = {
  222. role: 'assistant',
  223. id: getUuidv4(),
  224. content: `This is a mock response`
  225. }
  226. setMessage((message) => ([...message, newUserMessage, newAssistantMessage]));
  227. }, []);
  228. const renderInputArea = useCallback((props) => {
  229. return (<CustomInputRender {...props} />)
  230. }, []);
  231. return (
  232. <div
  233. style={{ height: 600}}
  234. >
  235. <Chat
  236. style={commonOuterStyle}
  237. chats={message}
  238. roleConfig={roleInfo}
  239. onChatsChange={onChatsChange}
  240. onMessageSend={onMessageSend}
  241. renderInputArea={renderInputArea}
  242. uploadProps={uploadProps}
  243. />
  244. </div>
  245. )
  246. }
  247. function CustomInputRender2(props) {
  248. const { defaultNode, onClear, onSend, detailProps } = props;
  249. const { clearContextNode, uploadNode, inputNode, sendNode, onClick } = detailProps;
  250. return <div style={{margin: '8px 16px', display: 'flex', flexDirection:'row',
  251. alignItems: 'flex-end', borderRadius: 16,padding: 10, border: '1px solid var(--semi-color-border)'}}
  252. onClick={onClick}
  253. >
  254. {uploadNode}
  255. {inputNode}
  256. {sendNode}
  257. </div>
  258. }
  259. export const CustomRenderInputArea2 = () => {
  260. const [message, setMessage] = useState(initMessage.slice(0, 1));
  261. const onChatsChange = useCallback((chats) => {
  262. setMessage(chats);
  263. }, []);
  264. const onMessageSend = useCallback((content, attachment) => {
  265. const newAssistantMessage = {
  266. role: 'assistant',
  267. id: getUuidv4(),
  268. content: `This is a mock response`
  269. }
  270. setMessage((message) => ([...message, newAssistantMessage]));
  271. }, []);
  272. const renderInputArea = useCallback((props) => {
  273. return (<CustomInputRender2 {...props} />)
  274. }, []);
  275. return (
  276. <div
  277. style={{ height: 600}}
  278. >
  279. <Chat
  280. style={commonOuterStyle}
  281. chats={message}
  282. roleConfig={roleInfo}
  283. onChatsChange={onChatsChange}
  284. onMessageSend={onMessageSend}
  285. renderInputArea={renderInputArea}
  286. uploadProps={uploadProps}
  287. />
  288. </div>
  289. )
  290. }
  291. export const CustomRenderAvatar = (props) => {
  292. const customRenderAvatar = useCallback((props)=> {
  293. const { role, defaultAvatar } = props;
  294. return <Avatar size="extra-small" shape="square" style={{ flexShrink: '0'}}>{role.name}</Avatar >
  295. }, []);
  296. const customRenderTitle = useCallback((props)=> null, []);
  297. return (<div
  298. style={{ height: 600 }}
  299. >
  300. <Chat
  301. style={commonOuterStyle}
  302. chats={initMessage.slice(0, 4)}
  303. roleConfig={roleInfo}
  304. chatBoxRenderConfig={{
  305. renderChatBoxTitle: customRenderTitle,
  306. renderChatBoxAvatar: customRenderAvatar
  307. }}
  308. uploadProps={uploadProps}
  309. />
  310. </div>);
  311. }
  312. export const CustomRenderTitle = (props) => {
  313. const customRenderTitle = useCallback((props) => {
  314. const { role, message, defaultTitle } = props;
  315. if (message.role === 'user') {
  316. return null;
  317. }
  318. return <span style={{ display:' flex', alignItems: 'center', justifyContent: 'center', columnGap: '10px'}}>
  319. <Avatar size="extra-small" shape="square" src={role.avatar} />
  320. {defaultTitle}
  321. </span>
  322. }, []);
  323. const customRenderAvatar = useCallback((props)=> null, []);
  324. return (<div
  325. style={{ height: 600}}
  326. >
  327. <Chat
  328. placeholder={"不处理输入信息,仅用于展示自定义头像和标题"}
  329. style={commonOuterStyle}
  330. chats={simpleInitMessage}
  331. roleConfig={roleInfo}
  332. chatBoxRenderConfig={{
  333. renderChatBoxTitle: customRenderTitle,
  334. renderChatBoxAvatar: customRenderAvatar
  335. }}
  336. uploadProps={uploadProps}
  337. />
  338. </div>);
  339. }
  340. export const CustomFullChatBox = () => {
  341. const customRenderChatBox = useCallback((props) => {
  342. const { role, message, defaultNodes, className } = props;
  343. const date = new Date(message.createAt);
  344. const year = date.getFullYear();
  345. const month = ('0' + (date.getMonth() + 1)).slice(-2);
  346. const day = ('0' + date.getDate()).slice(-2);
  347. const hours = ('0' + date.getHours()).slice(-2);
  348. const minutes = ('0' + date.getMinutes()).slice(-2);
  349. const seconds = ('0' + date.getSeconds()).slice(-2);
  350. const formattedDate = `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
  351. return <div className={className}>
  352. <div style={{ display: 'flex', flexDirection: 'column', rowGap: 5, alignItems: message.role === 'user' ? 'end' : ''}}>
  353. <span style={{color: 'var(--semi-color-text-2', fontSize: '12px'}}>{formattedDate}</span>
  354. <div style={{ width: 'fit-content'}}>
  355. {defaultNodes.content}
  356. </div>
  357. {defaultNodes.action}
  358. </div>
  359. </div>
  360. }, []);
  361. return (<div
  362. style={{ height: 600}}
  363. >
  364. <Chat
  365. style={commonOuterStyle}
  366. chats={simpleInitMessage}
  367. roleConfig={roleInfo}
  368. chatBoxRenderConfig={{
  369. renderFullChatBox: customRenderChatBox
  370. }}
  371. uploadProps={uploadProps}
  372. />
  373. </div>);
  374. }
  375. const CustomActions = React.memo((props) => {
  376. const { role, message, defaultActions, className } = props;
  377. const myRef = useRef();
  378. const getContainer = useCallback(() => {
  379. if (myRef.current) {
  380. const element = myRef.current;
  381. let parentElement = element.parentElement;
  382. while (parentElement) {
  383. if (parentElement.classList.contains('semi-chat-chatBox-wrap')) {
  384. return parentElement;
  385. }
  386. parentElement = parentElement.parentElement;
  387. }
  388. }
  389. }, [myRef]);
  390. return <span
  391. className={className}
  392. ref={myRef}
  393. >
  394. {defaultActions.map((item, index)=> {
  395. return <span key={index}>{item}</span>
  396. })}
  397. {<Dropdown
  398. key="dropdown"
  399. render={
  400. <Dropdown.Menu >
  401. <Dropdown.Item icon={<IconForward />}>分享</Dropdown.Item>
  402. </Dropdown.Menu>
  403. }
  404. trigger="click"
  405. position="top"
  406. getPopupContainer={getContainer}
  407. >
  408. <Button
  409. className='semi-chat-chatBox-action-btn'
  410. icon={<IconMoreStroked/>}
  411. theme='borderless'
  412. type='tertiary'
  413. />
  414. </Dropdown>}
  415. </span>
  416. });
  417. export const CustomRenderAction = () => {
  418. const [message, setMessage] = useState(simpleInitMessage);
  419. const customRenderAction = useCallback((props) => {
  420. return <CustomActions {...props} />
  421. }, []);
  422. const onChatsChange = useCallback((chats) => {
  423. setMessage(chats);
  424. }, []);
  425. return (<div
  426. style={{ height: 600}}
  427. >
  428. <Chat
  429. chats={message}
  430. onChatsChange={onChatsChange}
  431. style={commonOuterStyle}
  432. roleConfig={roleInfo}
  433. chatBoxRenderConfig={{
  434. renderChatBoxAction: customRenderAction
  435. }}
  436. uploadProps={uploadProps}
  437. />
  438. </div>);
  439. }
  440. export const CustomRenderContent = () => {
  441. const renderContent = useCallback((props) => {
  442. const { role, message, defaultNode, className } = props;
  443. return <div className={className}>
  444. <span>---custom render content---</span>
  445. <MarkdownRender raw={message?.content}/>
  446. </div>
  447. }, []);
  448. return (<div
  449. style={{ height: 600}}
  450. >
  451. <Chat
  452. style={commonOuterStyle}
  453. chats={simpleInitMessage}
  454. roleConfig={roleInfo}
  455. chatBoxRenderConfig={{
  456. renderChatBoxContent: renderContent
  457. }}
  458. uploadProps={uploadProps}
  459. />
  460. </div>);
  461. }
  462. // const Card = (source) => {
  463. // return (<span className="demo-card">
  464. // <span className="demo-card-title"></span>
  465. // <span className="demo-card-link"></span>
  466. // <span className="demo-card-content"></span>
  467. // </span>)
  468. // }
  469. const SourceCard = (props) => {
  470. const [open, setOpen] = useState(true);
  471. const [show, setShow] = useState(false);
  472. const spanRef = useRef();
  473. const onOpen = useCallback(() => {
  474. setOpen(false);
  475. setShow(true);
  476. }, []);
  477. const onClose = useCallback(() => {
  478. setOpen(true);
  479. setTimeout(() => {
  480. setShow(false);
  481. }, 350)
  482. }, []);
  483. return (<div style={{
  484. transition: open ? 'height 0.4s ease, width 0.4s ease': 'height 0.4s ease',
  485. height: open ? '30px' : '184px',
  486. width: open ? '237px': '100%',
  487. background: 'var(--semi-color-tertiary-light-hover)',
  488. borderRadius: 16,
  489. boxSizing: 'border-box',
  490. }}
  491. >
  492. <span
  493. ref={spanRef}
  494. style={{
  495. display: !open ? 'none' : 'flex',
  496. width: 'fit-content',
  497. columnGap: 10,
  498. background: 'var(--semi-color-tertiary-light-hover)',
  499. borderRadius: '16px',
  500. padding: '5px 10px',
  501. point: 'cursor',
  502. fontSize: 14,
  503. color: 'var(--semi-color-text-1)',
  504. }}
  505. onClick={onOpen}
  506. >
  507. <span>基于{props.sources.length}个搜索来源</span>
  508. <AvatarGroup size="extra-extra-small" >
  509. {props.sources.map((s, index) => (<Avatar key={index} src={s.avatar}></Avatar>))}
  510. </AvatarGroup>
  511. </span>
  512. <span
  513. style={{
  514. height: '100%',
  515. boxSizing: 'border-box',
  516. display: !open ? 'flex' : 'none',
  517. flexDirection: 'column',
  518. background: 'var(--semi-color-tertiary-light-hover)', borderRadius: '16px', padding: 12, boxSize: 'border-box'
  519. }}
  520. onClick={onClose}
  521. >
  522. <span style={{display: 'flex', alignItems: 'center', justifyContent: 'space-between',
  523. padding: '5px 10px', columnGap: 10, color: 'var(--semi-color-text-1)'
  524. }}>
  525. <span style={{fontSize: 14, fontWeight: 500}}>Source</span>
  526. <IconChevronUp />
  527. </span>
  528. <span style={{display: 'flex', flexWrap: 'wrap', gap: 10, overflow: 'scroll', padding: '5px 10px'}}>
  529. {props.sources.map(s => (
  530. <span style={{
  531. display: 'flex',
  532. flexDirection: 'column',
  533. rowGap: 5,
  534. flexBasis: 150,
  535. flexGrow: 1,
  536. border: "1px solid var(--semi-color-border)",
  537. borderRadius: 12,
  538. padding: 12,
  539. fontSize: 12
  540. }}>
  541. <span style={{display: 'flex', columnGap: 5, alignItems: 'center', }}>
  542. <Avatar style={{width: 16, height: 16, flexShrink: 0 }} shape="square" src={s.avatar} />
  543. <span style={{ color: 'var(--semi-color-text-2)', textOverflow: 'ellipsis'}}>{s.title}</span>
  544. </span>
  545. <span style={{
  546. color: 'var(--semi-color-primary)',
  547. fontSize: 12,
  548. }}
  549. >{s.subTitle}</span>
  550. <span style={{
  551. display: '-webkit-box',
  552. "-webkit-box-orient": 'vertical',
  553. WebkitLineClamp: '3',
  554. textOverflow: 'ellipsis',
  555. overflow: 'hidden',
  556. color: 'var(--semi-color-text-2)',
  557. }}>{s.content}</span>
  558. </span>))}
  559. </span>
  560. </span>
  561. </div>
  562. )
  563. }
  564. export const CustomRenderContentPlus = () => {
  565. const chat = [
  566. {
  567. role: 'assistant',
  568. id: '3',
  569. createAt: 1715676751919,
  570. content: "Semi Design 是由抖音前端团队,MED 产品设计团队设计、开发并维护的设计系统。它作为全面、易用、优质的现代应用 UI 解决方案,从字节跳动各业务线的复杂场景提炼而来,支撑近千计平台产品,服务内外部 10 万+ 用户。",
  571. source: [
  572. {
  573. avatar: 'https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/other/logo.png',
  574. url: '/zh-CN/start/introduction',
  575. title: 'semi Design',
  576. subTitle: 'Semi design website',
  577. content: 'Semi Design 是由抖音前端团队,MED 产品设计团队设计、开发并维护的设计系统。'
  578. },
  579. {
  580. avatar: 'https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/other/logo.png',
  581. url: '/dsm/landing',
  582. subTitle: 'Semi DSM website',
  583. title: 'Semi 设计系统',
  584. content: '从 Semi Design,到 Any Design 快速定义你的设计系统,并应用在设计稿和代码中'
  585. },
  586. {
  587. avatar: 'https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/other/logo.png',
  588. url: '/code/zh-CN/start/introduction',
  589. subTitle: 'Semi D2C website',
  590. title: '设计稿转代码',
  591. content: 'Semi 设计稿转代码(Semi Design to Code,或简称 Semi D2C),是由抖音前端 Semi Design 团队推出的全新的提效工具'
  592. },
  593. {
  594. avatar: 'https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/other/logo.png',
  595. url: '/zh-CN/start/introduction',
  596. title: 'semi Design',
  597. subTitle: 'Semi design website',
  598. content: 'Semi Design 是由抖音前端团队,MED 产品设计团队设计、开发并维护的设计系统。'
  599. },
  600. {
  601. avatar: 'https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/other/logo.png',
  602. url: '/dsm/landing',
  603. subTitle: 'Semi DSM website',
  604. title: 'Semi 设计系统',
  605. content: '从 Semi Design,到 Any Design 快速定义你的设计系统,并应用在设计稿和代码中'
  606. },
  607. {
  608. avatar: 'https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/other/logo.png',
  609. url: '/code/zh-CN/start/introduction',
  610. subTitle: 'Semi D2C website',
  611. title: '设计稿转代码',
  612. content: 'Semi 设计稿转代码(Semi Design to Code,或简称 Semi D2C),是由抖音前端 Semi Design 团队推出的全新的提效工具'
  613. },
  614. ]
  615. }];
  616. const renderContent = useCallback((props) => {
  617. const { role, message, defaultNode, className } = props;
  618. return <div className={className}>
  619. <SourceCard sources={message?.source} />
  620. <MarkdownRender raw={message?.content}/>
  621. </div>
  622. }, []);
  623. return (<div
  624. style={{ height: 600}}
  625. >
  626. <Chat
  627. style={commonOuterStyle}
  628. chats={chat}
  629. roleConfig={roleInfo}
  630. chatBoxRenderConfig={{
  631. renderChatBoxContent: renderContent
  632. }}
  633. uploadProps={uploadProps}
  634. />
  635. <div></div>
  636. </div>);
  637. }
  638. export const LeftAlign = () => {
  639. return (<div
  640. style={{ height: 600}}
  641. >
  642. <Chat
  643. style={commonOuterStyle}
  644. chats={simpleInitMessage}
  645. roleConfig={roleInfo}
  646. align='leftAlign'
  647. uploadProps={uploadProps}
  648. />
  649. </div>);
  650. }
  651. export const MessageStatus = () => {
  652. const messages = [
  653. initMessage[1],
  654. {
  655. ...initMessage[2],
  656. content: '请求错误',
  657. status: 'error'
  658. },
  659. {
  660. id: 'loading',
  661. role: 'assistant',
  662. status: 'loading'
  663. },
  664. ]
  665. return (<div
  666. style={{ height: 600}}
  667. >
  668. <Chat
  669. style={commonOuterStyle}
  670. chats={messages}
  671. roleConfig={roleInfo}
  672. uploadProps={uploadProps}
  673. />
  674. </div>);
  675. }
  676. export const MockResponseMessage = () => {
  677. const [message, setMessage] = useState([ initMessage[0]]);
  678. const intervalId = useRef();
  679. const onChatsChange = useCallback((chats) => {
  680. console.log('onChatsChange', chats);
  681. setMessage(chats);
  682. }, []);
  683. const onMessageSend = useCallback((content, attachment) => {
  684. setMessage((message) => {
  685. return [
  686. ...message,
  687. {
  688. role: 'user',
  689. createAt: Date.now(),
  690. id: getUuidv4(),
  691. content: content,
  692. attachment: attachment,
  693. },
  694. {
  695. role: 'assistant',
  696. status: 'loading',
  697. createAt: Date.now(),
  698. id: getUuidv4()
  699. }
  700. ]
  701. });
  702. generateMockResponse(content);
  703. },[])
  704. const generateMockResponse = useCallback((content) => {
  705. const id = setInterval(() => {
  706. setMessage((message) => {
  707. const lastMessage = message[message.length - 1];
  708. let newMessage = {};
  709. if (lastMessage.status === 'loading') {
  710. newMessage = {
  711. role: 'assistant',
  712. id: getUuidv4(),
  713. content: `mock Response for ${content} \n`,
  714. status: 'incomplete'
  715. }
  716. } else if (lastMessage.status === 'incomplete') {
  717. if (lastMessage.content.length > 200) {
  718. clearInterval(id);
  719. intervalId.current = null
  720. newMessage = {
  721. role: 'assistant',
  722. id: getUuidv4(),
  723. content: `${lastMessage.content} mock stream message`,
  724. status: 'complete'
  725. }
  726. } else {
  727. newMessage = {
  728. role: 'assistant',
  729. id: getUuidv4(),
  730. content: `${lastMessage.content} mock stream message`,
  731. status: 'incomplete'
  732. }
  733. }
  734. }
  735. return [
  736. ...message.slice(0, -1),
  737. newMessage
  738. ]
  739. })
  740. }, 400);
  741. intervalId.current = id;
  742. }, []);
  743. const onStopGenerator = useCallback(() => {
  744. if (intervalId.current) {
  745. clearInterval(intervalId.current);
  746. setMessage((message) => {
  747. const lastMessage = message[message.length - 1];
  748. if (lastMessage.status && lastMessage.status !== 'complete') {
  749. const lastMessage = message[message.length - 1];
  750. let newMessage = {...lastMessage};
  751. newMessage.status = 'complete';
  752. return [
  753. ...message.slice(0, -1),
  754. newMessage
  755. ]
  756. } else {
  757. return message;
  758. }
  759. })
  760. }
  761. }, [intervalId]);
  762. return (
  763. <div
  764. style={{ height: 300}}
  765. >
  766. <Chat
  767. style={commonOuterStyle}
  768. chats={message}
  769. roleConfig={roleInfo}
  770. onChatsChange={onChatsChange}
  771. onMessageSend={onMessageSend}
  772. onStopGenerator={onStopGenerator}
  773. showStopGenerate={true}
  774. uploadProps={uploadProps}
  775. />
  776. </div>
  777. );
  778. }
  779. export const CustomRenderHint = () => {
  780. const [message, setMessage] = useState(initMessage.slice(0, 3));
  781. const [hint, setHint] = useState(hintsExample);
  782. const commonHintStyle = useMemo(() => ({
  783. border: '1px solid var(--semi-color-border)',
  784. padding: '10px',
  785. borderRadius: '10px',
  786. width: 'fit-content',
  787. color: 'var( --semi-color-text-1)',
  788. display: 'flex',
  789. alignItems: 'center',
  790. cursor: 'pointer',
  791. fontSize: '14px'
  792. }), []);
  793. const renderHintBox = useCallback((props) => {
  794. const { content, onHintClick, index } = props;
  795. return <div style={commonHintStyle} onClick={onHintClick} key={index}>
  796. {content}
  797. <IconArrowRight style={{ marginLeft: 10 }}>click me</IconArrowRight>
  798. </div>
  799. }, []);
  800. const onChatsChange = useCallback((chats) => {
  801. setMessage(chats);
  802. }, []);
  803. const onHintClick = useCallback((hint) => {
  804. setHint([]);
  805. }, []);
  806. const onClear = useCallback(() => {
  807. setHint([]);
  808. }, []);
  809. return <div
  810. style={{ height: 600}}
  811. >
  812. <Chat
  813. style={commonOuterStyle}
  814. chats={message}
  815. onChatsChange={onChatsChange}
  816. onHintClick={onHintClick}
  817. hints={hint}
  818. roleConfig={roleInfo}
  819. renderHintBox={renderHintBox}
  820. onClear={onClear}
  821. uploadProps={uploadProps}
  822. />
  823. </div>
  824. }
  825. export const CustomRenderDivider = () => {
  826. const [message, setMessage] = useState(infoWithDivider);
  827. const renderDivider = useCallback((message) => (
  828. <Divider key={message.id} >
  829. <span style={{fontSize: '14px', lineHeight: '14px', fontWeight: 400, margin: '0 8px'}}>以下为新消息</span>
  830. </Divider>
  831. ), []);
  832. return (
  833. <div style={{ height: 600 }}>
  834. <Chat
  835. placeholder={'不处理输入信息,仅用于展示附件'}
  836. style={commonOuterStyle}
  837. chats={message}
  838. roleConfig={roleInfo}
  839. uploadProps={uploadProps}
  840. renderDivider={renderDivider}
  841. />
  842. </div>
  843. );
  844. }
  845. export const MarkdownRenderProps = () => {
  846. const [message, setMessage] = useState(infoWithJSX);
  847. const components = {};
  848. components['MyButton'] = ({ children,onClick }) => {
  849. return <Button type={"primary"} onClick={onClick} style={{marginBottom:"12px"}}> {children} </Button>
  850. }
  851. const markdownRenderProps = {
  852. format: 'mdx',
  853. components: {...MarkdownRender.defaultComponents,...components}
  854. }
  855. return (
  856. <div
  857. style={{ height: 600}}
  858. >
  859. <Chat
  860. placeholder={'不处理输入信息,仅用于展示附件'}
  861. style={commonOuterStyle}
  862. chats={message}
  863. roleConfig={roleInfo}
  864. uploadProps={uploadProps}
  865. markdownRenderProps={markdownRenderProps}
  866. />
  867. </div>
  868. )
  869. }
  870. export const EnableUpload = () => {
  871. const [message, setMessage] = useState(simpleInitMessage);
  872. const [allowUpload, setAllowUpload] = useState('allowAll');
  873. const onClear = useCallback((clearMessage) => {
  874. console.log('onClear');
  875. }, []);
  876. const onMessageSend = useCallback((content, attachment) => {
  877. const newAssistantMessage = {
  878. role: 'assistant',
  879. id: getUuidv4(),
  880. content: "这是一条 mock 回复信息",
  881. }
  882. setMessage((message) => {
  883. return [
  884. ...message,
  885. newAssistantMessage
  886. ]
  887. })
  888. }, []);
  889. const onMessageDelete = useCallback((message) => {
  890. console.log('message delete', message);
  891. }, []);
  892. const onChatsChange = useCallback((chats) => {
  893. console.log('onChatsChange', chats);
  894. setMessage(chats);
  895. }, []);
  896. const onMessageGoodFeedback = useCallback((message) => {
  897. console.log('message good feedback', message);
  898. }, []);
  899. const onMessageBadFeedback = useCallback((message) => {
  900. console.log('message bad feedback', message);
  901. }, []);
  902. const onMessageReset = useCallback((message) => {
  903. console.log('message reset', message);
  904. }, []);
  905. const onInputChange = useCallback((props) => {
  906. console.log('onInputChange', props);
  907. }, []);
  908. const onUploadChange = useCallback((e) => {
  909. setAllowUpload(e.target.value)
  910. }, []);
  911. const getUploadProps = useCallback((allowUpload) => {
  912. switch(allowUpload) {
  913. case 'allowAll':
  914. return true;
  915. case 'disallowAll':
  916. return false;
  917. case 'disablePaste':
  918. return { pasteUpload: false }
  919. case 'disableDrag':
  920. return { dragUpload: false }
  921. case 'disableClick':
  922. return { clickUpload: false}
  923. default:
  924. return true;
  925. }
  926. }, [])
  927. return (
  928. <>
  929. <RadioGroup onChange={onUploadChange} value={allowUpload}>
  930. <Radio value={'allowAll'} checked={allowUpload} >允许上传</Radio>
  931. <Radio value={'disallowAll'} checked={!allowUpload} >不允许上传</Radio>
  932. <Radio value={'disablePaste'} checked={!allowUpload} >不允许粘贴上传</Radio>
  933. <Radio value={'disableDrag'} checked={!allowUpload} >不允许拖拽上传</Radio>
  934. <Radio value={'disableClick'} checked={!allowUpload} >不允许点击上传</Radio>
  935. </RadioGroup>
  936. <br/><br/>
  937. <div style={{ height: 450}}>
  938. <Chat
  939. style={commonOuterStyle}
  940. chats={message}
  941. roleConfig={roleInfo}
  942. onClear={onClear}
  943. onMessageSend={onMessageSend}
  944. onMessageDelete={onMessageDelete}
  945. onMessageGoodFeedback={onMessageGoodFeedback}
  946. onMessageBadFeedback={onMessageBadFeedback}
  947. onChatsChange={onChatsChange}
  948. onMessageReset={onMessageReset}
  949. onInputChange={onInputChange}
  950. enableUpload={getUploadProps(allowUpload)}
  951. />
  952. </div>
  953. </>
  954. )
  955. }