@octo-cyber/sea-king 0.5.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/controllers/character.controller.d.ts +10 -0
- package/dist/controllers/character.controller.d.ts.map +1 -0
- package/dist/controllers/character.controller.js +58 -0
- package/dist/controllers/character.controller.js.map +1 -0
- package/dist/controllers/chat.controller.d.ts +13 -0
- package/dist/controllers/chat.controller.d.ts.map +1 -0
- package/dist/controllers/chat.controller.js +113 -0
- package/dist/controllers/chat.controller.js.map +1 -0
- package/dist/controllers/crisis.controller.d.ts +10 -0
- package/dist/controllers/crisis.controller.d.ts.map +1 -0
- package/dist/controllers/crisis.controller.js +69 -0
- package/dist/controllers/crisis.controller.js.map +1 -0
- package/dist/controllers/index.d.ts +10 -0
- package/dist/controllers/index.d.ts.map +1 -0
- package/dist/controllers/index.js +24 -0
- package/dist/controllers/index.js.map +1 -0
- package/dist/controllers/leaderboard.controller.d.ts +9 -0
- package/dist/controllers/leaderboard.controller.d.ts.map +1 -0
- package/dist/controllers/leaderboard.controller.js +51 -0
- package/dist/controllers/leaderboard.controller.js.map +1 -0
- package/dist/controllers/live-chat.controller.d.ts +14 -0
- package/dist/controllers/live-chat.controller.d.ts.map +1 -0
- package/dist/controllers/live-chat.controller.js +95 -0
- package/dist/controllers/live-chat.controller.js.map +1 -0
- package/dist/controllers/matchmaking.controller.d.ts +22 -0
- package/dist/controllers/matchmaking.controller.d.ts.map +1 -0
- package/dist/controllers/matchmaking.controller.js +157 -0
- package/dist/controllers/matchmaking.controller.js.map +1 -0
- package/dist/controllers/phrase.controller.d.ts +11 -0
- package/dist/controllers/phrase.controller.d.ts.map +1 -0
- package/dist/controllers/phrase.controller.js +78 -0
- package/dist/controllers/phrase.controller.js.map +1 -0
- package/dist/controllers/profile.controller.d.ts +9 -0
- package/dist/controllers/profile.controller.d.ts.map +1 -0
- package/dist/controllers/profile.controller.js +44 -0
- package/dist/controllers/profile.controller.js.map +1 -0
- package/dist/controllers/turing.controller.d.ts +18 -0
- package/dist/controllers/turing.controller.d.ts.map +1 -0
- package/dist/controllers/turing.controller.js +103 -0
- package/dist/controllers/turing.controller.js.map +1 -0
- package/dist/entities/chat-character.entity.d.ts +34 -0
- package/dist/entities/chat-character.entity.d.ts.map +1 -0
- package/dist/entities/chat-character.entity.js +101 -0
- package/dist/entities/chat-character.entity.js.map +1 -0
- package/dist/entities/chat-message.entity.d.ts +26 -0
- package/dist/entities/chat-message.entity.d.ts.map +1 -0
- package/dist/entities/chat-message.entity.js +84 -0
- package/dist/entities/chat-message.entity.js.map +1 -0
- package/dist/entities/chat-session.entity.d.ts +31 -0
- package/dist/entities/chat-session.entity.d.ts.map +1 -0
- package/dist/entities/chat-session.entity.js +95 -0
- package/dist/entities/chat-session.entity.js.map +1 -0
- package/dist/entities/chat-thread.entity.d.ts +30 -0
- package/dist/entities/chat-thread.entity.d.ts.map +1 -0
- package/dist/entities/chat-thread.entity.js +105 -0
- package/dist/entities/chat-thread.entity.js.map +1 -0
- package/dist/entities/crisis-event.entity.d.ts +30 -0
- package/dist/entities/crisis-event.entity.d.ts.map +1 -0
- package/dist/entities/crisis-event.entity.js +90 -0
- package/dist/entities/crisis-event.entity.js.map +1 -0
- package/dist/entities/index.d.ts +38 -0
- package/dist/entities/index.d.ts.map +1 -0
- package/dist/entities/index.js +62 -0
- package/dist/entities/index.js.map +1 -0
- package/dist/entities/leaderboard-entry.entity.d.ts +22 -0
- package/dist/entities/leaderboard-entry.entity.d.ts.map +1 -0
- package/dist/entities/leaderboard-entry.entity.js +75 -0
- package/dist/entities/leaderboard-entry.entity.js.map +1 -0
- package/dist/entities/live-message.entity.d.ts +34 -0
- package/dist/entities/live-message.entity.d.ts.map +1 -0
- package/dist/entities/live-message.entity.js +99 -0
- package/dist/entities/live-message.entity.js.map +1 -0
- package/dist/entities/live-thread.entity.d.ts +50 -0
- package/dist/entities/live-thread.entity.d.ts.map +1 -0
- package/dist/entities/live-thread.entity.js +144 -0
- package/dist/entities/live-thread.entity.js.map +1 -0
- package/dist/entities/match-room.entity.d.ts +44 -0
- package/dist/entities/match-room.entity.d.ts.map +1 -0
- package/dist/entities/match-room.entity.js +131 -0
- package/dist/entities/match-room.entity.js.map +1 -0
- package/dist/entities/phrase-template.entity.d.ts +28 -0
- package/dist/entities/phrase-template.entity.d.ts.map +1 -0
- package/dist/entities/phrase-template.entity.js +84 -0
- package/dist/entities/phrase-template.entity.js.map +1 -0
- package/dist/entities/player-profile.entity.d.ts +36 -0
- package/dist/entities/player-profile.entity.d.ts.map +1 -0
- package/dist/entities/player-profile.entity.js +112 -0
- package/dist/entities/player-profile.entity.js.map +1 -0
- package/dist/entities/player-queue.entity.d.ts +24 -0
- package/dist/entities/player-queue.entity.d.ts.map +1 -0
- package/dist/entities/player-queue.entity.js +76 -0
- package/dist/entities/player-queue.entity.js.map +1 -0
- package/dist/entities/turing-guess.entity.d.ts +29 -0
- package/dist/entities/turing-guess.entity.d.ts.map +1 -0
- package/dist/entities/turing-guess.entity.js +94 -0
- package/dist/entities/turing-guess.entity.js.map +1 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +70 -0
- package/dist/index.js.map +1 -0
- package/dist/schemas/sea-king.schema.d.ts +109 -0
- package/dist/schemas/sea-king.schema.d.ts.map +1 -0
- package/dist/schemas/sea-king.schema.js +103 -0
- package/dist/schemas/sea-king.schema.js.map +1 -0
- package/dist/sea-king.module.d.ts +19 -0
- package/dist/sea-king.module.d.ts.map +1 -0
- package/dist/sea-king.module.js +114 -0
- package/dist/sea-king.module.js.map +1 -0
- package/dist/services/character.service.d.ts +30 -0
- package/dist/services/character.service.d.ts.map +1 -0
- package/dist/services/character.service.js +75 -0
- package/dist/services/character.service.js.map +1 -0
- package/dist/services/chat.service.d.ts +49 -0
- package/dist/services/chat.service.d.ts.map +1 -0
- package/dist/services/chat.service.js +332 -0
- package/dist/services/chat.service.js.map +1 -0
- package/dist/services/crisis.service.d.ts +17 -0
- package/dist/services/crisis.service.d.ts.map +1 -0
- package/dist/services/crisis.service.js +177 -0
- package/dist/services/crisis.service.js.map +1 -0
- package/dist/services/index.d.ts +13 -0
- package/dist/services/index.d.ts.map +1 -0
- package/dist/services/index.js +28 -0
- package/dist/services/index.js.map +1 -0
- package/dist/services/leaderboard.service.d.ts +29 -0
- package/dist/services/leaderboard.service.d.ts.map +1 -0
- package/dist/services/leaderboard.service.js +64 -0
- package/dist/services/leaderboard.service.js.map +1 -0
- package/dist/services/live-chat.service.d.ts +71 -0
- package/dist/services/live-chat.service.d.ts.map +1 -0
- package/dist/services/live-chat.service.js +273 -0
- package/dist/services/live-chat.service.js.map +1 -0
- package/dist/services/matchmaking.service.d.ts +60 -0
- package/dist/services/matchmaking.service.d.ts.map +1 -0
- package/dist/services/matchmaking.service.js +208 -0
- package/dist/services/matchmaking.service.js.map +1 -0
- package/dist/services/phrase.service.d.ts +27 -0
- package/dist/services/phrase.service.d.ts.map +1 -0
- package/dist/services/phrase.service.js +70 -0
- package/dist/services/phrase.service.js.map +1 -0
- package/dist/services/profile.service.d.ts +41 -0
- package/dist/services/profile.service.d.ts.map +1 -0
- package/dist/services/profile.service.js +107 -0
- package/dist/services/profile.service.js.map +1 -0
- package/dist/services/seed.service.d.ts +19 -0
- package/dist/services/seed.service.d.ts.map +1 -0
- package/dist/services/seed.service.js +550 -0
- package/dist/services/seed.service.js.map +1 -0
- package/dist/services/turing.service.d.ts +74 -0
- package/dist/services/turing.service.d.ts.map +1 -0
- package/dist/services/turing.service.js +175 -0
- package/dist/services/turing.service.js.map +1 -0
- package/dist/services/ws-hub.service.d.ts +51 -0
- package/dist/services/ws-hub.service.d.ts.map +1 -0
- package/dist/services/ws-hub.service.js +174 -0
- package/dist/services/ws-hub.service.js.map +1 -0
- package/dist/types.d.ts +12 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +8 -0
- package/dist/types.js.map +1 -0
- package/package.json +69 -0
- package/web/index.ts +30 -0
- package/web/manifest.ts +40 -0
- package/web/messages/en-US.json +113 -0
- package/web/messages/zh-CN.json +113 -0
- package/web/pages/AnalysisPage.tsx +198 -0
- package/web/pages/ChatPage.tsx +482 -0
- package/web/pages/DashboardPage.tsx +281 -0
- package/web/pages/LeaderboardPage.tsx +208 -0
- package/web/pages/PhrasesPage.tsx +221 -0
- package/web/services/sea-king-service.ts +151 -0
- package/web/types/sea-king.ts +249 -0
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useState, useEffect } from 'react';
|
|
4
|
+
import { useTranslations } from 'next-intl';
|
|
5
|
+
import { BookHeart, Eye, EyeOff, Shuffle, Filter } from 'lucide-react';
|
|
6
|
+
import { listPhrases, getPhrasePractice } from '../services/sea-king-service';
|
|
7
|
+
import type { PhraseTemplate } from '../types/sea-king';
|
|
8
|
+
|
|
9
|
+
const SCENE_INFO: Record<string, { emoji: string; color: string }> = {
|
|
10
|
+
first_meet: { emoji: '👋', color: 'bg-blue-100 text-blue-700' },
|
|
11
|
+
daily_care: { emoji: '💝', color: 'bg-pink-100 text-pink-700' },
|
|
12
|
+
resolve_awkward: { emoji: '😅', color: 'bg-amber-100 text-amber-700' },
|
|
13
|
+
elegant_reject: { emoji: '🙏', color: 'bg-purple-100 text-purple-700' },
|
|
14
|
+
flirt: { emoji: '😏', color: 'bg-rose-100 text-rose-700' },
|
|
15
|
+
cool_down: { emoji: '❄️', color: 'bg-cyan-100 text-cyan-700' },
|
|
16
|
+
crisis_response: { emoji: '🚨', color: 'bg-red-100 text-red-700' },
|
|
17
|
+
morning_night: { emoji: '🌙', color: 'bg-indigo-100 text-indigo-700' },
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export default function PhrasesPage() {
|
|
21
|
+
const t = useTranslations('seaKing.phrases');
|
|
22
|
+
|
|
23
|
+
const [phrases, setPhrases] = useState<PhraseTemplate[]>([]);
|
|
24
|
+
const [loading, setLoading] = useState(true);
|
|
25
|
+
const [selectedScene, setSelectedScene] = useState<string | undefined>();
|
|
26
|
+
const [revealedIds, setRevealedIds] = useState<Set<string>>(new Set());
|
|
27
|
+
const [practiceMode, setPracticeMode] = useState(false);
|
|
28
|
+
|
|
29
|
+
useEffect(() => {
|
|
30
|
+
loadPhrases();
|
|
31
|
+
}, [selectedScene]);
|
|
32
|
+
|
|
33
|
+
const loadPhrases = async () => {
|
|
34
|
+
setLoading(true);
|
|
35
|
+
try {
|
|
36
|
+
if (practiceMode) {
|
|
37
|
+
const data = await getPhrasePractice({ count: 5, scene: selectedScene });
|
|
38
|
+
setPhrases(data);
|
|
39
|
+
} else {
|
|
40
|
+
const data = await listPhrases({ scene: selectedScene, pageSize: 50 });
|
|
41
|
+
setPhrases(data.items);
|
|
42
|
+
}
|
|
43
|
+
} catch {
|
|
44
|
+
// ignore
|
|
45
|
+
} finally {
|
|
46
|
+
setLoading(false);
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
const toggleReveal = (id: string) => {
|
|
51
|
+
setRevealedIds((prev) => {
|
|
52
|
+
const next = new Set(prev);
|
|
53
|
+
if (next.has(id)) next.delete(id);
|
|
54
|
+
else next.add(id);
|
|
55
|
+
return next;
|
|
56
|
+
});
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
const scenes = Object.keys(SCENE_INFO);
|
|
60
|
+
|
|
61
|
+
return (
|
|
62
|
+
<div className="space-y-6 p-6">
|
|
63
|
+
{/* Header */}
|
|
64
|
+
<div className="bg-gradient-to-r from-pink-500 via-rose-500 to-red-500 rounded-2xl p-8 text-white relative overflow-hidden">
|
|
65
|
+
<div className="absolute top-0 right-0 opacity-10 text-[180px] leading-none select-none">
|
|
66
|
+
💬
|
|
67
|
+
</div>
|
|
68
|
+
<div className="relative z-10">
|
|
69
|
+
<div className="flex items-center gap-3 mb-2">
|
|
70
|
+
<BookHeart className="h-8 w-8" />
|
|
71
|
+
<h1 className="text-3xl font-bold">{t('title')}</h1>
|
|
72
|
+
</div>
|
|
73
|
+
<p className="text-pink-100 text-lg mt-2">{t('subtitle')}</p>
|
|
74
|
+
</div>
|
|
75
|
+
</div>
|
|
76
|
+
|
|
77
|
+
{/* Controls */}
|
|
78
|
+
<div className="flex flex-wrap items-center gap-3">
|
|
79
|
+
<div className="flex items-center gap-2">
|
|
80
|
+
<Filter className="h-4 w-4 text-gray-400" />
|
|
81
|
+
<button
|
|
82
|
+
onClick={() => setSelectedScene(undefined)}
|
|
83
|
+
className={`px-3 py-1.5 rounded-lg text-sm transition-colors ${
|
|
84
|
+
!selectedScene ? 'bg-pink-500 text-white' : 'bg-gray-100 hover:bg-gray-200'
|
|
85
|
+
}`}
|
|
86
|
+
>
|
|
87
|
+
{t('allScenes')}
|
|
88
|
+
</button>
|
|
89
|
+
{scenes.map((scene) => {
|
|
90
|
+
const info = SCENE_INFO[scene]!;
|
|
91
|
+
return (
|
|
92
|
+
<button
|
|
93
|
+
key={scene}
|
|
94
|
+
onClick={() => setSelectedScene(scene)}
|
|
95
|
+
className={`px-3 py-1.5 rounded-lg text-sm transition-colors flex items-center gap-1 ${
|
|
96
|
+
selectedScene === scene ? 'bg-pink-500 text-white' : `${info.color} hover:opacity-80`
|
|
97
|
+
}`}
|
|
98
|
+
>
|
|
99
|
+
<span>{info.emoji}</span>
|
|
100
|
+
{t(`scenes.${scene}`)}
|
|
101
|
+
</button>
|
|
102
|
+
);
|
|
103
|
+
})}
|
|
104
|
+
</div>
|
|
105
|
+
|
|
106
|
+
<div className="ml-auto flex gap-2">
|
|
107
|
+
<button
|
|
108
|
+
onClick={() => {
|
|
109
|
+
setPracticeMode(!practiceMode);
|
|
110
|
+
setRevealedIds(new Set());
|
|
111
|
+
}}
|
|
112
|
+
className={`px-4 py-2 rounded-lg text-sm font-medium flex items-center gap-2 transition-colors ${
|
|
113
|
+
practiceMode
|
|
114
|
+
? 'bg-amber-500 text-white'
|
|
115
|
+
: 'bg-gray-100 hover:bg-gray-200'
|
|
116
|
+
}`}
|
|
117
|
+
>
|
|
118
|
+
<Shuffle className="h-4 w-4" />
|
|
119
|
+
{t('practiceMode')}
|
|
120
|
+
</button>
|
|
121
|
+
</div>
|
|
122
|
+
</div>
|
|
123
|
+
|
|
124
|
+
{/* Phrases Grid */}
|
|
125
|
+
{loading ? (
|
|
126
|
+
<div className="flex items-center justify-center min-h-[200px]">
|
|
127
|
+
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-pink-500" />
|
|
128
|
+
</div>
|
|
129
|
+
) : (
|
|
130
|
+
<div className="grid grid-cols-1 lg:grid-cols-2 gap-4">
|
|
131
|
+
{phrases.map((phrase) => {
|
|
132
|
+
const isRevealed = revealedIds.has(phrase.id);
|
|
133
|
+
const sceneInfo = SCENE_INFO[phrase.scene] ?? { emoji: '💬', color: 'bg-gray-100' };
|
|
134
|
+
|
|
135
|
+
return (
|
|
136
|
+
<div
|
|
137
|
+
key={phrase.id}
|
|
138
|
+
className="bg-white rounded-xl border shadow-sm hover:shadow-md transition-shadow overflow-hidden"
|
|
139
|
+
>
|
|
140
|
+
{/* Scene Header */}
|
|
141
|
+
<div className="px-4 py-2 bg-gray-50 border-b flex items-center justify-between">
|
|
142
|
+
<span className={`text-xs px-2 py-0.5 rounded-full ${sceneInfo.color}`}>
|
|
143
|
+
{sceneInfo.emoji} {t(`scenes.${phrase.scene}`)}
|
|
144
|
+
</span>
|
|
145
|
+
<span className="text-xs text-gray-400">
|
|
146
|
+
+{phrase.favorBonus} 好感度
|
|
147
|
+
</span>
|
|
148
|
+
</div>
|
|
149
|
+
|
|
150
|
+
{/* Trigger */}
|
|
151
|
+
<div className="p-4">
|
|
152
|
+
<div className="mb-3">
|
|
153
|
+
<div className="text-xs text-gray-400 mb-1">{t('theyAsk')}</div>
|
|
154
|
+
<div className="bg-gray-100 rounded-xl px-4 py-3 text-sm">
|
|
155
|
+
{phrase.trigger}
|
|
156
|
+
</div>
|
|
157
|
+
</div>
|
|
158
|
+
|
|
159
|
+
{/* Bad Reply */}
|
|
160
|
+
<div className="mb-3">
|
|
161
|
+
<div className="text-xs text-red-400 mb-1 flex items-center gap-1">
|
|
162
|
+
❌ {t('badReply')}
|
|
163
|
+
</div>
|
|
164
|
+
<div className="bg-red-50 rounded-xl px-4 py-3 text-sm text-red-700 border border-red-200">
|
|
165
|
+
{phrase.badReply}
|
|
166
|
+
</div>
|
|
167
|
+
</div>
|
|
168
|
+
|
|
169
|
+
{/* Good Reply (can be hidden in practice mode) */}
|
|
170
|
+
<div className="mb-3">
|
|
171
|
+
<div className="text-xs text-green-500 mb-1 flex items-center justify-between">
|
|
172
|
+
<span className="flex items-center gap-1">
|
|
173
|
+
✅ {t('goodReply')}
|
|
174
|
+
</span>
|
|
175
|
+
{practiceMode && (
|
|
176
|
+
<button
|
|
177
|
+
onClick={() => toggleReveal(phrase.id)}
|
|
178
|
+
className="flex items-center gap-1 text-gray-400 hover:text-gray-600"
|
|
179
|
+
>
|
|
180
|
+
{isRevealed ? (
|
|
181
|
+
<EyeOff className="h-3 w-3" />
|
|
182
|
+
) : (
|
|
183
|
+
<Eye className="h-3 w-3" />
|
|
184
|
+
)}
|
|
185
|
+
{isRevealed ? t('hide') : t('reveal')}
|
|
186
|
+
</button>
|
|
187
|
+
)}
|
|
188
|
+
</div>
|
|
189
|
+
{(!practiceMode || isRevealed) ? (
|
|
190
|
+
<div className="bg-green-50 rounded-xl px-4 py-3 text-sm text-green-700 border border-green-200">
|
|
191
|
+
{phrase.goodReply}
|
|
192
|
+
</div>
|
|
193
|
+
) : (
|
|
194
|
+
<div className="bg-gray-100 rounded-xl px-4 py-3 text-sm text-gray-400 text-center border-2 border-dashed border-gray-300">
|
|
195
|
+
🤔 {t('thinkFirst')}
|
|
196
|
+
</div>
|
|
197
|
+
)}
|
|
198
|
+
</div>
|
|
199
|
+
|
|
200
|
+
{/* Explanation */}
|
|
201
|
+
{(!practiceMode || isRevealed) && (
|
|
202
|
+
<div className="bg-blue-50 rounded-xl px-4 py-3 text-xs text-blue-700 border border-blue-200">
|
|
203
|
+
💡 {phrase.explanation}
|
|
204
|
+
</div>
|
|
205
|
+
)}
|
|
206
|
+
</div>
|
|
207
|
+
</div>
|
|
208
|
+
);
|
|
209
|
+
})}
|
|
210
|
+
</div>
|
|
211
|
+
)}
|
|
212
|
+
|
|
213
|
+
{phrases.length === 0 && !loading && (
|
|
214
|
+
<div className="text-center py-12 text-gray-400">
|
|
215
|
+
<span className="text-4xl">📭</span>
|
|
216
|
+
<p className="mt-2">{t('noPhrases')}</p>
|
|
217
|
+
</div>
|
|
218
|
+
)}
|
|
219
|
+
</div>
|
|
220
|
+
);
|
|
221
|
+
}
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
ChatCharacter,
|
|
3
|
+
ChatSession,
|
|
4
|
+
ChatMessage,
|
|
5
|
+
CrisisEvent,
|
|
6
|
+
PlayerAnalysis,
|
|
7
|
+
PhraseTemplate,
|
|
8
|
+
LeaderboardEntry,
|
|
9
|
+
SendMessageResult,
|
|
10
|
+
} from '../types/sea-king';
|
|
11
|
+
|
|
12
|
+
const BASE = '/api/v1/sea-king';
|
|
13
|
+
|
|
14
|
+
async function fetchJson<T>(url: string, init?: RequestInit): Promise<T> {
|
|
15
|
+
const res = await fetch(url, {
|
|
16
|
+
headers: { 'Content-Type': 'application/json' },
|
|
17
|
+
...init,
|
|
18
|
+
});
|
|
19
|
+
if (!res.ok) {
|
|
20
|
+
const err = await res.json().catch(() => ({ error: res.statusText }));
|
|
21
|
+
throw new Error(err.error ?? `HTTP ${res.status}`);
|
|
22
|
+
}
|
|
23
|
+
if (res.status === 204) return undefined as any;
|
|
24
|
+
return res.json();
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// ─── Characters ────────────────────────────────────────────────────────────
|
|
28
|
+
|
|
29
|
+
export async function listCharacters(): Promise<ChatCharacter[]> {
|
|
30
|
+
return fetchJson(`${BASE}/characters`);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export async function getCharacter(id: string): Promise<ChatCharacter> {
|
|
34
|
+
return fetchJson(`${BASE}/characters/${id}`);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// ─── Chat Sessions ─────────────────────────────────────────────────────────
|
|
38
|
+
|
|
39
|
+
export async function startSession(params: {
|
|
40
|
+
characterCount?: number;
|
|
41
|
+
difficulty?: string;
|
|
42
|
+
preferredPersonalities?: string[];
|
|
43
|
+
}): Promise<{ session: ChatSession; threads: any[] }> {
|
|
44
|
+
return fetchJson(`${BASE}/chat/sessions`, {
|
|
45
|
+
method: 'POST',
|
|
46
|
+
body: JSON.stringify(params),
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export async function getSession(sessionId: string): Promise<ChatSession> {
|
|
51
|
+
return fetchJson(`${BASE}/chat/sessions/${sessionId}`);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export async function sendMessage(params: {
|
|
55
|
+
threadId: string;
|
|
56
|
+
content: string;
|
|
57
|
+
}): Promise<SendMessageResult> {
|
|
58
|
+
return fetchJson(`${BASE}/chat/messages`, {
|
|
59
|
+
method: 'POST',
|
|
60
|
+
body: JSON.stringify(params),
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export async function getThreadMessages(
|
|
65
|
+
threadId: string,
|
|
66
|
+
page?: number,
|
|
67
|
+
pageSize?: number,
|
|
68
|
+
): Promise<{ items: ChatMessage[]; total: number }> {
|
|
69
|
+
const qs = new URLSearchParams();
|
|
70
|
+
if (page) qs.set('page', String(page));
|
|
71
|
+
if (pageSize) qs.set('pageSize', String(pageSize));
|
|
72
|
+
return fetchJson(`${BASE}/chat/threads/${threadId}/messages?${qs}`);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export async function endSession(sessionId: string): Promise<ChatSession> {
|
|
76
|
+
return fetchJson(`${BASE}/chat/sessions/${sessionId}/end`, { method: 'POST' });
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export async function getChatHistory(
|
|
80
|
+
page?: number,
|
|
81
|
+
pageSize?: number,
|
|
82
|
+
): Promise<{ items: ChatSession[]; total: number }> {
|
|
83
|
+
const qs = new URLSearchParams();
|
|
84
|
+
if (page) qs.set('page', String(page));
|
|
85
|
+
if (pageSize) qs.set('pageSize', String(pageSize));
|
|
86
|
+
return fetchJson(`${BASE}/chat/history?${qs}`);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// ─── Crisis Events ─────────────────────────────────────────────────────────
|
|
90
|
+
|
|
91
|
+
export async function triggerCrisis(sessionId: string): Promise<CrisisEvent> {
|
|
92
|
+
return fetchJson(`${BASE}/crisis/trigger/${sessionId}`, { method: 'POST' });
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export async function resolveCrisis(params: {
|
|
96
|
+
crisisId: string;
|
|
97
|
+
response: string;
|
|
98
|
+
}): Promise<CrisisEvent> {
|
|
99
|
+
return fetchJson(`${BASE}/crisis/resolve`, {
|
|
100
|
+
method: 'POST',
|
|
101
|
+
body: JSON.stringify(params),
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// ─── Profile & Analysis ────────────────────────────────────────────────────
|
|
106
|
+
|
|
107
|
+
export async function getAnalysis(): Promise<PlayerAnalysis> {
|
|
108
|
+
return fetchJson(`${BASE}/profile/analysis`);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// ─── Phrase Library ────────────────────────────────────────────────────────
|
|
112
|
+
|
|
113
|
+
export async function listPhrases(params?: {
|
|
114
|
+
page?: number;
|
|
115
|
+
pageSize?: number;
|
|
116
|
+
scene?: string;
|
|
117
|
+
difficulty?: string;
|
|
118
|
+
}): Promise<{ items: PhraseTemplate[]; total: number }> {
|
|
119
|
+
const qs = new URLSearchParams();
|
|
120
|
+
if (params?.page) qs.set('page', String(params.page));
|
|
121
|
+
if (params?.pageSize) qs.set('pageSize', String(params.pageSize));
|
|
122
|
+
if (params?.scene) qs.set('scene', params.scene);
|
|
123
|
+
if (params?.difficulty) qs.set('difficulty', params.difficulty);
|
|
124
|
+
return fetchJson(`${BASE}/phrases?${qs}`);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export async function getPhrasePractice(params?: {
|
|
128
|
+
count?: number;
|
|
129
|
+
scene?: string;
|
|
130
|
+
difficulty?: string;
|
|
131
|
+
}): Promise<PhraseTemplate[]> {
|
|
132
|
+
const qs = new URLSearchParams();
|
|
133
|
+
if (params?.count) qs.set('count', String(params.count));
|
|
134
|
+
if (params?.scene) qs.set('scene', params.scene);
|
|
135
|
+
if (params?.difficulty) qs.set('difficulty', params.difficulty);
|
|
136
|
+
return fetchJson(`${BASE}/phrases/practice?${qs}`);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// ─── Leaderboard ───────────────────────────────────────────────────────────
|
|
140
|
+
|
|
141
|
+
export async function getLeaderboard(params?: {
|
|
142
|
+
page?: number;
|
|
143
|
+
pageSize?: number;
|
|
144
|
+
season?: string;
|
|
145
|
+
}): Promise<{ items: LeaderboardEntry[]; total: number }> {
|
|
146
|
+
const qs = new URLSearchParams();
|
|
147
|
+
if (params?.page) qs.set('page', String(params.page));
|
|
148
|
+
if (params?.pageSize) qs.set('pageSize', String(params.pageSize));
|
|
149
|
+
if (params?.season) qs.set('season', params.season);
|
|
150
|
+
return fetchJson(`${BASE}/leaderboard?${qs}`);
|
|
151
|
+
}
|
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
export interface ChatCharacter {
|
|
2
|
+
id: string;
|
|
3
|
+
name: string;
|
|
4
|
+
avatar: string;
|
|
5
|
+
personality: string;
|
|
6
|
+
gender: string;
|
|
7
|
+
backstory: string;
|
|
8
|
+
speakingStyle: string;
|
|
9
|
+
favorTraits: string | null;
|
|
10
|
+
peeves: string | null;
|
|
11
|
+
createdAt: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface ChatSession {
|
|
15
|
+
id: string;
|
|
16
|
+
userId: string;
|
|
17
|
+
status: 'active' | 'completed' | 'failed' | 'abandoned';
|
|
18
|
+
characterCount: number;
|
|
19
|
+
totalFavorScore: number;
|
|
20
|
+
crisisCount: number;
|
|
21
|
+
durationSeconds: number;
|
|
22
|
+
rank: string;
|
|
23
|
+
summary: string | null;
|
|
24
|
+
threads: ChatThread[];
|
|
25
|
+
crises: CrisisEvent[];
|
|
26
|
+
createdAt: string;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface ChatThread {
|
|
30
|
+
id: string;
|
|
31
|
+
sessionId: string;
|
|
32
|
+
characterId: string;
|
|
33
|
+
favorability: number;
|
|
34
|
+
favorHistory: string;
|
|
35
|
+
threadStatus: string;
|
|
36
|
+
unreadCount: number;
|
|
37
|
+
lastMessage: string | null;
|
|
38
|
+
lastActiveAt: string | null;
|
|
39
|
+
character: ChatCharacter;
|
|
40
|
+
messages: ChatMessage[];
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export interface ChatMessage {
|
|
44
|
+
id: string;
|
|
45
|
+
threadId: string;
|
|
46
|
+
characterId: string | null;
|
|
47
|
+
role: 'user' | 'character' | 'system';
|
|
48
|
+
content: string;
|
|
49
|
+
favorDelta: number;
|
|
50
|
+
favorReason: string | null;
|
|
51
|
+
sentiment: string | null;
|
|
52
|
+
createdAt: string;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export interface CrisisEvent {
|
|
56
|
+
id: string;
|
|
57
|
+
sessionId: string;
|
|
58
|
+
crisisType: string;
|
|
59
|
+
description: string;
|
|
60
|
+
involvedCharacterIds: string;
|
|
61
|
+
status: 'triggered' | 'resolved' | 'failed';
|
|
62
|
+
userResponse: string | null;
|
|
63
|
+
resolutionScore: number | null;
|
|
64
|
+
feedback: string | null;
|
|
65
|
+
timeLimit: number;
|
|
66
|
+
createdAt: string;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export interface PlayerProfile {
|
|
70
|
+
id: string;
|
|
71
|
+
userId: string;
|
|
72
|
+
rank: string;
|
|
73
|
+
rankPoints: number;
|
|
74
|
+
totalSessions: number;
|
|
75
|
+
totalFavorGained: number;
|
|
76
|
+
crisisResolved: number;
|
|
77
|
+
humorScore: number;
|
|
78
|
+
empathyScore: number;
|
|
79
|
+
topicLeadScore: number;
|
|
80
|
+
timeManageScore: number;
|
|
81
|
+
crisisHandleScore: number;
|
|
82
|
+
bestStreak: number;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export interface PlayerAnalysis {
|
|
86
|
+
profile: PlayerProfile;
|
|
87
|
+
rankInfo: {
|
|
88
|
+
label: string;
|
|
89
|
+
emoji: string;
|
|
90
|
+
nextRank: string | null;
|
|
91
|
+
pointsToNext: number;
|
|
92
|
+
};
|
|
93
|
+
radarData: Array<{ dimension: string; score: number; label: string }>;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export interface PhraseTemplate {
|
|
97
|
+
id: string;
|
|
98
|
+
scene: string;
|
|
99
|
+
trigger: string;
|
|
100
|
+
badReply: string;
|
|
101
|
+
goodReply: string;
|
|
102
|
+
explanation: string;
|
|
103
|
+
favorBonus: number;
|
|
104
|
+
difficulty: string;
|
|
105
|
+
tags: string | null;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export interface LeaderboardEntry {
|
|
109
|
+
id: string;
|
|
110
|
+
userId: string;
|
|
111
|
+
displayName: string;
|
|
112
|
+
totalScore: number;
|
|
113
|
+
rank: string;
|
|
114
|
+
maxConcurrentChats: number;
|
|
115
|
+
crisisSuccessRate: number;
|
|
116
|
+
season: string;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export interface SendMessageResult {
|
|
120
|
+
userMessage: ChatMessage;
|
|
121
|
+
characterReply: ChatMessage;
|
|
122
|
+
favorDelta: number;
|
|
123
|
+
newFavorability: number;
|
|
124
|
+
threadStatus: string;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// ─── Live Mode Types (真人混战) ─────────────────────────────────────────────
|
|
128
|
+
|
|
129
|
+
export interface MatchRoom {
|
|
130
|
+
id: string;
|
|
131
|
+
roomName: string;
|
|
132
|
+
mode: 'blind' | 'turing' | 'ranked';
|
|
133
|
+
status: 'waiting' | 'active' | 'revealing' | 'completed' | 'disbanded';
|
|
134
|
+
hostUserId: string;
|
|
135
|
+
maxPlayers: number;
|
|
136
|
+
humanCount: number;
|
|
137
|
+
aiCount: number;
|
|
138
|
+
roundTimeLimit: number;
|
|
139
|
+
totalRounds: number;
|
|
140
|
+
currentRound: number;
|
|
141
|
+
password: string | null;
|
|
142
|
+
threads: LiveThread[];
|
|
143
|
+
createdAt: string;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
export interface LiveThread {
|
|
147
|
+
id: string;
|
|
148
|
+
roomId: string;
|
|
149
|
+
participantAId: string;
|
|
150
|
+
participantAName: string;
|
|
151
|
+
participantAAvatar: string;
|
|
152
|
+
participantBId: string;
|
|
153
|
+
participantBName: string;
|
|
154
|
+
participantBAvatar: string;
|
|
155
|
+
/** 前端不知道对方是人还是AI,这就是核心秘密 */
|
|
156
|
+
favorabilityAtoB: number;
|
|
157
|
+
favorabilityBtoA: number;
|
|
158
|
+
status: 'active' | 'paused' | 'completed' | 'ai_replaced';
|
|
159
|
+
messageCount: number;
|
|
160
|
+
createdAt: string;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
export interface LiveMessage {
|
|
164
|
+
id: string;
|
|
165
|
+
threadId: string;
|
|
166
|
+
senderId: string;
|
|
167
|
+
senderName: string;
|
|
168
|
+
content: string;
|
|
169
|
+
/** sourceType 不会传到前端 — 这是以假乱真的核心 */
|
|
170
|
+
favorDelta: number;
|
|
171
|
+
favorReason: string | null;
|
|
172
|
+
sentiment: string;
|
|
173
|
+
createdAt: string;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
export interface PlayerQueueEntry {
|
|
177
|
+
id: string;
|
|
178
|
+
userId: string;
|
|
179
|
+
displayName: string;
|
|
180
|
+
rank: string;
|
|
181
|
+
preferredMode: string;
|
|
182
|
+
status: 'waiting' | 'matched' | 'expired' | 'cancelled';
|
|
183
|
+
matchedRoomId: string | null;
|
|
184
|
+
createdAt: string;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
export interface Persona {
|
|
188
|
+
name: string;
|
|
189
|
+
avatar: string;
|
|
190
|
+
personality: string;
|
|
191
|
+
backstory: string;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/** 图灵猜测(提交时) */
|
|
195
|
+
export interface TuringGuessSubmit {
|
|
196
|
+
threadId: string;
|
|
197
|
+
targetId: string;
|
|
198
|
+
guess: 'human' | 'ai';
|
|
199
|
+
confidence: number;
|
|
200
|
+
reason?: string;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/** 图灵揭晓结果(单条) */
|
|
204
|
+
export interface TuringRevealItem {
|
|
205
|
+
threadId: string;
|
|
206
|
+
targetId: string;
|
|
207
|
+
targetName: string;
|
|
208
|
+
guess: 'human' | 'ai';
|
|
209
|
+
actual: 'human' | 'ai';
|
|
210
|
+
wasAiTakeover: boolean;
|
|
211
|
+
isCorrect: boolean;
|
|
212
|
+
pointsDelta: number;
|
|
213
|
+
confidence: number;
|
|
214
|
+
reason: string | null;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/** 图灵揭晓结果(房间级) */
|
|
218
|
+
export interface TuringRevealResult {
|
|
219
|
+
roomId: string;
|
|
220
|
+
results: TuringRevealItem[];
|
|
221
|
+
totalPoints: number;
|
|
222
|
+
accuracy: number;
|
|
223
|
+
title: string;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/** 图灵个人战绩 */
|
|
227
|
+
export interface TuringStats {
|
|
228
|
+
totalGuesses: number;
|
|
229
|
+
correctGuesses: number;
|
|
230
|
+
accuracy: number;
|
|
231
|
+
totalPoints: number;
|
|
232
|
+
bestStreak: number;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/** WebSocket 事件 */
|
|
236
|
+
export interface WSEvent {
|
|
237
|
+
type: string;
|
|
238
|
+
data: unknown;
|
|
239
|
+
timestamp: number;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/** 在线状态 */
|
|
243
|
+
export interface RoomParticipant {
|
|
244
|
+
id: string;
|
|
245
|
+
name: string;
|
|
246
|
+
avatar: string;
|
|
247
|
+
isOnline: boolean;
|
|
248
|
+
/** 不暴露 isAI! */
|
|
249
|
+
}
|