@canmingir/link 1.2.55 → 1.2.57

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 (220) hide show
  1. package/.eslintignore +0 -1
  2. package/.github/workflows/publish.yml +6 -1
  3. package/LICENSE +201 -0
  4. package/bun.lock +20 -30
  5. package/package.json +4 -1
  6. package/src/assets/illustrations/avatar-shape.jsx +10 -8
  7. package/src/assets/illustrations/background-shape.jsx +0 -2
  8. package/src/assets/illustrations/booking-illustration.jsx +8 -6
  9. package/src/assets/illustrations/check-in-illustration.jsx +8 -6
  10. package/src/assets/illustrations/check-out-illustration.jsx +8 -6
  11. package/src/assets/illustrations/coming-soon-illustration.jsx +8 -6
  12. package/src/assets/illustrations/forbidden-illustration.jsx +8 -6
  13. package/src/assets/illustrations/maintenance-illustration.jsx +8 -6
  14. package/src/assets/illustrations/motivation-illustration.jsx +8 -6
  15. package/src/assets/illustrations/order-complete-illustration.jsx +8 -6
  16. package/src/assets/illustrations/page-not-found-illustration.jsx +8 -6
  17. package/src/assets/illustrations/seo-illustration.jsx +8 -6
  18. package/src/assets/illustrations/sever-error-illustration.jsx +8 -6
  19. package/src/assets/illustrations/upgrade-storage-illustration.jsx +8 -6
  20. package/src/assets/illustrations/upload-illustration.jsx +8 -6
  21. package/src/components/Iconify/Iconify.jsx +0 -1
  22. package/src/components/chart/chart.js +0 -2
  23. package/src/components/chart/index.js +0 -2
  24. package/src/components/chart/use-chart.js +0 -2
  25. package/src/components/custom-popover/styles.js +0 -2
  26. package/src/components/custom-popover/use-popover.js +0 -2
  27. package/src/components/custom-popover/utils.js +0 -2
  28. package/src/components/file-thumbnail/utils.js +0 -12
  29. package/src/components/image/utils.js +0 -2
  30. package/src/components/label/styles.js +0 -2
  31. package/src/components/logo/logo.jsx +1 -1
  32. package/src/components/scrollbar/styles.js +0 -2
  33. package/src/components/settings/context/settings-context.js +0 -2
  34. package/src/hooks/use-boolean.js +0 -2
  35. package/src/hooks/use-event-listener.js +0 -2
  36. package/src/hooks/use-local-storage.js +0 -4
  37. package/src/hooks/use-off-set-top.js +0 -2
  38. package/src/hooks/use-responsive.js +0 -4
  39. package/src/hooks/use-scroll-to-top.js +0 -2
  40. package/src/layouts/CompactLayout/CompactLayout.jsx +0 -2
  41. package/src/layouts/DashboardLayout/DashboardLayout.jsx +0 -2
  42. package/src/layouts/DashboardLayout/main.jsx +0 -2
  43. package/src/layouts/DashboardLayout/nav-horizontal.jsx +0 -2
  44. package/src/layouts/DashboardLayout/nav-vertical.jsx +6 -5
  45. package/src/layouts/FullScreenLayout/FullScreenLayout.jsx +3 -4
  46. package/src/layouts/FullScreenLayout/nav-horizontal.jsx +0 -2
  47. package/src/layouts/FullScreenLayout/nav-vertical.jsx +4 -5
  48. package/src/layouts/MainLayout/header.jsx +1 -3
  49. package/src/layouts/MainLayout/nav/desktop/index.jsx +0 -2
  50. package/src/layouts/MainLayout/nav/desktop/nav-item.jsx +0 -6
  51. package/src/layouts/MainLayout/nav/desktop/nav-list.jsx +16 -16
  52. package/src/layouts/MainLayout/nav/mobile/index.jsx +1 -3
  53. package/src/layouts/MainLayout/nav/mobile/nav-item.jsx +0 -4
  54. package/src/layouts/MainLayout/nav/mobile/nav-list.jsx +0 -2
  55. package/src/layouts/TwoSideLayout/TwoSideLayout.jsx +0 -1
  56. package/src/layouts/auth/classic.jsx +3 -4
  57. package/src/layouts/auth/modern-compact.jsx +0 -2
  58. package/src/layouts/auth/modern.jsx +0 -2
  59. package/src/layouts/common/ProjectBar/utils.js +0 -10
  60. package/src/layouts/common/account-popover.jsx +0 -2
  61. package/src/layouts/common/header-shadow.jsx +0 -2
  62. package/src/layouts/common/header-sim.jsx +7 -4
  63. package/src/layouts/common/header-simple.jsx +14 -8
  64. package/src/layouts/common/nav-toggle-button.jsx +1 -3
  65. package/src/layouts/common/notifications-popover/index.jsx +9 -10
  66. package/src/layouts/common/notifications-popover/notification-item.jsx +20 -17
  67. package/src/layouts/common/settings-button.jsx +0 -2
  68. package/src/layouts/config-layout.js +0 -2
  69. package/src/lib/ChatMessage/AIMessage.tsx +163 -0
  70. package/src/lib/ChatMessage/ErrorMessage.tsx +63 -0
  71. package/src/lib/ChatMessage/HumanMessage.tsx +142 -0
  72. package/src/lib/ChatMessage/LoadingMessage.tsx +27 -0
  73. package/src/lib/ChatMessage/MessageList.tsx +81 -0
  74. package/src/lib/ChatMessage/ToolMessage.tsx +346 -0
  75. package/src/lib/ChatMessage/index.js +6 -0
  76. package/src/lib/Image/utils.js +0 -2
  77. package/src/lib/PresetSelector/PresetSelector.tsx +112 -0
  78. package/src/lib/PresetSelector/index.js +1 -0
  79. package/src/lib/Scrollbar/styles.js +0 -2
  80. package/src/lib/SidebarChat/ChatDrawer.tsx +407 -0
  81. package/src/lib/SidebarChat/SessionPopover.tsx +221 -0
  82. package/src/lib/SidebarChat/SidebarChat.tsx +222 -0
  83. package/src/lib/SidebarChat/SidebarSessionList.tsx +304 -0
  84. package/src/lib/SidebarChat/assets.d.ts +4 -0
  85. package/src/lib/SidebarChat/cleanIconName.ts +4 -0
  86. package/src/lib/SidebarChat/index.js +1 -0
  87. package/src/lib/SidebarChat/messageSFX.mp3 +0 -0
  88. package/src/lib/SidebarChat/types.ts +14 -0
  89. package/src/lib/index.js +11 -0
  90. package/src/pages/404.jsx +0 -2
  91. package/src/routes/components/router-link.jsx +0 -2
  92. package/src/routes/hooks/use-active-link.js +0 -2
  93. package/src/routes/hooks/use-pathname.js +0 -2
  94. package/src/routes/hooks/use-router.js +0 -2
  95. package/src/theme/css.js +0 -12
  96. package/src/theme/custom-shadows.js +0 -2
  97. package/src/theme/options/contrast.js +0 -2
  98. package/src/theme/options/presets.js +0 -4
  99. package/src/theme/options/right-to-left.jsx +0 -2
  100. package/src/theme/overrides/components/accordion.js +0 -2
  101. package/src/theme/overrides/components/alert.js +0 -4
  102. package/src/theme/overrides/components/appbar.js +0 -2
  103. package/src/theme/overrides/components/autocomplete.js +0 -2
  104. package/src/theme/overrides/components/avatar.js +0 -4
  105. package/src/theme/overrides/components/backdrop.js +0 -2
  106. package/src/theme/overrides/components/badge.js +0 -2
  107. package/src/theme/overrides/components/breadcrumbs.js +0 -2
  108. package/src/theme/overrides/components/button-group.js +0 -4
  109. package/src/theme/overrides/components/button.js +0 -4
  110. package/src/theme/overrides/components/card.js +0 -2
  111. package/src/theme/overrides/components/checkbox.js +0 -2
  112. package/src/theme/overrides/components/chip.js +0 -4
  113. package/src/theme/overrides/components/css-baseline.js +0 -2
  114. package/src/theme/overrides/components/data-grid.js +0 -2
  115. package/src/theme/overrides/components/date-picker.jsx +0 -2
  116. package/src/theme/overrides/components/dialog.js +0 -2
  117. package/src/theme/overrides/components/drawer.js +0 -2
  118. package/src/theme/overrides/components/fab.js +0 -4
  119. package/src/theme/overrides/components/form.js +0 -2
  120. package/src/theme/overrides/components/list.js +0 -2
  121. package/src/theme/overrides/components/loading-button.js +0 -2
  122. package/src/theme/overrides/components/menu.js +0 -2
  123. package/src/theme/overrides/components/pagination.js +0 -4
  124. package/src/theme/overrides/components/paper.js +0 -2
  125. package/src/theme/overrides/components/popover.js +0 -2
  126. package/src/theme/overrides/components/progress.js +0 -4
  127. package/src/theme/overrides/components/radio.js +0 -2
  128. package/src/theme/overrides/components/rating.js +0 -2
  129. package/src/theme/overrides/components/select.js +0 -2
  130. package/src/theme/overrides/components/skeleton.js +0 -2
  131. package/src/theme/overrides/components/slider.js +0 -2
  132. package/src/theme/overrides/components/stepper.js +0 -2
  133. package/src/theme/overrides/components/svg-icon.js +0 -2
  134. package/src/theme/overrides/components/switch.js +0 -2
  135. package/src/theme/overrides/components/table.js +0 -2
  136. package/src/theme/overrides/components/tabs.js +0 -2
  137. package/src/theme/overrides/components/textfield.js +0 -2
  138. package/src/theme/overrides/components/timeline.js +0 -2
  139. package/src/theme/overrides/components/toggle-button.js +0 -4
  140. package/src/theme/overrides/components/tooltip.js +0 -2
  141. package/src/theme/overrides/components/tree-view.js +0 -2
  142. package/src/theme/overrides/components/typography.js +0 -2
  143. package/src/theme/overrides/default-props.jsx +4 -7
  144. package/src/theme/palette.js +0 -4
  145. package/src/theme/shadows.js +0 -2
  146. package/src/theme/typography.js +0 -4
  147. package/src/utils/flatten-array.js +0 -2
  148. package/src/utils/format-number.js +0 -2
  149. package/src/utils/format-time.js +0 -2
  150. package/src/utils/storage-available.js +0 -2
  151. package/src/widgets/Login/Login.jsx +0 -2
  152. package/src/widgets/error/not-found-view.jsx +0 -2
  153. package/vite/vite.js +8 -1
  154. package/project/.eslintrc.cjs +0 -22
  155. package/project/config.js +0 -18
  156. package/project/config.menu.js +0 -39
  157. package/project/config.template.js +0 -31
  158. package/project/cypress/e2e/layouts.cy.js +0 -170
  159. package/project/cypress/e2e/login.cy.js +0 -35
  160. package/project/cypress/e2e/selectbar.cy.js +0 -80
  161. package/project/cypress/fixtures/CONFIG/MENU_CONFIG.js +0 -39
  162. package/project/cypress/fixtures/EMPEROR/EMPERORS_GET.json +0 -51
  163. package/project/cypress/fixtures/EMPEROR/EMPERORS_POST.json +0 -9
  164. package/project/cypress/fixtures/OAUTH/GITHUB/user.json +0 -46
  165. package/project/cypress/fixtures/example.json +0 -5
  166. package/project/cypress/support/commands.js +0 -104
  167. package/project/cypress/support/e2e.js +0 -1
  168. package/project/cypress.config.js +0 -10
  169. package/project/index.html +0 -12
  170. package/project/media/ProjectIcons/3d.png +0 -0
  171. package/project/media/ProjectIcons/box.png +0 -0
  172. package/project/media/ProjectIcons/cafe.png +0 -0
  173. package/project/media/ProjectIcons/cargo.png +0 -0
  174. package/project/media/ProjectIcons/cloud.png +0 -0
  175. package/project/media/ProjectIcons/code.png +0 -0
  176. package/project/media/ProjectIcons/company.png +0 -0
  177. package/project/media/ProjectIcons/conversation.png +0 -0
  178. package/project/media/ProjectIcons/distributed.png +0 -0
  179. package/project/media/ProjectIcons/fingerprint.png +0 -0
  180. package/project/media/ProjectIcons/forest.png +0 -0
  181. package/project/media/ProjectIcons/headphones.png +0 -0
  182. package/project/media/ProjectIcons/office.png +0 -0
  183. package/project/media/ProjectIcons/path.png +0 -0
  184. package/project/media/ProjectIcons/printer.png +0 -0
  185. package/project/media/ProjectIcons/project.png +0 -0
  186. package/project/media/ProjectIcons/resume.png +0 -0
  187. package/project/media/ProjectIcons/rocket.png +0 -0
  188. package/project/media/ProjectIcons/rtruck.png +0 -0
  189. package/project/media/ProjectIcons/sign.png +0 -0
  190. package/project/media/ProjectIcons/site.png +0 -0
  191. package/project/media/ProjectIcons/truck.png +0 -0
  192. package/project/media/logo.png +0 -0
  193. package/project/package-lock.json +0 -22434
  194. package/project/package.json +0 -42
  195. package/project/public/assets/background/overlay_2.jpg +0 -0
  196. package/project/public/assets/illustrations/illustration_dashboard.png +0 -0
  197. package/project/public/media/largeLogo.png +0 -0
  198. package/project/public/media/logo.png +0 -0
  199. package/project/public/vite.svg +0 -1
  200. package/project/routes.jsx +0 -47
  201. package/project/server/mock.json +0 -110
  202. package/project/server/oauthMock.js +0 -27
  203. package/project/server/package-lock.json +0 -1505
  204. package/project/server/package.json +0 -20
  205. package/project/server/server.js +0 -127
  206. package/project/src/Container.jsx +0 -7
  207. package/project/src/components/ActionButton.jsx +0 -18
  208. package/project/src/hooks/useEmperor.jsx +0 -21
  209. package/project/src/http/index.js +0 -39
  210. package/project/src/main.jsx +0 -10
  211. package/project/src/pages/Battles.jsx +0 -16
  212. package/project/src/pages/Emperor.jsx +0 -50
  213. package/project/src/pages/index.jsx +0 -33
  214. package/project/src/theme.js +0 -151
  215. package/project/src/widgets/ActionButton.jsx +0 -68
  216. package/project/src/widgets/AddNewEmperor.jsx +0 -120
  217. package/project/src/widgets/ProjectContainer.jsx +0 -27
  218. package/project/vite.config.js +0 -6
  219. package/project/vite.config.js.timestamp-1709564450418-45fdd27900806.mjs +0 -341
  220. package/src/layouts/MainLayout/footer.jsx +0 -153
@@ -0,0 +1,63 @@
1
+ import { Iconify } from "@canmingir/link/platform/components";
2
+ import { alpha } from "@mui/material/styles";
3
+
4
+ import { Box, IconButton, Stack } from "@mui/material";
5
+ import React, { memo } from "react";
6
+
7
+ const ErrorMessage: React.FC<{
8
+ content: string;
9
+ messageRef?: React.RefObject<HTMLDivElement>;
10
+ onClose?: () => void;
11
+ }> = memo(({ content, messageRef, onClose }) => (
12
+ <Stack
13
+ ref={messageRef}
14
+ sx={{
15
+ p: 2.5,
16
+ alignContent: "flex-start",
17
+ justifyContent: "flex-start",
18
+ backgroundColor: (theme) => alpha(theme.palette.error.dark, 0.2),
19
+ borderRadius: 2,
20
+ height: "auto",
21
+ mt: 1.5,
22
+ boxShadow: 1,
23
+ border: (theme) => `1px solid ${alpha(theme.palette.error.light, 0.4)}`,
24
+ }}
25
+ >
26
+ <Stack direction="row" spacing={1} alignItems="center">
27
+ <Iconify
28
+ icon="solar:danger-triangle-bold"
29
+ sx={{
30
+ width: 20,
31
+ height: 20,
32
+ color: (theme) => theme.palette.error.main,
33
+ }}
34
+ />
35
+ <Box
36
+ sx={{
37
+ width: "100%",
38
+ color: (theme) => theme.palette.error.light,
39
+ fontSize: "0.95rem",
40
+ lineHeight: 1.6,
41
+ }}
42
+ >
43
+ {content}
44
+ </Box>
45
+ {onClose && (
46
+ <IconButton
47
+ onClick={onClose}
48
+ size="small"
49
+ sx={{
50
+ color: (theme) => theme.palette.error.light,
51
+ "&:hover": {
52
+ backgroundColor: (theme) => alpha(theme.palette.error.main, 0.1),
53
+ },
54
+ }}
55
+ >
56
+ <Iconify icon="mdi:close" sx={{ width: 18, height: 18 }} />
57
+ </IconButton>
58
+ )}
59
+ </Stack>
60
+ </Stack>
61
+ ));
62
+
63
+ export { ErrorMessage };
@@ -0,0 +1,142 @@
1
+ import Editor from "@monaco-editor/react";
2
+ import { alpha } from "@mui/material/styles";
3
+ import { Box, Stack, Typography } from "@mui/material";
4
+
5
+ import { memo, useMemo } from "react";
6
+
7
+ function tryParseJson(content: string): boolean {
8
+ const trimmed = content.trim();
9
+ if (!trimmed.startsWith("{") && !trimmed.startsWith("[")) return false;
10
+ try {
11
+ JSON.parse(trimmed);
12
+ return true;
13
+ } catch {
14
+ return false;
15
+ }
16
+ }
17
+
18
+ const HumanMessage = memo(
19
+ ({
20
+ message,
21
+ selectedId,
22
+ messageRef,
23
+ }: {
24
+ message: { id: string; content: string };
25
+ selectedId: string;
26
+ messageRef: React.RefObject<HTMLDivElement>;
27
+ }) => {
28
+ const isJson = useMemo(
29
+ () => tryParseJson(message.content),
30
+ [message.content]
31
+ );
32
+
33
+ const formattedJson = useMemo(() => {
34
+ if (!isJson) return null;
35
+ try {
36
+ return JSON.stringify(JSON.parse(message.content), null, 2);
37
+ } catch {
38
+ return null;
39
+ }
40
+ }, [isJson, message.content]);
41
+
42
+ const editorHeight = useMemo(() => {
43
+ if (!formattedJson) return 0;
44
+ const lines = formattedJson.split("\n").length;
45
+ return Math.min(Math.max(lines * 19 + 24, 60), 200);
46
+ }, [formattedJson]);
47
+
48
+ return (
49
+ <Stack
50
+ ref={messageRef}
51
+ sx={{
52
+ p: isJson ? 1.5 : 2.5,
53
+ minHeight: "auto",
54
+ mt: 1.5,
55
+ ml: isJson ? 0 : "auto",
56
+ maxWidth: isJson ? "100%" : "85%",
57
+ alignContent: "center",
58
+ justifyContent: "center",
59
+ borderWidth: message?.id === selectedId ? 3 : 0,
60
+ borderRadius: isJson ? 2 : "16px 16px 4px 16px",
61
+ borderStyle: "none",
62
+ borderColor: "transparent",
63
+ backgroundColor: (theme) =>
64
+ isJson
65
+ ? alpha(theme.palette.grey[800], 0.5)
66
+ : alpha(theme.palette.grey[700], 0.4),
67
+ animation: "none",
68
+ boxShadow: 1,
69
+ }}
70
+ >
71
+ {isJson ? (
72
+ <Box>
73
+ <Typography
74
+ variant="caption"
75
+ sx={{
76
+ display: "block",
77
+ mb: 0.75,
78
+ color: (theme) => alpha(theme.palette.text.secondary, 0.6),
79
+ fontSize: "0.68rem",
80
+ fontWeight: 600,
81
+ textTransform: "uppercase",
82
+ letterSpacing: 0.5,
83
+ }}
84
+ >
85
+ JSON Request
86
+ </Typography>
87
+ <Box
88
+ sx={{
89
+ border: (theme) =>
90
+ `1px solid ${alpha(theme.palette.grey[500], 0.2)}`,
91
+ borderRadius: 1,
92
+ overflow: "hidden",
93
+ bgcolor: "#1e1e1e",
94
+ }}
95
+ >
96
+ <Editor
97
+ height={`${editorHeight}px`}
98
+ defaultLanguage="json"
99
+ value={formattedJson}
100
+ theme="vs-dark"
101
+ options={{
102
+ readOnly: true,
103
+ minimap: { enabled: false },
104
+ fontSize: 12,
105
+ lineNumbers: "off",
106
+ scrollBeyondLastLine: false,
107
+ wordWrap: "on",
108
+ tabSize: 2,
109
+ folding: false,
110
+ glyphMargin: false,
111
+ lineDecorationsWidth: 0,
112
+ lineNumbersMinChars: 0,
113
+ padding: { top: 8, bottom: 8 },
114
+ scrollbar: { vertical: "hidden", horizontal: "hidden" },
115
+ renderLineHighlight: "none",
116
+ selectionHighlight: false,
117
+ occurrencesHighlight: "off",
118
+ contextmenu: false,
119
+ domReadOnly: true,
120
+ }}
121
+ />
122
+ </Box>
123
+ </Box>
124
+ ) : (
125
+ <Typography
126
+ variant="body1"
127
+ textAlign="end"
128
+ sx={{
129
+ fontSize: "0.95rem",
130
+ lineHeight: 1.6,
131
+ wordWrap: "break-word",
132
+ }}
133
+ >
134
+ {message.content}
135
+ </Typography>
136
+ )}
137
+ </Stack>
138
+ );
139
+ }
140
+ );
141
+
142
+ export { HumanMessage };
@@ -0,0 +1,27 @@
1
+ import { Iconify } from "@canmingir/link/platform/components";
2
+ import { Stack } from "@mui/material";
3
+ import { alpha } from "@mui/material/styles";
4
+ import { memo } from "react";
5
+
6
+ const LoadingMessage = memo(
7
+ ({
8
+ messagesEndRef,
9
+ }: {
10
+ messagesEndRef: { current: HTMLDivElement | null };
11
+ }) => (
12
+ <Stack
13
+ ref={messagesEndRef}
14
+ sx={{
15
+ p: 2,
16
+ height: 50,
17
+ backgroundColor: (theme) => alpha(theme.palette.primary.dark, 0.5),
18
+ borderRadius: 1,
19
+ mt: 1,
20
+ }}
21
+ >
22
+ <Iconify icon="svg-spinners:tadpole" sx={{ width: 25, height: 25 }} />
23
+ </Stack>
24
+ )
25
+ );
26
+
27
+ export { LoadingMessage };
@@ -0,0 +1,81 @@
1
+ import { AIMessage } from "./AIMessage";
2
+ import { ErrorMessage } from "./ErrorMessage";
3
+ import { HumanMessage } from "./HumanMessage";
4
+ import { ToolDecision, ToolMessage, ToolRenderers } from "./ToolMessage";
5
+
6
+ import React, { memo } from "react";
7
+
8
+ const MessageList = memo(
9
+ ({
10
+ error,
11
+ messages,
12
+ selectedId,
13
+ messagesEndRef,
14
+ highlightedMessage,
15
+ onErrorClose,
16
+ toolRenderers,
17
+ onToolDecision,
18
+ }: {
19
+ error?: string;
20
+ messages: { id: string; content: string; role: string }[];
21
+ selectedId: string;
22
+ messagesEndRef: { current: HTMLDivElement | null };
23
+ highlightedMessage: { current: HTMLDivElement | null };
24
+ onErrorClose?: () => void;
25
+ toolRenderers?: ToolRenderers;
26
+ onToolDecision?: (toolCallId: string, decision: ToolDecision) => void;
27
+ }) => (
28
+ <>
29
+ {messages.map((item, index) => {
30
+ const isLastMessage = index === messages.length - 1;
31
+ const isSelected = item.id === selectedId;
32
+
33
+ if (item.role === "USER") {
34
+ return (
35
+ <HumanMessage
36
+ key={item.id || index}
37
+ message={item}
38
+ selectedId={selectedId}
39
+ messageRef={
40
+ isSelected
41
+ ? highlightedMessage
42
+ : isLastMessage
43
+ ? messagesEndRef
44
+ : undefined
45
+ }
46
+ />
47
+ );
48
+ }
49
+
50
+ if (item.role === "TOOL") {
51
+ return (
52
+ <ToolMessage
53
+ key={item.id || index}
54
+ content={item.content}
55
+ messageRef={isLastMessage ? messagesEndRef : undefined}
56
+ renderers={toolRenderers}
57
+ onDecision={onToolDecision}
58
+ />
59
+ );
60
+ }
61
+
62
+ return (
63
+ <AIMessage
64
+ key={item.id || index}
65
+ content={item.content}
66
+ messageRef={isLastMessage ? messagesEndRef : undefined}
67
+ />
68
+ );
69
+ })}
70
+ {error && (
71
+ <ErrorMessage
72
+ key="error-message"
73
+ content={error}
74
+ onClose={onErrorClose}
75
+ />
76
+ )}
77
+ </>
78
+ )
79
+ );
80
+
81
+ export { MessageList };
@@ -0,0 +1,346 @@
1
+ import { Iconify } from "@canmingir/link/platform/components";
2
+ import { alpha } from "@mui/material/styles";
3
+
4
+ import { Box, Button, Collapse, Stack, Typography } from "@mui/material";
5
+ import React, { memo, useMemo, useState } from "react";
6
+
7
+ export type ToolStatus =
8
+ | "awaiting_approval"
9
+ | "pending"
10
+ | "success"
11
+ | "error"
12
+ | "declined";
13
+
14
+ export type ToolDecision = "approve" | "decline" | "approve_all";
15
+
16
+ export interface ToolMessageContent {
17
+ id: string;
18
+ name: string;
19
+ input?: Record<string, unknown>;
20
+ output?: string;
21
+ status: ToolStatus;
22
+ }
23
+
24
+ export interface ToolRendererProps {
25
+ name: string;
26
+ input?: Record<string, unknown>;
27
+ output?: string;
28
+ status: ToolStatus;
29
+ }
30
+
31
+ export type ToolRenderers = Record<
32
+ string,
33
+ React.ComponentType<ToolRendererProps>
34
+ >;
35
+
36
+ function formatLabel(name: string): string {
37
+ return name
38
+ .split("_")
39
+ .filter(Boolean)
40
+ .map((word) => word.charAt(0).toUpperCase() + word.slice(1))
41
+ .join(" ");
42
+ }
43
+
44
+ function formatValue(value: unknown): string {
45
+ if (value === null || value === undefined || value === "") return "—";
46
+ if (typeof value === "string") return value;
47
+ if (typeof value === "number" || typeof value === "boolean") {
48
+ return String(value);
49
+ }
50
+ if (Array.isArray(value)) {
51
+ return value.length ? value.map(formatValue).join(", ") : "—";
52
+ }
53
+ if (typeof value === "object") {
54
+ return Object.entries(value as Record<string, unknown>)
55
+ .map(([key, val]) => `${formatLabel(key)}: ${formatValue(val)}`)
56
+ .join(", ");
57
+ }
58
+ return String(value);
59
+ }
60
+
61
+ function statusIcon(status: ToolStatus): string {
62
+ if (status === "success") return "solar:check-circle-bold";
63
+ if (status === "error") return "solar:close-circle-bold";
64
+ if (status === "declined") return "solar:forbidden-circle-bold";
65
+ if (status === "awaiting_approval") return "solar:shield-warning-bold";
66
+ return "svg-spinners:tadpole";
67
+ }
68
+
69
+ function statusColor(
70
+ status: ToolStatus
71
+ ): "success.main" | "error.main" | "warning.main" | "text.secondary" {
72
+ if (status === "success") return "success.main";
73
+ if (status === "error") return "error.main";
74
+ if (status === "awaiting_approval") return "warning.main";
75
+ return "text.secondary";
76
+ }
77
+
78
+ function FieldRow({ label, value }: { label: string; value: unknown }) {
79
+ return (
80
+ <Stack direction="row" spacing={1} sx={{ py: 0.4 }}>
81
+ <Typography
82
+ sx={{
83
+ fontSize: "0.72rem",
84
+ fontWeight: 600,
85
+ color: "text.secondary",
86
+ minWidth: 90,
87
+ flexShrink: 0,
88
+ }}
89
+ >
90
+ {label}
91
+ </Typography>
92
+ <Typography sx={{ fontSize: "0.78rem", wordBreak: "break-word" }}>
93
+ {formatValue(value)}
94
+ </Typography>
95
+ </Stack>
96
+ );
97
+ }
98
+
99
+ function FieldList({ data }: { data: Record<string, unknown> }) {
100
+ const entries = Object.entries(data);
101
+ if (!entries.length) {
102
+ return (
103
+ <Typography sx={{ fontSize: "0.78rem", color: "text.secondary" }}>
104
+ No fields
105
+ </Typography>
106
+ );
107
+ }
108
+
109
+ return (
110
+ <Stack>
111
+ {entries.map(([key, value]) => (
112
+ <FieldRow key={key} label={formatLabel(key)} value={value} />
113
+ ))}
114
+ </Stack>
115
+ );
116
+ }
117
+
118
+ function ResultView({ output }: { output: string }) {
119
+ const parsed = useMemo(() => {
120
+ try {
121
+ return JSON.parse(output);
122
+ } catch {
123
+ return undefined;
124
+ }
125
+ }, [output]);
126
+
127
+ if (parsed === undefined) {
128
+ return (
129
+ <Typography sx={{ fontSize: "0.78rem", whiteSpace: "pre-wrap" }}>
130
+ {output}
131
+ </Typography>
132
+ );
133
+ }
134
+
135
+ if (Array.isArray(parsed)) {
136
+ if (!parsed.length) {
137
+ return (
138
+ <Typography sx={{ fontSize: "0.78rem", color: "text.secondary" }}>
139
+ Empty result
140
+ </Typography>
141
+ );
142
+ }
143
+
144
+ return (
145
+ <Stack spacing={1}>
146
+ {parsed.map((item, index) =>
147
+ item && typeof item === "object" ? (
148
+ <Box
149
+ key={index}
150
+ sx={{
151
+ pl: 1,
152
+ borderLeft: (theme) =>
153
+ `2px solid ${alpha(theme.palette.grey[500], 0.3)}`,
154
+ }}
155
+ >
156
+ <FieldList data={item as Record<string, unknown>} />
157
+ </Box>
158
+ ) : (
159
+ <Typography key={index} sx={{ fontSize: "0.78rem" }}>
160
+ {formatValue(item)}
161
+ </Typography>
162
+ )
163
+ )}
164
+ </Stack>
165
+ );
166
+ }
167
+
168
+ if (parsed && typeof parsed === "object") {
169
+ return <FieldList data={parsed as Record<string, unknown>} />;
170
+ }
171
+
172
+ return (
173
+ <Typography sx={{ fontSize: "0.78rem" }}>{formatValue(parsed)}</Typography>
174
+ );
175
+ }
176
+
177
+ function ApprovalActions({
178
+ toolCallId,
179
+ onDecision,
180
+ }: {
181
+ toolCallId: string;
182
+ onDecision: (toolCallId: string, decision: ToolDecision) => void;
183
+ }) {
184
+ return (
185
+ <Stack direction="row" spacing={1} sx={{ mt: 1 }}>
186
+ <Button
187
+ size="small"
188
+ variant="contained"
189
+ color="success"
190
+ onClick={() => onDecision(toolCallId, "approve")}
191
+ sx={{ fontSize: "0.7rem", py: 0.25 }}
192
+ >
193
+ Approve
194
+ </Button>
195
+ <Button
196
+ size="small"
197
+ variant="outlined"
198
+ color="error"
199
+ onClick={() => onDecision(toolCallId, "decline")}
200
+ sx={{ fontSize: "0.7rem", py: 0.25 }}
201
+ >
202
+ Decline
203
+ </Button>
204
+ <Button
205
+ size="small"
206
+ variant="text"
207
+ onClick={() => onDecision(toolCallId, "approve_all")}
208
+ sx={{ fontSize: "0.7rem", py: 0.25 }}
209
+ >
210
+ Approve All
211
+ </Button>
212
+ </Stack>
213
+ );
214
+ }
215
+
216
+ const ToolMessage: React.FC<{
217
+ content: string;
218
+ messageRef?: React.RefObject<HTMLDivElement>;
219
+ renderers?: ToolRenderers;
220
+ onDecision?: (toolCallId: string, decision: ToolDecision) => void;
221
+ }> = memo(({ content, messageRef, renderers, onDecision }) => {
222
+ const [expanded, setExpanded] = useState(false);
223
+
224
+ const tool = useMemo<ToolMessageContent | null>(() => {
225
+ try {
226
+ const parsed = JSON.parse(content);
227
+ if (parsed && typeof parsed.name === "string") return parsed;
228
+ return null;
229
+ } catch {
230
+ return null;
231
+ }
232
+ }, [content]);
233
+
234
+ if (!tool) return null;
235
+
236
+ const CustomBody = renderers?.[tool.name];
237
+ const awaitingApproval = tool.status === "awaiting_approval";
238
+ const alwaysShowBody = CustomBody || awaitingApproval;
239
+
240
+ return (
241
+ <Stack
242
+ ref={messageRef}
243
+ sx={{
244
+ p: 1.5,
245
+ mt: 1.5,
246
+ borderRadius: 2,
247
+ backgroundColor: (theme) => alpha(theme.palette.grey[800], 0.4),
248
+ border: (theme) =>
249
+ `1px solid ${alpha(
250
+ awaitingApproval ? theme.palette.warning.main : theme.palette.grey[500],
251
+ awaitingApproval ? 0.4 : 0.2
252
+ )}`,
253
+ }}
254
+ >
255
+ <Stack
256
+ direction="row"
257
+ spacing={1}
258
+ alignItems="center"
259
+ onClick={alwaysShowBody ? undefined : () => setExpanded((e) => !e)}
260
+ sx={{ cursor: alwaysShowBody ? "default" : "pointer" }}
261
+ >
262
+ <Iconify
263
+ icon="solar:widget-bold-duotone"
264
+ sx={{ width: 18, height: 18, color: "text.secondary" }}
265
+ />
266
+ <Typography sx={{ fontSize: "0.8rem", fontWeight: 600, flex: 1 }}>
267
+ {formatLabel(tool.name)}
268
+ </Typography>
269
+ <Iconify
270
+ icon={statusIcon(tool.status)}
271
+ sx={{ width: 16, height: 16, color: statusColor(tool.status) }}
272
+ />
273
+ {!alwaysShowBody && (
274
+ <Iconify
275
+ icon={expanded ? "mdi:chevron-up" : "mdi:chevron-down"}
276
+ sx={{ width: 16, height: 16, color: "text.secondary" }}
277
+ />
278
+ )}
279
+ </Stack>
280
+
281
+ {CustomBody ? (
282
+ <Box sx={{ mt: 1 }}>
283
+ <CustomBody
284
+ name={tool.name}
285
+ input={tool.input}
286
+ output={tool.output}
287
+ status={tool.status}
288
+ />
289
+ </Box>
290
+ ) : awaitingApproval ? (
291
+ <Box sx={{ mt: 1 }}>
292
+ {tool.input && <FieldList data={tool.input} />}
293
+ </Box>
294
+ ) : (
295
+ <Collapse in={expanded}>
296
+ <Box sx={{ mt: 1 }}>
297
+ {tool.input && (
298
+ <>
299
+ <Typography
300
+ variant="caption"
301
+ sx={{
302
+ display: "block",
303
+ color: (theme) => alpha(theme.palette.text.secondary, 0.7),
304
+ fontSize: "0.65rem",
305
+ fontWeight: 600,
306
+ textTransform: "uppercase",
307
+ letterSpacing: 0.5,
308
+ }}
309
+ >
310
+ Arguments
311
+ </Typography>
312
+ <FieldList data={tool.input} />
313
+ </>
314
+ )}
315
+
316
+ {tool.output !== undefined && (
317
+ <>
318
+ <Typography
319
+ variant="caption"
320
+ sx={{
321
+ display: "block",
322
+ mt: 1,
323
+ color: (theme) => alpha(theme.palette.text.secondary, 0.7),
324
+ fontSize: "0.65rem",
325
+ fontWeight: 600,
326
+ textTransform: "uppercase",
327
+ letterSpacing: 0.5,
328
+ }}
329
+ >
330
+ Result
331
+ </Typography>
332
+ <ResultView output={tool.output} />
333
+ </>
334
+ )}
335
+ </Box>
336
+ </Collapse>
337
+ )}
338
+
339
+ {awaitingApproval && onDecision && (
340
+ <ApprovalActions toolCallId={tool.id} onDecision={onDecision} />
341
+ )}
342
+ </Stack>
343
+ );
344
+ });
345
+
346
+ export { ToolMessage };
@@ -0,0 +1,6 @@
1
+ export { AIMessage } from "./AIMessage";
2
+ export { ErrorMessage } from "./ErrorMessage";
3
+ export { HumanMessage } from "./HumanMessage";
4
+ export { LoadingMessage } from "./LoadingMessage";
5
+ export { MessageList } from "./MessageList";
6
+ export { ToolMessage } from "./ToolMessage";
@@ -1,5 +1,3 @@
1
- // ----------------------------------------------------------------------
2
-
3
1
  export function getRatio(ratio = "1/1") {
4
2
  return {
5
3
  "4/3": "calc(100% / 4 * 3)",