@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,21 +0,0 @@
1
- /**
2
- * 工具结果渲染器
3
- * 根据工具名称选择合适的渲染组件
4
- */
5
-
6
- import { FC, useContext, useMemo, type ComponentType } from 'react'
7
- import type { ToolRendererProps } from '@huyooo/ai-chat-shared'
8
- import { ToolRenderersContext } from '../../context/RenderersContext'
9
- import { DefaultToolResult } from './tool-results'
10
-
11
- export const ToolResultRenderer: FC<ToolRendererProps> = (props) => {
12
- // 从上层获取自定义工具渲染器
13
- const customRenderers = useContext(ToolRenderersContext)
14
-
15
- // 解析使用哪个组件
16
- const Component = useMemo<ComponentType<ToolRendererProps>>(() => {
17
- return customRenderers[props.toolName] || DefaultToolResult
18
- }, [customRenderers, props.toolName])
19
-
20
- return <Component {...props} />
21
- }
@@ -1,60 +0,0 @@
1
- /**
2
- * 代码块组件
3
- */
4
-
5
- import { FC, useState, useCallback, useMemo } from 'react'
6
- import { Icon } from '@iconify/react'
7
- import { highlightCode, getLanguageDisplayName } from '@huyooo/ai-chat-shared'
8
- import type { CodeBlock as CodeBlockType } from '@huyooo/ai-chat-shared'
9
- import './blocks.css'
10
-
11
- interface CodeBlockProps {
12
- block: CodeBlockType
13
- onCopy?: (code: string) => void
14
- }
15
-
16
- export const CodeBlock: FC<CodeBlockProps> = ({ block, onCopy }) => {
17
- const [copied, setCopied] = useState(false)
18
-
19
- // 语言显示名称
20
- const languageDisplay = useMemo(
21
- () => getLanguageDisplayName(block.language),
22
- [block.language]
23
- )
24
-
25
- // 高亮后的代码
26
- const highlightedCode = useMemo(
27
- () => highlightCode(block.content, block.language),
28
- [block.content, block.language]
29
- )
30
-
31
- /** 复制代码 */
32
- const handleCopy = useCallback(async () => {
33
- try {
34
- await navigator.clipboard.writeText(block.content)
35
- setCopied(true)
36
- onCopy?.(block.content)
37
- setTimeout(() => setCopied(false), 2000)
38
- } catch (err) {
39
- console.error('复制失败:', err)
40
- }
41
- }, [block.content, onCopy])
42
-
43
- return (
44
- <div className="code-block">
45
- {/* 头部:语言 + 操作按钮 */}
46
- <div className="code-header">
47
- <span className="code-language">{languageDisplay}</span>
48
- <div className="code-actions">
49
- <button className="code-action-btn" title="复制代码" onClick={handleCopy}>
50
- <Icon icon={copied ? 'lucide:check' : 'lucide:copy'} width={14} />
51
- </button>
52
- </div>
53
- </div>
54
- {/* 代码内容 */}
55
- <pre className="code-content">
56
- <code dangerouslySetInnerHTML={{ __html: highlightedCode }} />
57
- </pre>
58
- </div>
59
- )
60
- }
@@ -1,15 +0,0 @@
1
- /**
2
- * 文本块组件
3
- */
4
-
5
- import { FC } from 'react'
6
- import type { TextBlock as TextBlockType } from '@huyooo/ai-chat-shared'
7
- import './blocks.css'
8
-
9
- interface TextBlockProps {
10
- block: TextBlockType
11
- }
12
-
13
- export const TextBlock: FC<TextBlockProps> = ({ block }) => {
14
- return <div className="text-block">{block.content}</div>
15
- }
@@ -1,141 +0,0 @@
1
- /**
2
- * 内容块共享样式
3
- */
4
-
5
- .text-block {
6
- font-size: 14px;
7
- line-height: 1.7;
8
- color: var(--chat-text, #ccc);
9
- white-space: pre-wrap;
10
- word-break: break-word;
11
- }
12
-
13
- .code-block {
14
- background: var(--chat-code-bg, #1f2937);
15
- border-radius: 8px;
16
- overflow: hidden;
17
- margin: 8px 0;
18
- }
19
-
20
- .code-header {
21
- display: flex;
22
- align-items: center;
23
- justify-content: space-between;
24
- padding: 8px 12px;
25
- background: rgba(0, 0, 0, 0.2);
26
- border-bottom: 1px solid rgba(255, 255, 255, 0.1);
27
- }
28
-
29
- .code-language {
30
- font-size: 12px;
31
- color: var(--chat-text-muted, #888);
32
- font-family: "SF Mono", Monaco, monospace;
33
- }
34
-
35
- .code-actions {
36
- display: flex;
37
- gap: 4px;
38
- }
39
-
40
- .code-action-btn {
41
- display: flex;
42
- align-items: center;
43
- justify-content: center;
44
- width: 28px;
45
- height: 28px;
46
- border: none;
47
- background: transparent;
48
- border-radius: 4px;
49
- color: var(--chat-text-muted, #888);
50
- cursor: pointer;
51
- transition: all 0.15s;
52
- }
53
-
54
- .code-action-btn:hover {
55
- background: rgba(255, 255, 255, 0.1);
56
- color: var(--chat-text, #ccc);
57
- }
58
-
59
- .code-content {
60
- margin: 0;
61
- padding: 12px;
62
- overflow-x: auto;
63
- font-family: "SF Mono", Monaco, Consolas, monospace;
64
- font-size: 13px;
65
- line-height: 1.5;
66
- color: var(--chat-code-text, #e5e7eb);
67
- }
68
-
69
- /* 统一滚动条样式 */
70
- .code-content::-webkit-scrollbar {
71
- width: 6px;
72
- height: 6px;
73
- }
74
-
75
- .code-content::-webkit-scrollbar-track {
76
- background: transparent;
77
- }
78
-
79
- .code-content::-webkit-scrollbar-thumb {
80
- background: var(--chat-scrollbar, rgba(255, 255, 255, 0.2));
81
- border-radius: 3px;
82
- }
83
-
84
- .code-content::-webkit-scrollbar-thumb:hover {
85
- background: var(--chat-scrollbar-hover, rgba(255, 255, 255, 0.3));
86
- }
87
-
88
- .code-content code {
89
- font-family: inherit;
90
- }
91
-
92
- /* highlight.js 主题 */
93
- .code-content .hljs-keyword {
94
- color: #c678dd;
95
- }
96
- .code-content .hljs-string {
97
- color: #98c379;
98
- }
99
- .code-content .hljs-number {
100
- color: #d19a66;
101
- }
102
- .code-content .hljs-comment {
103
- color: #5c6370;
104
- font-style: italic;
105
- }
106
- .code-content .hljs-function {
107
- color: #61afef;
108
- }
109
- .code-content .hljs-class {
110
- color: #e5c07b;
111
- }
112
- .code-content .hljs-variable {
113
- color: #e06c75;
114
- }
115
- .code-content .hljs-operator {
116
- color: #56b6c2;
117
- }
118
- .code-content .hljs-punctuation {
119
- color: #abb2bf;
120
- }
121
- .code-content .hljs-property {
122
- color: #e06c75;
123
- }
124
- .code-content .hljs-attr {
125
- color: #d19a66;
126
- }
127
- .code-content .hljs-built_in {
128
- color: #e5c07b;
129
- }
130
- .code-content .hljs-title {
131
- color: #61afef;
132
- }
133
- .code-content .hljs-params {
134
- color: #abb2bf;
135
- }
136
- .code-content .hljs-literal {
137
- color: #56b6c2;
138
- }
139
- .code-content .hljs-type {
140
- color: #e5c07b;
141
- }
@@ -1,6 +0,0 @@
1
- /**
2
- * 内容块组件导出
3
- */
4
-
5
- export { TextBlock } from './TextBlock'
6
- export { CodeBlock } from './CodeBlock'
@@ -1,96 +0,0 @@
1
- import { useState, useContext, useMemo, type FC, type ComponentType } from 'react'
2
- import { Icon } from '@iconify/react'
3
- import { ToolRenderersContext } from '../../../context/RenderersContext'
4
- import type { ToolRendererProps } from '@huyooo/ai-chat-shared'
5
- import type { StepsExpandedType } from '../../../types'
6
- import './ToolResultPart.css'
7
-
8
- interface ToolResultPartProps {
9
- id: string
10
- name: string
11
- args?: Record<string, unknown>
12
- result: unknown
13
- status: 'done' | 'error' | 'cancelled' | 'skipped'
14
- expandedType?: StepsExpandedType
15
- }
16
-
17
- export const ToolResultPart: FC<ToolResultPartProps> = ({
18
- name,
19
- args,
20
- result,
21
- status,
22
- expandedType = 'auto'
23
- }) => {
24
- const toolRenderers = useContext(ToolRenderersContext)
25
-
26
- // 根据模式计算初始状态(工具结果完成后默认折叠,除非 mode 是 open)
27
- const getInitialExpanded = () => {
28
- if (expandedType === 'open') return true
29
- return false // close 和 auto 模式下默认折叠
30
- }
31
-
32
- const [expanded, setExpanded] = useState(getInitialExpanded)
33
-
34
- // 获取自定义渲染器
35
- const CustomRenderer = useMemo(() => {
36
- return toolRenderers[name] as ComponentType<ToolRendererProps> | undefined
37
- }, [toolRenderers, name])
38
-
39
- // 格式化结果
40
- const formattedResult = useMemo(() => {
41
- if (typeof result === 'string') {
42
- return result
43
- }
44
- return JSON.stringify(result, null, 2)
45
- }, [result])
46
-
47
- // 将 ToolResultPart 的 status 映射到 ToolRendererProps 的 status
48
- const rendererStatus = useMemo((): 'running' | 'completed' | 'error' => {
49
- if (status === 'error') return 'error'
50
- if (status === 'cancelled') return 'error'
51
- // 'done' 和 'skipped' 都映射为 'completed'
52
- return 'completed'
53
- }, [status])
54
-
55
- // 如果有自定义渲染器,使用它
56
- if (CustomRenderer) {
57
- return (
58
- <div className="tool-result-part">
59
- <CustomRenderer
60
- toolName={name}
61
- toolArgs={args || {}}
62
- toolResult={result}
63
- status={rendererStatus}
64
- />
65
- </div>
66
- )
67
- }
68
-
69
- // 默认渲染:可折叠的 JSON
70
- return (
71
- <div className="tool-result-part">
72
- <div className={`default-result ${expanded ? 'expanded' : ''}`}>
73
- <div className="result-header" onClick={() => setExpanded(!expanded)}>
74
- <div className="result-icon">
75
- {status === 'error' ? (
76
- <Icon icon="lucide:x-circle" width={14} className="error" />
77
- ) : (
78
- <Icon icon="lucide:check-circle" width={14} className="success" />
79
- )}
80
- </div>
81
- <span className="result-name">{name}</span>
82
- <Icon
83
- icon={expanded ? 'lucide:chevron-up' : 'lucide:chevron-down'}
84
- width={14}
85
- className="result-chevron"
86
- />
87
- </div>
88
- {expanded && (
89
- <div className="result-content">
90
- <pre>{formattedResult}</pre>
91
- </div>
92
- )}
93
- </div>
94
- </div>
95
- )
96
- }
@@ -1,26 +0,0 @@
1
- /**
2
- * 默认工具结果渲染器
3
- */
4
-
5
- import { FC, useMemo } from 'react'
6
- import type { ToolRendererProps } from '@huyooo/ai-chat-shared'
7
- import './tool-results.css'
8
-
9
- export const DefaultToolResult: FC<ToolRendererProps> = ({ toolResult }) => {
10
- const formattedResult = useMemo(() => {
11
- if (typeof toolResult === 'string') {
12
- return toolResult
13
- }
14
- try {
15
- return JSON.stringify(toolResult, null, 2)
16
- } catch {
17
- return String(toolResult)
18
- }
19
- }, [toolResult])
20
-
21
- return (
22
- <div className="default-tool-result">
23
- <pre className="result-content">{formattedResult}</pre>
24
- </div>
25
- )
26
- }
@@ -1,69 +0,0 @@
1
- /**
2
- * 搜索结果渲染器
3
- */
4
-
5
- import { FC, useMemo, useCallback } from 'react'
6
- import { Icon } from '@iconify/react'
7
- import type { ToolRendererProps, SearchResultItem } from '@huyooo/ai-chat-shared'
8
- import './tool-results.css'
9
-
10
- export const SearchResults: FC<ToolRendererProps> = ({ toolResult }) => {
11
- const results = useMemo<SearchResultItem[]>(() => {
12
- if (Array.isArray(toolResult)) {
13
- return toolResult as SearchResultItem[]
14
- }
15
- if (typeof toolResult === 'object' && toolResult !== null && 'results' in toolResult) {
16
- return (toolResult as { results: SearchResultItem[] }).results
17
- }
18
- return []
19
- }, [toolResult])
20
-
21
- /** 获取域名 */
22
- const getDomain = useCallback((url: string): string => {
23
- try {
24
- return new URL(url).hostname
25
- } catch {
26
- return url
27
- }
28
- }, [])
29
-
30
- /** 打开外部链接 */
31
- const openExternal = useCallback((url: string) => {
32
- const bridge = (window as { aiChatBridge?: { openExternal: (url: string) => Promise<void> } })
33
- .aiChatBridge
34
- if (bridge?.openExternal) {
35
- bridge.openExternal(url)
36
- } else {
37
- window.open(url, '_blank')
38
- }
39
- }, [])
40
-
41
- return (
42
- <div className="search-results-card">
43
- <div className="search-header">
44
- <Icon icon="lucide:search" width={16} />
45
- <span>搜索结果</span>
46
- <span className="search-count">{results.length} 条</span>
47
- </div>
48
- <div className="search-list">
49
- {results.map((item, index) => (
50
- <a
51
- key={index}
52
- href={item.url}
53
- target="_blank"
54
- rel="noopener noreferrer"
55
- className="search-item"
56
- onClick={(e) => {
57
- e.preventDefault()
58
- openExternal(item.url)
59
- }}
60
- >
61
- <div className="item-title">{item.title}</div>
62
- {item.snippet && <div className="item-snippet">{item.snippet}</div>}
63
- <div className="item-url">{getDomain(item.url)}</div>
64
- </a>
65
- ))}
66
- </div>
67
- </div>
68
- )
69
- }
@@ -1,63 +0,0 @@
1
- /**
2
- * 天气卡片渲染器
3
- */
4
-
5
- import { FC, useMemo } from 'react'
6
- import { Icon } from '@iconify/react'
7
- import type { ToolRendererProps, WeatherData } from '@huyooo/ai-chat-shared'
8
- import './tool-results.css'
9
-
10
- export const WeatherCard: FC<ToolRendererProps> = ({ toolResult }) => {
11
- const weather = useMemo<WeatherData>(() => {
12
- if (typeof toolResult === 'object' && toolResult !== null) {
13
- return toolResult as WeatherData
14
- }
15
- return {
16
- city: '未知',
17
- temperature: 0,
18
- condition: '未知',
19
- }
20
- }, [toolResult])
21
-
22
- return (
23
- <div className="weather-card">
24
- <div className="weather-header">
25
- <Icon icon="lucide:cloud-sun" width={24} className="weather-icon" />
26
- <div className="weather-location">{weather.city}</div>
27
- </div>
28
- <div className="weather-main">
29
- <div className="weather-temp">{weather.temperature}°</div>
30
- <div className="weather-condition">{weather.condition}</div>
31
- </div>
32
- {(weather.humidity || weather.wind) && (
33
- <div className="weather-details">
34
- {weather.humidity && (
35
- <div className="weather-detail">
36
- <Icon icon="lucide:droplets" width={14} />
37
- <span>{weather.humidity}%</span>
38
- </div>
39
- )}
40
- {weather.wind && (
41
- <div className="weather-detail">
42
- <Icon icon="lucide:wind" width={14} />
43
- <span>{weather.wind}</span>
44
- </div>
45
- )}
46
- </div>
47
- )}
48
- {weather.forecast?.length ? (
49
- <div className="weather-forecast">
50
- {weather.forecast.map((item) => (
51
- <div key={item.date} className="forecast-item">
52
- <div className="forecast-date">{item.date}</div>
53
- <div className="forecast-temp">
54
- {item.low}° / {item.high}°
55
- </div>
56
- <div className="forecast-condition">{item.condition}</div>
57
- </div>
58
- ))}
59
- </div>
60
- ) : null}
61
- </div>
62
- )
63
- }
@@ -1,7 +0,0 @@
1
- /**
2
- * 工具结果渲染组件导出
3
- */
4
-
5
- export { DefaultToolResult } from './DefaultToolResult'
6
- export { WeatherCard } from './WeatherCard'
7
- export { SearchResults } from './SearchResults'
@@ -1,181 +0,0 @@
1
- /**
2
- * 工具结果渲染器共享样式
3
- */
4
-
5
- /* 默认工具结果 */
6
- .default-tool-result {
7
- background: var(--chat-bg, #1e1e1e);
8
- border-radius: 6px;
9
- overflow: hidden;
10
- }
11
-
12
- .result-content {
13
- margin: 0;
14
- padding: 8px;
15
- font-size: 13px;
16
- line-height: 1.5;
17
- color: var(--chat-code-text, var(--chat-text, #999));
18
- font-family: "SF Mono", Monaco, monospace;
19
- white-space: pre-wrap;
20
- word-break: break-word;
21
- overflow-x: auto;
22
- background: var(--chat-code-bg);
23
- border-top: 1px solid var(--chat-border);
24
- }
25
-
26
- /* 天气卡片 */
27
- .weather-card {
28
- background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
29
- border-radius: 12px;
30
- padding: 16px;
31
- color: #fff;
32
- min-width: 200px;
33
- }
34
-
35
- .weather-header {
36
- display: flex;
37
- align-items: center;
38
- gap: 8px;
39
- margin-bottom: 12px;
40
- }
41
-
42
- .weather-icon {
43
- opacity: 0.9;
44
- }
45
-
46
- .weather-location {
47
- font-size: 14px;
48
- font-weight: 500;
49
- }
50
-
51
- .weather-main {
52
- margin-bottom: 12px;
53
- }
54
-
55
- .weather-temp {
56
- font-size: 48px;
57
- font-weight: 300;
58
- line-height: 1;
59
- }
60
-
61
- .weather-condition {
62
- font-size: 14px;
63
- opacity: 0.9;
64
- margin-top: 4px;
65
- }
66
-
67
- .weather-details {
68
- display: flex;
69
- gap: 16px;
70
- padding-top: 12px;
71
- border-top: 1px solid rgba(255, 255, 255, 0.2);
72
- }
73
-
74
- .weather-detail {
75
- display: flex;
76
- align-items: center;
77
- gap: 4px;
78
- font-size: 13px;
79
- opacity: 0.9;
80
- }
81
-
82
- .weather-forecast {
83
- margin-top: 12px;
84
- padding-top: 12px;
85
- border-top: 1px solid rgba(255, 255, 255, 0.2);
86
- display: flex;
87
- gap: 12px;
88
- overflow-x: auto;
89
- }
90
-
91
- .forecast-item {
92
- text-align: center;
93
- min-width: 60px;
94
- }
95
-
96
- .forecast-date {
97
- font-size: 12px;
98
- opacity: 0.8;
99
- margin-bottom: 4px;
100
- }
101
-
102
- .forecast-temp {
103
- font-size: 13px;
104
- font-weight: 500;
105
- }
106
-
107
- .forecast-condition {
108
- font-size: 11px;
109
- opacity: 0.8;
110
- margin-top: 2px;
111
- }
112
-
113
- /* 搜索结果卡片 */
114
- .search-results-card {
115
- background: var(--chat-muted, #2d2d2d);
116
- border-radius: 8px;
117
- overflow: hidden;
118
- }
119
-
120
- .search-header {
121
- display: flex;
122
- align-items: center;
123
- gap: 8px;
124
- padding: 10px 12px;
125
- background: rgba(0, 0, 0, 0.2);
126
- color: var(--chat-text-muted, #888);
127
- font-size: 13px;
128
- }
129
-
130
- .search-count {
131
- margin-left: auto;
132
- font-size: 12px;
133
- opacity: 0.7;
134
- }
135
-
136
- .search-list {
137
- display: flex;
138
- flex-direction: column;
139
- }
140
-
141
- .search-item {
142
- display: block;
143
- padding: 10px 12px;
144
- text-decoration: none;
145
- border-bottom: 1px solid rgba(255, 255, 255, 0.05);
146
- transition: background 0.15s;
147
- }
148
-
149
- .search-item:last-child {
150
- border-bottom: none;
151
- }
152
-
153
- .search-item:hover {
154
- background: rgba(255, 255, 255, 0.05);
155
- }
156
-
157
- .item-title {
158
- font-size: 14px;
159
- color: var(--chat-text, #ccc);
160
- font-weight: 500;
161
- margin-bottom: 4px;
162
- overflow: hidden;
163
- text-overflow: ellipsis;
164
- white-space: nowrap;
165
- }
166
-
167
- .item-snippet {
168
- font-size: 12px;
169
- color: var(--chat-text-muted, #888);
170
- line-height: 1.4;
171
- margin-bottom: 4px;
172
- display: -webkit-box;
173
- -webkit-line-clamp: 2;
174
- -webkit-box-orient: vertical;
175
- overflow: hidden;
176
- }
177
-
178
- .item-url {
179
- font-size: 11px;
180
- color: var(--chat-text-muted, #666);
181
- }