@huyooo/ai-chat-frontend-react 0.2.19 → 0.2.20

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.
package/dist/index.css CHANGED
@@ -772,11 +772,11 @@
772
772
  position: relative;
773
773
  display: flex;
774
774
  align-items: center;
775
- width: 100%;
776
775
  padding: 10px 16px;
777
776
  background: var(--chat-muted, #2a2a2a);
778
777
  border-radius: 8px;
779
778
  overflow: hidden;
779
+ box-sizing: border-box;
780
780
  }
781
781
  .loading-indicator.has-content-above {
782
782
  margin-top: 8px;
@@ -836,6 +836,64 @@
836
836
  border-radius: 10px;
837
837
  border: 1px solid var(--chat-border, #333);
838
838
  }
839
+ .token-usage,
840
+ .response-duration {
841
+ display: inline-flex;
842
+ align-items: center;
843
+ gap: 3px;
844
+ font-size: 11px;
845
+ color: var(--chat-text-muted, #888);
846
+ cursor: default;
847
+ }
848
+ .token-usage {
849
+ position: relative;
850
+ }
851
+ .token-usage:hover .usage-tooltip {
852
+ opacity: 1;
853
+ visibility: visible;
854
+ transform: translateX(-50%) translateY(0);
855
+ }
856
+ .usage-tooltip {
857
+ position: absolute;
858
+ bottom: calc(100% + 6px);
859
+ left: 50%;
860
+ transform: translateX(-50%) translateY(4px);
861
+ display: flex;
862
+ flex-direction: column;
863
+ gap: 2px;
864
+ padding: 6px 10px;
865
+ background: var(--chat-bg, #1e1e1e);
866
+ border: 1px solid var(--chat-border, #444);
867
+ border-radius: 6px;
868
+ font-size: 11px;
869
+ color: var(--chat-text-muted, #aaa);
870
+ white-space: nowrap;
871
+ opacity: 0;
872
+ visibility: hidden;
873
+ transition:
874
+ opacity 0.15s,
875
+ visibility 0.15s,
876
+ transform 0.15s;
877
+ pointer-events: none;
878
+ z-index: 10;
879
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
880
+ }
881
+ .usage-tooltip::after {
882
+ content: "";
883
+ position: absolute;
884
+ top: 100%;
885
+ left: 50%;
886
+ transform: translateX(-50%);
887
+ border: 4px solid transparent;
888
+ border-top-color: var(--chat-border, #444);
889
+ }
890
+ .usage-tooltip-total {
891
+ border-top: 1px solid var(--chat-border, #333);
892
+ padding-top: 2px;
893
+ margin-top: 1px;
894
+ color: var(--chat-text, #ccc);
895
+ font-weight: 500;
896
+ }
839
897
  .action-buttons {
840
898
  display: flex;
841
899
  align-items: center;
@@ -2595,6 +2653,8 @@ body {
2595
2653
  /* src/components/input/DropdownSelector.css */
2596
2654
  .dropdown-selector {
2597
2655
  position: relative;
2656
+ }
2657
+ .dropdown-selector .selector-trigger {
2598
2658
  display: flex;
2599
2659
  align-items: center;
2600
2660
  gap: 4px;
@@ -2609,12 +2669,12 @@ body {
2609
2669
  cursor: pointer;
2610
2670
  transition: all 0.15s;
2611
2671
  }
2612
- .dropdown-selector:hover:not(.disabled) {
2672
+ .dropdown-selector:not(.disabled) .selector-trigger:hover {
2613
2673
  background: rgba(0, 0, 0, 0.06);
2614
2674
  background: color-mix(in srgb, var(--chat-text, #ccc) 10%, transparent);
2615
2675
  color: var(--chat-text, #ccc);
2616
2676
  }
2617
- .dropdown-selector.disabled {
2677
+ .dropdown-selector.disabled .selector-trigger {
2618
2678
  opacity: 0.6;
2619
2679
  cursor: not-allowed;
2620
2680
  }
@@ -3079,6 +3139,79 @@ body {
3079
3139
  margin: 0;
3080
3140
  }
3081
3141
 
3142
+ /* src/components/message/parts/PlanPart.css */
3143
+ .plan-steps {
3144
+ display: flex;
3145
+ flex-direction: column;
3146
+ gap: 6px;
3147
+ padding: 2px 0;
3148
+ }
3149
+ .plan-step {
3150
+ display: flex;
3151
+ align-items: center;
3152
+ gap: 8px;
3153
+ font-size: 13px;
3154
+ line-height: 1.5;
3155
+ color: var(--chat-text-muted, #999);
3156
+ transition: color 0.2s ease;
3157
+ }
3158
+ .plan-step--done {
3159
+ color: var(--chat-success, #22c55e);
3160
+ }
3161
+ .plan-step--in_progress {
3162
+ color: var(--chat-plan-accent, #8b5cf6);
3163
+ font-weight: 500;
3164
+ }
3165
+ .plan-step--failed {
3166
+ color: var(--chat-error, #ef4444);
3167
+ }
3168
+ .plan-step--pending {
3169
+ color: var(--chat-text-muted, #666);
3170
+ }
3171
+ .plan-step-icon {
3172
+ display: flex;
3173
+ align-items: center;
3174
+ justify-content: center;
3175
+ width: 16px;
3176
+ height: 16px;
3177
+ flex-shrink: 0;
3178
+ font-size: 12px;
3179
+ }
3180
+ .plan-icon-done {
3181
+ color: var(--chat-success, #22c55e);
3182
+ font-weight: bold;
3183
+ }
3184
+ .plan-icon-progress {
3185
+ display: flex;
3186
+ align-items: center;
3187
+ justify-content: center;
3188
+ }
3189
+ .plan-spinner {
3190
+ width: 12px;
3191
+ height: 12px;
3192
+ border: 2px solid var(--chat-plan-accent, #8b5cf6);
3193
+ border-top-color: transparent;
3194
+ border-radius: 50%;
3195
+ animation: plan-spin 0.8s linear infinite;
3196
+ }
3197
+ .plan-icon-failed {
3198
+ color: var(--chat-error, #ef4444);
3199
+ font-weight: bold;
3200
+ }
3201
+ .plan-icon-pending {
3202
+ color: var(--chat-text-muted, #666);
3203
+ font-size: 10px;
3204
+ }
3205
+ .plan-step-title {
3206
+ flex: 1;
3207
+ word-break: break-word;
3208
+ }
3209
+ @keyframes plan-spin {
3210
+ to {
3211
+ transform: rotate(360deg);
3212
+ }
3213
+ }
3214
+
3082
3215
  /* src/components/message/PartsRenderer.css */
3083
3216
  .parts-renderer {
3084
3217
  display: flex;
@@ -4355,3 +4488,95 @@ body {
4355
4488
  color: var(--chat-text-muted, #888);
4356
4489
  margin-top: 8px;
4357
4490
  }
4491
+ .skill-form {
4492
+ display: flex;
4493
+ flex-direction: column;
4494
+ gap: 8px;
4495
+ padding: 0 0 8px 0;
4496
+ }
4497
+ .skill-input {
4498
+ width: 100%;
4499
+ padding: 8px 12px;
4500
+ background: var(--chat-input-bg, #1e1e1e);
4501
+ border: 1px solid var(--chat-border, #333);
4502
+ border-radius: 6px;
4503
+ color: var(--chat-text, #fff);
4504
+ font-size: 13px;
4505
+ outline: none;
4506
+ box-sizing: border-box;
4507
+ }
4508
+ .skill-input:focus,
4509
+ .skill-textarea:focus {
4510
+ border-color: var(--chat-primary, #4f8fff);
4511
+ }
4512
+ .skill-textarea {
4513
+ width: 100%;
4514
+ padding: 8px 12px;
4515
+ background: var(--chat-input-bg, #1e1e1e);
4516
+ border: 1px solid var(--chat-border, #333);
4517
+ border-radius: 6px;
4518
+ color: var(--chat-text, #fff);
4519
+ font-size: 13px;
4520
+ outline: none;
4521
+ resize: vertical;
4522
+ font-family: inherit;
4523
+ box-sizing: border-box;
4524
+ }
4525
+ .skill-form-actions {
4526
+ display: flex;
4527
+ gap: 8px;
4528
+ justify-content: flex-end;
4529
+ }
4530
+ .skill-btn {
4531
+ padding: 6px 16px;
4532
+ border: none;
4533
+ border-radius: 6px;
4534
+ font-size: 13px;
4535
+ cursor: pointer;
4536
+ }
4537
+ .skill-btn-primary {
4538
+ background: var(--chat-primary, #4f8fff);
4539
+ color: #fff;
4540
+ }
4541
+ .skill-btn-primary:disabled {
4542
+ opacity: 0.5;
4543
+ cursor: not-allowed;
4544
+ }
4545
+ .skill-btn-secondary {
4546
+ background: var(--chat-border, #333);
4547
+ color: var(--chat-text, #fff);
4548
+ }
4549
+ .skill-actions {
4550
+ display: flex;
4551
+ align-items: center;
4552
+ gap: 4px;
4553
+ flex-shrink: 0;
4554
+ }
4555
+ .skill-action-btn {
4556
+ display: flex;
4557
+ align-items: center;
4558
+ justify-content: center;
4559
+ width: 28px;
4560
+ height: 28px;
4561
+ border: none;
4562
+ background: transparent;
4563
+ color: var(--chat-text-muted, #888);
4564
+ border-radius: 4px;
4565
+ cursor: pointer;
4566
+ }
4567
+ .skill-action-btn:hover {
4568
+ background: var(--chat-border, #333);
4569
+ color: var(--chat-text, #fff);
4570
+ }
4571
+ .skill-action-delete:hover {
4572
+ color: var(--chat-error, #ef4444);
4573
+ }
4574
+ .skill-badge {
4575
+ display: inline-block;
4576
+ margin-left: 6px;
4577
+ padding: 1px 6px;
4578
+ font-size: 11px;
4579
+ color: var(--chat-text-muted, #888);
4580
+ background: var(--chat-border, #333);
4581
+ border-radius: 4px;
4582
+ }
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { ChatMode as ChatMode$1, ThinkingMode, ModelOption as ModelOption$1, SessionRecord as SessionRecord$1, ChatAdapter, AutoRunConfig } from '@huyooo/ai-chat-bridge-electron/renderer';
2
- export { ChatAdapter, ChatEvent, ChatEventType, ChatMode, ChatOptions, MessageRecord, ModelOption, ProviderType, SessionRecord, ThinkingMode } from '@huyooo/ai-chat-bridge-electron/renderer';
1
+ export { ChatAdapter, ChatEvent, ChatEventType, ChatMode, ChatOptions, MessageRecord, ModelOption, ProviderType, SessionRecord, ThinkingMode } from '@huyooo/ai-chat-types';
2
+ import { ChatMode as ChatMode$1, ThinkingMode, ModelOption as ModelOption$1, SessionRecord as SessionRecord$1, ChatAdapter, AutoRunConfig, SkillRecord } from '@huyooo/ai-chat-bridge-electron/renderer';
3
3
  import * as react from 'react';
4
4
  import { FC, ReactNode, ComponentType } from 'react';
5
5
  export { CodeBlock as CodeBlockType, ContentBlock, ContentBlockType, SearchResultItem, TextBlock as TextBlockType, WeatherData, getLanguageDisplayName, highlightCode, parseContent, renderMarkdown } from '@huyooo/ai-chat-shared';
@@ -125,6 +125,18 @@ interface ErrorPart$1 {
125
125
  category?: string;
126
126
  retryable?: boolean;
127
127
  }
128
+ /** 计划步骤 */
129
+ interface PlanStepItem {
130
+ id: string;
131
+ title: string;
132
+ status: 'pending' | 'in_progress' | 'done' | 'failed';
133
+ }
134
+ /** 计划进度 Part(由 update_plan 工具的 resultType:'plan' 生成) */
135
+ interface PlanPart {
136
+ type: 'plan';
137
+ steps: PlanStepItem[];
138
+ status: 'running' | 'done';
139
+ }
128
140
  /** 天气 Part(由 get_weather 工具生成)*/
129
141
  interface WeatherPart {
130
142
  type: 'weather';
@@ -142,7 +154,7 @@ interface CustomPart {
142
154
  [key: string]: unknown;
143
155
  }
144
156
  /** 内置 Part 联合类型 */
145
- type BuiltinPart = TextPart$1 | CodePart | ThinkingPart$1 | SearchPart$1 | ToolCallPart$1 | ImagePart$1 | ErrorPart$1;
157
+ type BuiltinPart = TextPart$1 | CodePart | ThinkingPart$1 | SearchPart$1 | ToolCallPart$1 | ImagePart$1 | ErrorPart$1 | PlanPart;
146
158
  /** 内容 Part 联合类型(包含内置和扩展类型)*/
147
159
  type ContentPart = BuiltinPart | WeatherPart | CustomPart;
148
160
  /** 内容 Part 类型字符串 */
@@ -176,6 +188,8 @@ interface ChatMessage {
176
188
  images?: string[];
177
189
  /** 是否正在加载 */
178
190
  loading?: boolean;
191
+ /** Agent 当前阶段(后端通过 agent_status 事件推送,thinking=显示 loading) */
192
+ agentPhase?: 'thinking';
179
193
  /** 是否已复制 */
180
194
  copied?: boolean;
181
195
  /** 消息时间戳 */
@@ -184,6 +198,23 @@ interface ChatMessage {
184
198
  error?: ErrorDetails;
185
199
  /** 是否被用户中止 */
186
200
  aborted?: boolean;
201
+ /** Token 使用统计(done 事件携带) */
202
+ usage?: {
203
+ promptTokens: number;
204
+ completionTokens: number;
205
+ totalTokens: number;
206
+ reasoningTokens?: number;
207
+ cachedTokens?: number;
208
+ };
209
+ /** 响应耗时(毫秒) */
210
+ duration?: number;
211
+ /** 计费信息(云端模式,done 后由 ai-server 返回) */
212
+ billing?: {
213
+ pointsConsumed: number;
214
+ remainingPoints: number;
215
+ costYuan?: number;
216
+ model?: string;
217
+ };
187
218
  }
188
219
  /** 获取消息的纯文本内容(用于复制、保存等) */
189
220
  declare function getMessageText(message: ChatMessage): string;
@@ -275,6 +306,9 @@ declare function useChat(options: UseChatOptions): {
275
306
  description: string;
276
307
  }[];
277
308
  saveEnabledTools: (tools: string[] | undefined) => Promise<void>;
309
+ skills: SkillRecord[];
310
+ loadSkills: () => Promise<void>;
311
+ toggleSkill: (id: string) => Promise<void>;
278
312
  };
279
313
 
280
314
  /**
@@ -291,6 +325,10 @@ interface ChatInputContextValue {
291
325
  isLoading: boolean;
292
326
  /** Electron adapter(用于 @ 文件选择) */
293
327
  adapter?: ChatAdapter;
328
+ /** Skills 列表 */
329
+ skills?: SkillRecord[];
330
+ /** 切换 Skill 启用状态 */
331
+ toggleSkill?: (id: string) => void;
294
332
  setMode: (value: ChatMode) => void;
295
333
  setModel: (value: string) => void;
296
334
  setWebSearch: (value: boolean) => void;
@@ -396,6 +434,14 @@ declare const ChatPanel: react.ForwardRefExoticComponent<ChatPanelProps & react.
396
434
  * 新架构:使用 ContentPart 数组渲染消息内容
397
435
  */
398
436
 
437
+ /** Token 使用统计 */
438
+ interface TokenUsage {
439
+ promptTokens: number;
440
+ completionTokens: number;
441
+ totalTokens: number;
442
+ reasoningTokens?: number;
443
+ cachedTokens?: number;
444
+ }
399
445
  interface MessageBubbleProps {
400
446
  role: 'user' | 'assistant';
401
447
  /** 内容 parts 数组 - 新架构核心 */
@@ -408,10 +454,23 @@ interface MessageBubbleProps {
408
454
  images?: string[];
409
455
  /** 是否正在加载 */
410
456
  loading?: boolean;
457
+ /** Agent 当前阶段(后端推送,thinking=显示 loading) */
458
+ agentPhase?: 'thinking';
411
459
  /** 是否已复制 */
412
460
  copied?: boolean;
413
461
  /** 消息时间戳 */
414
462
  timestamp?: Date | string | number;
463
+ /** Token 使用统计 */
464
+ usage?: TokenUsage;
465
+ /** 响应耗时(毫秒) */
466
+ duration?: number;
467
+ /** 计费信息(云端模式) */
468
+ billing?: {
469
+ pointsConsumed: number;
470
+ remainingPoints: number;
471
+ costYuan?: number;
472
+ model?: string;
473
+ };
415
474
  onCopy?: () => void;
416
475
  onRegenerate?: () => void;
417
476
  /** 编辑用户消息后重新发送 */