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

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 (76) hide show
  1. package/dist/index.css +0 -1
  2. package/dist/index.js +1 -5418
  3. package/package.json +4 -5
  4. package/dist/index.css.map +0 -1
  5. package/dist/index.js.map +0 -1
  6. package/src/adapter.ts +0 -68
  7. package/src/components/ChatPanel.tsx +0 -553
  8. package/src/components/common/ConfirmDialog.css +0 -136
  9. package/src/components/common/ConfirmDialog.tsx +0 -91
  10. package/src/components/common/CopyButton.css +0 -22
  11. package/src/components/common/CopyButton.tsx +0 -46
  12. package/src/components/common/IndexingSettings.css +0 -207
  13. package/src/components/common/IndexingSettings.tsx +0 -398
  14. package/src/components/common/SettingsPanel.css +0 -337
  15. package/src/components/common/SettingsPanel.tsx +0 -215
  16. package/src/components/common/Toast.css +0 -50
  17. package/src/components/common/Toast.tsx +0 -38
  18. package/src/components/common/ToggleSwitch.css +0 -52
  19. package/src/components/common/ToggleSwitch.tsx +0 -20
  20. package/src/components/header/ChatHeader.css +0 -285
  21. package/src/components/header/ChatHeader.tsx +0 -376
  22. package/src/components/input/AtFilePicker.css +0 -147
  23. package/src/components/input/AtFilePicker.tsx +0 -519
  24. package/src/components/input/ChatInput.css +0 -283
  25. package/src/components/input/ChatInput.tsx +0 -575
  26. package/src/components/input/DropdownSelector.css +0 -231
  27. package/src/components/input/DropdownSelector.tsx +0 -333
  28. package/src/components/input/ImagePreviewModal.css +0 -124
  29. package/src/components/input/ImagePreviewModal.tsx +0 -118
  30. package/src/components/input/at-views/AtBranchView.tsx +0 -34
  31. package/src/components/input/at-views/AtBrowserView.tsx +0 -34
  32. package/src/components/input/at-views/AtChatsView.tsx +0 -34
  33. package/src/components/input/at-views/AtDocsView.tsx +0 -34
  34. package/src/components/input/at-views/AtFilesView.tsx +0 -168
  35. package/src/components/input/at-views/AtTerminalsView.tsx +0 -34
  36. package/src/components/input/at-views/AtViewStyles.css +0 -143
  37. package/src/components/input/at-views/index.ts +0 -9
  38. package/src/components/message/ContentRenderer.css +0 -9
  39. package/src/components/message/MessageBubble.css +0 -193
  40. package/src/components/message/MessageBubble.tsx +0 -240
  41. package/src/components/message/PartsRenderer.css +0 -12
  42. package/src/components/message/PartsRenderer.tsx +0 -168
  43. package/src/components/message/WelcomeMessage.css +0 -221
  44. package/src/components/message/WelcomeMessage.tsx +0 -93
  45. package/src/components/message/parts/CollapsibleCard.css +0 -80
  46. package/src/components/message/parts/CollapsibleCard.tsx +0 -80
  47. package/src/components/message/parts/ErrorPart.css +0 -9
  48. package/src/components/message/parts/ErrorPart.tsx +0 -40
  49. package/src/components/message/parts/ImagePart.css +0 -49
  50. package/src/components/message/parts/ImagePart.tsx +0 -54
  51. package/src/components/message/parts/SearchPart.css +0 -44
  52. package/src/components/message/parts/SearchPart.tsx +0 -63
  53. package/src/components/message/parts/TextPart.css +0 -579
  54. package/src/components/message/parts/TextPart.tsx +0 -213
  55. package/src/components/message/parts/ThinkingPart.css +0 -9
  56. package/src/components/message/parts/ThinkingPart.tsx +0 -48
  57. package/src/components/message/parts/ToolCallPart.css +0 -246
  58. package/src/components/message/parts/ToolCallPart.tsx +0 -289
  59. package/src/components/message/parts/ToolResultPart.css +0 -67
  60. package/src/components/message/parts/index.ts +0 -13
  61. package/src/components/message/parts/visual-predicate.ts +0 -43
  62. package/src/components/message/parts/visual-render.ts +0 -19
  63. package/src/components/message/parts/visual.ts +0 -12
  64. package/src/components/message/welcome-types.ts +0 -46
  65. package/src/context/AutoRunConfigContext.tsx +0 -13
  66. package/src/context/ChatAdapterContext.tsx +0 -8
  67. package/src/context/ChatInputContext.tsx +0 -40
  68. package/src/context/RenderersContext.tsx +0 -35
  69. package/src/hooks/useChat.ts +0 -1569
  70. package/src/hooks/useImageUpload.ts +0 -345
  71. package/src/hooks/useVoiceInput.ts +0 -454
  72. package/src/hooks/useVoiceToTextInput.ts +0 -87
  73. package/src/index.ts +0 -151
  74. package/src/styles.css +0 -330
  75. package/src/types/index.ts +0 -196
  76. package/src/utils/fileIcon.ts +0 -49
@@ -1,221 +0,0 @@
1
- .welcome-message {
2
- display: flex;
3
- flex-direction: column;
4
- align-items: center;
5
- justify-content: center;
6
- gap: 28px;
7
- padding: 40px 24px;
8
- max-width: 640px;
9
- margin: 0 auto;
10
- min-height: 100%;
11
- }
12
-
13
- /* 标题区域 */
14
- .welcome-header {
15
- display: flex;
16
- flex-direction: column;
17
- align-items: center;
18
- text-align: center;
19
- gap: 8px;
20
- }
21
-
22
- .welcome-title-row {
23
- display: flex;
24
- align-items: center;
25
- gap: 10px;
26
- }
27
-
28
- .welcome-icon {
29
- color: var(--chat-text-muted, #888);
30
- }
31
-
32
- .welcome-title {
33
- font-size: 24px;
34
- font-weight: 600;
35
- color: var(--chat-text, #fff);
36
- margin: 0;
37
- }
38
-
39
- .welcome-subtitle {
40
- font-size: 13px;
41
- color: var(--chat-text-muted, #888);
42
- margin: 0;
43
- }
44
-
45
- /* 区域标题 */
46
- .section-header {
47
- display: flex;
48
- align-items: center;
49
- gap: 6px;
50
- margin-bottom: 12px;
51
- padding-left: 2px;
52
- }
53
-
54
- .section-icon {
55
- color: var(--chat-text-muted, #666);
56
- }
57
-
58
- .section-title {
59
- font-size: 12px;
60
- font-weight: 500;
61
- color: var(--chat-text-muted, #888);
62
- text-transform: uppercase;
63
- letter-spacing: 0.5px;
64
- }
65
-
66
- /* 能力标签 */
67
- .features-section {
68
- width: 100%;
69
- }
70
-
71
- .features-list {
72
- display: flex;
73
- flex-wrap: wrap;
74
- gap: 8px;
75
- justify-content: center;
76
- }
77
-
78
- .feature-tag {
79
- display: inline-flex;
80
- align-items: center;
81
- gap: 6px;
82
- padding: 6px 12px;
83
- background: var(--chat-muted, #2a2a2a);
84
- border: 1px solid var(--chat-border, #3a3a3a);
85
- border-radius: 20px;
86
- font-size: 12px;
87
- color: var(--chat-text, #ccc);
88
- transition: all 0.15s;
89
- }
90
-
91
- .feature-tag:hover {
92
- background: rgba(255, 255, 255, 0.08);
93
- border-color: rgba(255, 255, 255, 0.15);
94
- }
95
-
96
- .feature-icon {
97
- color: var(--chat-text-muted, #888);
98
- }
99
-
100
- /* 快捷操作 */
101
- .tasks-section {
102
- width: 100%;
103
- }
104
-
105
- .tasks-grid {
106
- display: grid;
107
- gap: 10px;
108
- }
109
-
110
- /* 单个任务:居中显示 */
111
- .tasks-single {
112
- grid-template-columns: minmax(200px, 320px);
113
- justify-content: center;
114
- }
115
-
116
- /* 两个任务:两列 */
117
- .tasks-two {
118
- grid-template-columns: repeat(2, 1fr);
119
- }
120
-
121
- /* 三个任务:三列 */
122
- .tasks-three {
123
- grid-template-columns: repeat(3, 1fr);
124
- }
125
-
126
- /* 多个任务:两列自动换行 */
127
- .tasks-multi {
128
- grid-template-columns: repeat(2, 1fr);
129
- }
130
-
131
- .task-card {
132
- display: flex;
133
- align-items: center;
134
- gap: 10px;
135
- padding: 12px 14px;
136
- background: var(--chat-muted, #2a2a2a);
137
- border: 1px solid var(--chat-border, #3a3a3a);
138
- border-radius: 10px;
139
- color: var(--chat-text, #ccc);
140
- text-align: left;
141
- cursor: pointer;
142
- transition: all 0.2s;
143
- }
144
-
145
- .task-card:hover {
146
- background: rgba(255, 255, 255, 0.08);
147
- border-color: rgba(59, 130, 246, 0.5);
148
- transform: translateY(-1px);
149
- box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
150
- }
151
-
152
- .task-icon {
153
- flex-shrink: 0;
154
- width: 32px;
155
- height: 32px;
156
- display: flex;
157
- align-items: center;
158
- justify-content: center;
159
- background: rgba(59, 130, 246, 0.15);
160
- border-radius: 8px;
161
- color: #60a5fa;
162
- }
163
-
164
- .task-card:hover .task-icon {
165
- background: rgba(59, 130, 246, 0.25);
166
- }
167
-
168
- .task-content {
169
- flex: 1;
170
- min-width: 0;
171
- }
172
-
173
- .task-name {
174
- font-size: 13px;
175
- font-weight: 500;
176
- color: var(--chat-text, #fff);
177
- margin-bottom: 2px;
178
- }
179
-
180
- .task-desc {
181
- font-size: 11px;
182
- color: var(--chat-text-muted, #888);
183
- white-space: nowrap;
184
- overflow: hidden;
185
- text-overflow: ellipsis;
186
- }
187
-
188
- .task-arrow {
189
- flex-shrink: 0;
190
- color: var(--chat-text-muted, #666);
191
- opacity: 0;
192
- transform: translateX(-4px);
193
- transition: all 0.2s;
194
- }
195
-
196
- .task-card:hover .task-arrow {
197
- opacity: 1;
198
- transform: translateX(0);
199
- }
200
-
201
- /* 响应式 */
202
- @media (max-width: 540px) {
203
- .welcome-message {
204
- padding: 32px 16px;
205
- gap: 24px;
206
- }
207
-
208
- .welcome-title {
209
- font-size: 24px;
210
- }
211
-
212
- .tasks-two,
213
- .tasks-three,
214
- .tasks-multi {
215
- grid-template-columns: 1fr;
216
- }
217
-
218
- .task-arrow {
219
- display: none;
220
- }
221
- }
@@ -1,93 +0,0 @@
1
- /**
2
- * WelcomeMessage Component
3
- * 与 Vue 版本 WelcomeMessage.vue 保持一致
4
- */
5
-
6
- import { type FC, useMemo } from 'react'
7
- import './WelcomeMessage.css'
8
- import { Icon } from '@iconify/react'
9
- import { type WelcomeConfig, defaultWelcomeConfig } from './welcome-types'
10
-
11
- interface WelcomeMessageProps {
12
- /** 欢迎页配置(可部分配置,未配置项使用默认值) */
13
- config?: Partial<WelcomeConfig>
14
- /** 快捷操作回调 */
15
- onQuickAction: (text: string) => void
16
- }
17
-
18
- export const WelcomeMessage: FC<WelcomeMessageProps> = ({ config: propsConfig, onQuickAction }) => {
19
- // 合并配置
20
- const config = useMemo<WelcomeConfig>(() => ({
21
- title: propsConfig?.title ?? defaultWelcomeConfig.title,
22
- subtitle: propsConfig?.subtitle ?? defaultWelcomeConfig.subtitle,
23
- icon: propsConfig?.icon ?? defaultWelcomeConfig.icon,
24
- features: propsConfig?.features ?? defaultWelcomeConfig.features,
25
- tasks: propsConfig?.tasks ?? defaultWelcomeConfig.tasks,
26
- }), [propsConfig])
27
-
28
- // 根据任务数量动态选择网格布局
29
- const tasksGridClass = useMemo(() => {
30
- const count = config.tasks.length
31
- if (count === 1) return 'tasks-grid tasks-single'
32
- if (count === 2) return 'tasks-grid tasks-two'
33
- if (count === 3) return 'tasks-grid tasks-three'
34
- return 'tasks-grid tasks-multi'
35
- }, [config.tasks.length])
36
-
37
- return (
38
- <div className="welcome-message">
39
- {/* 标题区域 */}
40
- <div className="welcome-header">
41
- <div className="welcome-title-row">
42
- <Icon icon={config.icon} width={28} className="welcome-icon" />
43
- <h2 className="welcome-title">{config.title}</h2>
44
- </div>
45
- <p className="welcome-subtitle">{config.subtitle}</p>
46
- </div>
47
-
48
- {/* 能力标签 */}
49
- {config.features.length > 0 && (
50
- <div className="features-section">
51
- <div className="section-header">
52
- <Icon icon="lucide:zap" width={14} className="section-icon" />
53
- <span className="section-title">支持的能力</span>
54
- </div>
55
- <div className="features-list">
56
- {config.features.map((feature) => (
57
- <div key={feature.name} className="feature-tag">
58
- <Icon icon={feature.icon} width={14} className="feature-icon" />
59
- <span>{feature.name}</span>
60
- </div>
61
- ))}
62
- </div>
63
- </div>
64
- )}
65
-
66
- {/* 快捷操作 */}
67
- {config.tasks.length > 0 && (
68
- <div className="tasks-section">
69
- <div className="section-header">
70
- <Icon icon="lucide:rocket" width={14} className="section-icon" />
71
- <span className="section-title">快捷操作</span>
72
- </div>
73
- <div className={tasksGridClass}>
74
- {config.tasks.map((task) => (
75
- <button
76
- key={task.name}
77
- className="task-card"
78
- onClick={() => onQuickAction(task.prompt)}
79
- >
80
- <Icon icon={task.icon} width={18} className="task-icon" />
81
- <div className="task-content">
82
- <div className="task-name">{task.name}</div>
83
- <div className="task-desc">{task.desc}</div>
84
- </div>
85
- <Icon icon="lucide:arrow-right" width={14} className="task-arrow" />
86
- </button>
87
- ))}
88
- </div>
89
- </div>
90
- )}
91
- </div>
92
- )
93
- }
@@ -1,80 +0,0 @@
1
- .collapsible-card {
2
- border-radius: 8px;
3
- background: var(--chat-muted, #2a2a2a);
4
- border: 1px solid var(--chat-border, #333);
5
- overflow: hidden;
6
- }
7
-
8
- .card-header {
9
- display: flex;
10
- align-items: center;
11
- justify-content: space-between;
12
- gap: 8px;
13
- padding: 8px 12px;
14
- user-select: none;
15
- }
16
-
17
- .card-header-left {
18
- display: flex;
19
- align-items: center;
20
- gap: 8px;
21
- flex: 1;
22
- }
23
-
24
- .card-header-left.clickable {
25
- cursor: pointer;
26
- }
27
-
28
- .card-header-right {
29
- display: flex;
30
- align-items: center;
31
- gap: 4px;
32
- flex-shrink: 0;
33
- }
34
-
35
- .card-icon {
36
- display: flex;
37
- align-items: center;
38
- justify-content: center;
39
- flex-shrink: 0;
40
- }
41
-
42
- .spinning {
43
- animation: spin 1s linear infinite;
44
- }
45
-
46
- @keyframes spin {
47
- from {
48
- transform: rotate(0deg);
49
- }
50
- to {
51
- transform: rotate(360deg);
52
- }
53
- }
54
-
55
- .card-title {
56
- font-size: 13px;
57
- color: var(--chat-text-muted, #888);
58
- }
59
-
60
- .card-subtitle {
61
- font-size: 12px;
62
- color: var(--chat-text-muted, #666);
63
- }
64
-
65
- .card-chevron {
66
- color: var(--chat-text-muted, #666);
67
- transition: transform 0.2s;
68
- flex-shrink: 0;
69
- cursor: pointer;
70
- }
71
-
72
- .collapsible-card.expanded .card-chevron {
73
- transform: rotate(180deg);
74
- }
75
-
76
- .card-content {
77
- padding: 0 12px 12px;
78
- min-width: 0;
79
- overflow: hidden;
80
- }
@@ -1,80 +0,0 @@
1
- import type { FC, ReactNode } from 'react'
2
- import { Icon } from '@iconify/react'
3
- import './CollapsibleCard.css'
4
-
5
- interface CollapsibleCardProps {
6
- /** 图标名称 */
7
- icon: string
8
- /** 图标颜色 */
9
- iconColor?: string
10
- /** 标题 */
11
- title: string
12
- /** 标题颜色 */
13
- titleColor?: string
14
- /** 副标题 */
15
- subtitle?: string
16
- /** 是否展开 */
17
- expanded: boolean
18
- /** 展开状态变化回调 */
19
- onExpandedChange: (expanded: boolean) => void
20
- /** 图标是否旋转(加载状态) */
21
- spinning?: boolean
22
- /** 是否可折叠(显示 chevron) */
23
- collapsible?: boolean
24
- /** 子内容 */
25
- children?: ReactNode
26
- /** Header 操作按钮 */
27
- headerActions?: ReactNode
28
- }
29
-
30
- export const CollapsibleCard: FC<CollapsibleCardProps> = ({
31
- icon,
32
- iconColor = 'var(--chat-accent, #3b82f6)',
33
- title,
34
- titleColor,
35
- subtitle,
36
- expanded,
37
- onExpandedChange,
38
- spinning = false,
39
- collapsible = true,
40
- children,
41
- headerActions,
42
- }) => {
43
- return (
44
- <div className={`collapsible-card ${expanded ? 'expanded' : ''}`}>
45
- <div className="card-header">
46
- <div
47
- className={`card-header-left ${collapsible ? 'clickable' : ''}`}
48
- onClick={() => collapsible && onExpandedChange(!expanded)}
49
- >
50
- <div className="card-icon" style={{ color: iconColor }}>
51
- {spinning ? (
52
- <Icon icon="lucide:loader-2" width={14} className="spinning" />
53
- ) : (
54
- <Icon icon={icon} width={14} />
55
- )}
56
- </div>
57
- <span className="card-title" style={titleColor ? { color: titleColor } : undefined}>
58
- {title}
59
- </span>
60
- {subtitle && <span className="card-subtitle">{subtitle}</span>}
61
- </div>
62
- <div className="card-header-right">
63
- {headerActions}
64
- {collapsible && (
65
- <Icon
66
- icon={expanded ? 'lucide:chevron-up' : 'lucide:chevron-down'}
67
- width={14}
68
- className="card-chevron"
69
- onClick={(e) => {
70
- e.stopPropagation()
71
- onExpandedChange(!expanded)
72
- }}
73
- />
74
- )}
75
- </div>
76
- </div>
77
- {expanded && <div className="card-content">{children}</div>}
78
- </div>
79
- )
80
- }
@@ -1,9 +0,0 @@
1
- .error-content {
2
- font-size: 13px;
3
- line-height: 1.5;
4
- color: var(--chat-text-muted, #999);
5
- white-space: pre-wrap;
6
- word-break: break-word;
7
- overflow-wrap: break-word;
8
- margin: 0;
9
- }
@@ -1,40 +0,0 @@
1
- import { useState, type FC } from 'react'
2
- import { CollapsibleCard } from './CollapsibleCard'
3
- import './ErrorPart.css'
4
-
5
- interface ErrorPartProps {
6
- message: string
7
- category?: string
8
- retryable?: boolean
9
- }
10
-
11
- /** 分类标签映射 */
12
- const categoryLabels: Record<string, string> = {
13
- api: 'API 错误',
14
- network: '网络错误',
15
- timeout: '请求超时',
16
- auth: '认证失败',
17
- rate_limit: '请求限流',
18
- server: '服务器错误',
19
- unknown: '未知错误',
20
- }
21
-
22
- export const ErrorPart: FC<ErrorPartProps> = ({ message, category }) => {
23
- // 默认展开显示错误信息
24
- const [expanded, setExpanded] = useState(true)
25
-
26
- const categoryLabel = categoryLabels[category || 'unknown'] || category || '请求失败'
27
-
28
- return (
29
- <CollapsibleCard
30
- icon="lucide:alert-circle"
31
- iconColor="var(--chat-error, #ef4444)"
32
- title={categoryLabel}
33
- titleColor="var(--chat-error, #ef4444)"
34
- expanded={expanded}
35
- onExpandedChange={setExpanded}
36
- >
37
- <div className="error-content">{message}</div>
38
- </CollapsibleCard>
39
- )
40
- }
@@ -1,49 +0,0 @@
1
- .image-part {
2
- position: relative;
3
- max-width: 100%;
4
- display: inline-block;
5
- }
6
-
7
- .image-content {
8
- max-width: 100%;
9
- max-height: 400px;
10
- border-radius: 8px;
11
- cursor: pointer;
12
- transition: opacity 0.2s;
13
- }
14
-
15
- .image-content:hover {
16
- opacity: 0.9;
17
- }
18
-
19
- .image-loading,
20
- .image-error {
21
- display: flex;
22
- flex-direction: column;
23
- align-items: center;
24
- justify-content: center;
25
- gap: 8px;
26
- padding: 24px;
27
- background: var(--chat-muted, #2a2a2a);
28
- border-radius: 8px;
29
- color: var(--chat-text-muted, #888);
30
- min-width: 200px;
31
- min-height: 150px;
32
- }
33
-
34
- .spinning {
35
- animation: spin 1s linear infinite;
36
- }
37
-
38
- @keyframes spin {
39
- from {
40
- transform: rotate(0deg);
41
- }
42
- to {
43
- transform: rotate(360deg);
44
- }
45
- }
46
-
47
- .image-error span {
48
- font-size: 13px;
49
- }
@@ -1,54 +0,0 @@
1
- import { useState, type FC } from 'react'
2
- import { Icon } from '@iconify/react'
3
- import './ImagePart.css'
4
-
5
- interface ImagePartProps {
6
- url: string
7
- alt?: string
8
- }
9
-
10
- export const ImagePart: FC<ImagePartProps> = ({ url, alt }) => {
11
- const [loading, setLoading] = useState(true)
12
- const [error, setError] = useState(false)
13
-
14
- const handleLoad = () => {
15
- setLoading(false)
16
- setError(false)
17
- }
18
-
19
- const handleError = () => {
20
- setLoading(false)
21
- setError(true)
22
- }
23
-
24
- const openPreview = () => {
25
- window.open(url, '_blank')
26
- }
27
-
28
- return (
29
- <div className="image-part">
30
- {!error && (
31
- <img
32
- src={url}
33
- alt={alt || '图片'}
34
- className="image-content"
35
- onClick={openPreview}
36
- onLoad={handleLoad}
37
- onError={handleError}
38
- style={{ display: loading ? 'none' : 'block' }}
39
- />
40
- )}
41
- {loading && !error && (
42
- <div className="image-loading">
43
- <Icon icon="lucide:loader-2" width={24} className="spinning" />
44
- </div>
45
- )}
46
- {error && (
47
- <div className="image-error">
48
- <Icon icon="lucide:image-off" width={24} />
49
- <span>图片加载失败</span>
50
- </div>
51
- )}
52
- </div>
53
- )
54
- }
@@ -1,44 +0,0 @@
1
- .search-results {
2
- display: flex;
3
- flex-direction: column;
4
- gap: 8px;
5
- }
6
-
7
- .search-item {
8
- display: block;
9
- padding: 8px;
10
- border-radius: 6px;
11
- background: var(--chat-bg, #1e1e1e);
12
- text-decoration: none;
13
- transition: background 0.15s;
14
- }
15
-
16
- .search-item:hover {
17
- background: var(--chat-hover, #333);
18
- }
19
-
20
- .search-item-title {
21
- font-size: 13px;
22
- font-weight: 500;
23
- color: var(--chat-accent, #3b82f6);
24
- margin-bottom: 4px;
25
- }
26
-
27
- .search-item-snippet {
28
- font-size: 12px;
29
- color: var(--chat-text-muted, #888);
30
- line-height: 1.4;
31
- margin-bottom: 4px;
32
- display: -webkit-box;
33
- -webkit-line-clamp: 2;
34
- -webkit-box-orient: vertical;
35
- overflow: hidden;
36
- }
37
-
38
- .search-item-url {
39
- font-size: 11px;
40
- color: var(--chat-text-muted, #666);
41
- overflow: hidden;
42
- text-overflow: ellipsis;
43
- white-space: nowrap;
44
- }