hydro-ai-helper 2.1.0 → 2.2.0

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.
@@ -2,6 +2,17 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.RequestStatsModel = void 0;
4
4
  const TTL_DAYS = 90;
5
+ // Histogram upper bounds (ms) for latency. Bucket counts are summable across
6
+ // instances/days, so the dashboard can merge them and interpolate p50/p95/p99 —
7
+ // percentiles themselves are not averageable, raw buckets are.
8
+ const LATENCY_BUCKETS_MS = [250, 500, 1000, 2000, 5000, 10000, 20000, 30000, 60000];
9
+ function latencyBucketKey(latencyMs) {
10
+ for (const bound of LATENCY_BUCKETS_MS) {
11
+ if (latencyMs <= bound)
12
+ return String(bound);
13
+ }
14
+ return 'inf';
15
+ }
5
16
  class RequestStatsModel {
6
17
  constructor(db) {
7
18
  this.collection = db.collection('ai_request_daily_stats');
@@ -21,6 +32,7 @@ class RequestStatsModel {
21
32
  $inc: {
22
33
  successCount: 1,
23
34
  totalLatencyMs: latencyMs,
35
+ [`latencyBuckets.${latencyBucketKey(latencyMs)}`]: 1,
24
36
  },
25
37
  $set: { updatedAt: new Date() },
26
38
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -44,7 +56,7 @@ class RequestStatsModel {
44
56
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
45
57
  const doc = await this.collection.findOne({ _id: today });
46
58
  if (!doc) {
47
- return { successCount: 0, failureCount: 0, avgLatencyMs: 0, errorCountByCategory: {} };
59
+ return { successCount: 0, failureCount: 0, avgLatencyMs: 0, errorCountByCategory: {}, latencyBuckets: {} };
48
60
  }
49
61
  const avgLatencyMs = doc.successCount > 0
50
62
  ? Math.round(doc.totalLatencyMs / doc.successCount)
@@ -54,6 +66,7 @@ class RequestStatsModel {
54
66
  failureCount: doc.failureCount || 0,
55
67
  avgLatencyMs,
56
68
  errorCountByCategory: doc.errorCountByCategory || {},
69
+ latencyBuckets: doc.latencyBuckets || {},
57
70
  };
58
71
  }
59
72
  }
@@ -1 +1 @@
1
- {"version":3,"file":"requestStats.js","sourceRoot":"","sources":["../../src/models/requestStats.ts"],"names":[],"mappings":";;;AAkBA,MAAM,QAAQ,GAAG,EAAE,CAAC;AAEpB,MAAa,iBAAiB;IAG5B,YAAY,EAAM;QAChB,IAAI,CAAC,UAAU,GAAG,EAAE,CAAC,UAAU,CAAoB,wBAAwB,CAAC,CAAC;IAC/E,CAAC;IAED,KAAK,CAAC,aAAa;QACjB,MAAM,IAAI,CAAC,UAAU,CAAC,WAAW,CAC/B,EAAE,SAAS,EAAE,CAAC,EAAE,EAChB,EAAE,kBAAkB,EAAE,QAAQ,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,EAAE,IAAI,EAAE,mBAAmB,EAAE,CAC3E,CAAC;QACF,OAAO,CAAC,GAAG,CAAC,kDAAkD,CAAC,CAAC;IAClE,CAAC;IAEO,MAAM,CAAC,UAAU;QACvB,OAAO,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;IAC/C,CAAC;IAED,KAAK,CAAC,aAAa,CAAC,SAAiB;QACnC,MAAM,OAAO,GAAG,iBAAiB,CAAC,UAAU,EAAE,CAAC;QAC/C,MAAM,IAAI,CAAC,UAAU,CAAC,SAAS;QAC7B,8DAA8D;QAC9D,EAAE,GAAG,EAAE,OAAO,EAAS,EACvB;YACE,IAAI,EAAE;gBACJ,YAAY,EAAE,CAAC;gBACf,cAAc,EAAE,SAAS;aAC1B;YACD,IAAI,EAAE,EAAE,SAAS,EAAE,IAAI,IAAI,EAAE,EAAE;YACjC,8DAA8D;SACtD,EACR,EAAE,MAAM,EAAE,IAAI,EAAE,CACjB,CAAC;IACJ,CAAC;IAED,KAAK,CAAC,aAAa,CAAC,QAAgB;QAClC,MAAM,OAAO,GAAG,iBAAiB,CAAC,UAAU,EAAE,CAAC;QAC/C,MAAM,IAAI,CAAC,UAAU,CAAC,SAAS;QAC7B,8DAA8D;QAC9D,EAAE,GAAG,EAAE,OAAO,EAAS,EACvB;YACE,IAAI,EAAE;gBACJ,YAAY,EAAE,CAAC;gBACf,CAAC,wBAAwB,QAAQ,EAAE,CAAC,EAAE,CAAC;aACxC;YACD,IAAI,EAAE,EAAE,SAAS,EAAE,IAAI,IAAI,EAAE,EAAE;YACjC,8DAA8D;SACtD,EACR,EAAE,MAAM,EAAE,IAAI,EAAE,CACjB,CAAC;IACJ,CAAC;IAED,KAAK,CAAC,WAAW;QACf,MAAM,KAAK,GAAG,iBAAiB,CAAC,UAAU,EAAE,CAAC;QAC7C,8DAA8D;QAC9D,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC,EAAE,GAAG,EAAE,KAAK,EAAS,CAAC,CAAC;QAEjE,IAAI,CAAC,GAAG,EAAE,CAAC;YACT,OAAO,EAAE,YAAY,EAAE,CAAC,EAAE,YAAY,EAAE,CAAC,EAAE,YAAY,EAAE,CAAC,EAAE,oBAAoB,EAAE,EAAE,EAAE,CAAC;QACzF,CAAC;QAED,MAAM,YAAY,GAAG,GAAG,CAAC,YAAY,GAAG,CAAC;YACvC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,cAAc,GAAG,GAAG,CAAC,YAAY,CAAC;YACnD,CAAC,CAAC,CAAC,CAAC;QAEN,OAAO;YACL,YAAY,EAAE,GAAG,CAAC,YAAY,IAAI,CAAC;YACnC,YAAY,EAAE,GAAG,CAAC,YAAY,IAAI,CAAC;YACnC,YAAY;YACZ,oBAAoB,EAAE,GAAG,CAAC,oBAAoB,IAAI,EAAE;SACrD,CAAC;IACJ,CAAC;CACF;AAzED,8CAyEC"}
1
+ {"version":3,"file":"requestStats.js","sourceRoot":"","sources":["../../src/models/requestStats.ts"],"names":[],"mappings":"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"}
@@ -127,6 +127,7 @@ class TelemetryService {
127
127
  droppedErrorCount: selfStats?.droppedCount ?? 0,
128
128
  activeEndpointCount: aiConfig?.endpoints.filter(e => e.enabled).length ?? 0,
129
129
  featureStats: featureStats ?? [],
130
+ latencyBuckets: requestStats?.latencyBuckets ?? {},
130
131
  };
131
132
  }
132
133
  /**
@@ -177,6 +178,7 @@ class TelemetryService {
177
178
  api_success_count_24h: stats.apiSuccessCount24h,
178
179
  api_failure_count_24h: stats.apiFailureCount24h,
179
180
  avg_latency_ms_24h: stats.avgLatencyMs24h,
181
+ latency_buckets: stats.latencyBuckets,
180
182
  error_count_24h: stats.errorCount24h,
181
183
  suppressed_error_count: stats.suppressedErrorCount,
182
184
  dropped_error_count: stats.droppedErrorCount,
@@ -1 +1 @@
1
- {"version":3,"file":"telemetryService.js","sourceRoot":"","sources":["../../src/services/telemetryService.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"telemetryService.js","sourceRoot":"","sources":["../../src/services/telemetryService.ts"],"names":[],"mappings":"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"}
@@ -1,9 +1,30 @@
1
1
  import React from 'react';
2
2
  import { i18n } from '@hydrooj/ui-default';
3
- import {
4
- COLORS, SPACING, RADIUS, SHADOWS, TRANSITIONS, FONT_FAMILY,
5
- getButtonStyle, getPillStyle, getInputStyle,
6
- } from '../utils/styles';
3
+ import { TypeIcon, SendIcon, AttachIcon, RefreshIcon, RemoveIcon } from './icons';
4
+
5
+ // ── 方案 A · 克制蓝 调色 ───────────────────────────────────────────────
6
+ const A = {
7
+ border: '#eef1f5',
8
+ cardBorder: '#e3e8ef',
9
+ inputBg: '#f8fafc',
10
+ primary: '#2563eb',
11
+ gradient: 'linear-gradient(135deg, #2563eb, #5b8def)',
12
+ textPrimary: '#1e2536',
13
+ textSecondary: '#475569',
14
+ textMuted: '#64748b',
15
+ textFaint: '#94a3b8',
16
+ placeholder: '#aab4c2',
17
+ selBorder: '#2563eb',
18
+ selBg: '#f3f7ff',
19
+ idleBorder: '#eef1f5',
20
+ idleBg: '#fafbfd',
21
+ iconIdleBg: '#eef2f8',
22
+ success: '#10b981',
23
+ warning: '#f59e0b',
24
+ error: '#ef4444',
25
+ disabledBg: '#f1f5f9',
26
+ disabledText: '#cbd5e1',
27
+ };
7
28
 
8
29
  interface QuestionType {
9
30
  value: string;
@@ -42,12 +63,49 @@ export const ChatInput: React.FC<ChatInputProps> = ({
42
63
  const isFollowUp = conversationHistoryLength > 0;
43
64
  const canSubmit = isFirstConversation ? !!questionType : !!userThinking.trim();
44
65
 
66
+ // ── 问题类型卡片(2×2 网格,替换原 pill 行)─────────────────────────
67
+ const renderTypeCard = (type: QuestionType) => {
68
+ const isSelected = questionType === type.value;
69
+ return (
70
+ <label
71
+ key={type.value}
72
+ className="ai-type-card"
73
+ style={{
74
+ display: 'block', padding: '11px 12px', borderRadius: '12px', cursor: 'pointer',
75
+ border: `1.5px solid ${isSelected ? A.selBorder : A.idleBorder}`,
76
+ background: isSelected ? A.selBg : A.idleBg,
77
+ transition: 'all 160ms cubic-bezier(.4,0,.2,1)', userSelect: 'none',
78
+ }}
79
+ >
80
+ <input
81
+ type="radio" name="questionType" value={type.value}
82
+ checked={isSelected} onChange={(e) => onQuestionTypeChange(e.target.value)}
83
+ style={{ display: 'none' }}
84
+ />
85
+ <div style={{ display: 'flex', alignItems: 'center', gap: '8px', marginBottom: '5px' }}>
86
+ <div style={{
87
+ width: '24px', height: '24px', borderRadius: '7px',
88
+ background: isSelected ? A.primary : A.iconIdleBg,
89
+ color: isSelected ? '#fff' : A.textMuted,
90
+ display: 'flex', alignItems: 'center', justifyContent: 'center', flexShrink: 0,
91
+ }}>
92
+ <TypeIcon type={type.value} size={14} />
93
+ </div>
94
+ <span style={{ fontSize: '13px', fontWeight: 600, color: A.textPrimary }}>{i18n(type.label)}</span>
95
+ </div>
96
+ {type.description && (
97
+ <div style={{ fontSize: '11px', color: A.textMuted, lineHeight: 1.4 }}>{i18n(type.description)}</div>
98
+ )}
99
+ </label>
100
+ );
101
+ };
102
+
45
103
  const renderIncludeCodeCheckbox = (labelText: string) => (
46
104
  <label
47
105
  style={{
48
106
  display: 'flex', alignItems: 'center',
49
107
  cursor: questionType === 'optimize' ? 'not-allowed' : 'pointer',
50
- fontSize: '12px', color: questionType === 'optimize' ? COLORS.textDisabled : COLORS.textSecondary,
108
+ fontSize: '11.5px', color: questionType === 'optimize' ? A.disabledText : A.textMuted,
51
109
  whiteSpace: 'nowrap', alignSelf: 'center',
52
110
  }}
53
111
  title={questionType === 'optimize' ? i18n('ai_helper_student_optimize_code_required') : undefined}
@@ -55,11 +113,11 @@ export const ChatInput: React.FC<ChatInputProps> = ({
55
113
  <input
56
114
  type="checkbox" checked={includeCode} disabled={questionType === 'optimize'}
57
115
  onChange={(e) => onIncludeCodeChange(e.target.checked)}
58
- style={{ marginRight: '6px', accentColor: COLORS.primary }}
116
+ style={{ marginRight: '6px', accentColor: A.primary }}
59
117
  />
60
118
  {labelText}
61
- {questionType === 'optimize' && <span style={{ marginLeft: '4px', color: COLORS.warning, fontSize: '11px' }}>({i18n('ai_helper_student_required')})</span>}
62
- {includeCode && code && questionType !== 'optimize' && <span style={{ marginLeft: '4px', color: COLORS.success, fontSize: '11px' }}>&#10003;</span>}
119
+ {questionType === 'optimize' && <span style={{ marginLeft: '4px', color: A.warning, fontSize: '11px' }}>({i18n('ai_helper_student_required')})</span>}
120
+ {includeCode && code && questionType !== 'optimize' && <span style={{ marginLeft: '4px', color: A.success, fontSize: '11px' }}>&#10003;</span>}
63
121
  </label>
64
122
  );
65
123
 
@@ -69,14 +127,11 @@ export const ChatInput: React.FC<ChatInputProps> = ({
69
127
  onChange={(e) => onUserThinkingChange(e.target.value)}
70
128
  placeholder={isFirstConversation ? i18n('ai_helper_student_placeholder_first') : i18n('ai_helper_student_placeholder_followup')}
71
129
  style={{
72
- ...getInputStyle(),
73
- border: 'none', outline: 'none', boxShadow: 'none',
74
- backgroundColor: 'transparent',
75
- flex: 1, minHeight, maxHeight,
76
- resize: 'none' as const, boxSizing: 'border-box' as const,
130
+ width: '100%', border: 'none', outline: 'none', boxShadow: 'none',
131
+ background: 'transparent', color: A.textPrimary,
132
+ fontSize: '12.5px', lineHeight: 1.6, fontFamily: 'inherit',
133
+ flex: 1, minHeight, maxHeight, resize: 'none', boxSizing: 'border-box', padding: 0,
77
134
  }}
78
- onFocus={() => {}}
79
- onBlur={() => {}}
80
135
  onKeyDown={(e) => {
81
136
  if ((e.ctrlKey || e.metaKey) && e.key === 'Enter') { e.preventDefault(); onSubmit(); }
82
137
  }}
@@ -85,137 +140,84 @@ export const ChatInput: React.FC<ChatInputProps> = ({
85
140
 
86
141
  const disabledSubmit = isLoading || !canSubmit;
87
142
  const submitStyle: React.CSSProperties = {
88
- width: '36px',
89
- height: '36px',
90
- borderRadius: '50%',
91
- background: disabledSubmit ? COLORS.bgDisabled : COLORS.gradient,
92
- color: disabledSubmit ? COLORS.textDisabled : '#ffffff',
93
- border: 'none',
94
- display: 'flex',
95
- alignItems: 'center',
96
- justifyContent: 'center',
97
- cursor: disabledSubmit ? 'not-allowed' : 'pointer',
98
- transition: 'all 150ms ease',
143
+ width: '32px', height: '32px', borderRadius: '50%',
144
+ background: disabledSubmit ? A.disabledBg : A.gradient,
145
+ color: disabledSubmit ? A.disabledText : '#ffffff',
146
+ border: 'none', display: 'flex', alignItems: 'center', justifyContent: 'center',
147
+ cursor: disabledSubmit ? 'not-allowed' : 'pointer', transition: 'all 150ms ease',
99
148
  boxShadow: disabledSubmit ? 'none' : '0 2px 6px rgba(37, 99, 235, 0.3)',
100
- flexShrink: 0,
101
- fontSize: '16px',
102
- padding: 0,
149
+ flexShrink: 0, padding: 0,
150
+ };
151
+
152
+ const pillButton: React.CSSProperties = {
153
+ display: 'flex', alignItems: 'center', gap: '5px', fontSize: '11.5px',
154
+ color: A.textSecondary, padding: '5px 11px', border: `1px solid ${A.cardBorder}`,
155
+ borderRadius: '999px', background: 'transparent', cursor: 'pointer', fontFamily: 'inherit',
103
156
  };
104
157
 
105
158
  return (
106
- <div style={{ background: COLORS.bgPage, flexShrink: 0 }}>
159
+ <div style={{ background: '#fff', flexShrink: 0 }}>
160
+ <style>{`.ai-type-card:hover{border-color:#c5d4f5 !important}.ai-input-card:focus-within{border-color:#2563eb !important;box-shadow:0 0 0 3px rgba(37,99,235,.18)}`}</style>
107
161
  {errorBanner}
108
162
 
109
- {/* Question type pills - above the card */}
163
+ {/* 问题类型卡片 首次对话 */}
110
164
  {isFirstConversation && (
111
- <div style={{ padding: `${SPACING.sm} ${SPACING.base} 0` }}>
112
- <div style={{ fontSize: '12px', color: COLORS.textSecondary, marginBottom: SPACING.xs }}>{i18n('ai_helper_student_select_type')}:</div>
113
- <div className="hide-scrollbar" style={{ display: 'flex', overflowX: 'auto', gap: SPACING.sm }}>
114
- {questionTypes.map(type => {
115
- const isSelected = questionType === type.value;
116
- return (
117
- <label
118
- key={type.value}
119
- style={{
120
- ...getPillStyle(isSelected),
121
- userSelect: 'none' as const,
122
- }}
123
- >
124
- <input
125
- type="radio" name="questionType" value={type.value}
126
- checked={isSelected} onChange={(e) => onQuestionTypeChange(e.target.value)}
127
- style={{ display: 'none' }}
128
- />
129
- {i18n(type.label)}
130
- </label>
131
- );
132
- })}
165
+ <div style={{ padding: '12px 16px 0' }}>
166
+ <div style={{ fontSize: '12px', color: A.textFaint, marginBottom: '8px' }}>{i18n('ai_helper_student_select_type')}</div>
167
+ <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '8px' }}>
168
+ {questionTypes.map(renderTypeCard)}
133
169
  </div>
134
- {questionType && (() => {
135
- const selected = questionTypes.find(t => t.value === questionType);
136
- const descKey = (selected as any)?.description;
137
- return descKey ? (
138
- <div style={{ fontSize: '12px', color: COLORS.textSecondary, marginTop: SPACING.xs }}>
139
- {i18n(descKey)}
140
- {questionType === 'debug' && i18n('ai_helper_student_debug_auto_attach')}
141
- </div>
142
- ) : null;
143
- })()}
170
+ {questionType === 'debug' && (
171
+ <div style={{ fontSize: '11.5px', color: A.textMuted, marginTop: '8px' }}>
172
+ {i18n('ai_helper_student_debug_auto_attach')}
173
+ </div>
174
+ )}
144
175
  </div>
145
176
  )}
146
177
 
147
- {/* Follow-up action buttons - above the card */}
178
+ {/* 追问操作 后续对话 */}
148
179
  {isFollowUp && (
149
- <div style={{ display: 'flex', gap: SPACING.sm, padding: `${SPACING.sm} ${SPACING.base} 0` }}>
150
- <button
151
- type="button" onClick={onRefreshCode}
152
- style={{
153
- ...getButtonStyle('secondary'),
154
- fontSize: '12px', padding: `${SPACING.xs} ${SPACING.md}`,
155
- display: 'flex', alignItems: 'center', gap: SPACING.xs,
156
- }}
157
- >
158
- 📎 {includeCode ? i18n('ai_helper_student_code_attached') : i18n('ai_helper_student_attach_code')}
180
+ <div style={{ display: 'flex', gap: '8px', padding: '10px 16px 0' }}>
181
+ <button type="button" onClick={onRefreshCode} style={pillButton}>
182
+ <AttachIcon size={12} /> {includeCode ? i18n('ai_helper_student_code_attached') : i18n('ai_helper_student_attach_code')}
159
183
  </button>
160
- <button
161
- type="button" onClick={onNewConversation}
162
- style={{
163
- ...getButtonStyle('ghost'),
164
- fontSize: '12px', padding: `${SPACING.xs} ${SPACING.md}`,
165
- border: `1px solid ${COLORS.border}`,
166
- }}
167
- >
168
- 🔄 {i18n('ai_helper_student_new_conversation')}
184
+ <button type="button" onClick={onNewConversation} style={pillButton}>
185
+ <RefreshIcon size={12} /> {i18n('ai_helper_student_new_conversation')}
169
186
  </button>
170
187
  </div>
171
188
  )}
172
189
 
190
+ {/* 已附带代码预览 */}
173
191
  {isFollowUp && includeCode && code && (
174
- <div style={{
175
- background: COLORS.bgPage, border: `1px solid ${COLORS.border}`, borderRadius: RADIUS.md,
176
- padding: SPACING.sm, margin: `${SPACING.sm} ${SPACING.base} 0`, fontSize: '11px',
177
- }}>
178
- <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: SPACING.xs }}>
179
- <span style={{ color: COLORS.textSecondary }}>📝 {i18n('ai_helper_student_code_attached')} ({code.length} {i18n('ai_helper_student_chars')})</span>
180
- <button
181
- type="button" onClick={onCodeClear}
182
- style={{ background: 'none', border: 'none', color: COLORS.error, cursor: 'pointer', fontSize: '11px', padding: '2px 4px' }}
183
- >
184
- ✕ {i18n('ai_helper_student_remove')}
192
+ <div style={{ background: A.inputBg, border: `1px solid ${A.cardBorder}`, borderRadius: '10px', padding: '8px', margin: '8px 16px 0', fontSize: '11px' }}>
193
+ <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '4px' }}>
194
+ <span style={{ color: A.textMuted }}>📝 {i18n('ai_helper_student_code_attached')} ({code.length} {i18n('ai_helper_student_chars')})</span>
195
+ <button type="button" onClick={onCodeClear} style={{ display: 'flex', alignItems: 'center', gap: '3px', background: 'none', border: 'none', color: A.error, cursor: 'pointer', fontSize: '11px', padding: '2px 4px', fontFamily: 'inherit' }}>
196
+ <RemoveIcon size={11} /> {i18n('ai_helper_student_remove')}
185
197
  </button>
186
198
  </div>
187
- <pre style={{
188
- margin: 0, fontFamily: 'Consolas, Monaco, "Courier New", monospace',
189
- whiteSpace: 'pre-wrap', wordBreak: 'break-all', color: COLORS.textPrimary, maxHeight: '60px', overflow: 'auto',
190
- }}>
199
+ <pre style={{ margin: 0, fontFamily: 'Consolas, Monaco, "Courier New", monospace', whiteSpace: 'pre-wrap', wordBreak: 'break-all', color: A.textPrimary, maxHeight: '60px', overflow: 'auto' }}>
191
200
  {code.length > 200 ? code.substring(0, 200) + '...' : code}
192
201
  </pre>
193
202
  </div>
194
203
  )}
195
204
 
196
- {/* Unified input card */}
197
- <div className="chat-input-card" style={{
198
- margin: '12px', padding: '10px 12px 10px 16px', borderRadius: '24px',
199
- border: `1px solid ${COLORS.border}`, backgroundColor: '#f8fafc',
200
- boxShadow: '0 4px 12px rgba(0, 0, 0, 0.04)', transition: 'all 200ms ease',
205
+ {/* 统一输入卡片 */}
206
+ <div className="ai-input-card" style={{
207
+ margin: '12px 16px', padding: '11px 12px', borderRadius: '16px',
208
+ border: `1px solid ${A.cardBorder}`, background: A.inputBg,
209
+ transition: 'all 200ms ease',
201
210
  }}>
202
- {renderTextarea(isFirstConversation ? '48px' : '24px', '120px')}
203
- <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginTop: '4px' }}>
204
- {isFirstConversation ? renderIncludeCodeCheckbox(`📎 ${i18n('ai_helper_student_attach_current_code')}`) : <div />}
211
+ {renderTextarea(isFirstConversation ? '40px' : '24px', '120px')}
212
+ <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginTop: '10px' }}>
213
+ {isFirstConversation ? renderIncludeCodeCheckbox(i18n('ai_helper_student_attach_current_code')) : <div />}
205
214
  {isLoading ? (
206
- <button
207
- onClick={onCancel}
208
- style={{ ...getButtonStyle('danger'), whiteSpace: 'nowrap', borderRadius: RADIUS.full, padding: '6px 16px', fontSize: '13px' }}
209
- >
215
+ <button onClick={onCancel} style={{ background: A.error, color: '#fff', border: 'none', borderRadius: '999px', padding: '6px 16px', fontSize: '13px', cursor: 'pointer', whiteSpace: 'nowrap', fontFamily: 'inherit' }}>
210
216
  {i18n('ai_helper_student_cancel')}
211
217
  </button>
212
218
  ) : (
213
- <button
214
- onClick={onSubmit} disabled={disabledSubmit}
215
- style={submitStyle}
216
- title={i18n('ai_helper_student_send_shortcut')}
217
- >
218
- &#x27A4;
219
+ <button onClick={onSubmit} disabled={disabledSubmit} style={submitStyle} title={i18n('ai_helper_student_send_shortcut')}>
220
+ <SendIcon size={15} />
219
221
  </button>
220
222
  )}
221
223
  </div>
@@ -1,10 +1,27 @@
1
- import React from 'react';
1
+ import React, { useMemo } from 'react';
2
2
  import { i18n } from '@hydrooj/ui-default';
3
3
  import { renderMarkdown as renderMarkdownSafe, renderStreamingMarkdown } from '../utils/markdown';
4
- import { COLORS, SPACING, RADIUS, SHADOWS, ZINDEX, FONT_FAMILY } from '../utils/styles';
4
+ import { ZINDEX } from '../utils/styles';
5
5
  import { ThinkingBlock } from './ThinkingBlock';
6
+ import { AIMark } from './icons';
6
7
  import type { Message, ProblemInfo } from './types';
7
8
 
9
+ // ── 方案 A · 克制蓝 调色(局部常量,避免改动共享的 styles.ts)──────────────
10
+ const A = {
11
+ border: '#eef1f5',
12
+ problemBg: '#f7f9fc',
13
+ pillBg: '#e7eeff',
14
+ pillText: '#2563eb',
15
+ textPrimary: '#1e2536',
16
+ textSecondary: '#64748b',
17
+ textMuted: '#94a3b8',
18
+ textFaint: '#aab4c2',
19
+ aiBubbleBg: '#f4f8ff',
20
+ aiBubbleBorder: '#e2ecfb',
21
+ userBubbleBg: '#2563eb',
22
+ mono: "'JetBrains Mono', ui-monospace, 'SFMono-Regular', monospace",
23
+ };
24
+
8
25
  interface ParsedContent {
9
26
  content: string;
10
27
  isThinkingStreaming: boolean;
@@ -16,12 +33,10 @@ function parseMessageContent(text: string): ParsedContent {
16
33
 
17
34
  const thinkEnd = text.indexOf('</think>');
18
35
  if (thinkEnd === -1) {
19
- // Think tag opened but not closed — still streaming thinking
20
36
  const content = text.substring(0, thinkStart);
21
37
  return { content, isThinkingStreaming: true };
22
38
  }
23
39
 
24
- // Strip the entire <think>...</think> block (content is just a placeholder)
25
40
  const content = text.substring(0, thinkStart) + text.substring(thinkEnd + 8);
26
41
  return { content: content.trim(), isThinkingStreaming: false };
27
42
  }
@@ -49,7 +64,7 @@ const renderMarkdown = (text: string, streaming?: boolean) => {
49
64
  <div
50
65
  className="markdown-body"
51
66
  dangerouslySetInnerHTML={{ __html: html }}
52
- style={{ fontSize: '13px', lineHeight: '1.6' }}
67
+ style={{ fontSize: '13px', lineHeight: '1.7' }}
53
68
  />
54
69
  );
55
70
  };
@@ -63,11 +78,11 @@ export const ChatMessageList: React.FC<ChatMessageListProps> = ({
63
78
  const renderProblemInfoCard = () => {
64
79
  if (problemInfo) {
65
80
  return (
66
- <div style={{ display: 'flex', alignItems: 'center', gap: '8px', padding: '10px 16px', background: 'rgba(248,250,252,0.8)', borderBottom: `1px solid ${COLORS.border}` }}>
67
- <span style={{ fontSize: '11px', fontWeight: 'bold', padding: '2px 6px', background: COLORS.primaryLight, color: COLORS.primary, borderRadius: '4px' }}>
81
+ <div style={{ display: 'flex', alignItems: 'center', gap: '8px', padding: '9px 16px', background: A.problemBg, borderBottom: `1px solid ${A.border}` }}>
82
+ <span style={{ fontSize: '11px', fontWeight: 700, padding: '2px 7px', background: A.pillBg, color: A.pillText, borderRadius: '5px', fontFamily: A.mono, flexShrink: 0 }}>
68
83
  {problemInfo.problemId}
69
84
  </span>
70
- <span style={{ fontSize: '13px', color: COLORS.textSecondary, whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis', flex: 1 }}>
85
+ <span style={{ fontSize: '12.5px', color: A.textSecondary, whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis', flex: 1 }}>
71
86
  {problemInfo.title}
72
87
  </span>
73
88
  </div>
@@ -75,19 +90,14 @@ export const ChatMessageList: React.FC<ChatMessageListProps> = ({
75
90
  }
76
91
  if (problemInfoError) {
77
92
  return (
78
- <div style={{
79
- display: 'flex', alignItems: 'center', gap: '8px', padding: '10px 16px', background: COLORS.warningBg, borderBottom: `1px solid ${COLORS.warningBorder}`
80
- }}>
81
- <span style={{ fontSize: '12px', color: COLORS.warningText, whiteSpace: 'nowrap' }}>⚠️ {i18n('ai_helper_student_cannot_get_problem')}</span>
93
+ <div style={{ display: 'flex', alignItems: 'center', gap: '8px', padding: '9px 16px', background: '#fffbeb', borderBottom: '1px solid #fde68a' }}>
94
+ <span style={{ fontSize: '12px', color: '#92400e', whiteSpace: 'nowrap' }}>⚠ {i18n('ai_helper_student_cannot_get_problem')}</span>
82
95
  <input
83
96
  type="text"
84
97
  placeholder={i18n('ai_helper_student_manual_title_placeholder')}
85
98
  value={manualTitle}
86
99
  onChange={(e) => onManualTitleChange(e.target.value)}
87
- style={{
88
- flex: 1, padding: SPACING.xs, border: `1px solid ${COLORS.warningBorder}`,
89
- borderRadius: RADIUS.sm, fontSize: '12px', boxSizing: 'border-box'
90
- }}
100
+ style={{ flex: 1, padding: '4px 8px', border: '1px solid #fde68a', borderRadius: '6px', fontSize: '12px', boxSizing: 'border-box' }}
91
101
  />
92
102
  </div>
93
103
  );
@@ -98,16 +108,16 @@ export const ChatMessageList: React.FC<ChatMessageListProps> = ({
98
108
  const renderEmptyState = () => (
99
109
  <div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', flex: 1, padding: '40px 24px', textAlign: 'center' }}>
100
110
  <div style={{
101
- width: '72px', height: '72px', borderRadius: '50%',
102
- background: 'linear-gradient(135deg, #2563eb, #3b82f6)',
111
+ width: '64px', height: '64px', borderRadius: '18px',
112
+ background: 'linear-gradient(135deg, #2563eb, #5b8def)',
103
113
  display: 'flex', alignItems: 'center', justifyContent: 'center',
104
- fontSize: '36px', marginBottom: '24px',
105
- boxShadow: '0 8px 24px rgba(37, 99, 235, 0.2)',
106
- animation: 'float 3s ease-in-out infinite',
107
- }}>🤖</div>
108
- <div style={{ fontSize: '18px', fontWeight: 600, color: COLORS.textPrimary, marginBottom: '8px' }}>{i18n('ai_helper_student_welcome_title')}</div>
109
- <div style={{ fontSize: '13px', color: COLORS.textSecondary, lineHeight: '1.8' }}>
110
- {i18n('ai_helper_student_welcome_desc_line1')}<br/>{i18n('ai_helper_student_welcome_desc_line2')}
114
+ fontFamily: A.mono, fontSize: '24px', fontWeight: 700, color: '#fff',
115
+ letterSpacing: '-1px', marginBottom: '18px',
116
+ boxShadow: '0 8px 20px rgba(37, 99, 235, 0.25)',
117
+ }}>AI</div>
118
+ <div style={{ fontSize: '17px', fontWeight: 700, color: A.textPrimary, marginBottom: '6px' }}>{i18n('ai_helper_student_welcome_title')}</div>
119
+ <div style={{ fontSize: '12.5px', color: A.textMuted, lineHeight: '1.7' }}>
120
+ {i18n('ai_helper_student_welcome_desc_line1')}<br />{i18n('ai_helper_student_welcome_desc_line2')}
111
121
  </div>
112
122
  </div>
113
123
  );
@@ -116,20 +126,18 @@ export const ChatMessageList: React.FC<ChatMessageListProps> = ({
116
126
  const parsed = msg.role === 'ai' ? parseMessageContent(msg.content) : null;
117
127
  const isStudent = msg.role === 'student';
118
128
  return (
119
- <div key={idx} style={{ display: 'flex', flexDirection: isStudent ? 'row-reverse' : 'row', gap: SPACING.sm, alignItems: 'flex-start' }}>
120
- {/* Avatar indicator */}
121
- <div style={{
122
- width: '28px', height: '28px', borderRadius: RADIUS.full, flexShrink: 0,
123
- display: 'flex', alignItems: 'center', justifyContent: 'center', fontSize: '14px',
124
- background: isStudent ? COLORS.primary : COLORS.primaryLight,
125
- color: isStudent ? '#ffffff' : COLORS.primary,
126
- border: isStudent ? 'none' : `1px solid ${COLORS.border}`,
127
- }}>
128
- {isStudent ? '\u{1F464}' : '\u{1F916}'}
129
- </div>
130
- <div style={{ maxWidth: '80%', display: 'flex', flexDirection: 'column', gap: '2px' }}>
129
+ <div key={idx} style={{ display: 'flex', flexDirection: isStudent ? 'row-reverse' : 'row', gap: '8px', alignItems: 'flex-start' }}>
130
+ {/* Avatar */}
131
+ {isStudent ? (
132
+ <div style={{ width: '28px', height: '28px', borderRadius: '50%', flexShrink: 0, display: 'flex', alignItems: 'center', justifyContent: 'center', fontSize: '12px', fontWeight: 600, background: A.userBubbleBg, color: '#fff' }}>
133
+ {i18n('ai_helper_student_me')}
134
+ </div>
135
+ ) : (
136
+ <AIMark size={28} radius={8} fontSize={10} />
137
+ )}
138
+ <div style={{ maxWidth: '82%', display: 'flex', flexDirection: 'column', gap: '3px' }}>
131
139
  {/* Speaker label */}
132
- <div style={{ fontSize: '11px', color: COLORS.textMuted, textAlign: isStudent ? 'right' : 'left' }}>
140
+ <div style={{ fontSize: '10.5px', color: A.textFaint, textAlign: isStudent ? 'right' : 'left' }}>
133
141
  {isStudent ? i18n('ai_helper_student_me') : i18n('ai_helper_student_ai_assistant')}
134
142
  </div>
135
143
  {/* Bubble */}
@@ -137,13 +145,12 @@ export const ChatMessageList: React.FC<ChatMessageListProps> = ({
137
145
  data-ai-message={msg.role === 'ai' ? 'true' : undefined}
138
146
  data-message-id={msg.role === 'ai' ? msg.id : undefined}
139
147
  style={{
140
- padding: `${SPACING.sm} ${SPACING.md}`,
141
- borderRadius: isStudent ? '12px 12px 4px 12px' : '12px 12px 12px 4px',
142
- background: isStudent ? COLORS.primary : '#f0f7ff',
143
- color: isStudent ? '#ffffff' : COLORS.textPrimary,
144
- fontSize: '13px', lineHeight: '1.6',
145
- border: isStudent ? 'none' : '1px solid #dbeafe',
146
- boxShadow: SHADOWS.sm,
148
+ padding: '10px 13px',
149
+ borderRadius: isStudent ? '14px 14px 4px 14px' : '14px 14px 14px 4px',
150
+ background: isStudent ? A.userBubbleBg : A.aiBubbleBg,
151
+ color: isStudent ? '#ffffff' : A.textPrimary,
152
+ fontSize: '13px', lineHeight: '1.7',
153
+ border: isStudent ? 'none' : `1px solid ${A.aiBubbleBorder}`,
147
154
  }}
148
155
  >
149
156
  {msg.role === 'ai' && parsed ? (
@@ -152,18 +159,12 @@ export const ChatMessageList: React.FC<ChatMessageListProps> = ({
152
159
  <div style={{ whiteSpace: 'pre-wrap' }}>{msg.content}</div>
153
160
  )}
154
161
  </div>
155
- {/* Attached code block for student messages */}
162
+ {/* Attached code (student) */}
156
163
  {isStudent && msg.code && (
157
- <div style={{
158
- background: COLORS.bgPage, border: `1px solid ${COLORS.border}`,
159
- borderRadius: RADIUS.md, padding: SPACING.sm, fontSize: '12px',
160
- maxWidth: '100%', overflow: 'hidden',
161
- }}>
162
- <div style={{ fontSize: '11px', color: COLORS.textMuted, marginBottom: SPACING.xs, display: 'flex', alignItems: 'center', gap: SPACING.xs }}>
163
- 📝 {i18n('ai_helper_student_attached_code')}
164
- </div>
164
+ <div style={{ background: '#f8fafc', border: `1px solid ${A.border}`, borderRadius: '10px', padding: '8px', fontSize: '12px', maxWidth: '100%', overflow: 'hidden' }}>
165
+ <div style={{ fontSize: '11px', color: A.textMuted, marginBottom: '4px' }}>📝 {i18n('ai_helper_student_attached_code')}</div>
165
166
  <div className="markdown-body" dangerouslySetInnerHTML={{
166
- __html: renderMarkdownSafe(`\`\`\`\n${msg.code.length > 500 ? msg.code.substring(0, 500) + `\n// ... ${i18n('ai_helper_student_code_truncated')}` : msg.code}\n\`\`\``).innerHTML
167
+ __html: renderMarkdownSafe(`\`\`\`\n${msg.code.length > 500 ? msg.code.substring(0, 500) + `\n// ... ${i18n('ai_helper_student_code_truncated')}` : msg.code}\n\`\`\``),
167
168
  }} />
168
169
  </div>
169
170
  )}
@@ -172,36 +173,55 @@ export const ChatMessageList: React.FC<ChatMessageListProps> = ({
172
173
  );
173
174
  };
174
175
 
176
+ // Memoize message nodes on `messages`. Selecting text only changes popup
177
+ // state, so without this the whole list re-renders on every selection — and
178
+ // in the live runtime that re-render coincided with the AI bubble's rendered
179
+ // DOM being replaced (observed via MutationObserver), which detached the
180
+ // user's selection and collapsed the highlight. Stable element references
181
+ // make React bail out of these subtrees, so the selection's DOM (and the
182
+ // native highlight) survive; it also avoids re-rendering completed messages
183
+ // on every keystroke. (renderMarkdown itself is deterministic — verified
184
+ // end-to-end — so this is not about unstable HTML output.)
185
+ const messageNodes = useMemo(
186
+ () => messages.map((msg, idx) => renderMessage(msg, idx)),
187
+ [messages],
188
+ );
189
+
175
190
  return (
176
191
  <div
177
192
  ref={chatContainerRef}
178
193
  onMouseUp={onTextSelection}
179
- style={{ flex: 1, overflowY: 'auto', padding: SPACING.base, display: 'flex', flexDirection: 'column', gap: SPACING.md }}
194
+ style={{ flex: 1, overflowY: 'auto', padding: '18px 16px', display: 'flex', flexDirection: 'column', gap: '16px' }}
180
195
  >
181
- {/* Problem info breadcrumb */}
182
- {renderProblemInfoCard()}
196
+ {/* dot-pulse keyframes for streaming/loading */}
197
+ <style>{`@keyframes dotpulse{0%,80%,100%{opacity:.25;transform:translateY(0)}40%{opacity:1;transform:translateY(-3px)}}`}</style>
198
+
199
+ {/* Problem info breadcrumb (full-bleed: negate the body padding) */}
200
+ <div style={{ margin: '-18px -16px 0' }}>{renderProblemInfoCard()}</div>
183
201
 
184
202
  {/* Empty state */}
185
203
  {messages.length === 0 && !isStreaming && !isLoading && renderEmptyState()}
186
204
 
187
- {/* Messages */}
188
- {messages.map((msg, idx) => renderMessage(msg, idx))}
205
+ {/* Messages (memoized — see messageNodes above) */}
206
+ {messageNodes}
189
207
 
190
208
  {/* Streaming output */}
191
209
  {isStreaming && streamingContent && (() => {
192
210
  const parsed = parseMessageContent(streamingContent);
193
211
  return (
194
- <div style={{ display: 'flex', flexDirection: 'row', gap: SPACING.sm }}>
212
+ <div style={{ display: 'flex', flexDirection: 'row', gap: '8px', alignItems: 'flex-start' }}>
213
+ <AIMark size={28} radius={8} fontSize={10} />
195
214
  <div style={{
196
- maxWidth: '85%', padding: '10px 14px', borderRadius: '12px 12px 12px 4px',
197
- background: COLORS.primaryLight, color: COLORS.textPrimary, fontSize: '13px', lineHeight: '1.6'
215
+ maxWidth: '82%', padding: '11px 14px', borderRadius: '14px 14px 14px 4px',
216
+ background: A.aiBubbleBg, border: `1px solid ${A.aiBubbleBorder}`,
217
+ color: A.textPrimary, fontSize: '13px', lineHeight: '1.7',
198
218
  }}>
199
219
  <ThinkingBlock isStreaming={parsed.isThinkingStreaming} />
200
220
  {(!parsed.isThinkingStreaming && parsed.content) && renderMarkdown(parsed.content, true)}
201
221
  {!parsed.isThinkingStreaming && (
202
222
  <span style={{
203
- display: 'inline-block', width: '6px', height: '14px', background: COLORS.primary,
204
- marginLeft: '2px', animation: 'blink 1s step-end infinite', verticalAlign: 'text-bottom'
223
+ display: 'inline-block', width: '6px', height: '14px', background: A.pillText,
224
+ marginLeft: '2px', animation: 'blink 1s step-end infinite', verticalAlign: 'text-bottom',
205
225
  }} />
206
226
  )}
207
227
  </div>
@@ -209,12 +229,17 @@ export const ChatMessageList: React.FC<ChatMessageListProps> = ({
209
229
  );
210
230
  })()}
211
231
 
212
- {/* Loading indicator */}
232
+ {/* Loading (pre-stream) */}
213
233
  {isLoading && !isStreaming && (
214
- <div style={{ display: 'flex', gap: SPACING.sm }}>
215
- <div style={{ padding: '10px 14px', borderRadius: '12px 12px 12px 4px', background: COLORS.primaryLight, color: COLORS.textSecondary, fontSize: '13px' }}>
216
- <span style={{ animation: 'pulse 1.5s ease-in-out infinite' }}>{i18n('ai_helper_student_loading')}</span>
217
- <span style={{ color: COLORS.textMuted, fontSize: '11px', marginLeft: SPACING.sm }}>{i18n('ai_helper_student_click_cancel')}</span>
234
+ <div style={{ display: 'flex', gap: '8px', alignItems: 'flex-start' }}>
235
+ <AIMark size={28} radius={8} fontSize={10} />
236
+ <div style={{ padding: '12px 14px', borderRadius: '14px 14px 14px 4px', background: A.aiBubbleBg, border: `1px solid ${A.aiBubbleBorder}`, color: A.textSecondary, fontSize: '13px', display: 'flex', alignItems: 'center', gap: '8px' }}>
237
+ <span>{i18n('ai_helper_student_loading')}</span>
238
+ <span style={{ display: 'inline-flex', gap: '3px' }}>
239
+ <span style={{ width: '5px', height: '5px', borderRadius: '50%', background: A.pillText, animation: 'dotpulse 1.2s infinite' }} />
240
+ <span style={{ width: '5px', height: '5px', borderRadius: '50%', background: A.pillText, animation: 'dotpulse 1.2s infinite 0.2s' }} />
241
+ <span style={{ width: '5px', height: '5px', borderRadius: '50%', background: A.pillText, animation: 'dotpulse 1.2s infinite 0.4s' }} />
242
+ </span>
218
243
  </div>
219
244
  </div>
220
245
  )}
@@ -225,9 +250,9 @@ export const ChatMessageList: React.FC<ChatMessageListProps> = ({
225
250
  style={{
226
251
  position: 'fixed', left: popupPosition.x, top: popupPosition.y,
227
252
  transform: 'translateX(-50%)', zIndex: ZINDEX.dropdown,
228
- background: COLORS.textPrimary, color: '#ffffff', padding: `6px ${SPACING.md}`,
229
- borderRadius: RADIUS.md, fontSize: '12px', cursor: 'pointer',
230
- boxShadow: SHADOWS.md, whiteSpace: 'nowrap'
253
+ background: A.textPrimary, color: '#ffffff', padding: '6px 12px',
254
+ borderRadius: '8px', fontSize: '12px', cursor: 'pointer',
255
+ boxShadow: '0 4px 12px rgba(0,0,0,.18)', whiteSpace: 'nowrap',
231
256
  }}
232
257
  onMouseDown={(e) => e.preventDefault()}
233
258
  onClick={onDontUnderstand}
@@ -1,27 +1,30 @@
1
1
  import React from 'react';
2
2
  import { i18n } from '@hydrooj/ui-default';
3
- import { COLORS, ANIMATIONS } from '../utils/styles';
4
3
 
5
4
  interface ThinkingBlockProps {
6
5
  isStreaming: boolean;
7
6
  }
8
7
 
8
+ /**
9
+ * 方案 A · 克制蓝 — 深度思考指示
10
+ * 三个蓝点的脉冲动画(需要 dotpulse keyframes,已由 ChatMessageList 注入)。
11
+ */
9
12
  export const ThinkingBlock: React.FC<ThinkingBlockProps> = ({ isStreaming }) => {
10
13
  if (!isStreaming) return null;
11
14
 
15
+ const dot = (delay: string): React.CSSProperties => ({
16
+ width: '5px', height: '5px', borderRadius: '50%', background: '#2563eb',
17
+ animation: `dotpulse 1.2s infinite ${delay}`,
18
+ });
19
+
12
20
  return (
13
- <div style={{
14
- display: 'flex',
15
- alignItems: 'center',
16
- gap: '6px',
17
- padding: '6px 0',
18
- fontSize: '12px',
19
- color: COLORS.primary,
20
- }}>
21
- <svg width="14" height="14" viewBox="0 0 24 24" fill="none" style={{ flexShrink: 0 }}>
22
- <circle cx="12" cy="12" r="10" stroke={COLORS.primary} strokeWidth="2" strokeDasharray="31.4" strokeDashoffset="10" style={{ animation: ANIMATIONS.spin }} />
23
- </svg>
21
+ <div style={{ display: 'flex', alignItems: 'center', gap: '8px', padding: '2px 0', fontSize: '12.5px', color: '#64748b' }}>
24
22
  <span>{i18n('ai_helper_student_thinking')}</span>
23
+ <span style={{ display: 'inline-flex', gap: '3px' }}>
24
+ <span style={dot('0s')} />
25
+ <span style={dot('0.2s')} />
26
+ <span style={dot('0.4s')} />
27
+ </span>
25
28
  </div>
26
29
  );
27
30
  };
@@ -1,4 +1,4 @@
1
- import { useState, useRef, useEffect, useCallback } from 'react';
1
+ import { useState, useCallback } from 'react';
2
2
 
3
3
  interface UseTextSelectionOptions {
4
4
  onClarify: (text: string, sourceId: string) => void;
@@ -8,20 +8,17 @@ export function useTextSelection({ onClarify }: UseTextSelectionOptions) {
8
8
  const [selectedText, setSelectedText] = useState('');
9
9
  const [selectedSourceAiMessageId, setSelectedSourceAiMessageId] = useState('');
10
10
  const [popupPosition, setPopupPosition] = useState<{ x: number; y: number } | null>(null);
11
- const savedRangeRef = useRef<Range | null>(null);
12
11
  const [pendingAutoSubmit, setPendingAutoSubmit] = useState(false);
13
12
 
14
13
  const handleTextSelection = useCallback(() => {
15
14
  const selection = window.getSelection();
16
15
  if (!selection || selection.isCollapsed) {
17
16
  setPopupPosition(null);
18
- savedRangeRef.current = null;
19
17
  return;
20
18
  }
21
19
  const text = selection.toString().trim();
22
20
  if (!text) {
23
21
  setPopupPosition(null);
24
- savedRangeRef.current = null;
25
22
  return;
26
23
  }
27
24
  let node = selection.anchorNode;
@@ -36,42 +33,31 @@ export function useTextSelection({ onClarify }: UseTextSelectionOptions) {
36
33
  node = node.parentNode;
37
34
  }
38
35
  if (isInAiMessage) {
39
- const range = selection.getRangeAt(0);
40
- const rect = range.getBoundingClientRect();
41
- savedRangeRef.current = range.cloneRange();
36
+ // Only read the selection's geometry to position the popup. We must NOT
37
+ // re-apply the selection (removeAllRanges()/addRange()) afterwards: the
38
+ // native highlight already survives the React re-render, and forcing a
39
+ // programmatic re-selection makes Chrome drop the painted highlight,
40
+ // so the user loses sight of what they selected while the popup is open.
41
+ const rect = selection.getRangeAt(0).getBoundingClientRect();
42
42
  setSelectedText(text);
43
43
  setSelectedSourceAiMessageId(aiMessageId);
44
44
  setPopupPosition({ x: rect.left + rect.width / 2, y: rect.top - 40 });
45
45
  } else {
46
46
  setPopupPosition(null);
47
- savedRangeRef.current = null;
48
47
  }
49
48
  }, []);
50
49
 
51
50
  const handleDontUnderstand = useCallback(() => {
52
51
  if (!selectedSourceAiMessageId) {
53
52
  setPopupPosition(null);
54
- savedRangeRef.current = null;
55
53
  return;
56
54
  }
57
55
  const truncated = selectedText.length > 100 ? selectedText.substring(0, 100) + '...' : selectedText;
58
56
  onClarify(truncated, selectedSourceAiMessageId);
59
57
  setPopupPosition(null);
60
- savedRangeRef.current = null;
61
58
  setPendingAutoSubmit(true);
62
59
  }, [selectedText, selectedSourceAiMessageId, onClarify]);
63
60
 
64
- // Restore selection after React render
65
- useEffect(() => {
66
- if (popupPosition && savedRangeRef.current) {
67
- const selection = window.getSelection();
68
- if (selection) {
69
- selection.removeAllRanges();
70
- selection.addRange(savedRangeRef.current);
71
- }
72
- }
73
- }, [popupPosition]);
74
-
75
61
  return {
76
62
  selectedText,
77
63
  selectedSourceAiMessageId,
@@ -0,0 +1,81 @@
1
+ import React from 'react';
2
+
3
+ /**
4
+ * 方案 A · 克制蓝 — 统一图标集(单线、随 color 着色)
5
+ * 用于问题类型卡片、输入框操作与发送按钮,替换原先的 emoji。
6
+ */
7
+
8
+ type IconProps = { size?: number };
9
+
10
+ const base = (size: number): React.SVGProps<SVGSVGElement> => ({
11
+ width: size, height: size, viewBox: '0 0 24 24', fill: 'none',
12
+ stroke: 'currentColor', strokeLinecap: 'round', strokeLinejoin: 'round',
13
+ });
14
+
15
+ /** 问题类型图标:understand / think / debug / optimize */
16
+ export const TypeIcon: React.FC<IconProps & { type: string }> = ({ type, size = 14 }) => {
17
+ switch (type) {
18
+ case 'understand':
19
+ return (
20
+ <svg {...base(size)} strokeWidth={2}>
21
+ <circle cx="12" cy="12" r="9" />
22
+ <path d="M9.6 9.3a2.4 2.4 0 0 1 4.3 1.4c0 1.6-2 1.7-2 3.1" />
23
+ <circle cx="11.9" cy="17.2" r="0.9" fill="currentColor" stroke="none" />
24
+ </svg>
25
+ );
26
+ case 'think':
27
+ return (
28
+ <svg {...base(size)} strokeWidth={2}>
29
+ <path d="M9 18h6M10 21h4" />
30
+ <path d="M12 3a6 6 0 0 0-3.8 10.6c.6.5.9 1.1.9 2.4h5.8c0-1.3.3-1.9.9-2.4A6 6 0 0 0 12 3z" />
31
+ </svg>
32
+ );
33
+ case 'debug':
34
+ return (
35
+ <svg {...base(size)} strokeWidth={2}>
36
+ <polyline points="9 8 5 12 9 16" />
37
+ <polyline points="15 8 19 12 15 16" />
38
+ </svg>
39
+ );
40
+ case 'optimize':
41
+ return (
42
+ <svg {...base(size)} strokeWidth={1.8}>
43
+ <path d="M12 3l1.7 6.3L20 11l-6.3 1.7L12 19l-1.7-6.3L4 11l6.3-1.7z" />
44
+ </svg>
45
+ );
46
+ default:
47
+ return null;
48
+ }
49
+ };
50
+
51
+ export const SendIcon: React.FC<IconProps> = ({ size = 15 }) => (
52
+ <svg {...base(size)} strokeWidth={2.2}><path d="M5 12h13M13 6l6 6-6 6" /></svg>
53
+ );
54
+
55
+ export const AttachIcon: React.FC<IconProps> = ({ size = 12 }) => (
56
+ <svg {...base(size)} strokeWidth={2}>
57
+ <path d="M21 15V8a2 2 0 0 0-2-2h-7l-2-2H5a2 2 0 0 0-2 2v11a2 2 0 0 0 2 2h7" />
58
+ </svg>
59
+ );
60
+
61
+ export const RefreshIcon: React.FC<IconProps> = ({ size = 12 }) => (
62
+ <svg {...base(size)} strokeWidth={2}><path d="M3 12a9 9 0 1 0 3-6.7L3 8M3 4v4h4" /></svg>
63
+ );
64
+
65
+ export const RemoveIcon: React.FC<IconProps> = ({ size = 11 }) => (
66
+ <svg {...base(size)} strokeWidth={2}><path d="M6 6l12 12M18 6L6 18" /></svg>
67
+ );
68
+
69
+ /** 字母标识:替换原机器人 emoji 头像 */
70
+ export const AIMark: React.FC<{ size?: number; radius?: number; fontSize?: number }> = ({
71
+ size = 28, radius = 8, fontSize = 11,
72
+ }) => (
73
+ <div style={{
74
+ width: size, height: size, borderRadius: radius,
75
+ background: 'linear-gradient(135deg, #2563eb, #5b8def)',
76
+ display: 'flex', alignItems: 'center', justifyContent: 'center',
77
+ fontFamily: "'JetBrains Mono', ui-monospace, 'SFMono-Regular', monospace",
78
+ fontSize, fontWeight: 700, color: '#ffffff', letterSpacing: '-0.5px',
79
+ flexShrink: 0,
80
+ }}>AI</div>
81
+ );
package/locales/en.yaml CHANGED
@@ -552,7 +552,6 @@ ai_helper_student_optimize_code_required: "Code is required for optimization"
552
552
  ai_helper_student_cancel: "Cancel"
553
553
  ai_helper_student_send_shortcut: "Send (Ctrl+Enter)"
554
554
  ai_helper_student_loading: "AI is thinking..."
555
- ai_helper_student_click_cancel: "Click cancel to stop"
556
555
  ai_helper_student_thinking: "Deep thinking..."
557
556
  ai_helper_student_retry: "Retry"
558
557
  ai_helper_student_new_conversation: "New conversation"
package/locales/zh.yaml CHANGED
@@ -552,7 +552,6 @@ ai_helper_student_optimize_code_required: "代码优化需要附带代码"
552
552
  ai_helper_student_cancel: "取消"
553
553
  ai_helper_student_send_shortcut: "发送 (Ctrl+Enter)"
554
554
  ai_helper_student_loading: "AI 正在思考..."
555
- ai_helper_student_click_cancel: "点击取消可中断"
556
555
  ai_helper_student_thinking: "正在深度思考..."
557
556
  ai_helper_student_retry: "重试"
558
557
  ai_helper_student_new_conversation: "新对话"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hydro-ai-helper",
3
- "version": "2.1.0",
3
+ "version": "2.2.0",
4
4
  "description": "HydroOJ AI Learning Assistant - 一个教学优先的 AI 辅助学习插件",
5
5
  "main": "dist/index.js",
6
6
  "author": "Alture",