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.
- package/dist/models/requestStats.js +14 -1
- package/dist/models/requestStats.js.map +1 -1
- package/dist/services/telemetryService.js +2 -0
- package/dist/services/telemetryService.js.map +1 -1
- package/frontend/student/ChatInput.tsx +118 -116
- package/frontend/student/ChatMessageList.tsx +98 -73
- package/frontend/student/ThinkingBlock.tsx +15 -12
- package/frontend/student/hooks/useTextSelection.ts +7 -21
- package/frontend/student/icons.tsx +81 -0
- package/locales/en.yaml +0 -1
- package/locales/zh.yaml +0 -1
- package/package.json +1 -1
|
@@ -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":";;;
|
|
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
|
-
|
|
5
|
-
|
|
6
|
-
|
|
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: '
|
|
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:
|
|
116
|
+
style={{ marginRight: '6px', accentColor: A.primary }}
|
|
59
117
|
/>
|
|
60
118
|
{labelText}
|
|
61
|
-
{questionType === 'optimize' && <span style={{ marginLeft: '4px', color:
|
|
62
|
-
{includeCode && code && questionType !== 'optimize' && <span style={{ marginLeft: '4px', color:
|
|
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' }}>✓</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
|
-
|
|
73
|
-
|
|
74
|
-
|
|
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: '
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
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
|
-
|
|
102
|
-
|
|
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:
|
|
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
|
-
{/*
|
|
163
|
+
{/* 问题类型卡片 — 首次对话 */}
|
|
110
164
|
{isFirstConversation && (
|
|
111
|
-
<div style={{ padding:
|
|
112
|
-
<div style={{ fontSize: '12px', color:
|
|
113
|
-
<div
|
|
114
|
-
{questionTypes.map(
|
|
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
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
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
|
-
{/*
|
|
178
|
+
{/* 追问操作 — 后续对话 */}
|
|
148
179
|
{isFollowUp && (
|
|
149
|
-
<div style={{ display: 'flex', gap:
|
|
150
|
-
<button
|
|
151
|
-
|
|
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
|
-
|
|
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
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
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
|
-
{/*
|
|
197
|
-
<div className="
|
|
198
|
-
margin: '12px', padding: '
|
|
199
|
-
border: `1px solid ${
|
|
200
|
-
|
|
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 ? '
|
|
203
|
-
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginTop: '
|
|
204
|
-
{isFirstConversation ? renderIncludeCodeCheckbox(
|
|
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
|
-
|
|
215
|
-
style={submitStyle}
|
|
216
|
-
title={i18n('ai_helper_student_send_shortcut')}
|
|
217
|
-
>
|
|
218
|
-
➤
|
|
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 {
|
|
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.
|
|
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: '
|
|
67
|
-
<span style={{ fontSize: '11px', fontWeight:
|
|
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: '
|
|
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
|
-
|
|
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: '
|
|
102
|
-
background: 'linear-gradient(135deg, #2563eb, #
|
|
111
|
+
width: '64px', height: '64px', borderRadius: '18px',
|
|
112
|
+
background: 'linear-gradient(135deg, #2563eb, #5b8def)',
|
|
103
113
|
display: 'flex', alignItems: 'center', justifyContent: 'center',
|
|
104
|
-
fontSize: '
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
}}
|
|
108
|
-
<div style={{ fontSize: '
|
|
109
|
-
<div style={{ fontSize: '
|
|
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:
|
|
120
|
-
{/* Avatar
|
|
121
|
-
|
|
122
|
-
width: '28px', height: '28px', borderRadius:
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
}
|
|
128
|
-
|
|
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: '
|
|
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:
|
|
141
|
-
borderRadius: isStudent ? '
|
|
142
|
-
background: isStudent ?
|
|
143
|
-
color: isStudent ? '#ffffff' :
|
|
144
|
-
fontSize: '13px', lineHeight: '1.
|
|
145
|
-
border: isStudent ? 'none' :
|
|
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
|
|
162
|
+
{/* Attached code (student) */}
|
|
156
163
|
{isStudent && msg.code && (
|
|
157
|
-
<div style={{
|
|
158
|
-
|
|
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\`\`\``)
|
|
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:
|
|
194
|
+
style={{ flex: 1, overflowY: 'auto', padding: '18px 16px', display: 'flex', flexDirection: 'column', gap: '16px' }}
|
|
180
195
|
>
|
|
181
|
-
{/*
|
|
182
|
-
{
|
|
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
|
-
{
|
|
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:
|
|
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: '
|
|
197
|
-
background:
|
|
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:
|
|
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
|
|
232
|
+
{/* Loading (pre-stream) */}
|
|
213
233
|
{isLoading && !isStreaming && (
|
|
214
|
-
<div style={{ display: 'flex', gap:
|
|
215
|
-
<
|
|
216
|
-
|
|
217
|
-
<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:
|
|
229
|
-
borderRadius:
|
|
230
|
-
boxShadow:
|
|
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,
|
|
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
|
-
|
|
40
|
-
|
|
41
|
-
|
|
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: "新对话"
|