@huyooo/ai-chat-frontend-react 0.2.12 → 0.2.14

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 (110) hide show
  1. package/README.md +99 -84
  2. package/dist/KaTeX_AMS-Regular-CYEKBG2K.woff +0 -0
  3. package/dist/KaTeX_AMS-Regular-JKX5W2C4.ttf +0 -0
  4. package/dist/KaTeX_AMS-Regular-U6PRYMIZ.woff2 +0 -0
  5. package/dist/KaTeX_Caligraphic-Bold-5QL5CMTE.woff2 +0 -0
  6. package/dist/KaTeX_Caligraphic-Bold-WZ3QSGD3.woff +0 -0
  7. package/dist/KaTeX_Caligraphic-Bold-ZTS3R3HK.ttf +0 -0
  8. package/dist/KaTeX_Caligraphic-Regular-3LKEU76G.woff +0 -0
  9. package/dist/KaTeX_Caligraphic-Regular-A7XRTZ5Q.ttf +0 -0
  10. package/dist/KaTeX_Caligraphic-Regular-KX5MEWCF.woff2 +0 -0
  11. package/dist/KaTeX_Fraktur-Bold-2QVFK6NQ.woff2 +0 -0
  12. package/dist/KaTeX_Fraktur-Bold-T4SWXBMT.woff +0 -0
  13. package/dist/KaTeX_Fraktur-Bold-WGHVTYOR.ttf +0 -0
  14. package/dist/KaTeX_Fraktur-Regular-2PEIFJSJ.woff2 +0 -0
  15. package/dist/KaTeX_Fraktur-Regular-5U4OPH2X.ttf +0 -0
  16. package/dist/KaTeX_Fraktur-Regular-PQMHCIK6.woff +0 -0
  17. package/dist/KaTeX_Main-Bold-2GA4IZIN.woff +0 -0
  18. package/dist/KaTeX_Main-Bold-W5FBVCZM.ttf +0 -0
  19. package/dist/KaTeX_Main-Bold-YP5VVQRP.woff2 +0 -0
  20. package/dist/KaTeX_Main-BoldItalic-4P4C7HJH.woff +0 -0
  21. package/dist/KaTeX_Main-BoldItalic-N4V3DX7S.woff2 +0 -0
  22. package/dist/KaTeX_Main-BoldItalic-ODMLBJJQ.ttf +0 -0
  23. package/dist/KaTeX_Main-Italic-I43T2HSR.ttf +0 -0
  24. package/dist/KaTeX_Main-Italic-RELBIK7M.woff2 +0 -0
  25. package/dist/KaTeX_Main-Italic-SASNQFN2.woff +0 -0
  26. package/dist/KaTeX_Main-Regular-ARRPAO67.woff2 +0 -0
  27. package/dist/KaTeX_Main-Regular-P5I74A2A.woff +0 -0
  28. package/dist/KaTeX_Main-Regular-W74P5G27.ttf +0 -0
  29. package/dist/KaTeX_Math-BoldItalic-6EBV3DK5.woff +0 -0
  30. package/dist/KaTeX_Math-BoldItalic-K4WTGH3J.woff2 +0 -0
  31. package/dist/KaTeX_Math-BoldItalic-VB447A4D.ttf +0 -0
  32. package/dist/KaTeX_Math-Italic-6KGCHLFN.woff2 +0 -0
  33. package/dist/KaTeX_Math-Italic-KKK3USB2.woff +0 -0
  34. package/dist/KaTeX_Math-Italic-SON4MRCA.ttf +0 -0
  35. package/dist/KaTeX_SansSerif-Bold-RRNVJFFW.woff2 +0 -0
  36. package/dist/KaTeX_SansSerif-Bold-STQ6RXC7.ttf +0 -0
  37. package/dist/KaTeX_SansSerif-Bold-X5M5EMOD.woff +0 -0
  38. package/dist/KaTeX_SansSerif-Italic-HMPFTM52.woff2 +0 -0
  39. package/dist/KaTeX_SansSerif-Italic-PSN4QKYX.woff +0 -0
  40. package/dist/KaTeX_SansSerif-Italic-WTBAZBGY.ttf +0 -0
  41. package/dist/KaTeX_SansSerif-Regular-2TL3USAE.ttf +0 -0
  42. package/dist/KaTeX_SansSerif-Regular-OQCII6EP.woff +0 -0
  43. package/dist/KaTeX_SansSerif-Regular-XIQ62X4E.woff2 +0 -0
  44. package/dist/KaTeX_Script-Regular-72OLXYNA.ttf +0 -0
  45. package/dist/KaTeX_Script-Regular-A5IFOEBS.woff +0 -0
  46. package/dist/KaTeX_Script-Regular-APUWIHLP.woff2 +0 -0
  47. package/dist/KaTeX_Size1-Regular-4HRHTS65.woff +0 -0
  48. package/dist/KaTeX_Size1-Regular-5LRUTBFT.woff2 +0 -0
  49. package/dist/KaTeX_Size1-Regular-7K6AASVL.ttf +0 -0
  50. package/dist/KaTeX_Size2-Regular-222HN3GT.ttf +0 -0
  51. package/dist/KaTeX_Size2-Regular-K5ZHAIS6.woff +0 -0
  52. package/dist/KaTeX_Size2-Regular-LELKET5D.woff2 +0 -0
  53. package/dist/KaTeX_Size3-Regular-TLFPAHDE.woff +0 -0
  54. package/dist/KaTeX_Size3-Regular-UFCO6WCA.ttf +0 -0
  55. package/dist/KaTeX_Size3-Regular-WQRQ47UD.woff2 +0 -0
  56. package/dist/KaTeX_Size4-Regular-7PGNVPQK.ttf +0 -0
  57. package/dist/KaTeX_Size4-Regular-CDMV7U5C.woff2 +0 -0
  58. package/dist/KaTeX_Size4-Regular-PKMWZHNC.woff +0 -0
  59. package/dist/KaTeX_Typewriter-Regular-3F5K6SQ6.ttf +0 -0
  60. package/dist/KaTeX_Typewriter-Regular-MJMFSK64.woff +0 -0
  61. package/dist/KaTeX_Typewriter-Regular-VBYJ4NRC.woff2 +0 -0
  62. package/dist/index.css +2156 -603
  63. package/dist/index.css.map +1 -1
  64. package/dist/index.d.ts +126 -92
  65. package/dist/index.js +1605 -976
  66. package/dist/index.js.map +1 -1
  67. package/dist/style.css +130 -0
  68. package/package.json +3 -3
  69. package/src/components/ChatPanel.tsx +82 -19
  70. package/src/components/common/SettingsPanel.css +81 -0
  71. package/src/components/common/SettingsPanel.tsx +96 -1
  72. package/src/components/input/ChatInput.css +0 -1
  73. package/src/components/input/ChatInput.tsx +48 -26
  74. package/src/components/input/DropdownSelector.css +66 -0
  75. package/src/components/input/DropdownSelector.tsx +157 -19
  76. package/src/components/message/MessageBubble.css +5 -2
  77. package/src/components/message/MessageBubble.tsx +44 -35
  78. package/src/components/message/PartsRenderer.css +8 -0
  79. package/src/components/message/PartsRenderer.tsx +137 -83
  80. package/src/components/message/parts/CollapsibleCard.css +4 -2
  81. package/src/components/message/parts/CollapsibleCard.tsx +4 -1
  82. package/src/components/message/parts/ImagePart.css +0 -1
  83. package/src/components/message/parts/TextPart.css +574 -5
  84. package/src/components/message/parts/TextPart.tsx +201 -8
  85. package/src/components/message/parts/ToolCallPart.css +139 -115
  86. package/src/components/message/parts/ToolCallPart.tsx +138 -134
  87. package/src/components/message/parts/ToolResultPart.css +0 -1
  88. package/src/components/message/parts/index.ts +3 -1
  89. package/src/components/message/parts/visual-predicate.ts +43 -0
  90. package/src/components/message/parts/visual-render.ts +19 -0
  91. package/src/components/message/parts/visual.ts +12 -0
  92. package/src/context/RenderersContext.tsx +19 -25
  93. package/src/hooks/useChat.ts +567 -79
  94. package/src/hooks/useImageUpload.ts +104 -12
  95. package/src/hooks/useVoiceInput.ts +17 -0
  96. package/src/index.ts +19 -16
  97. package/src/styles.css +130 -0
  98. package/src/types/index.ts +52 -68
  99. package/src/components/message/ContentRenderer.tsx +0 -63
  100. package/src/components/message/ToolResultRenderer.tsx +0 -21
  101. package/src/components/message/blocks/CodeBlock.tsx +0 -60
  102. package/src/components/message/blocks/TextBlock.tsx +0 -15
  103. package/src/components/message/blocks/blocks.css +0 -141
  104. package/src/components/message/blocks/index.ts +0 -6
  105. package/src/components/message/parts/ToolResultPart.tsx +0 -96
  106. package/src/components/message/tool-results/DefaultToolResult.tsx +0 -26
  107. package/src/components/message/tool-results/SearchResults.tsx +0 -69
  108. package/src/components/message/tool-results/WeatherCard.tsx +0 -63
  109. package/src/components/message/tool-results/index.ts +0 -7
  110. package/src/components/message/tool-results/tool-results.css +0 -181
@@ -1,114 +1,168 @@
1
- import type { FC } from 'react'
2
- import type { ContentPart, StepsExpandedType } from '../../types'
1
+ import { useContext, useMemo, type FC, type ComponentType } from 'react'
2
+ import type {
3
+ ContentPart,
4
+ StepsExpandedType,
5
+ TextPart as TextPartType,
6
+ ThinkingPart as ThinkingPartType,
7
+ SearchPart as SearchPartType,
8
+ ToolCallPart as ToolCallPartType,
9
+ ImagePart as ImagePartType,
10
+ ErrorPart as ErrorPartType,
11
+ } from '../../types'
3
12
  import type { ChatAdapter } from '../../adapter'
4
13
  import type { AutoRunConfig } from '@huyooo/ai-chat-bridge-electron/renderer'
14
+ import { PartRenderersContext } from '../../context/RenderersContext'
5
15
  import {
6
16
  TextPart,
7
17
  ThinkingPart,
8
18
  SearchPart,
9
- ToolCallPart,
10
- ToolResultPart,
11
19
  ImagePart,
12
- ErrorPart
20
+ ErrorPart,
13
21
  } from './parts'
22
+ import { ToolCallPart } from './parts/ToolCallPart'
14
23
  import './PartsRenderer.css'
15
24
 
25
+ /** Part 渲染器映射类型 */
26
+ type PartRenderers = Record<string, ComponentType<Record<string, unknown>>>
27
+
16
28
  interface PartsRendererProps {
17
29
  parts: ContentPart[]
18
30
  expandedType?: StepsExpandedType
31
+ /** 自定义 Part 渲染器(props 传入优先级高于 context) */
32
+ partRenderers?: PartRenderers
19
33
  // 工具调用相关
20
34
  adapter?: ChatAdapter
35
+ onCancelToolCall?: (toolCallId: string) => void
21
36
  autoRunConfig?: AutoRunConfig
22
37
  onSaveConfig?: (config: AutoRunConfig) => Promise<void>
23
38
  }
24
39
 
40
+ // ==================== 类型守卫函数 ====================
41
+
42
+ function isText(part: ContentPart): part is TextPartType {
43
+ return part.type === 'text'
44
+ }
45
+
46
+ function isThinking(part: ContentPart): part is ThinkingPartType {
47
+ return part.type === 'thinking'
48
+ }
49
+
50
+ function isSearch(part: ContentPart): part is SearchPartType {
51
+ return part.type === 'search'
52
+ }
53
+
54
+ function isToolCall(part: ContentPart): part is ToolCallPartType {
55
+ return part.type === 'tool_call'
56
+ }
57
+
58
+ function isImage(part: ContentPart): part is ImagePartType {
59
+ return part.type === 'image'
60
+ }
61
+
62
+ function isError(part: ContentPart): part is ErrorPartType {
63
+ return part.type === 'error'
64
+ }
65
+
25
66
  export const PartsRenderer: FC<PartsRendererProps> = ({
26
67
  parts,
27
68
  expandedType = 'auto',
69
+ partRenderers: propRenderers,
28
70
  adapter,
71
+ onCancelToolCall,
29
72
  autoRunConfig,
30
73
  onSaveConfig,
31
74
  }) => {
75
+ // 从 context 获取渲染器,props 优先
76
+ const contextRenderers = useContext(PartRenderersContext)
77
+ const partRenderers = propRenderers ?? contextRenderers
78
+
79
+ /** 可见的 parts */
80
+ const visibleParts = useMemo(() => parts, [parts])
81
+
82
+ const renderPart = (part: ContentPart) => {
83
+ // 先检查是否有自定义渲染器
84
+ const CustomRenderer = partRenderers[part.type]
85
+ if (CustomRenderer) {
86
+ return <CustomRenderer {...part} />
87
+ }
88
+
89
+ // 内置类型渲染(使用类型守卫)
90
+ if (isText(part)) {
91
+ return <TextPart text={part.text} />
92
+ }
93
+
94
+ if (isThinking(part)) {
95
+ return (
96
+ <ThinkingPart
97
+ text={part.text}
98
+ status={part.status}
99
+ duration={part.duration}
100
+ expandedType={expandedType}
101
+ />
102
+ )
103
+ }
104
+
105
+ if (isSearch(part)) {
106
+ return (
107
+ <SearchPart
108
+ query={part.query}
109
+ results={part.results}
110
+ status={part.status}
111
+ expandedType={expandedType}
112
+ />
113
+ )
114
+ }
115
+
116
+ if (isToolCall(part)) {
117
+ return (
118
+ <ToolCallPart
119
+ id={part.id}
120
+ name={part.name}
121
+ args={part.args}
122
+ output={part.output}
123
+ status={part.status}
124
+ expandedType={expandedType}
125
+ adapter={adapter}
126
+ onCancelToolCall={onCancelToolCall}
127
+ autoRunConfig={autoRunConfig}
128
+ onSaveConfig={onSaveConfig}
129
+ />
130
+ )
131
+ }
132
+
133
+ if (isImage(part)) {
134
+ return <ImagePart url={part.url} />
135
+ }
136
+
137
+ if (isError(part)) {
138
+ return (
139
+ <ErrorPart
140
+ message={part.message}
141
+ category={part.category}
142
+ retryable={part.retryable}
143
+ />
144
+ )
145
+ }
146
+
147
+ // 未知类型,显示 JSON
148
+ return (
149
+ <div className="unknown-part">
150
+ <pre>{JSON.stringify(part, null, 2)}</pre>
151
+ </div>
152
+ )
153
+ }
154
+
32
155
  return (
33
156
  <div className="parts-renderer">
34
- {parts.map((part, index) => {
35
- switch (part.type) {
36
- case 'text':
37
- return <TextPart key={index} text={part.text} />
38
-
39
- case 'thinking':
40
- return (
41
- <ThinkingPart
42
- key={index}
43
- text={part.text}
44
- status={part.status}
45
- duration={part.duration}
46
- expandedType={expandedType}
47
- />
48
- )
49
-
50
- case 'search':
51
- return (
52
- <SearchPart
53
- key={index}
54
- query={part.query}
55
- results={part.results}
56
- status={part.status}
57
- expandedType={expandedType}
58
- />
59
- )
60
-
61
- case 'tool_call':
62
- return (
63
- <ToolCallPart
64
- key={index}
65
- id={part.id}
66
- name={part.name}
67
- args={part.args}
68
- status={part.status}
69
- result={part.result}
70
- expandedType={expandedType}
71
- adapter={adapter}
72
- autoRunConfig={autoRunConfig}
73
- onSaveConfig={onSaveConfig}
74
- />
75
- )
76
-
77
- case 'tool_result':
78
- return (
79
- <ToolResultPart
80
- key={index}
81
- id={part.id}
82
- name={part.name}
83
- args={part.args}
84
- result={part.result}
85
- status={part.status}
86
- expandedType={expandedType}
87
- />
88
- )
89
-
90
- case 'image':
91
- return (
92
- <ImagePart
93
- key={index}
94
- url={part.url}
95
- />
96
- )
97
-
98
- case 'error':
99
- return (
100
- <ErrorPart
101
- key={index}
102
- message={part.message}
103
- category={part.category}
104
- retryable={part.retryable}
105
- />
106
- )
107
-
108
- default:
109
- return null
110
- }
111
- })}
157
+ {visibleParts.map((part, index) => (
158
+ <div
159
+ key={index}
160
+ className="part-item"
161
+ style={{ marginTop: index > 0 ? '8px' : '0' }}
162
+ >
163
+ {renderPart(part)}
164
+ </div>
165
+ ))}
112
166
  </div>
113
167
  )
114
168
  }
@@ -1,5 +1,4 @@
1
1
  .collapsible-card {
2
- margin: 8px 0;
3
2
  border-radius: 8px;
4
3
  background: var(--chat-muted, #2a2a2a);
5
4
  border: 1px solid var(--chat-border, #333);
@@ -20,6 +19,9 @@
20
19
  align-items: center;
21
20
  gap: 8px;
22
21
  flex: 1;
22
+ }
23
+
24
+ .card-header-left.clickable {
23
25
  cursor: pointer;
24
26
  }
25
27
 
@@ -72,7 +74,7 @@
72
74
  }
73
75
 
74
76
  .card-content {
75
- padding: 12px;
77
+ padding: 0 12px 12px;
76
78
  min-width: 0;
77
79
  overflow: hidden;
78
80
  }
@@ -43,7 +43,10 @@ export const CollapsibleCard: FC<CollapsibleCardProps> = ({
43
43
  return (
44
44
  <div className={`collapsible-card ${expanded ? 'expanded' : ''}`}>
45
45
  <div className="card-header">
46
- <div className="card-header-left" onClick={() => onExpandedChange(!expanded)}>
46
+ <div
47
+ className={`card-header-left ${collapsible ? 'clickable' : ''}`}
48
+ onClick={() => collapsible && onExpandedChange(!expanded)}
49
+ >
47
50
  <div className="card-icon" style={{ color: iconColor }}>
48
51
  {spinning ? (
49
52
  <Icon icon="lucide:loader-2" width={14} className="spinning" />
@@ -1,6 +1,5 @@
1
1
  .image-part {
2
2
  position: relative;
3
- margin: 8px 0;
4
3
  max-width: 100%;
5
4
  display: inline-block;
6
5
  }