@asgard-js/react 0.0.36 → 0.0.37-canary.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (128) hide show
  1. package/dist/components/chatbot/chatbot-footer/chatbot-footer.d.ts.map +1 -1
  2. package/dist/context/asgard-service-context.d.ts.map +1 -1
  3. package/dist/context/asgard-theme-context.d.ts.map +1 -1
  4. package/dist/hooks/use-react-markdown-renderer.d.ts.map +1 -1
  5. package/dist/index.js +18160 -18881
  6. package/package.json +2 -2
  7. package/.babelrc +0 -12
  8. package/dist/utils/color-utils.d.ts +0 -18
  9. package/dist/utils/color-utils.d.ts.map +0 -1
  10. package/eslint.config.cjs +0 -12
  11. package/src/components/chatbot/chatbot-body/chatbot-body.module.scss +0 -13
  12. package/src/components/chatbot/chatbot-body/chatbot-body.tsx +0 -45
  13. package/src/components/chatbot/chatbot-body/conversation-message-renderer.tsx +0 -55
  14. package/src/components/chatbot/chatbot-body/index.ts +0 -1
  15. package/src/components/chatbot/chatbot-container/chatbot-container.module.scss +0 -41
  16. package/src/components/chatbot/chatbot-container/chatbot-container.tsx +0 -49
  17. package/src/components/chatbot/chatbot-container/chatbot-full-screen-container.tsx +0 -54
  18. package/src/components/chatbot/chatbot-footer/chatbot-footer.module.scss +0 -67
  19. package/src/components/chatbot/chatbot-footer/chatbot-footer.tsx +0 -144
  20. package/src/components/chatbot/chatbot-footer/index.ts +0 -1
  21. package/src/components/chatbot/chatbot-footer/speech-input-button.tsx +0 -132
  22. package/src/components/chatbot/chatbot-header/chatbot-header.module.scss +0 -48
  23. package/src/components/chatbot/chatbot-header/chatbot-header.tsx +0 -98
  24. package/src/components/chatbot/chatbot-header/index.ts +0 -1
  25. package/src/components/chatbot/chatbot.spec.tsx +0 -8
  26. package/src/components/chatbot/chatbot.tsx +0 -118
  27. package/src/components/chatbot/profile-icon.tsx +0 -26
  28. package/src/components/index.ts +0 -2
  29. package/src/components/templates/avatar/avatar.module.scss +0 -6
  30. package/src/components/templates/avatar/avatar.tsx +0 -28
  31. package/src/components/templates/avatar/index.ts +0 -1
  32. package/src/components/templates/button-template/button-template.module.scss +0 -0
  33. package/src/components/templates/button-template/button-template.tsx +0 -45
  34. package/src/components/templates/button-template/card.module.scss +0 -58
  35. package/src/components/templates/button-template/card.spec.tsx +0 -213
  36. package/src/components/templates/button-template/card.tsx +0 -123
  37. package/src/components/templates/button-template/index.ts +0 -1
  38. package/src/components/templates/carousel-template/carousel-template.module.scss +0 -15
  39. package/src/components/templates/carousel-template/carousel-template.tsx +0 -49
  40. package/src/components/templates/carousel-template/index.ts +0 -1
  41. package/src/components/templates/chart-template/chart-template.module.scss +0 -52
  42. package/src/components/templates/chart-template/chart-template.tsx +0 -75
  43. package/src/components/templates/chart-template/index.ts +0 -1
  44. package/src/components/templates/hint-template/hint-template.module.scss +0 -39
  45. package/src/components/templates/hint-template/hint-template.tsx +0 -71
  46. package/src/components/templates/hint-template/index.ts +0 -1
  47. package/src/components/templates/image-template/image-template.module.scss +0 -67
  48. package/src/components/templates/image-template/image-template.tsx +0 -58
  49. package/src/components/templates/image-template/index.ts +0 -1
  50. package/src/components/templates/index.ts +0 -10
  51. package/src/components/templates/quick-replies/index.ts +0 -1
  52. package/src/components/templates/quick-replies/quick-replies.module.scss +0 -16
  53. package/src/components/templates/quick-replies/quick-replies.tsx +0 -44
  54. package/src/components/templates/template-box/index.ts +0 -2
  55. package/src/components/templates/template-box/template-box-content.module.scss +0 -13
  56. package/src/components/templates/template-box/template-box-content.tsx +0 -30
  57. package/src/components/templates/template-box/template-box.module.scss +0 -19
  58. package/src/components/templates/template-box/template-box.tsx +0 -48
  59. package/src/components/templates/text-template/bot-typing-box.tsx +0 -81
  60. package/src/components/templates/text-template/bot-typing-placeholder.tsx +0 -28
  61. package/src/components/templates/text-template/index.ts +0 -3
  62. package/src/components/templates/text-template/text-template.module.scss +0 -131
  63. package/src/components/templates/text-template/text-template.tsx +0 -90
  64. package/src/components/templates/text-template/use-react-markdown-renderer.spec.tsx +0 -758
  65. package/src/components/templates/time/index.ts +0 -1
  66. package/src/components/templates/time/time.module.scss +0 -6
  67. package/src/components/templates/time/time.tsx +0 -34
  68. package/src/context/asgard-app-initialization-context.tsx +0 -154
  69. package/src/context/asgard-service-context.tsx +0 -148
  70. package/src/context/asgard-template-context.tsx +0 -83
  71. package/src/context/asgard-theme-context.tsx +0 -417
  72. package/src/context/index.ts +0 -4
  73. package/src/hooks/index.ts +0 -11
  74. package/src/hooks/use-asgard-service-client.ts +0 -68
  75. package/src/hooks/use-channel.ts +0 -154
  76. package/src/hooks/use-debounce.ts +0 -18
  77. package/src/hooks/use-deep-compare-memo.ts +0 -19
  78. package/src/hooks/use-is-on-screen-keyboard-open.ts +0 -43
  79. package/src/hooks/use-on-screen-keyboard-scroll-fix.ts +0 -15
  80. package/src/hooks/use-prevent-over-scrolling.ts +0 -77
  81. package/src/hooks/use-react-markdown-renderer.tsx +0 -272
  82. package/src/hooks/use-resize-observer.tsx +0 -27
  83. package/src/hooks/use-update-vh.ts +0 -30
  84. package/src/hooks/use-viewport-size.ts +0 -51
  85. package/src/icons/add_a_photo.svg +0 -3
  86. package/src/icons/bot.svg +0 -14
  87. package/src/icons/close.svg +0 -3
  88. package/src/icons/distance.svg +0 -3
  89. package/src/icons/mic.svg +0 -3
  90. package/src/icons/photo_library.svg +0 -3
  91. package/src/icons/profile.svg +0 -28
  92. package/src/icons/refresh.svg +0 -3
  93. package/src/icons/send.svg +0 -3
  94. package/src/icons/stop.svg +0 -22
  95. package/src/icons/volume_up.svg +0 -3
  96. package/src/index.ts +0 -4
  97. package/src/models/bot-provider.ts +0 -108
  98. package/src/styles/_index.scss +0 -1
  99. package/src/styles/_styles.scss +0 -11
  100. package/src/styles/colors/_colors.scss +0 -10
  101. package/src/styles/colors/_index.scss +0 -1
  102. package/src/styles/colors/_variables.scss +0 -72
  103. package/src/styles/palette/_index.scss +0 -1
  104. package/src/styles/palette/_palette.scss +0 -42
  105. package/src/styles/palette/_variables.scss +0 -40
  106. package/src/styles/radius/_index.scss +0 -1
  107. package/src/styles/radius/_radius.scss +0 -8
  108. package/src/styles/radius/_variables.scss +0 -12
  109. package/src/styles/spacing/_index.scss +0 -1
  110. package/src/styles/spacing/_spacing.scss +0 -8
  111. package/src/styles/spacing/_variables.scss +0 -13
  112. package/src/styles/utils/_index.scss +0 -1
  113. package/src/styles/utils/_map.scss +0 -22
  114. package/src/test-setup.ts +0 -1
  115. package/src/utils/color-utils.ts +0 -38
  116. package/src/utils/deep-merge.ts +0 -26
  117. package/src/utils/extractors.ts +0 -20
  118. package/src/utils/format-time.ts +0 -8
  119. package/src/utils/index.ts +0 -1
  120. package/src/utils/is.ts +0 -72
  121. package/src/utils/selectors.ts +0 -7
  122. package/src/utils/uri-validation.spec.ts +0 -208
  123. package/src/utils/uri-validation.ts +0 -103
  124. package/tsconfig.json +0 -16
  125. package/tsconfig.lib.json +0 -63
  126. package/tsconfig.spec.json +0 -36
  127. package/tsconfig.tsbuildinfo +0 -1
  128. package/vite.config.ts +0 -63
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@asgard-js/react",
3
- "version": "0.0.36",
3
+ "version": "0.0.37-canary.1",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
@@ -54,7 +54,7 @@
54
54
  "vitest": "^1.6.0"
55
55
  },
56
56
  "peerDependencies": {
57
- "@asgard-js/core": "^0.0.36",
57
+ "@asgard-js/core": "^0.0.37-canary.1",
58
58
  "react": "^18.0.0",
59
59
  "react-dom": "^18.0.0"
60
60
  },
package/.babelrc DELETED
@@ -1,12 +0,0 @@
1
- {
2
- "presets": [
3
- [
4
- "@nx/react/babel",
5
- {
6
- "runtime": "automatic",
7
- "useBuiltIns": "usage"
8
- }
9
- ]
10
- ],
11
- "plugins": []
12
- }
@@ -1,18 +0,0 @@
1
- /**
2
- * 顏色工具函數
3
- */
4
- /**
5
- * 為顏色添加透明度
6
- * @param color 十六進制顏色值 (例如: #FF0000)
7
- * @param alpha 透明度 (0-1)
8
- * @returns 帶透明度的十六進制顏色值
9
- */
10
- export declare const addTransparency: (color: string, alpha: number) => string;
11
- /**
12
- * 加深顏色
13
- * @param color 十六進制顏色值 (例如: #FF0000)
14
- * @param amount 加深程度 (0-1)
15
- * @returns 加深後的十六進制顏色值
16
- */
17
- export declare const darkenColor: (color: string, amount: number) => string;
18
- //# sourceMappingURL=color-utils.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"color-utils.d.ts","sourceRoot":"","sources":["../../src/utils/color-utils.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH;;;;;GAKG;AACH,eAAO,MAAM,eAAe,UAAW,MAAM,SAAS,MAAM,KAAG,MAM9D,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,WAAW,UAAW,MAAM,UAAU,MAAM,KAAG,MAa3D,CAAC"}
package/eslint.config.cjs DELETED
@@ -1,12 +0,0 @@
1
- const nx = require('@nx/eslint-plugin');
2
- const baseConfig = require('../../eslint.config.cjs');
3
-
4
- module.exports = [
5
- ...baseConfig,
6
- ...nx.configs['flat/react'],
7
- {
8
- files: ['**/*.ts', '**/*.tsx', '**/*.js', '**/*.jsx'],
9
- // Override or add rules here
10
- rules: {},
11
- },
12
- ];
@@ -1,13 +0,0 @@
1
- .chatbot_body {
2
- overflow-x: hidden;
3
- overflow-y: scroll;
4
-
5
- .chatbot_body__content {
6
- margin: 0 auto;
7
- display: flex;
8
- flex-direction: column;
9
- padding: 16px;
10
- gap: 16px;
11
- max-width: 1200px;
12
- }
13
- }
@@ -1,45 +0,0 @@
1
- import { ReactNode, useEffect, useMemo } from 'react';
2
- import { useAsgardContext } from '../../../context/asgard-service-context';
3
- import styles from './chatbot-body.module.scss';
4
- import { ConversationMessageRenderer } from './conversation-message-renderer';
5
- import { BotTypingPlaceholder } from '../../templates';
6
- import { useAsgardThemeContext } from '../../../context/asgard-theme-context';
7
- import clsx from 'clsx';
8
-
9
- export function ChatbotBody(): ReactNode {
10
- const { chatbot } = useAsgardThemeContext();
11
-
12
- const { messages, messageBoxBottomRef, botTypingPlaceholder } =
13
- useAsgardContext();
14
-
15
- useEffect(() => {
16
- messageBoxBottomRef.current?.scrollIntoView({ behavior: 'smooth' });
17
- }, [messages, messageBoxBottomRef]);
18
-
19
- const contentStyles = useMemo(
20
- () => ({
21
- maxWidth: chatbot?.contentMaxWidth ?? '1200px',
22
- }),
23
- [chatbot]
24
- );
25
-
26
- return (
27
- <div
28
- className={clsx('asgard-chatbot-body', styles.chatbot_body)}
29
- style={chatbot?.body?.style}
30
- >
31
- <div className={styles.chatbot_body__content} style={contentStyles}>
32
- {Array.from(messages?.values() ?? []).map((message) => (
33
- <ConversationMessageRenderer
34
- key={message.messageId}
35
- message={message}
36
- />
37
- ))}
38
- <BotTypingPlaceholder
39
- placeholder={botTypingPlaceholder ?? '正在輸入訊息'}
40
- />
41
- <div ref={messageBoxBottomRef} />
42
- </div>
43
- </div>
44
- );
45
- }
@@ -1,55 +0,0 @@
1
- import { ReactNode } from 'react';
2
- import { ConversationMessage, MessageTemplateType } from '@asgard-js/core';
3
- import {
4
- BotTypingBox,
5
- ButtonTemplate,
6
- CarouselTemplate,
7
- HintTemplate,
8
- TextTemplate,
9
- ChartTemplate,
10
- ImageTemplate,
11
- } from '../../templates';
12
-
13
- interface ConversationMessageRendererProps {
14
- message: ConversationMessage;
15
- }
16
-
17
- export function ConversationMessageRenderer(
18
- props: ConversationMessageRendererProps
19
- ): ReactNode {
20
- const { message } = props;
21
-
22
- if (message.type === 'user') {
23
- return <TextTemplate message={message} />;
24
- }
25
-
26
- if (message.type === 'error') {
27
- return <HintTemplate message={message} />;
28
- }
29
-
30
- if (message.isTyping) {
31
- return (
32
- <BotTypingBox
33
- isTyping={message.isTyping}
34
- typingText={message.typingText}
35
- />
36
- );
37
- }
38
-
39
- switch (message.message.template?.type) {
40
- case MessageTemplateType.TEXT:
41
- return <TextTemplate message={message} />;
42
- case MessageTemplateType.HINT:
43
- return <HintTemplate message={message} />;
44
- case MessageTemplateType.BUTTON:
45
- return <ButtonTemplate message={message} />;
46
- case MessageTemplateType.CAROUSEL:
47
- return <CarouselTemplate message={message} />;
48
- case MessageTemplateType.CHART:
49
- return <ChartTemplate message={message} />;
50
- case MessageTemplateType.IMAGE:
51
- return <ImageTemplate message={message} />;
52
- default:
53
- return <div />;
54
- }
55
- }
@@ -1 +0,0 @@
1
- export * from './chatbot-body';
@@ -1,41 +0,0 @@
1
- @use '../../../styles';
2
-
3
- @import url('https://fonts.googleapis.com/css2?family=Noto+Sans+TC:wght@100..900&family=Space+Grotesk:wght@300..700&display=swap');
4
-
5
- .chatbot_root {
6
- @include styles.generate();
7
-
8
- font-family: 'Space Grotesk', 'Noto Sans TC', sans-serif;
9
-
10
- * {
11
- line-height: 1.5;
12
- box-sizing: border-box;
13
- }
14
- }
15
-
16
- .full_screen {
17
- position: fixed;
18
- top: 0;
19
- left: 0;
20
- width: 100%;
21
- height: calc(var(--vh, 1vh) * 100);
22
- background-color: var(--asg-color-bg);
23
-
24
- .chatbot_container {
25
- height: 100%;
26
- transition: all 0.1s;
27
- padding: env(safe-area-inset-top) env(safe-area-inset-right)
28
- env(safe-area-inset-bottom) env(safe-area-inset-left);
29
-
30
- &.screen_keyboard_open {
31
- padding: env(safe-area-inset-top) env(safe-area-inset-right) 0
32
- env(safe-area-inset-left);
33
- }
34
- }
35
- }
36
-
37
- .chatbot_container {
38
- display: grid;
39
- grid-template-rows: max-content auto max-content;
40
- background-color: var(--asg-color-bg);
41
- }
@@ -1,49 +0,0 @@
1
- import { PropsWithChildren, ReactNode, useRef, CSSProperties } from 'react';
2
- import { useUpdateVh } from '../../../hooks';
3
- import { ChatbotFullScreenContainer } from './chatbot-full-screen-container';
4
- import classes from './chatbot-container.module.scss';
5
- import { useAsgardThemeContext } from '../../../context/asgard-theme-context';
6
- import clsx from 'clsx';
7
-
8
- interface ChatbotContainerProps extends PropsWithChildren {
9
- className?: string;
10
- style?: CSSProperties;
11
- fullScreen?: boolean;
12
- }
13
-
14
- export function ChatbotContainer(props: ChatbotContainerProps): ReactNode {
15
- const { fullScreen, children, className, style = {} } = props;
16
-
17
- const rootRef = useRef<HTMLDivElement>(null);
18
-
19
- useUpdateVh(rootRef);
20
-
21
- const {
22
- chatbot: {
23
- style: rootStyle,
24
- header,
25
- body,
26
- footer,
27
- ...chatbotInnerContainerStyle
28
- },
29
- } = useAsgardThemeContext();
30
-
31
- return (
32
- <div
33
- ref={rootRef}
34
- className={clsx(classes.chatbot_root, className)}
35
- style={Object.assign({}, rootStyle, style)}
36
- >
37
- {fullScreen ? (
38
- <ChatbotFullScreenContainer>{children}</ChatbotFullScreenContainer>
39
- ) : (
40
- <div
41
- className={classes.chatbot_container}
42
- style={chatbotInnerContainerStyle}
43
- >
44
- {children}
45
- </div>
46
- )}
47
- </div>
48
- );
49
- }
@@ -1,54 +0,0 @@
1
- import { PropsWithChildren, ReactNode, useMemo, useRef } from 'react';
2
- import clsx from 'clsx';
3
- import {
4
- useIsOnScreenKeyboardOpen,
5
- useOnScreenKeyboardScrollFix,
6
- usePreventOverScrolling,
7
- useViewportSize,
8
- } from '../../../hooks';
9
- import classes from './chatbot-container.module.scss';
10
- import { useAsgardThemeContext } from '../../../context/asgard-theme-context';
11
-
12
- export function ChatbotFullScreenContainer(
13
- props: PropsWithChildren
14
- ): ReactNode {
15
- const { children } = props;
16
-
17
- const containerRef = useRef<HTMLDivElement>(null);
18
-
19
- const theme = useAsgardThemeContext();
20
-
21
- usePreventOverScrolling(containerRef);
22
-
23
- useOnScreenKeyboardScrollFix();
24
-
25
- const [, height] = useViewportSize() ?? [];
26
-
27
- const isOnScreenKeyboardOpen = useIsOnScreenKeyboardOpen();
28
-
29
- const styles = useMemo(() => {
30
- return Object.assign(
31
- theme?.chatbot?.backgroundColor
32
- ? {
33
- backgroundColor: theme.chatbot?.backgroundColor,
34
- }
35
- : {},
36
- isOnScreenKeyboardOpen ? { height } : {}
37
- );
38
- }, [height, isOnScreenKeyboardOpen, theme]);
39
-
40
- return (
41
- <div className={classes.full_screen}>
42
- <div
43
- ref={containerRef}
44
- className={clsx(
45
- classes.chatbot_container,
46
- isOnScreenKeyboardOpen && classes.screen_keyboard_open
47
- )}
48
- style={styles}
49
- >
50
- {children}
51
- </div>
52
- </div>
53
- );
54
- }
@@ -1,67 +0,0 @@
1
- .chatbot_footer {
2
- border-top: 1px solid #434343;
3
-
4
- .chatbot_footer__content {
5
- display: grid;
6
- align-items: flex-end;
7
- grid-template-columns: auto 24px;
8
- margin: 0 auto;
9
- max-width: 1200px;
10
- padding: var(--asg-spacing-3) var(--asg-spacing-4);
11
- gap: var(--asg-spacing-2);
12
- }
13
- }
14
-
15
- .chatbot_textarea {
16
- font: inherit;
17
- font-size: 16px;
18
- height: 36px;
19
- max-height: 240px;
20
- padding: 8px;
21
- padding-left: 12px;
22
- background: rgba(31, 31, 31, 1);
23
- border: 1px solid rgba(67, 67, 67, 1);
24
- border-radius: 2px;
25
- color: rgba(140, 140, 140, 1);
26
- resize: none;
27
- overflow: hidden;
28
-
29
- &::placeholder {
30
- color: var(--asg-color-text-placeholder);
31
- }
32
- }
33
-
34
- .chatbot_submit_button {
35
- font: inherit;
36
- width: 24px;
37
- height: 100%;
38
- max-height: 38px;
39
- display: flex;
40
- justify-content: center;
41
- align-items: center;
42
- font-size: 16px;
43
- border: none;
44
- background: transparent;
45
- border-radius: 50%;
46
- color: white;
47
- cursor: pointer;
48
-
49
- > svg {
50
- -webkit-touch-callout: none;
51
- -webkit-user-select: none;
52
- user-select: none;
53
- width: auto;
54
- min-width: 16px;
55
- height: 60%;
56
- max-height: 38px;
57
- min-height: 16px;
58
- }
59
-
60
- &.chatbot_submit_button__disabled {
61
- > svg {
62
- > path {
63
- fill: rgba(140, 140, 140, 1);
64
- }
65
- }
66
- }
67
- }
@@ -1,144 +0,0 @@
1
- import {
2
- ChangeEventHandler,
3
- KeyboardEventHandler,
4
- ReactNode,
5
- useCallback,
6
- useEffect,
7
- useMemo,
8
- useRef,
9
- useState,
10
- } from 'react';
11
- import { useAsgardContext } from '../../../context/asgard-service-context';
12
- import styles from './chatbot-footer.module.scss';
13
- import SendSvg from '../../../icons/send.svg?react';
14
- import { SpeechInputButton } from './speech-input-button';
15
- import clsx from 'clsx';
16
- import { useAsgardThemeContext } from '../../../context/asgard-theme-context';
17
- import { useAsgardAppInitializationContext } from '../../../context/asgard-app-initialization-context';
18
-
19
- export function ChatbotFooter(): ReactNode {
20
- const { sendMessage, isConnecting } = useAsgardContext();
21
-
22
- const { chatbot } = useAsgardThemeContext();
23
- const { data: { annotations } } = useAsgardAppInitializationContext();
24
-
25
- const [value, setValue] = useState('');
26
- const [isComposing, setIsComposing] = useState(false);
27
- const textareaRef = useRef<HTMLTextAreaElement>(null);
28
-
29
- const inputPlaceholder = annotations?.embedConfig?.inputPlaceholder || 'Enter message';
30
-
31
- const disabled = useMemo(
32
- () => isConnecting || !value.trim(),
33
- [isConnecting, value]
34
- );
35
-
36
- const contentStyles = useMemo(
37
- () => ({
38
- maxWidth: chatbot?.contentMaxWidth ?? '1200px',
39
- borderTopColor: chatbot?.borderColor,
40
- }),
41
- [chatbot]
42
- );
43
-
44
- const onChange = useCallback<ChangeEventHandler<HTMLTextAreaElement>>(
45
- (event) => {
46
- const element = event.target as HTMLTextAreaElement;
47
- const value = element.value;
48
-
49
- element.style.height = '36px';
50
-
51
- if (value) {
52
- element.style.height = `${element.scrollHeight}px`;
53
- }
54
-
55
- setValue(event.target.value);
56
- },
57
- []
58
- );
59
-
60
- const onSubmit = useCallback(() => {
61
- if (!isComposing && !isConnecting) {
62
- sendMessage?.({ text: value });
63
- setValue('');
64
-
65
- if (textareaRef.current) {
66
- textareaRef.current.style.height = '36px';
67
- }
68
- }
69
- }, [isComposing, isConnecting, sendMessage, value]);
70
-
71
- const onKeyDown = useCallback<KeyboardEventHandler<HTMLTextAreaElement>>(
72
- (event) => {
73
- if (
74
- event.key === 'Enter' &&
75
- !isComposing &&
76
- !isConnecting &&
77
- value.trim()
78
- ) {
79
- sendMessage?.({ text: value });
80
- setValue('');
81
-
82
- const element = event.target as HTMLTextAreaElement;
83
-
84
- element.style.height = '36px';
85
- }
86
- },
87
- [isComposing, isConnecting, sendMessage, value]
88
- );
89
-
90
- useEffect(() => {
91
- if (textareaRef.current) {
92
- textareaRef.current.style.setProperty(
93
- '--asg-color-text-placeholder',
94
- chatbot.footer?.textArea?.['::placeholder']?.color ??
95
- 'var(--asg-color-text-placeholder)'
96
- );
97
- }
98
- }, [chatbot.footer?.textArea]);
99
-
100
- return (
101
- <div
102
- className={clsx('asgard-chatbot-footer', styles.chatbot_footer)}
103
- style={chatbot.footer?.style}
104
- >
105
- <div className={styles.chatbot_footer__content} style={contentStyles}>
106
- <textarea
107
- ref={textareaRef}
108
- className={styles.chatbot_textarea}
109
- style={chatbot.footer?.textArea?.style}
110
- disabled={isConnecting}
111
- cols={40}
112
- value={value}
113
- placeholder={inputPlaceholder}
114
- onChange={onChange}
115
- onKeyDown={onKeyDown}
116
- onCompositionStart={() => setIsComposing(true)}
117
- onCompositionEnd={() => setIsComposing(false)}
118
- />
119
- {value ? (
120
- <button
121
- className={clsx(
122
- styles.chatbot_submit_button,
123
- disabled && styles.chatbot_submit_button__disabled
124
- )}
125
- style={chatbot.footer?.submitButton?.style}
126
- disabled={disabled}
127
- onClick={onSubmit}
128
- >
129
- <SendSvg />
130
- </button>
131
- ) : (
132
- <SpeechInputButton
133
- setValue={setValue}
134
- className={clsx(
135
- styles.chatbot_submit_button,
136
- isConnecting && styles.chatbot_submit_button__disabled
137
- )}
138
- style={chatbot.footer?.speechInputButton?.style}
139
- />
140
- )}
141
- </div>
142
- </div>
143
- );
144
- }
@@ -1 +0,0 @@
1
- export * from './chatbot-footer';
@@ -1,132 +0,0 @@
1
- import {
2
- Dispatch,
3
- MouseEventHandler,
4
- ReactNode,
5
- SetStateAction,
6
- TouchEventHandler,
7
- useCallback,
8
- useEffect,
9
- useRef,
10
- useState,
11
- CSSProperties,
12
- } from 'react';
13
- import MicSvg from '../../../icons/mic.svg?react';
14
- import StopSvg from '../../../icons/stop.svg?react';
15
-
16
- interface SpeechInputButtonProps {
17
- setValue: Dispatch<SetStateAction<string>>;
18
- className?: string;
19
- style?: CSSProperties;
20
- }
21
-
22
- export function SpeechInputButton(props: SpeechInputButtonProps): ReactNode {
23
- const { setValue, className, style } = props;
24
-
25
- const [listening, setListening] = useState(false);
26
- const recognitionRef = useRef<SpeechRecognition | null>(null);
27
-
28
- useEffect(() => {
29
- const SpeechRecognition =
30
- window.SpeechRecognition || window.webkitSpeechRecognition;
31
-
32
- if (!SpeechRecognition) return;
33
-
34
- const recognition = new SpeechRecognition();
35
- recognition.lang = 'zh-TW';
36
- recognition.continuous = true;
37
- recognition.interimResults = true;
38
-
39
- recognition.onresult = (event: SpeechRecognitionEvent): void => {
40
- for (let i = event.resultIndex; i < event.results.length; i++) {
41
- if (event.results[i].isFinal) {
42
- setValue((prev) => prev + event.results[i][0].transcript);
43
- }
44
- }
45
- };
46
-
47
- recognition.onerror = (event: SpeechRecognitionErrorEvent): void => {
48
- alert(`語音識別錯誤: ${JSON.stringify(event.error)}`);
49
- };
50
-
51
- recognition.onend = (): void => {
52
- setListening(false);
53
- };
54
-
55
- recognitionRef.current = recognition;
56
- }, [setValue]);
57
-
58
- const startListening = useCallback(() => {
59
- if (!recognitionRef.current) {
60
- alert('無法開始辨識: 語音識別器未初始化,請檢查是否有授權使用麥克風');
61
-
62
- return;
63
- }
64
-
65
- try {
66
- recognitionRef.current.start();
67
- setListening(true);
68
- } catch (error) {
69
- alert(`無法開始辨識: ${JSON.stringify(error)}`);
70
- }
71
- }, []);
72
-
73
- const stopListening = useCallback(() => {
74
- if (!recognitionRef.current) return;
75
-
76
- recognitionRef.current.stop();
77
- setListening(false);
78
- }, []);
79
-
80
- const onMouseDown = useCallback<MouseEventHandler<HTMLDivElement>>(
81
- (event) => {
82
- if (!listening) {
83
- event.preventDefault();
84
- startListening();
85
- }
86
- },
87
- [listening, startListening]
88
- );
89
-
90
- const onMouseUp = useCallback<MouseEventHandler<HTMLDivElement>>(
91
- (event) => {
92
- if (listening) {
93
- event.preventDefault();
94
- stopListening();
95
- }
96
- },
97
- [listening, stopListening]
98
- );
99
-
100
- const onTouchStart = useCallback<TouchEventHandler<HTMLDivElement>>(
101
- (event) => {
102
- if (!listening) {
103
- event.preventDefault();
104
- startListening();
105
- }
106
- },
107
- [listening, startListening]
108
- );
109
-
110
- const onTouchEnd = useCallback<TouchEventHandler<HTMLDivElement>>(
111
- (event) => {
112
- if (listening) {
113
- event.preventDefault();
114
- stopListening();
115
- }
116
- },
117
- [listening, stopListening]
118
- );
119
-
120
- return (
121
- <div
122
- className={className}
123
- style={style}
124
- onMouseDown={onMouseDown}
125
- onMouseUp={onMouseUp}
126
- onTouchStart={onTouchStart}
127
- onTouchEnd={onTouchEnd}
128
- >
129
- {listening ? <StopSvg /> : <MicSvg />}
130
- </div>
131
- );
132
- }